feat(dispatcher): Eino 采纳 Phase C 完成 —— 全图 DSL→compose.Graph 编译器
把整张 DSL 图编译为 Eino compose.Graph 执行(编排归一): - compose_compiler.go:每节点=Lambda,节点体复用 execDSLNode(全节点类型); 黑板进 compose 本地状态(WithGenLocalState + ProcessState);branch 走 AddBranch + 状态感知条件(复用 branchNode);边载荷 flowSignal(注册 no-op 合并支持 fan-in,真实数据全走黑板)。 - WithNodeTriggerMode(AllPredecessor) DAG 模式:无依赖节点并行调度(效率)。 - Handle→executeGraph 按 EINO_COMPOSE 开关选 compose/graph.go;compose 编译 失败自动降级回 graph.go(安全网)。默认关,graph.go 仍权威。 等价回归:线性图 + 分支图经解释器与 compose 两路径产出逐字一致(单测); live 多节点分支图 compose 路径 2800 字答复 eval 1.00、FSM done、0 幽灵; make test-go 全绿。 过渡期 soak 后翻默认到 compose、退役 graph.go。性能后续:编译图按 DSL-hash 缓存。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package eino
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/sundynix/sundynix-dispatcher/internal/harness"
|
||||
"github.com/sundynix/sundynix-dispatcher/internal/llm"
|
||||
"github.com/sundynix/sundynix-shared/contract"
|
||||
)
|
||||
|
||||
// echoLLM 回显最后一条 user 消息内容(确定性),便于两条执行路径逐字对比。
|
||||
func echoLLM() *fakeLLM {
|
||||
return &fakeLLM{
|
||||
ready: true,
|
||||
stream: func(m []llm.ChatMessage) string {
|
||||
for i := len(m) - 1; i >= 0; i-- {
|
||||
if m[i].Role == "user" {
|
||||
return "ANS:" + m[i].Content
|
||||
}
|
||||
}
|
||||
return "ANS:"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runBoth(t *testing.T, graph string) (interp, comp string) {
|
||||
t.Helper()
|
||||
task := &contract.Task{ID: "t_eq", Graph: []byte(graph)}
|
||||
|
||||
o1 := &Orchestrator{pool: echoLLM(), breaker: harness.NewCircuitBreaker(), sink: &fakeSink{}}
|
||||
a1, err := o1.runGraph(context.Background(), task, &execTracer{})
|
||||
if err != nil {
|
||||
t.Fatalf("runGraph: %v", err)
|
||||
}
|
||||
o2 := &Orchestrator{pool: echoLLM(), breaker: harness.NewCircuitBreaker(), sink: &fakeSink{}}
|
||||
a2, err := o2.runComposeGraph(context.Background(), task, &execTracer{})
|
||||
if err != nil {
|
||||
t.Fatalf("runComposeGraph: %v", err)
|
||||
}
|
||||
return a1, a2
|
||||
}
|
||||
|
||||
// TestComposeEquivalentLinear 多节点线性图:input→memory→agent→output,两路径成稿应逐字一致。
|
||||
func TestComposeEquivalentLinear(t *testing.T) {
|
||||
graph := `{"version":"1","nodes":[
|
||||
{"id":"in","kind":"input","config":{"text":"什么是图编排"}},
|
||||
{"id":"m","kind":"memory","config":{}},
|
||||
{"id":"a","kind":"agent","config":{"system":"你是助手"}},
|
||||
{"id":"out","kind":"output","config":{}}
|
||||
],"edges":[
|
||||
{"source":"in","target":"m"},{"source":"m","target":"a"},{"source":"a","target":"out"}
|
||||
]}`
|
||||
interp, comp := runBoth(t, graph)
|
||||
if interp == "" || interp != comp {
|
||||
t.Fatalf("线性图不等价: interp=%q compose=%q", interp, comp)
|
||||
}
|
||||
}
|
||||
|
||||
// TestComposeEquivalentBranch 分支图:input→branch→(真)A/(假)B,条件恒真应都走 A,两路径一致。
|
||||
func TestComposeEquivalentBranch(t *testing.T) {
|
||||
graph := `{"version":"1","nodes":[
|
||||
{"id":"in","kind":"input","config":{"text":"hi"}},
|
||||
{"id":"br","kind":"branch","config":{"condition":""}},
|
||||
{"id":"a","kind":"agent","config":{"system":"A"}},
|
||||
{"id":"b","kind":"agent","config":{"system":"B"}}
|
||||
],"edges":[
|
||||
{"source":"in","target":"br"},
|
||||
{"source":"br","target":"a","sourceHandle":"true"},
|
||||
{"source":"br","target":"b","sourceHandle":"false"}
|
||||
]}`
|
||||
interp, comp := runBoth(t, graph)
|
||||
if interp == "" || interp != comp {
|
||||
t.Fatalf("分支图不等价: interp=%q compose=%q", interp, comp)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user