Files
sundynix-agentix/EINO_ADOPTION.md
T
Blizzard 1c7e8e1ea9 docs: 修 DEPTH_ROADMAP/EINO 两处过期标记
- 删 T4.E 熔断器接回 failover 重复的过期 [ ] 项(222 行  为准);
  T2.1 熔断器联动注明已整合。
- EINO Phase D FSM 回写主题名笔误 sundynix.tasks.status → sundynix.status.task
  (文档 141 行自己记过这次迁移)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:29:01 +08:00

15 KiB
Raw Blame History

Eino 全面采纳迁移方案

目标:从"只借 Eino 的类型"演进为 Eino-native 的 Agent 编排核心,充分发挥框架的组件 / ADK / 编排 / 可观测能力。 原则:分阶段、不推倒重写、每步保持现有测试绿、可随时回退。自研 graph.go 解释器演进为 compose.Graph,而非盲删。


进度

  • Phase A · 地基llm.Pool 换 Eino ChatModel 组件(commit d84b1ec,验收通过)
  • Phase B · 质变MCP 工具→InvokableTool + ReAct agent(模型自主调工具,验收 7/7 命中)
  • Phase C · 编排归一 全图 DSL→compose.Graph 编译器(全节点 + branch + DAG 并行调度)+ callbacks→ExecEvent 归一,等价回归通过(EINO_COMPOSE 灰度开关,默认关;过渡期后 graph.go 退役)
  • [~] Phase D · 状态化执行 任务生命周期 FSM / HITL 人工审批中断(审批节点暂停→waiting→批准回 running / 拒绝→rejectedNATS 决定回传 + AckWait 续租,全栈含 Studio 审批节点 + 运行抽屉批准条)/ 多智能体(按场景)

组件化补完(A:检索 → ragRetriever(components/retriever.Retrievereino_components.go);提示词 → buildMessages 改用 prompt.FromMessages+MessagesPlaceholder;工具 → mcpTool(InvokableTool,模型自主调用)。至此终态架构 8 层中 模型/工具/检索/提示词/编排/智能体(单)/可观测 均已 Eino 组件化;剩 人机交互(中断恢复,按场景)。 自主 agent 工具集动态化:agent 工具集不再硬编码——mcp-go 注册表(单一事实源)每个工具声明 agent/params/injectlist_tools 上报,dispatcher agentTools() 动态发现并建 InvokableTool、运行时注入 user_id/session_id/kb(不暴露给模型)。加工具只改 mcp-go 注册表,dispatcher 零改动。 当前暴露 4 个(wiki_search/recall_user_memory/remember_user_fact/history_get);实测模型自主调用新暴露的 remember_user_fact 成功(参数自生成、user_id 服务端注入)。 性能注记compose 每任务编译实测 ~13µs(基准 BenchmarkComposeCompile),相对 LLM 秒级可忽略 → 编译图缓存判定为 premature optimization,暂不做;真正的并行效率已由 Phase C 的 DAG 调度(AllPredecessor)拿到。


0. 现状审计(2026-06-22

实际跑在 sundynix-dispatcher/internal/ 下:

维度 现状 用没用 Eino
消息/流类型 schema.MessageStreamReaderPipe 全链路在用 真用(承重)
ChatModel llm/pool.go 自研 OpenAI 兼容客户端(手写 SSE 流解析) 直连,绕过框架
eino/model.gopoolModel 实现了 model.BaseChatModel,但 newPoolModel 从未被调用 ⚠️ 空接缝
工具调用 NATS request-replyToolCall/ToolResult),图里画死 自研,无函数调用
编排 eino/graph.go 自研解释器(拓扑/分支真假边/map 并行),有测试 compose
检索 mcp-go RAGMilvus/Bleve/Neo4j RRF),经 wiki_search 工具 自研
提示词 report.go / memory_extract.go 手拼字符串
可观测 自研 ExecEvent 轨迹流 + Prometheus + eval 护栏 (不缺)
人机交互

结论:卡在最底层——没有真正的 ChatModel 组件,ADK / 编排都用不起来。采纳必须自底向上

Eino v0.9.9 可用包(已核对本地 module)

github.com/cloudwego/eino/schema              # ✅ 已用
github.com/cloudwego/eino/components/model    # ChatModel 接口
github.com/cloudwego/eino/components/tool     # Tool 接口
github.com/cloudwego/eino/components/retriever# Retriever 接口
github.com/cloudwego/eino/components/prompt   # ChatTemplate
github.com/cloudwego/eino/compose             # Graph / Workflow / branch / tool_node / lambda
github.com/cloudwego/eino/flow/agent/react    # ReAct agent
github.com/cloudwego/eino/flow/agent/multiagent
github.com/cloudwego/eino/adk                 # Agent Development Kitagent_tool / react / callback
github.com/cloudwego/eino/callbacks           # 回调/可观测
github.com/cloudwego/eino-ext/...             # ⚠️ 官方组件实现(openai 等),尚未拉取,Phase A 新增

终态架构

现在 终态
模型 自研 llm.Pool eino-ext openai 组件 + 我们的热更新/熔断包一层
工具 NATS CallTool,画死 每个 MCP 工具包成 components/tool.InvokableToolBindTools 给模型动态选
检索 wiki_search 工具 mcp-go RAG 包成 components/retriever.Retriever
提示词 手拼 components/prompt.ChatTemplate
编排 自研 graph.go compose.Graph(流式 reduce/branch + 类型化边)
智能体 flow/agent/reactadk 的 ReAct agent;后续多智能体
可观测 ExecEvent + Prom callbacks 桥接到现有 ExecEvent,统一
人机交互 中断/恢复(审批场景,需 checkpoint store

Phase A · 地基:接真 ChatModel 组件 P0 已完成(commit d84b1ec

落地:llm.Pool 内部已换成 eino-ext/openai 的 ChatModelChatStream→StreamChat→Generate),对外签名不变;go get eino-ext/components/model/openai v0.1.13;真实链路验证通过(提交任务流式出答复 + eval 1.00)。poolModel 接缝保留给 B/C。

目标:模型调用走 Eino 官方 ChatModel 组件,接上空着的 poolModel 接缝;行为对现有用例不变。

依赖go get github.com/cloudwego/eino-ext/components/model/openai(确认与 eino v0.9.9 兼容版本)。

改动

  • sundynix-dispatcher/internal/llm/pool.go
    • 内部把手写 HTTP/SSE 换成 eino openai 组件作为 backendSetConfig 时用激活配置 New 一个 ChatModel 实例(热更新 = 重建实例,加读写锁)。
    • 保留对外签名:Chat / ChatStream / StreamText / Ready / ModelName,让 graph.go/report.go/memory_extract.go 零改动
    • 熔断器(harness.CircuitBreaker)仍包在 Pool 这层。
  • sundynix-dispatcher/internal/eino/model.go
    • poolModel 真正包住组件后的 Pool(或直接暴露底层 model.BaseChatModel),供 Phase B/C 用。

验收

  • make test-go(含 eino/graph_test.gointegration_test.gomemory_extract_test.go)全绿。
  • 桌面端跑一次编排 / 一次报告 / 触发一次记忆 consolidate,输出与现状一致。
  • 「服务状态」面板 dispatcher 仍显示模型名 + 运行时长。

风险/回退:低。出问题回退到手写客户端(保留旧代码一版,绿后再删)。


Phase B · 质变:函数调用 + ADK 单智能体 〔P1〕 已完成

落地:新增 eino/react_agent.go——mcpTool 把 MCP 工具(NATS)适配成 tool.InvokableToolagent 节点带 autonomous:true 时走 flow/agent/react(首批工具:wiki_searchrecall_user_memorycontext 参数 user_id 服务端注入、不暴露给模型)。验收:自主工具调用 7/7 命中,args={} 证明注入生效,完整闭环(模型→tool→MCP→观察→答复)。

两个工程发现

  1. StreamToolCallChecker 必须扫整段流:默认只看首个流片段,deepseek 常先吐文本再给 tool call → 漏判致不调工具。已用 streamHasToolCall 扫全流修复(命中率 ~0 → 7/7)。
  2. eval 看不到工具结果 → 误判LLM 评委只见 query+answeragent 用召回记忆作答会被判"虚构"。Phase C 用 callbacks 把工具上下文喂给 eval 修正。

目标:模型能自主选择并调用 MCP 工具(ReAct),而非只跑画死的工具节点。这是"工作流执行器 → Agent 平台"的关键一跳。

改动

  • 新增 sundynix-dispatcher/internal/eino/tools.go
    • 把每个 MCP 工具包成 components/tool.InvokableTool 适配器:InvokableRun 内部走 subscriber.CallTool(ToolSubjectGo/Py, ToolCall)
    • 工具 schema(参数)从哪来:① 先手写少量核心工具的 JSON Schema;② 后续让 MCP list_tools 连参数 schema 一起上报(已有中文名/作用,扩字段即可)。
  • 新增 agent 节点类型
    • dsl/compile.go 识别 kind=="agent" 且开启"自主工具"开关 → 走 ReActflow/agent/reactadk)。
    • agent 绑定:ChatModelPhase A+ 一组 InvokableTool(按节点配置或 owner 可用工具集)。
  • graph.goagent 节点分支委托给 ReAct runner,复用现有 ExecEvent 回流。

验收

  • 画一个「agent + 若干工具」的图,给一句需要检索/记忆的提问,观测模型自己决定调了哪个 MCP 工具ExecEvent 里出现 tool 调用轨迹)。
  • 旧的静态工具流不受影响。

风险:中。ReAct 多轮会放大 token 成本与时延 → 设最大步数 + 复用熔断器;工具 schema 不准会导致乱调 → 先小工具集灰度。


Phase C · 编排归一:迁到 compose.Graph P2 已完成(并存灰度,等价回归通过)

全图编译器:compose_compiler.go 把整张 DSL 图编译为 compose.Graph——

  • 每个节点 = 一个 Lambda,节点体复用现有逻辑(execDSLNodeinput/memory/retriever/tool/agent/aggregate/render/map/output),黑板进 compose 本地状态(WithGenLocalState + ProcessState)。
  • branch = AddBranch + 状态感知条件(复用 branchNode 选路);边载荷用空 flowSignal(注册 no-op 合并支持 fan-in),真实数据全走黑板。
  • WithNodeTriggerMode(AllPredecessor) DAG 模式:无依赖节点并行调度(效率)。
  • Handle → executeGraphEINO_COMPOSE 开关选 compose / graph.gocompose 编译失败自动降级回 graph.go(安全网)。
  • 等价回归:线性图、分支图经解释器与 compose 两路径产出逐字一致(单测);live 多节点分支图 compose 路径 2800 字答复 eval 1.00、FSM done、0 幽灵。

待过渡期 soak 后把默认翻到 compose、退役 graph.go。性能后续:编译图按 DSL-hash 缓存(当前每任务编译一次)。

落地(并存+等价回归策略):对话主流程已可跑在 compose.Graph 上——

  • compose_graph.gorunConversationEINO_COMPOSE 开关分流;runComposeConversation 建图 START→ChatModel→ENDCompileStream,token 回流;模型未就绪/编译失败降级回 runAgent默认关,graph.go 仍是默认且权威。
  • compose_callbacks.gocomposeTracerutils/callbacks 把 ChatModel/Tool 的 start/end/error 桥到 ExecEvent(可观测归一)。
  • 测试:compose 图编译+运行、compose 对话流式回流、开关关→走 runAgent,三个单测;live 实测 compose 路径出 54 字答复 + eval 1.00,默认路径 eval 1.00。
  • 顺带修真 bugSubjectTaskStatus 原为 sundynix.tasks.status,落在任务流捕获通配 sundynix.tasks.> 内 → 状态事件被当成"幽灵任务"自我放大(实测污染 2300+ 条)。已挪到 sundynix.status.task + dispatcher 加空任务护栏。

剩余:branch / map / render / retriever / prompt 等节点逐步迁 compose(同并存+等价回归),对齐后 graph.go 退役。

目标:自研解释器退役,DSL 图编译为 compose.Graph,吃到流式 reduce/branch、类型化边、自动并发、callbacks。

改动

  • sundynix-dispatcher/internal/dsl/compile.go(兑现文件头那句 TODO"演进为 compose.NewGraph 的完整多节点编译"
    • DSL Flow → compose.Graph 编译器:节点映射为 ChatModel / ToolNode / Retriever / Lambda;连线 → 边;branch 真假 → compose 分支;map → 并发分支。
  • callbacks:实现 handler 把 Eino 回调桥接到现有 ExecEvent(节点 start/end/error + 耗时),统一可观测,不重复造。
  • report.go:报告多步流水线改用 compose(章节并行 + 汇聚天然契合)。
  • 检索节点 → components/retriever(包 mcp-go RAG);提示词 → components/prompt.ChatTemplate
  • 对齐策略compose 版与 graph.go 版并存,用 graph_test.go/integration_test.go 做等价回归;全绿且灰度通过后,graph.go 才退役。

验收:分支路由、map 并行、报告流水线在 compose 版下与旧版输出等价ExecEvent 轨迹不丢。

风险:高(动编排核心)。靠"并存 + 等价测试 + 灰度"控制,绝不一刀切。


Phase D · 状态化执行 〔later,按场景〕

主题:把"执行"从一次性 DAG 升级为可持久化、可恢复的状态机。三件事同一条线,一起做。

  • 任务生命周期 FSM 已完成:submitted → running → done / failed / timeout 显式状态机。
    • dispatcher 经新主题 sundynix.status.task 回写(TaskStatusEvent):进入执行→running、收尾→done/failed、整体超时上限 taskExecTimeout=3min→timeout;网关 SubscribeTaskStatus 落 PGTask.Status/Detail)。
    • UI 轮询:GET /api/v1/tasks/:id 返回 {status, detail}
    • 验收:submitted→running→done 实测流转、PG 持久化、3min 超时兜底——根治"卡运行中看不出来"。
    • 桌面端轮询接线仍可补(当前后端 + 端点已就绪)。
  • 中断/恢复(HITL:审批型工业流程(生成中途人工确认)。需 checkpoint 持久化(PG/Redis)。等有具体审批用例再做。
  • 多智能体协同flow/agent/multiagent):出现真实多角色编排需求时再上,现在无用例。

暂时不做

  • Ollama 官方组件:与"开发期不拉本地 Ollama"策略冲突(见记忆 llm-provider-strategy)。
  • 拿 callbacks 当唯一理由迁移:已有 ExecEvent + Prometheus + eval 护栏,callbacks 仅在 Phase C 顺带桥接。

落地顺序与依赖

AChatModel 组件)── 地基,解锁全部
   └─> BTool 适配 + ADK ReAct)── 质变:模型自主调工具
          └─> Ccompose 编排 + callbacks + retriever/prompt)── 编排归一
                 └─> D(状态化执行:任务生命周期 FSM / HITL 中断恢复 / 多智能体)── 按场景

逐项实现,每 Phase 一个(或多个)提交,跑 make test-go + 桌面端冒烟后再进下一阶段。


总验收/回归基线(每阶段都要过)

  1. make test-goshared / gateway / dispatcher / mcp-go 全绿。
  2. 桌面端冒烟:编排运行(含分支/并行)、报告生成+导出、记忆 consolidate、知识库检索。
  3. 管理端「服务状态」:四服务 + 五基建 + 工具注册正常。
  4. 行为等价:迁移前后同输入输出一致(Phase C 重点)。