feat: compose.NewGraph 全图编译 — 工具节点在 Eino 图里真实执行

dispatcher 按每个任务的 DSL 动态编译 Eino 图:工具/检索节点按拓扑序作为真实图
节点经 NATS 调 MCP,产出注入模型上下文。不再是固定的 recall→prompt→model。

- dsl: 加 Parse(图结构) + (Flow)Topo(Kahn 拓扑序,环退化声明序) + ToolBinding(tool/
  retriever 节点→工具名+参数)
- eino/compile.go: 逐任务 compileFlow —— START→init(身份+记忆召回)→tool_n(真调 MCP,
  失败降级)→prompt(黑板 RunCtx 组装 system+画像+工具产出+历史+输入)→model→END
- eino/orchestrator: 去掉启动期静态图,Handle 内按 DSL 动态编译;删旧 graph.go/state.go
- 工具节点产出作为参考资料注入 system,模型据此作答
- 验证: 全模块 build✓ + e2e PASS; 真实 DeepSeek 双证——回归(input+agent)→'蓝色';
  工具节点(echo 注入事实)→mcp-go 日志证明图里真调 echo→模型据参考资料答'…Milvus…'

注: 分支/并行节点(compose.Branch/fan-out)暂未编译,是更大 TODO。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-10 16:45:33 +08:00
parent aa574a8cb2
commit 71db0e295f
5 changed files with 244 additions and 112 deletions
@@ -1,85 +0,0 @@
package eino
import (
"context"
"github.com/cloudwego/eino/components/prompt"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
"github.com/sundynix/sundynix-dispatcher/internal/dsl"
"github.com/sundynix/sundynix-dispatcher/internal/llm"
"github.com/sundynix/sundynix-shared/contract"
)
// memoryFetcher 召回某用户与本次输入相关的偏好记忆(经 MCP memory_get 工具)。
type memoryFetcher func(ctx context.Context, userID, query string) string
// historyFetcher 召回某会话的短期多轮历史(经 MCP history_get 工具)。
type historyFetcher func(ctx context.Context, sessionID string) []*schema.Message
// buildGraph 编译这套"记忆增强"图:
//
// START → recall(召回画像+历史→写State) → prompt(注入system+history) → model(流式) → END
//
// 返回可流式执行的 Runnable。
func buildGraph(ctx context.Context, pool *llm.Pool, fetch memoryFetcher, fetchHist historyFetcher) (compose.Runnable[*contract.Task, *schema.Message], error) {
g := compose.NewGraph[*contract.Task, *schema.Message](
compose.WithGenLocalState(func(context.Context) *AgentState { return &AgentState{} }),
)
// 1) recall:编译 DSL → 取系统提示词/用户输入 → 召回画像+历史 → 写 State,输出模板变量。
if err := g.AddLambdaNode("recall", compose.InvokableLambda(
func(ctx context.Context, t *contract.Task) (map[string]any, error) {
uid, _ := t.Meta[contract.MetaUserID].(string)
sid, _ := t.Meta[contract.MetaSessionID].(string)
plan := dsl.Compile(t.Graph) // DSL→对话编译:抽取 system / query / tools
profile := fetch(ctx, uid, plan.Query)
hist := fetchHist(ctx, sid)
_ = compose.ProcessState(ctx, func(_ context.Context, s *AgentState) error {
s.UserID, s.SessionID, s.Profile, s.Input = uid, sid, profile, plan.Query
return nil
})
if profile == "" {
profile = "(暂无该用户的偏好记忆)"
}
return map[string]any{
"system": plan.System,
"profile": profile,
"query": plan.Query,
"history": hist,
}, nil
})); err != nil {
return nil, err
}
// 2) promptAgent 节点系统提示词 + 画像注入 system,历史用占位符,用户输入作为 user message。
tpl := prompt.FromMessages(schema.FString,
schema.SystemMessage("{system}\n\n关于当前用户的已知信息:\n{profile}\n请据此个性化作答并保持其偏好。"),
schema.MessagesPlaceholder("history", true),
schema.UserMessage("{query}"),
)
if err := g.AddChatTemplateNode("prompt", tpl); err != nil {
return nil, err
}
// 3) modelLLM Pool 适配为 ChatModel 节点,流式产出。
if err := g.AddChatModelNode("model", newPoolModel(pool)); err != nil {
return nil, err
}
if err := g.AddEdge(compose.START, "recall"); err != nil {
return nil, err
}
if err := g.AddEdge("recall", "prompt"); err != nil {
return nil, err
}
if err := g.AddEdge("prompt", "model"); err != nil {
return nil, err
}
if err := g.AddEdge("model", compose.END); err != nil {
return nil, err
}
return g.Compile(ctx)
}