Haystack Agent 深度指南工具调用循环、退出条件、Hooks 与 State 状态管理【免费下载链接】haystackOpen-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation. Built for scalable agents, RAG, multimodal applications, semantic search, and conversational systems.项目地址: https://gitcode.com/GitHub_Trending/ha/haystack导读本指南以 agents_api.md 中Agent与State两个核心类为主线系统讲解 Haystack 中由大语言模型驱动的工具使用型 Agent的完整工作机制Agent 如何循环调用 LLM 与工具、如何通过退出条件exit conditions终止运行、如何使用 Jinja2 模板化user_prompt复用输入、如何用六个 Hook 点before_run/before_llm/before_tool/after_tool/on_exit/after_run在运行循环中注入自定义逻辑以及State状态容器如何在 Agent 与工具之间共享上下文。读完本文你将能够基于 Agent 实现 与 State 实现 独立构建可复用的工具型 Agent并把它嵌入 Haystack Pipeline 中做多轮 RAG、翻译、检索-计算等实际任务。Agent 是什么一次运行循环的完整心智模型Agent是一个由大语言模型LLM驱动的工具使用组件核心行为只有一句话处理消息、调用工具直到满足退出条件为止参见 agent.py 类定义。其运行循环在源码中清晰可见run 方法初始化状态State被创建messages、step_count、token_usage、tool_call_counts、exit_reason等键被写入初始值进入while循环每一轮调用_run_step第 995 行执行一次chat-generator 调用 该次调用中模型请求的所有工具调用每完成一步step_count递增当命中退出条件或达到max_agent_steps时循环终止最后通过_public_outputs把状态中除去内部键之外的数据作为返回值交给调用方。一个重要的边界情况不给 Agent 配置任何工具时它就退化为一个标准的文本生成 LLM——生成一条回复后立即停止不进入工具循环。这一点在文档和源码 docstring 中都有明确说明。从源码结构看Agent 的运行还全程被 tracing 追踪run会创建haystack.agent.runspan每一步再嵌套haystack.agent.step与haystack.agent.step.llm子 span并把输入输出、step_count、exit_conditions、state_schema等写入 span 标签_create_agent_span。这意味着 Agent 天然可以接入 Haystack 的 tracing 体系进行可观测性分析。快速上手带搜索与计算工具的示例 Agent参考文档中的第一个示例即 agent.py 的 docstring 示例一个典型的两工具 Agent 长这样from haystack.components.agents import Agent from haystack.components.generators.chat import OpenAIChatGenerator from haystack.components.generators.utils import print_streaming_chunk from haystack.dataclasses import ChatMessage from haystack.tools import tool from typing import Annotated, Literal # Tool functions - in practice, these would have real implementations tool def search(query: Annotated[str, The search query]) - str: Search for information on the web. # Placeholder: would call actual search API return In France, a 15% service charge is typically included, but leaving 5-10% extra is appreciated. tool def calculator( operation: Annotated[Literal[multiply, percentage], The mathematical operation to perform], a: Annotated[float, First number], b: Annotated[float, Second number], ) - float: Perform mathematical calculations. if operation multiply: return a * b elif operation percentage: return (a / 100) * b return 0 agent Agent( system_prompt( You are a helpful assistant. Use the search tool to find information about a users question and the calculator tool to perform math. ), chat_generatorOpenAIChatGenerator(), tools[search, calculator], streaming_callbackprint_streaming_chunk, ) result agent.run( messages[ChatMessage.from_user(Calculate the appropriate tip for an €85 meal in France)] ) # Access the final response from the Agent # print(result[last_message].text)这段代码演示了三条核心知识点tool装饰器把普通函数变成工具函数的参数使用Annotated[str, 描述]提供参数说明Literal[...]限定枚举取值这些都会自动转换为供 LLM 理解的 JSON Schema 工具定义Agent的构造只需三样东西一个支持 tools 的chat_generator、一份tools列表、一个指导模型用法的system_prompt流式输出streaming_callbackprint_streaming_chunk会把 LLM 回复逐块打印。需要强调的是源码在__init__中通过反射检查了chat_generator.run是否接受tools参数第 460 行如果传入了工具而生成器不支持会直接抛出TypeError避免在运行时才发现不兼容。用模板化 user_prompt 实现可复用的 Pipeline 组件当 Agent 被嵌入 Pipeline 时你往往希望每次调用传入不同输入而不必手动构造ChatMessage。文档给出了基于 Jinja2 消息模板的user_prompt方案from haystack.components.agents import Agent from haystack.components.generators.chat import OpenAIChatGenerator from haystack.tools import tool from typing import Annotated tool def translate( text: Annotated[str, The text to translate], target_language: Annotated[str, The language to translate to], ) - str: Translate text to a target language. # Placeholder: would call an actual translation API return f[Translated {text} to {target_language}] agent Agent( chat_generatorOpenAIChatGenerator(), tools[translate], system_promptYou are a helpful translation assistant., user_prompt{% message roleuser%} Translate the following document to {{ language }}: {{ document }} {% endmessage %}, ) # The template variables language and document become inputs to the run method result agent.run( messages[], languageFrench, documentThe weather is lovely today and the sun is shining., ) print(result[last_message].text)这里的实现机制值得展开模板变量自动注册为组件输入user_prompt/system_prompt由内部的ChatPromptBuilder解析__init__中的 builder 构建模板中的{{ language }}、{{ document }}会被收集并通过_register_prompt_variables第 541 行注册为 Agent 的输入端口required_variables控制必填性默认值为*即所有模板变量都必须在run时提供否则抛异常设置为None则所有变量可选缺失的变量渲染为空字符串也可以传入具体的变量名列表变量命名有约束模板变量不能与state_schema中的键或run方法的参数名冲突否则抛出ValueError第 575-586 行消息顺序在_initialize_fresh_execution第 731-747 行中user_prompt渲染出的用户消息被追加到运行时传入的messages之后system_prompt渲染出的系统消息被放到最前面。退出条件exit conditions控制 Agent 何时停止exit_conditions是理解 Agent 行为的关键参数默认值为[text]。它支持两类条件__init__参数说明text当模型生成一条不含工具调用的完整回复时停止工具名当某个工具被成功执行后停止例如exit_conditions[save_result]表示一旦save_result工具跑完Agent 立即返回。退出原因的判定逻辑源码在_get_model_exit_reason第 154 行中实现了精细的判定最后一条消息不含工具调用且来自 assistant才可能触发退出若finish_reason为length或content_filter以该原因为退出理由此时回复可能不完整但 Agent 仍会停止方便下游感知否则若最后一条消息有文本以text退出空回复且无明确终止原因时不退出保留 Agent 对异常工具调用的恢复能力。退出条件与工具错误的关系_check_exit_conditions第 1133 行揭示了另一个细节若满足退出条件的工具在执行时出错则取消本次退出Agent 继续循环。源码会收集所有报错的工具tool_call_result.error为真的调用只要退出条件中的工具在错误集合里就返回None不退出。退出后的返回值exit_reason 与下游路由run的返回字典中exit_reason字段直接可用于下游路由例如配合ConditionalRouter分流。可能的取值包括run 方法返回说明text模型给出无工具调用的完整回复length/content_filter模型回复不完整可能只有部分文本某个工具名该工具满足了工具退出条件此时last_message是该工具的结果消息max_agent_steps达到步数上限仍未命中任何退出条件自定义原因hook 通过stop_run状态键提供的停止原因。源码中还定义了这些常量的字面值第 68-73 行与文档描述完全一致。用 Hooks 在运行循环中注入逻辑Hooks 是接收实时State对象的可调用对象在 Agent 循环的特定时点运行通过原地修改 State 来影响运行。文档定义了两个关键概念使用hook装饰器把普通函数变成 Hook通过hooks{hook_point: [hook1, hook2, ...]}注册同一时点的 hooks 按列表顺序执行。六个 Hook 点在 hooks/protocol.py 中定义了全部 Hook 点常量语义如下Hook 点触发时机典型用途before_run每次 run 一次状态初始化后、首次 LLM 调用前改写初始消息、预置 State如把用户问题转成任务简报不会像before_llm那样每步重复执行before_llm每次 chat-generator 调用前检查上下文长度、触发压缩compaction、注入消息before_tool模型请求工具后、工具执行前人工确认HITL、拒绝或改写工具调用after_tool工具执行完、结果消息写入 State 后退出检查与下次 LLM 调用前改写刚生成的工具结果卸载 offload、脱敏、截断、摘要on_exitAgent 即将因退出条件停止时通过continue_run让 Agent 继续运行注意仅在命中退出条件时触发因max_agent_steps停止时不会触发after_run每次 run 一次步数循环结束后、构建返回值前追加最终消息等最终调整无论因退出条件还是max_agent_steps停止都会触发与on_exit不同此处设置continue_run无效before_tool 的可改写机制before_tool有一个重要特性第 186-199 行hook 执行后Agent 会重新从state.data[messages]读取当前最后一条消息。如果该消息含工具调用就执行如果不含则该步不执行任何工具、不触发工具类退出条件直接回到下一次 LLM 调用除非已达max_agent_steps。这正是 HITL 确认类 hook 的工作基础——hook 可以把待确认的工具调用消息替换掉从而否决本次调用。on_exit 的经典用法强制调用指定工具文档示例展示了一个非常实用的场景——用on_exithook 保证 Agent 在结束前必须调用某个工具from haystack.components.agents import Agent from haystack.components.agents.state import State from haystack.components.generators.chat import OpenAIChatGenerator from haystack.dataclasses import ChatMessage from haystack.hooks import hook from haystack.tools import tool from typing import Annotated tool def save_result(content: Annotated[str, The result to save]) - str: Save the final result. # Placeholder: would persist content to a database or the file system return saved hook def require_save(state: State) - None: if state.get(tool_call_counts, {}).get(save_result, 0) 0: state.set(messages, [ChatMessage.from_system(Call save_result before finishing.)]) state.set(continue_run, True) # keep the Agent running instead of stopping agent Agent( chat_generatorOpenAIChatGenerator(), tools[save_result], hooks{on_exit: [require_save]}, )其底层机制是_continue_after_exit_hooks第 1163 行在每次退出尝试时先清零continue_run运行on_exithooks然后通过_consume_continue_run第 147 行读取并重置该标志——若 hook 设置了continue_runTrue循环继续且始终受max_agent_steps约束不会死循环。预留的内部状态键agent.py中还定义了若干 Agent 内部管理用的状态键第 84-99 行hooks 可以读取它们continue_runon_exit hook 设置后让 Agent 继续运行stop_runhook 设置后停止运行其值作为exit_reasontools当前步骤可用的扁平化工具列表供 hooks 检查如 HITL 确认hook_context每次 run 的请求级资源供 hooks 读取context_tokens每次 LLM 调用后刷新的近似上下文窗口大小供before_llmhook 触发压缩。这些键以及step_count、token_usage、tool_call_counts、exit_reason都是保留键用户不得在state_schema中重新定义第 472-479 行否则抛出ValueError。StateAgent 与工具共享的运行时上下文State是 Agent 及其工具执行期间存储共享信息的容器state.py 类定义可以用来存放文档、上下文和中间结果。它内部包装了一个由schema定义的_data字典每个 schema 条目形如parameter_name: { type: SomeType, # expected type handler: Optional[Callable[[Any, Any], Any]] # merge/update function }handler控制set()方法合并值的策略第 97-99 行列表类型默认使用merge_lists拼接/合并列表其他类型默认使用replace_values新值覆盖旧值。这两个默认 handler 定义在 state_utils.py 中merge_lists(current, new)把两个值归一为列表后拼接replace_values(current, new)直接返回新值。同时messages字段类型list[ChatMessage]会被自动加入 schema第 129-130 行且 schema 校验强制messages必须为list[ChatMessage]类型_validate_schema。正是这种设计让 Agent、工具与 hooks 能读写同一份对话上下文。State 的独立用法示例from haystack.components.agents.state import State my_state State( schema{gh_repo_name: {type: str}, user_name: {type: str}}, data{gh_repo_name: my_repo, user_name: my_user_name} )State 的 API 速览方法/属性签名说明__init__State(schema, dataNone)schema 中type必须是合法 Python 类型handler必须是可调用或Nonegetget(key, defaultNone)按键取值键不存在返回默认值setset(key, value, handler_overrideNone)按 schema 规则合并或覆盖值有handler_override优先用它否则用 schema 中该键的 handlerdata属性当前 State 的全部数据字典hashas(key) - bool判断键是否存在to_dictto_dict(skip_keysNone)序列化为字典可跳过指定键from_dictfrom_dict(data)从字典反序列化恢复 Statestate_schema 与 Agent 输入输出的联动当你在Agent中声明state_schema时Agent 的组件输入输出会自动联动第 504-524 行schema 中的每个键排除内部键都会注册为 Agent 的输出端口类型来自该键的type非运行元数据键还会注册为输入端口默认None运行时可作为**kwargs传入运行元数据键step_count、token_usage、tool_call_counts、exit_reason只作为输出暴露不作为输入。因此state_schema既是工具的共享内存也是 Agent 在 Pipeline 中的对外接口——下游组件可以直接消费 Agent 输出的last_message、step_count、token_usage或自定义状态键。run 与 run_async同步 / 异步两种执行路径run与run_async共享同一套逻辑状态初始化、步进循环、hooks、span 追踪区别在于异步路径会优先调用 chat generator 的run_async对仅支持同步的生成器则通过_execute_component_async派发到线程执行第 1086-1091 行。两者的参数与返回值完全一致参数messagesHaystackChatMessage列表streaming_callbackLLM 流式回复回调同一回调也可配置为在工具被调用时输出工具结果generation_kwargs传给 chat generator 的额外参数与初始化时的generation_kwargs按 key 合并运行时的值优先、仅初始化时设置的值保留第 348-350 行tools本次运行可用的 Tool 列表、Toolset或工具名字符串列表按名字从初始化配置的工具中挑选见_select_toolshook_context请求级资源字典hooks 可通过state.data.get(hook_context)读取适合 Web/服务端场景传递 WebSocket 连接、异步队列、Redis 客户端等对象**kwargs传给 State schema 的附加数据键必须匹配state_schema。返回值dictmessages本次运行交换的全部消息last_message最后一条消息step_count运行步数一次 LLM 调用 该调用触发的全部工具执行为一步含命中退出条件或max_agent_steps的最后一步token_usage所有 LLM 调用的 token 用量聚合来自每条消息的meta[usage]tool_call_counts各工具被调用次数的映射exit_reason停止原因见上文退出条件一节以及state_schema中定义的其他键。Agent 的完整生命周期warm_up、close 与 cloneAgent 遵循 Haystack 组件的资源生命周期约定warm_up/warm_up_async预热工具、hooks 和底层 chat generator第 592-606 行。注意run内部会自动调用warm_up异步路径调用warm_up_asyncclose/close_async释放 hooks 和 chat generator 的资源clone(**overrides)返回一个与当前 Agent 配置相同、但可用参数覆盖的新实例第 622-631 行例如agent.clone(system_prompt...)。这在多用户场景下非常实用——每个请求克隆一份独立配置而不共享可变状态。序列化与反序列化把 Agent 存进 YAML/JSONto_dict/from_dict让 Agent 可以序列化并重新加载第 633-679 行to_dict会序列化chat_generator组件转 dict、toolsTool/Toolset 序列化、prompt、exit_conditions、state_schema类型与 handler 函数序列化、streaming_callback可调用对象序列化和hookshooks 字典序列化from_dict反向恢复以上全部内容包括反序列化 chat generator、恢复 schema 中的类型与 handler、反序列化 hooks。这意味着你可以把配置好的 Agent 完整保存为 YAML/JSON 文件配合 marshal 模块实现配置即代码的部署方式也便于在 Pipeline 中与其他组件统一序列化。测试验证从测试用例看行为约定仓库的测试套件为本文内容提供了直接验证test/components/agents/test_agent.py 覆盖 Agent 的核心循环与退出逻辑test_agent_hooks.py 覆盖各 Hook 点的触发语义test_agent_hitl.py 覆盖before_tool确认场景。如果你要深入理解某个边界行为例如并行工具调用的退出顺序、before_run恢复 State 后计数器的续跑这些测试是最权威的行为规范文档。小结本指南完整梳理了 HaystackAgent的五大核心能力工具调用循环与退出条件text/工具名/max_agent_steps、模板化 prompt 的可复用输入Jinja2 required_variables、六点 Hooks 扩展机制before_run/before_llm/before_tool/after_tool/on_exit/after_run、State 共享状态容器schema 驱动的合并策略以及同步/异步双执行路径与完整生命周期管理。配合clone、to_dict/from_dict与 tracing 支持Agent 既能独立运行也能作为 Pipeline 中的一等公民组件参与编排是构建生产级 LLM 应用的核心积木。【免费下载链接】haystackOpen-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation. Built for scalable agents, RAG, multimodal applications, semantic search, and conversational systems.项目地址: https://gitcode.com/GitHub_Trending/ha/haystack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考