AgentWorkflow

一个 FunctionAgent 既检索又写综述,prompt 会打架。AgentWorkflow 把多个 FunctionAgent 收成一个可 run 的工作流:每个 Agent 有 namedescriptioncan_handoff_to,当前 Agent 决定把任务交给谁。这是消息交接,不是 StateGraph 的条件边。

段末注释AgentWorkflow 在 Workflow 之上预构建多 Agent 调度;handoff = 把对话与工具权交给另一个具名 Agent。

左:Research/Writer/Reviewer 传文件夹;右:提醒环和审批仍在 LangGraph(科普示意)


1. 一句话定位

维度 内容
角色 知识层侧的多角色交接:谁说话、谁调哪套工具
输入 → 输出 user_msg → 终态文本(内部多次 handoff + tool)
典型调用入口 FunctionAgent(name=, can_handoff_to=)AgentWorkflow(agents=, root_agent=).run()
与 LangChain / LangGraph 近邻是 Subgraph / 手写 handoff;持久化会话、HITL 仍用 LangGraph

出现背景:单 Agent 工具一多,模型乱调。拆成「检索员 / 执笔」后,用 description 让当前角色知道同事是干什么的,用 can_handoff_to 限制可交接对象,避免环形对吵失控。


2. 前置依赖与环境

1
2
3
pip install -U llama-index-core llama-index-llms-ollama llama-index-embeddings-ollama
ollama pull qwen3.5:9b
ollama pull nomic-embed-text
  • 每个 FunctionAgent 的 LLM 都要能 tool calling(handoff 也是一种工具)
  • run async;多轮仍需显式 Context
  • 本地 9B 级模型交接比单工具 round 更容易失败,先把 description 写死、temperature=0

3. 实现逻辑

1
2
3
4
5
6
7
1. 为每个角色建 FunctionAgent:name、description、tools、system_prompt、can_handoff_to
2. AgentWorkflow(agents=[...], root_agent="检索员的 name")
3. 可选 initial_state:给各工具共享的草稿槽
4. await wf.run(user_msg=...)
5. root 先说话;需要时发 handoff → 目标 Agent 接手 messages
6. 目标 Agent 调自己的 tools 或再 handoff
7. 某 Agent 不再调用工具/交接 → 终态

字段级变形

1
2
3
4
user_msg = "根据资料写一句:实验对象是谁"
→ root=Research 调 paper_search
→ handoff WriteAgent
→ WriteAgent 只把检索摘要写成一句话(不再搜)

4. 原理说明

主轴是:同一套 Workflow 运行时里切换「当前 Agent」;工具列表随角色变,而不是一个超级 tools 数组。

1
2
3
4
5
6
7
8
9
1. AgentWorkflow 校验恰好能解析 root_agent,且 handoff 目标 name 存在
2. run 把 user_msg 交给 root 的 FunctionAgent 循环
3. 模型除业务 tools 外还可能发 handoff(框架注入)
4. 交接后:当前 system_prompt / tools 换成目标 Agent 的
5. initial_state / Context 里的 KV 可被声明了 ctx 的工具读写(角色间传草稿)
6. 没有 reducer:后一次工具写 state 覆盖还是追加,取决于你的工具实现
7. 进程内 Context 可续跑;进程外无 Saver
少了 can_handoff_to 收口 → 三个角色互相甩锅到 timeout
少了互斥 description → root 直接写报告从不检索

FunctionAgent.name / description 出现在步骤 1。description其它 Agent 看,决定会不会被交接。

can_handoff_to 出现在步骤 1。列表为 name 字符串。空列表 = 不能交出去(叶子角色)。

AgentWorkflow(agents, root_agent=...) 出现在步骤 2。agents 里必须能唯一命中 root。

initial_state 出现在步骤 3。适合 report_draft 这类槽;不要把整个 Index 放进去。

单角色时 AgentWorkflow(agents=[one]) 等价于直接 FunctionAgent.run,不必叠这层。


5. 最小可运行示例

1
pip install -U llama-index-core llama-index-llms-ollama llama-index-embeddings-ollama
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import asyncio

from llama_index.core import Document, Settings, VectorStoreIndex
from llama_index.core.agent.workflow import AgentWorkflow, FunctionAgent
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.tools import QueryEngineTool
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.llms.ollama import Ollama

Settings.llm = Ollama(model="qwen3.5:9b", request_timeout=120.0, temperature=0)
Settings.embed_model = OllamaEmbedding(
model_name="nomic-embed-text",
base_url="http://localhost:11434",
)

index = VectorStoreIndex.from_documents(
[Document(text="定量 PCR 以小鼠肝脏 GAPDH 为内参。实验对象为 C57BL/6 小鼠。",
doc_id="paper_001")],
transformations=[SentenceSplitter(chunk_size=128, chunk_overlap=20)],
)
search = QueryEngineTool.from_defaults(
query_engine=index.as_query_engine(similarity_top_k=3),
name="paper_search",
description="检索方法学笔记中的物种、内参、剂量。",
)

research = FunctionAgent(
name="ResearchAgent",
description="负责检索资料,不写长文。",
tools=[search],
llm=Settings.llm,
system_prompt="先调用 paper_search,再把证据要点交给 WriterAgent。",
can_handoff_to=["WriterAgent"],
)
writer = FunctionAgent(
name="WriterAgent",
description="根据已检索要点写一句中文结论,不再检索。",
tools=[],
llm=Settings.llm,
system_prompt="只写一句结论;没有物种信息就说不知道。不要编造。",
can_handoff_to=[],
)

wf = AgentWorkflow(agents=[research, writer], root_agent="ResearchAgent")

async def main():
out = await wf.run(user_msg="实验对象是什么物种?一句话回答。")
print(out)
# 预期形态:含小鼠或 C57BL/6;若本地模型未 handoff,至少 Research 应调过检索

asyncio.run(main())

6. 重要配置参数

参数(API 名) 类型 / 默认值 功能说明 作用与影响 参考起点 / 常用范围 配置指导
name str,必填语义 Agent 稳定 ID,handoff 用 与 can_handoff_to 对不上则运行失败 ASCII 短名 不要中文空格
description str 给同事 Agent 的职责说明 写「通用助手」则乱交 一句话职责 + 不做的事 比 system_prompt 更影响选人
can_handoff_to list[str] 允许交接的 name 空 = 叶子;互相指向可能对吵 单向链先于全连通 先 Research→Writer 一条链
root_agent str 第一次说话的人 填错则校验失败 有工具的检索角色 不要让 Writer 当 root
tools list 该角色能调的工具 Writer 挂检索会跳过交接 检索工具只给 Research 按职责拆
initial_state dict,可选 跨角色草稿槽 工具不读它则形同虚设 小字符串 显式在 tool 里 get/set
timeout 整次交接+工具上限 对吵时靠它熔断 120~300 必设

7. 适用 / 不适用

维度 适用 不适用
任务形态 检索与写作职责冲突、要显式交接 一个 QE 一次问答
集成约束 模型能稳定 tool/handoff 本地模型频繁选错人——改回单 Agent 或规则路由
工程阶段 原型多角色 审批、跨进程恢复——LangGraph

8. 易踩坑

  1. 所有人 can_handoff_to 所有人:对吵到 timeout。
  2. Writer 也挂 paper_search:根本不交接。
  3. name 拼写与 can_handoff_to 不一致
  4. 把 AgentWorkflow 当 HITL:没有 interrupt 语义。

小结

  • 角色 = 自己的 tools + 允许交接的名字
  • root_agent 起步;description 给同事看。
  • 单角色不必套 AgentWorkflow。
  • 要环的可靠性与审批:知识工具留下,外壳换 LangGraph。

参考链接

-------------本文结束感谢您的阅读-------------