feat: DSL→对话编译 — Eino 图用节点字段而非整段 JSON 喂模型
dispatcher 真正解析 DSL 图:input 节点文本=用户消息,agent 节点 system=系统提示词,
不再把整段 DSL JSON 当 prompt 丢给模型。
- dispatcher/internal/dsl: Compile(graph)→Plan{System,Query,Tools}
(input.text/agent.prompt→query, agent.system→system, tool.tool→tools, 兜底默认)
- eino/graph: recall 调 dsl.Compile,模板加 {system}(Agent 系统提示词+画像注入)
- eino/orchestrator: 写回历史落真实 query 而非 DSL 原文
- frontend nodeCatalog: input 节点改 text 字段(用户输入,必填),检查器可编辑
- 验证: 全模块+前端 build✓; 真实 DeepSeek——curl DSL(input '中国首都?')→'北京';
真实浏览器——加 input 节点输入'NATS是什么'→运行→DeepSeek 简洁正确作答
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -30,10 +30,10 @@ export const NODE_KINDS: Record<string, NodeKind> = {
|
|||||||
badge: "bg-amber-100 text-amber-700",
|
badge: "bg-amber-100 text-amber-700",
|
||||||
desc: "用户提问 / 文件 / 变量",
|
desc: "用户提问 / 文件 / 变量",
|
||||||
fields: [
|
fields: [
|
||||||
|
{ key: "text", label: "用户输入", type: "textarea", placeholder: "要发送给模型的内容…", required: true },
|
||||||
{ key: "source", label: "来源", type: "select", options: ["用户输入", "本地文件", "变量"] },
|
{ key: "source", label: "来源", type: "select", options: ["用户输入", "本地文件", "变量"] },
|
||||||
{ key: "placeholder", label: "占位文案", type: "text", placeholder: "请输入…" },
|
|
||||||
],
|
],
|
||||||
defaults: { source: "用户输入", placeholder: "请输入…" },
|
defaults: { text: "", source: "用户输入" },
|
||||||
},
|
},
|
||||||
retriever: {
|
retriever: {
|
||||||
kind: "retriever",
|
kind: "retriever",
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
// Package dsl 把前端导出的 JSON DSL 图编译为可执行的对话计划。
|
||||||
|
// 当前从图中抽取「系统提示词 / 用户输入 / 工具节点」;后续可演进为
|
||||||
|
// compose.NewGraph 的完整多节点编译(分支/并行/工具节点逐一映射)。
|
||||||
|
package dsl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Node 是 DSL 图的一个节点(与前端 exportDsl 对齐)。
|
||||||
|
type Node struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Config map[string]any `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge 是一条连线。
|
||||||
|
type Edge struct {
|
||||||
|
Source string `json:"source"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flow 是整张图。
|
||||||
|
type Flow struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Nodes []Node `json:"nodes"`
|
||||||
|
Edges []Edge `json:"edges"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plan 是编译后的对话计划。
|
||||||
|
type Plan struct {
|
||||||
|
System string // 系统提示词(来自 agent 节点的 system;空则默认)
|
||||||
|
Query string // 用户输入(来自 input 节点 text / agent 节点 prompt)
|
||||||
|
Tools []string // 图中工具节点绑定的 MCP 工具名(供后续工具编排用)
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultSystem = "你是 sundynix-agentix 平台的 AI 助手。"
|
||||||
|
|
||||||
|
// Compile 解析 DSL 图,抽取对话计划。无法解析时退化为把原文当输入(兼容旧行为)。
|
||||||
|
func Compile(graph json.RawMessage) Plan {
|
||||||
|
var f Flow
|
||||||
|
if err := json.Unmarshal(graph, &f); err != nil || len(f.Nodes) == 0 {
|
||||||
|
return Plan{System: defaultSystem, Query: strings.TrimSpace(string(graph))}
|
||||||
|
}
|
||||||
|
|
||||||
|
var queries, systems, tools []string
|
||||||
|
for _, n := range f.Nodes {
|
||||||
|
switch n.Kind {
|
||||||
|
case "input":
|
||||||
|
if t := str(n.Config["text"]); t != "" {
|
||||||
|
queries = append(queries, t)
|
||||||
|
}
|
||||||
|
case "agent":
|
||||||
|
if s := str(n.Config["system"]); s != "" {
|
||||||
|
systems = append(systems, s)
|
||||||
|
}
|
||||||
|
if p := str(n.Config["prompt"]); p != "" { // 单 agent 节点快速测试时直接带 prompt
|
||||||
|
queries = append(queries, p)
|
||||||
|
}
|
||||||
|
case "tool":
|
||||||
|
if t := str(n.Config["tool"]); t != "" {
|
||||||
|
tools = append(tools, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
system := strings.Join(systems, "\n")
|
||||||
|
if system == "" {
|
||||||
|
system = defaultSystem
|
||||||
|
}
|
||||||
|
query := strings.Join(queries, "\n")
|
||||||
|
if query == "" {
|
||||||
|
query = "你好" // 无结构化输入时的兜底,避免给模型发空消息
|
||||||
|
}
|
||||||
|
return Plan{System: system, Query: query, Tools: tools}
|
||||||
|
}
|
||||||
|
|
||||||
|
func str(v any) string {
|
||||||
|
if v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
return strings.TrimSpace(s)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(fmt.Sprint(v))
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/cloudwego/eino/compose"
|
"github.com/cloudwego/eino/compose"
|
||||||
"github.com/cloudwego/eino/schema"
|
"github.com/cloudwego/eino/schema"
|
||||||
|
|
||||||
|
"github.com/sundynix/sundynix-dispatcher/internal/dsl"
|
||||||
"github.com/sundynix/sundynix-dispatcher/internal/llm"
|
"github.com/sundynix/sundynix-dispatcher/internal/llm"
|
||||||
"github.com/sundynix/sundynix-shared/contract"
|
"github.com/sundynix/sundynix-shared/contract"
|
||||||
)
|
)
|
||||||
@@ -27,28 +28,34 @@ func buildGraph(ctx context.Context, pool *llm.Pool, fetch memoryFetcher, fetchH
|
|||||||
compose.WithGenLocalState(func(context.Context) *AgentState { return &AgentState{} }),
|
compose.WithGenLocalState(func(context.Context) *AgentState { return &AgentState{} }),
|
||||||
)
|
)
|
||||||
|
|
||||||
// 1) recall:取 user_id/session_id → 召回画像(memory_get)+历史(history_get) → 写 State,输出模板变量。
|
// 1) recall:编译 DSL → 取系统提示词/用户输入 → 召回画像+历史 → 写 State,输出模板变量。
|
||||||
if err := g.AddLambdaNode("recall", compose.InvokableLambda(
|
if err := g.AddLambdaNode("recall", compose.InvokableLambda(
|
||||||
func(ctx context.Context, t *contract.Task) (map[string]any, error) {
|
func(ctx context.Context, t *contract.Task) (map[string]any, error) {
|
||||||
uid, _ := t.Meta[contract.MetaUserID].(string)
|
uid, _ := t.Meta[contract.MetaUserID].(string)
|
||||||
sid, _ := t.Meta[contract.MetaSessionID].(string)
|
sid, _ := t.Meta[contract.MetaSessionID].(string)
|
||||||
profile := fetch(ctx, uid, string(t.Graph))
|
plan := dsl.Compile(t.Graph) // DSL→对话编译:抽取 system / query / tools
|
||||||
|
profile := fetch(ctx, uid, plan.Query)
|
||||||
hist := fetchHist(ctx, sid)
|
hist := fetchHist(ctx, sid)
|
||||||
_ = compose.ProcessState(ctx, func(_ context.Context, s *AgentState) error {
|
_ = compose.ProcessState(ctx, func(_ context.Context, s *AgentState) error {
|
||||||
s.UserID, s.SessionID, s.Profile, s.Input = uid, sid, profile, string(t.Graph)
|
s.UserID, s.SessionID, s.Profile, s.Input = uid, sid, profile, plan.Query
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if profile == "" {
|
if profile == "" {
|
||||||
profile = "(暂无该用户的偏好记忆)"
|
profile = "(暂无该用户的偏好记忆)"
|
||||||
}
|
}
|
||||||
return map[string]any{"profile": profile, "query": string(t.Graph), "history": hist}, nil
|
return map[string]any{
|
||||||
|
"system": plan.System,
|
||||||
|
"profile": profile,
|
||||||
|
"query": plan.Query,
|
||||||
|
"history": hist,
|
||||||
|
}, nil
|
||||||
})); err != nil {
|
})); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) prompt:画像注入 system,历史用占位符插入,用户输入作为 user message。
|
// 2) prompt:Agent 节点系统提示词 + 画像注入 system,历史用占位符,用户输入作为 user message。
|
||||||
tpl := prompt.FromMessages(schema.FString,
|
tpl := prompt.FromMessages(schema.FString,
|
||||||
schema.SystemMessage("你在与特定用户对话。关于该用户的已知信息:\n{profile}\n请据此个性化作答并保持其偏好。"),
|
schema.SystemMessage("{system}\n\n关于当前用户的已知信息:\n{profile}\n请据此个性化作答并保持其偏好。"),
|
||||||
schema.MessagesPlaceholder("history", true),
|
schema.MessagesPlaceholder("history", true),
|
||||||
schema.UserMessage("{query}"),
|
schema.UserMessage("{query}"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/cloudwego/eino/compose"
|
"github.com/cloudwego/eino/compose"
|
||||||
"github.com/cloudwego/eino/schema"
|
"github.com/cloudwego/eino/schema"
|
||||||
|
|
||||||
|
"github.com/sundynix/sundynix-dispatcher/internal/dsl"
|
||||||
"github.com/sundynix/sundynix-dispatcher/internal/harness"
|
"github.com/sundynix/sundynix-dispatcher/internal/harness"
|
||||||
"github.com/sundynix/sundynix-dispatcher/internal/llm"
|
"github.com/sundynix/sundynix-dispatcher/internal/llm"
|
||||||
"github.com/sundynix/sundynix-shared/contract"
|
"github.com/sundynix/sundynix-shared/contract"
|
||||||
@@ -168,7 +169,7 @@ func (o *Orchestrator) memorize(t *contract.Task, answer string) {
|
|||||||
uid, _ := t.Meta[contract.MetaUserID].(string)
|
uid, _ := t.Meta[contract.MetaUserID].(string)
|
||||||
sid, _ := t.Meta[contract.MetaSessionID].(string)
|
sid, _ := t.Meta[contract.MetaSessionID].(string)
|
||||||
if sid != "" && o.tools != nil {
|
if sid != "" && o.tools != nil {
|
||||||
o.appendHistory(sid, "user", string(t.Graph))
|
o.appendHistory(sid, "user", dsl.Compile(t.Graph).Query) // 落真实用户输入,而非 DSL 原文
|
||||||
o.appendHistory(sid, "assistant", answer)
|
o.appendHistory(sid, "assistant", answer)
|
||||||
log.Printf("[eino] (writeback) task %s 已落会话历史 session=%s", t.ID, sid)
|
log.Printf("[eino] (writeback) task %s 已落会话历史 session=%s", t.ID, sid)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user