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:
@@ -39,10 +39,69 @@ type Plan struct {
|
||||
|
||||
const defaultSystem = "你是 sundynix-agentix 平台的 AI 助手。"
|
||||
|
||||
// Parse 把 DSL 原文解析为图结构。
|
||||
func Parse(graph json.RawMessage) (*Flow, error) {
|
||||
var f Flow
|
||||
if err := json.Unmarshal(graph, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &f, nil
|
||||
}
|
||||
|
||||
// Topo 返回节点的拓扑序(Kahn);无边/有环时退化为声明顺序。
|
||||
func (f *Flow) Topo() []Node {
|
||||
byID := make(map[string]Node, len(f.Nodes))
|
||||
indeg := make(map[string]int, len(f.Nodes))
|
||||
adj := make(map[string][]string)
|
||||
for _, n := range f.Nodes {
|
||||
byID[n.ID] = n
|
||||
indeg[n.ID] = 0
|
||||
}
|
||||
for _, e := range f.Edges {
|
||||
if _, ok := byID[e.Source]; !ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := byID[e.Target]; !ok {
|
||||
continue
|
||||
}
|
||||
adj[e.Source] = append(adj[e.Source], e.Target)
|
||||
indeg[e.Target]++
|
||||
}
|
||||
var queue, order []string
|
||||
for _, n := range f.Nodes { // 按声明序入队,保证确定性
|
||||
if indeg[n.ID] == 0 {
|
||||
queue = append(queue, n.ID)
|
||||
}
|
||||
}
|
||||
for len(queue) > 0 {
|
||||
id := queue[0]
|
||||
queue = queue[1:]
|
||||
order = append(order, id)
|
||||
for _, t := range adj[id] {
|
||||
indeg[t]--
|
||||
if indeg[t] == 0 {
|
||||
queue = append(queue, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
out := make([]Node, 0, len(f.Nodes))
|
||||
seen := make(map[string]bool)
|
||||
for _, id := range order {
|
||||
out = append(out, byID[id])
|
||||
seen[id] = true
|
||||
}
|
||||
for _, n := range f.Nodes { // 有环时补齐剩余
|
||||
if !seen[n.ID] {
|
||||
out = append(out, n)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Compile 解析 DSL 图,抽取对话计划。无法解析时退化为把原文当输入(兼容旧行为)。
|
||||
func Compile(graph json.RawMessage) Plan {
|
||||
var f Flow
|
||||
if err := json.Unmarshal(graph, &f); err != nil || len(f.Nodes) == 0 {
|
||||
f, err := Parse(graph)
|
||||
if err != nil || len(f.Nodes) == 0 {
|
||||
return Plan{System: defaultSystem, Query: strings.TrimSpace(string(graph))}
|
||||
}
|
||||
|
||||
@@ -78,6 +137,26 @@ func Compile(graph json.RawMessage) Plan {
|
||||
return Plan{System: system, Query: query, Tools: tools}
|
||||
}
|
||||
|
||||
// ToolBinding 从 tool/retriever 节点抽取要调用的 MCP 工具名与参数。
|
||||
// 非工具型节点返回空工具名(由编译器跳过)。
|
||||
func ToolBinding(n Node) (tool string, args map[string]any) {
|
||||
args = map[string]any{}
|
||||
switch n.Kind {
|
||||
case "tool":
|
||||
tool = str(n.Config["tool"])
|
||||
if raw := str(n.Config["args"]); raw != "" {
|
||||
_ = json.Unmarshal([]byte(raw), &args) // 前端 args 为 JSON 字符串
|
||||
}
|
||||
case "retriever":
|
||||
// 检索节点暂映射到 wiki_search;RAG 接真后改 memory_search / 真实混合检索。
|
||||
tool = "wiki_search"
|
||||
if kb := str(n.Config["kb"]); kb != "" {
|
||||
args["kb"] = kb
|
||||
}
|
||||
}
|
||||
return tool, args
|
||||
}
|
||||
|
||||
func str(v any) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
|
||||
Reference in New Issue
Block a user