21|Agent 与 Graph 结合:自主决策,确定性执行 - eino教程 21Agent 与 Graph 结合自主决策确定性执行《Eino 实战用 Go 构建 AI 应用与智能体》· 第 21 篇关键词GraphTool、AgentAsTool、流程复用、职责边界、确定性前置条件已完成 16从流程到 Agent 与 11Tool 与 ToolsNode。前几篇我们分别用 Graph 编排流程、用 Agent 做自主决策。现实项目里两者往往都要流程需要稳定可预测但入口又想灵活。直觉做法是把 Agent 塞进 Graph 当节点。第一篇引用的官方辨析明确指出这样做不优雅——Agent 需要多轮交互、长时记忆和异步输出而 Graph 节点期待确定性的输入输出。正确的结合方向是反过来的把 Graph 封装成 Agent 的工具。二、为什么不是“Agent 作为节点”先把“反向方案”为什么不合适说清楚这有助于理解边界。特征Graph 节点Agent输入上游节点的输出结构化输入加上会话历史输出确定的结构化值异步的全过程事件执行同步、可预期多轮、路径不定把 Agent 当作 Graph 节点会同时踩三个坑输入不完整。Agent 依赖历史而 Graph 节点只拿到上游输出。输出不好用。后续节点拿到的是一串事件而不是一个确定的值。阻塞主流程。Agent 的多轮交互会卡住本应快速推进的编排。官方文档的结论很直接Graph 中需要的是一个纯粹的 LLM 节点做好意图识别或内容生成而不是完整的 Agent。如果确实需要 Agent 的自主性更好的做法是给 Agent 加前置/后置处理而不是把它塞进静态图里。三、Graph 与 Tool 的相似性反过来看为什么“Graph 作为工具”就自然得多维度GraphTool输入结构化数据结构化数据JSON 参数交付物最终结果最终结果状态单次执行单次执行同步性同步对模型而言是同步两者几乎是同一个形状。因此封装起来几乎没有阻抗Graph 负责确定性的执行Tool 接口负责与 Agent 对接。网上很流行的一张关系图概括了这一点Agent理解目标、决定何时行动 └─ 调用 Tool └─ Tool 内部封装 Graph └─ Graph 按预定步骤确定执行 └─ 返回确定性结果给 Agent四、完整示例下面这段程序把一个知识问答流程封装成工具不访问外部服务packagemainimport(contextencoding/jsonfmtlogstringsgithub.com/cloudwego/eino/components/toolgithub.com/cloudwego/eino/composegithub.com/cloudwego/eino/schema)// ---- 一个确定性的知识问答流程先整理资料再生成回答 ----funcbuildKnowledgeGraph()(compose.Runnable[string,string],error){g:compose.NewGraph[string,string]()// 节点 1检索真实项目接 Retriever这里用固定资料模拟。retrieve:compose.InvokableLambda(func(_context.Context,_string)(string,error){return资料打印机离线时先检查网线再确认网段。,nil})// 节点 2生成回答真实项目接 ChatModel这里用字符串处理模拟。answer:compose.InvokableLambda(func(_context.Context,ctxTextstring)(string,error){return根据资料strings.TrimPrefix(ctxText,资料),nil})iferr:g.AddLambdaNode(retrieve,retrieve);err!nil{returnnil,err}iferr:g.AddLambdaNode(answer,answer);err!nil{returnnil,err}_g.AddEdge(compose.START,retrieve)_g.AddEdge(retrieve,answer)_g.AddEdge(answer,compose.END)returng.Compile(context.Background())}// ---- 把图封装为工具 ----typegraphToolstruct{namestringdescstringrun compose.Runnable[string,string]}func(t*graphTool)Info(_context.Context)(*schema.ToolInfo,error){returnschema.ToolInfo{Name:t.name,Desc:t.desc,ParamsOneOf:schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{query:{Type:schema.String,Desc:用户问题},}),},nil}func(t*graphTool)InvokableRun(ctx context.Context,argsJSONstring,_...tool.Option)(string,error){varargsstruct{Querystringjson:query}iferr:json.Unmarshal([]byte(argsJSON),args);err!nil{return,fmt.Errorf(参数解析失败: %w,err)}// 图在工具内部以确定性方式执行输入输出都是普通值。returnt.run.Invoke(ctx,args.Query)}funcmain(){iferr:run();err!nil{log.Fatal(err)}}funcrun()error{ctx:context.Background()graph,err:buildKnowledgeGraph()iferr!nil{returnfmt.Errorf(构建图失败: %w,err)}gt:graphTool{name:knowledge_qa,desc:基于内部知识库回答问题,run:graph}// 编译期断言这个类型满足 Eino 的工具接口可直接交给 Agent。var_tool.InvokableToolgt info,err:gt.Info(ctx)iferr!nil{returnerr}fmt.Printf(工具名%s 描述%s\n,info.Name,info.Desc)out,err:gt.InvokableRun(ctx,{query:打印机离线怎么办})iferr!nil{returnerr}fmt.Println(工具输出:,out)returnnil}实机输出工具名knowledge_qa 描述基于内部知识库回答问题 工具输出: 根据资料打印机离线时先检查网线再确认网段。关键点在于var _ tool.InvokableTool gt这行编译期断言只要类型满足接口它就能像第 11 篇的任何工具一样交给ToolsNode或ChatModelAgent。对 Agent 而言这个工具背后是图还是函数它并不关心。五、官方封装方式上面手写的graphTool有教学价值但每个项目重复写一遍并不划算。Eino 官方在示例仓库中提供了graphtool包思路正是一样的把任意 Graph 转成InvokableTool。// 官方 graphtool 的签名来自 eino-examplesfuncNewInvokableGraphTool[I,O any](graph compose.Graph[I,O],name,descstring,opts...compose.GraphCompileOption,)(*InvokableGraphTool[I,O],error)它内部处理了这几件事由输入类型推导工具的 JSON Schema。把模型给的 JSON 参数反序列化成I。编译并执行图。处理中断图内也可能有 HITL 点。把输出O序列化成字符串返回。生产项目建议直接用官方实现而不是自己造一遍。参考https://github.com/cloudwego/eino-examples/tree/main/adk/common/tool/graphtool官方还提供了三个典型示例Chain 文档摘要、Graph 多源研究、Workflow 订单处理——覆盖了三种编排方式封成工具的场景。六、这套组合的价值在哪好处说明复用现有流程已经调好的 RAG 流程、数据处理管道不必为 Agent 重写保证关键步骤确定授权、计算、落库等步骤在图内按固定顺序执行缩小暴露面Agent 只看到一个工具而不是内部十几个节点便于测试图可以脱离 Agent 单独测试各测各的一句话概括让 Agent 决定“做什么”让 Graph 决定“怎么做”。这个分工也回应了第一篇的一句话模型可以提出行动但关键业务怎么执行应该由可信代码控制。七、边界与注意事项1. 图内不要依赖对话历史。图的输入是结构化参数。需要历史信息时应由 Agent 在调用工具时把相关内容整理进参数而不是让图自己去猜。2. 输入输出要保持可序列化。工具的入参是 JSON 字符串出参也必须是字符串。复杂的结构要设计好序列化格式并考虑长度限制。3. 图内的中断需要透传。如果图内含审批点第 20 篇封装时要确保中断能正确传播到外层否则 Agent 会“卡住”而用户看不到确认提示。官方graphtool已处理这一点自己实现时务必注意。4. 工具描述决定 Agent 会不会选它。和所有工具一样描述要写清使用时机。图内部再精巧描述含糊也会被模型忽略。5. 鉴权仍在最内层。图封装成工具后权限校验依然要在图内部或具体工具内完成。“藏在图里”不是安全措施。八、什么时候该拆、什么时候该合情况建议流程固定、需要复用封装为工具交给 Agent 调用流程需要 Agent 逐步交互不要把 Agent 塞进图改为给 Agent 加处理逻辑只是想做一次意图识别或生成用 LLM 节点不需要完整 Agent关键步骤必须按顺序且可审计放进图内作为工具暴露判断标准是**“这一步是否需要反复交互”**。需要交互的交给 Agent一次完成且结果确定的封装成工具。九、小结把 Agent 当作 Graph 节点会造成输入不完整、输出不好用、阻塞主流程三个问题。Graph 与 Tool 在输入、交付物、状态、同步性上高度相似天然适合互相封装。用InfoInvokableRun就能把图变成工具官方graphtool包已提供成熟实现。Agent 决定做什么Graph 决定怎么做。图内不要依赖对话历史注意序列化、长度限制和中断透传。鉴权仍须在最内层完成封装不构成安全边界。第四篇《Agent 开发》到此完成。下一篇进入第五篇讨论多智能体协作。参考资料与版本说明资料核对日期为2026 年 10 月 9 日Agent 还是 GraphAI 应用路线辨析Eino 官方示例graphtoolEino 主仓库本文示例按Eino v0.9.21编写图内节点使用自定义 Lambda不访问外部服务已实机运行正文输出为真实结果。官方graphtool包的签名与职责依据示例仓库文档整理未在本篇实机运行。