Commit Graph

41 Commits

Author SHA1 Message Date
Blizzard 700845d64a feat(prod): DB 连接池上限 + LLM 失败暴露为 failed(生产级并发收尾)
为高并发生产做的三项收尾(配合已有的任务/工具并发消费):

1. DB 连接池上限(pgsql.go / memory/store.go):SetMaxOpenConns(默认 25,
   DB_MAX_OPEN_CONNS 可调)+ MaxIdleConns 5 + ConnMaxLifetime 1h。
   防高并发无限开连接打爆 PG(max_connections 默认 100)。
2. LLM 失败暴露为 failed(graph.go):board.fatalErr —— agent 模型调用出错即上抛,
   runGraph 中止后续节点并返回错误 → Handle 判 failed(带原因),不再静默 done-空。
   可观测/可告警,生产排障必需。

压测验证(dispatcher 并发=50, 池=25, deepseek-v4-pro 推理):
- 平台同一秒并发收下 40 任务,全程零 DB/连接错误,平台开销≈0(裸 LLM 1.8s vs 平台 P50 1.7s)。
- 并发 10 健康 4.7/s;20+ 延迟暴涨 = DeepSeek 开发账号并发限流(外部),非平台。
- 失败注入(错误模型名)→ 任务正确判 failed 并回传原因。

结论:平台并发机制达生产级;真实吞吐上限 = 自托管模型容量(生产 Qwen,可加卡线性扩)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 16:14:19 +08:00
Blizzard 38a74e3911 fix(history): 空答复不落历史 + 发送前过滤空消息(根治会话毒化)
现象:某轮 LLM 返回空(余额不足/失败/降级)→ 空 assistant 消息被写进会话历史
→ 此后该 session 每次请求都带一条空 content 的 assistant 消息 → DeepSeek 400
"Invalid assistant message: content or tool_calls must be set" → 又空 → 又写空 → 自我循环毒化。

- Fix A(断源):orchestrator.memorize 对空答复直接 return,不落历史(失败的一轮不留痕)。
- Fix B(兜底):buildMessages 发送前剔除 content 与 tool_calls 均空的历史消息,
  防御任何来源的脏历史(含存量)。

验证:清理被污染的 default 会话后恢复正常;新逻辑下空答复不再写入。dispatcher
build+vet+test 全绿。

注:诊断中发现激活模型 deepseek-v4-pro 是推理模型(答案在 reasoning_content,content 为空),
已在管理端切回 deepseek-chat。仍待办:LLM 调用失败应暴露为 failed 而非 done-空输出。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:53:30 +08:00
Blizzard 16a6c4b1aa feat(hitl): 人工审批中断(Eino Phase D)—— 审批节点暂停→批准续跑/拒绝中止
专用「审批」节点方案,全栈打通。

后端:
- contract:TaskWaiting/TaskRejected 状态 + ApprovalSubject/ApprovalDecision。
- bus:PublishApproval + WaitApproval(订阅决定主题,带超时);消费者 AckWait
  30s→15min(阻塞等人审期间消息未 ack,否则重投成重复任务)。
- dispatcher:ApprovalWaiter 接口 + approvalNode——执行到审批节点发 await 事件 +
  置 waiting,阻塞等决定。批准→回 running 放行下游;拒绝/超时→errRejected 哨兵→
  剪下游→rejected,优雅收尾不计熔断。超时安全默认拒绝。
  taskExecTimeout 3→10min(审批5 < 执行10 < AckWait15)。
- 网关:POST /tasks/:id/approve(仅 waiting 态受理,幂等)。

桌面端:
- Studio 新增「人工审批」节点(nodeCatalog,可填标题/说明)。
- run.ts pendingApproval() 从 exec 流派生待审中断 + waiting 节点状态。
- BottomDrawer ApprovalBar:琥珀审批条(摘要 + 批准/拒绝 + 备注,调 api.approveTask)。
- ExecTrace waiting 状态灯。

验证:后端 curl 实测 waiting→批准→running→done;waiting→拒绝→rejected(下游未跑)。
前端 tsc + vitest 36 过(pendingApproval 4 例 + waiting 状态)。全模块 build+vet+test 全绿。
EINO_ADOPTION Phase D 标记 HITL 完成。

未覆盖:compose 路径(EINO_COMPOSE,默认关)的 approval 节点;桌面端审批条未在 GUI 实点。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:18:47 +08:00
Blizzard 218fba559c feat(observability): slog 日志带 trace_id,与链路双向互跳
新增 sundynix-shared/otelx/slog.go:traceHandler 包装 slog.Handler,凡 ctx 有
活跃 span 的 slog.InfoContext(ctx,...) 自动注入 trace_id/span_id;SetupSlog(服务名)
装全局 JSON slog(service 标签 + LOG_LEVEL 控级)并设默认;TraceID(ctx) 辅助取 hex。

- 三个服务 main 启动调 otelx.SetupSlog。
- gateway 访问日志 Observe() 改用 slog.InfoContext(c.Request.Context(),...)(删旧
  accessLogger)→ 每条 HTTP 日志带 trace_id。
- dispatcher orchestrator.Handle 的 received/done/error 改 ctx-aware slog → 任务
  执行日志带 trace_id。
- otelx 单测 5 例(注入/无 span 不注入/With() 后仍生效/级别解析)。
- production_readiness.md 1.1:可观测性三件套(metrics+logs带trace_id+traces)闭环。

验证:dispatcher「task done」日志 trace_id 拿去 Jaeger /api/traces/<id> 命中同一条
11 span/3 服务链路;gateway 访问日志亦带同一 trace_id。四模块 build+vet+test 全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 12:41:33 +08:00
Blizzard a2d184b7ec feat(observability): OpenTelemetry 全链路追踪(Jaeger + NATS 跨总线传播)
新增 sundynix-shared/otelx:otelx.Init(ctx,服务名) 注册 W3C 传播器 + OTLP/HTTP
批量导出(默认 localhost:4318,docker 里的 Jaeger);OTEL_SDK_DISABLED=true 关导出。
Jaeger 不在线 / 导出器构建失败都不阻断启动(可观测性是增益而非依赖)。

- NATS 跨进程传播(无现成中间件):bus/trace.go 的 natsHeaderCarrier + inject/extract,
  PublishTask/CallTool 注入 traceparent,ConsumeTasks/ServeTool 抽出续上 → 链路跨总线连成一棵树。
- 埋点:gateway 挂 otelgin(HTTP server span,链路根);dispatcher task.execute →
  node.<kind>(每节点,nctx 下传使工具/LLM 挂到节点下)→ llm.stream/llm.generate;
  bus 自动出 tool.call(client)↔tool.serve(server) 成对跨服务 span。
- docker-compose 加 jaeger all-in-one(UI :16686,OTLP :4318)。
- 依赖修复:otlptracehttp 触发 genproto 单体(旧)vs 拆分模块 ambiguous import(milvus 拉旧版),
  pin genproto 至后拆分版(go.work 工作区全局生效)。
- production_readiness.md 1.1 更新为「已实现」。

验证:真实 input→retriever→agent 任务在 Jaeger 出 14 span / 3 服务的完整树,
跨 NATS(publish→consume)、跨服务(tool.call→tool.serve)均连通,
瓶颈 kb_search 692ms、llm 1597ms 一眼可见;四模块 build+vet+test 全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:35:08 +08:00
Blizzard 46ef3df221 feat(security): LLM api_key 端到端加密(AES-256-GCM,磁盘+线缆均密文)
新增 sundynix-shared/secrets:AES-256-GCM,密钥由 SUNDYNIX_SECRET_KEY 经
SHA-256 派生;密文带 enc:1: 版本前缀,历史明文行自动透传(下次保存升级为密文)。

- 网关 SaveModel 加密落库;ListModels/TestModel 解密后脱敏/探测;
  空或脱敏占位的 api_key 视为「未改」→ 沿用库内既有密文(不二次加密)。
- 密文经 NATS 原样下发;消费方解密集中在 bus 层 decryptConfig
  (RequestConfig + SubscribeConfigUpdated)→ dispatcher/mcp-go 零改动。
- secrets.MustHaveKeyInProd():生产未设 SUNDYNIX_SECRET_KEY 直接 fatal,
  gateway/dispatcher/mcp-go 启动各调一次(三服务须配相同密钥)。
- 修复 store.SaveModel 整行 Save 把 active 清零的旧 bug:改 Select(...).Updates
  只覆盖可编辑列,改 key 不再顺手取消模型激活。
- secrets 单测:往返/空串/随机 nonce/错密钥 fail-closed/历史明文透传。
- production_readiness.md 2.1 更新为「已落地」。

验证:DB 列由明文 sk-…(35) → 密文 enc:1:…(90);dispatcher 从密文广播解密后
model config set;真实任务 √256→16、12×12→144 打通 DeepSeek(非降级桩)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:36:34 +08:00
Blizzard 5cfed55e91 fix(dispatcher,mcp-go): 配置拉取改为后台重试,根治启动竞态
此前 dispatcher(chat)/mcp-go(embedding) 启动时一次性请求控制面配置,3s 扑空即
降级,且只能干等热更新广播——若消费方早于 gateway 启动,会全程降级(LLM 跑桩、
RAG 无向量),必须手动重启才恢复。

改为:先订阅热更新,再后台 RequestConfigWithRetry(重试至拿到配置,容忍 gateway
晚启)。新增 shared/bus.RequestConfigWithRetry + dispatcher Subscriber 包装。

验收:故意先起 dispatcher/mcp-go、后起 gateway,二者自动重试拿到 chat/embedding
配置,无需手动重启;make test-go 全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:57:06 +08:00
Blizzard f238ae1455 feat(dispatcher): 编排式多智能体接力 —— 上游 agent 产出沿图传给下游
让画布上串联的多个 agent 真正协作完成一件事(如 检索→撰写→审查 出报告),
而非各自对原 query 重答:
- board 增 agentOut(各上游 agent 产出按序);RunCtx 增 Upstream,buildMessages
  注入"前序协作 agent 的产出,请在此基础上继续"。
- runAgent / runReactAgent / runComposeConversation 三条路径统一:注入 b.agentOut
  作上下文,产出经 recordAgentOutput 入黑板(append agentOut + 设为当前成稿 answer,
  多 agent 时最后一个 agent 产出即最终成品)。

不需要 eino multiagent 组件——编排式协作由现有图引擎 + 上下文传递实现。
测试 TestAgentCollaborationPassesOutput:下游 agent 确见上游产出、成稿=下游产出;
make test-go 全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:42:58 +08:00
Blizzard 79437e1c1c feat(mcp-py,dispatcher): 自主 agent 工具动态发现覆盖 mcp-py
把动态工具发现从 mcp-go 扩到两台 MCP:
- mcp-py:TOOL_META 改成与 mcp-go 同形(cn/desc/agent/params/inject),
  list_tools 上报这些;run_code 标 agent 可用(params: code)→ 自主 agent
  可执行 Python 代码做计算/数据处理。
- dispatcher:agentTools 拆出 discoverTools(subject),分别探 mcp-go / mcp-py
  的 list_tools 并合并;某台 MCP 离线即跳过(降级,不阻断)。

验收:mcp-py 未启动时自主 agent 仍正常(探测秒跳过),一次自主调用了
history_get + memory_get 两个动态发现的 go 工具(注入 session_id/user_id);
go 全绿、mcp-py py_compile 通过。mcp-py 起着时 run_code 即自动入 agent 菜单。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:34:07 +08:00
Blizzard 7388f2741d feat(mcp-go,dispatcher): 自主 agent 工具集动态化 —— list_tools 自描述 + 动态发现
杜绝硬编码:自主 agent 的工具菜单不再写死在 dispatcher,而是从 mcp-go
注册表(单一事实源)动态发现。加工具只改 mcp-go 一处,dispatcher 零改动。

- mcp-go:toolDef 增 agent/agentName/params/inject 元信息(paramSpec 声明
  模型可填参数;inject 声明服务端注入、不暴露给模型的参数如 user_id);
  list_tools 上报这些。当前标 agent 的 4 个:wiki_search / recall_user_memory
  / remember_user_fact / history_get。
- dispatcher:agentTools() 改为调 list_tools → 取 agent_exposed → 按上报的
  params 建 schema.ToolInfo → 生成 mcpTool;inject 参数(user_id/session_id/
  kb/task_id)运行时绑定。删除硬编码的 2 个工具。

验收:实测自主 agent 调用新暴露的 remember_user_fact(memory_upsert)成功——
参数由模型按 schema 自生成(key/value),user_id 服务端注入(map 带 task_id
佐证);make test-go 全绿;管理端状态面板兼容(忽略多余 JSON 字段)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:29:20 +08:00
Blizzard 45ad852806 feat(dispatcher): Eino 组件化补完 —— 检索 Retriever + 提示词 ChatTemplate
补完终态架构剩余两层(A 收尾):
- 检索:新增 ragRetriever(eino_components.go)实现 components/retriever.Retriever,
  包 mcp-go kb_search(NATS)、命中转 *schema.Document;retrieve() 改经组件,
  retriever 节点与报告分章检索统一走它。
- 提示词:buildMessages 改用 prompt.FromMessages + MessagesPlaceholder
  (FString 仅解析模板串、值原样注入 → JSON 花括号安全),产出消息序列与旧手拼等价。
- 工具:mcpTool 即 InvokableTool(Phase B),用于模型自主调用;静态 tool 节点
  确定性裸调用 by design。

测试:buildMessages 形状(含花括号)+ ragRetriever 文档解析单测;make test-go 全绿。

性能:compose 每任务编译实测 ~13µs(基准),相对 LLM 秒级可忽略 → 编译图缓存
判为 premature optimization 暂不做;并行效率已由 Phase C DAG 调度拿到。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:20:52 +08:00
Blizzard 2e927eca0a 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>
2026-06-23 11:02:32 +08:00
Blizzard 2ee16d1f99 feat(dispatcher): Eino 采纳 Phase C —— 对话主流程跑 compose.Graph + callbacks 归一
按"并存 + 等价回归"策略,对话主流程可跑在 Eino compose.Graph 上:
- compose_graph.go:runConversation 按 EINO_COMPOSE 灰度开关分流;
  runComposeConversation 建图 START→ChatModel→END,Compile→Stream 回流 token;
  模型未就绪/编译失败降级回 runAgent。默认关,graph.go 仍是默认且权威。
- compose_callbacks.go:composeTracer 用 utils/callbacks 把 ChatModel/Tool 的
  start/end/error 桥到 ExecEvent(可观测归一,不再各处手写 emit)。
- LLM 接口 + Pool 增 ChatModel();fakeLLM 加 cm 字段 + stub Eino 模型。
- 测试:compose 图编译运行 / compose 对话流式 / 开关关→走 runAgent。

顺带修真 bug:SubjectTaskStatus 原 sundynix.tasks.status 落在任务流通配
sundynix.tasks.> 内 → 状态事件被当成"幽灵任务"自我放大(实测污染 2300+ 条)。
挪到 sundynix.status.task + dispatcher 加空任务护栏。

验收:make test-go 全绿;live compose 路径 54 字答复 eval 1.00、默认路径
eval 1.00、幽灵任务 0 复发。剩余 branch/map/render 等节点逐步迁移后 graph.go 退役。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:42:50 +08:00
Blizzard 71102d2424 feat(dispatcher,gateway): Eino 采纳 Phase D —— 任务生命周期状态机
submitted → running → done / failed / timeout 显式状态机,根治"卡运行中
看不出来"(此前 Task.Status 只写死 submitted、从不流转)。

- contract:新增 SubjectTaskStatus 主题 + TaskStatusEvent + 状态常量
- shared/bus:PublishTaskStatus / SubscribeTaskStatus(core NATS pub-sub)
- dispatcher:StatusSink 接口 + Orchestrator.Handle 状态钩子——进入执行
  →running、收尾 finishStatus→done/failed、整体超时上限 taskExecTimeout
  =3min→timeout;经 sub 回写
- gateway:SubscribeTaskStatus 落 PG(Task 增 Detail 字段,AutoMigrate);
  新增 GET /api/v1/tasks/:id 供 UI 轮询状态

验收:实测 submitted→running→done 流转 + PG 持久化 + 端点查询闭环;
make test-go 全绿。HITL 中断恢复 / 多智能体仍按场景待做。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:48:44 +08:00
Blizzard fa2e2ebeeb feat(dispatcher): Eino 采纳 Phase B —— ReAct 智能体 + MCP 工具自主调用
agent 节点带 autonomous:true 时走 ReAct(flow/agent/react),模型在
推理-工具循环里自行决定调哪些 MCP 工具,而非只跑画死的工具节点。

- 新增 eino/react_agent.go:mcpTool 把 MCP 工具(NATS)适配成
  components/tool.InvokableTool;agentTools 暴露 wiki_search +
  recall_user_memory(context 参数 user_id 服务端注入,不暴露给模型);
  runReactAgent 流式回流答复,工具调用经适配器落 ExecEvent 轨迹。
- LLM 接口 + *llm.Pool 增 ToolCallingModel()(openai 组件实现
  model.ToolCallingChatModel);fakeLLM 同步桩(返回 nil → 降级普通对话)。
- graph.go:agent 节点按 autonomous 开关分流 ReAct / 普通;自主 agent
  不预注入画像(让其经 recall_user_memory 工具按需自取)。

关键修复:默认 StreamToolCallChecker 只看首个流片段,deepseek 常先吐
文本再给 tool call 致漏判 → 改 streamHasToolCall 扫整段流(命中率 ~0→7/7)。

验收:自主工具调用 7/7 命中,args={} 证明注入生效,完整闭环跑通;
make test-go 全绿。已知 eval 看不到工具结果会误判 agent(Phase C 修)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:35:19 +08:00
Blizzard d84b1eceb5 feat(dispatcher): Eino 采纳 Phase A —— llm.Pool 内部换成 Eino ChatModel 组件
把手写的 OpenAI HTTP/SSE 客户端换成 eino-ext/openai 的 ChatModel 组件:
- SetConfig 用激活配置构建 ChatModel(热更新=重建实例,读写锁保护)
- ChatStream → cm.Stream(StreamReader 逐 chunk);Chat → cm.Generate
- 对外签名零变更(Chat/ChatStream/StreamText/Ready/ModelName),
  graph.go / report.go / memory_extract.go 不动
- Ready() 语义升级为「ChatModel 构建成功」,更准
- StreamText 降级桩保留

验收:make test-go 全模块绿;真实链路提交任务经新组件流式出答复
(28 字 + eval 1.00,Stream/Generate 两条路径都过)。

为 Phase B(BindTools 函数调用 + ADK)铺好底层组件。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 16:54:31 +08:00
Blizzard 763646d5c5 chore(dispatcher): 升级 eino v0.9.5 → v0.9.9 + 全面采纳迁移方案
- dispatcher 升级 cloudwego/eino 到 v0.9.9(go work sync 同步各模块
  transitive 依赖,如 golang.org/x/term v0.44.0);编译 + eino 链路测试通过
- 新增 EINO_ADOPTION.md:从"只借类型"演进为 Eino-native 编排核心的分阶段
  方案(A 地基 ChatModel 组件 → B ADK 函数调用 → C compose 编排归一 →
  D 状态化执行:任务生命周期 FSM / HITL 中断恢复 / 多智能体)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 16:38:24 +08:00
Blizzard 8bcb90cdb2 feat(admin): 服务状态面板(基建/服务探活 + MCP 工具注册)+ mcp-go 工具注册表
管理端新增「运维 › 服务状态」:总览横幅 + 摘要数字 + 请求链路拓扑
(客户端→网关→NATS→调度→MCP,按健康三态着色)+ 应用服务卡(含探针
延迟)+ 基建磁贴 + MCP 工具按能力域分组(中文名/作用)。

探活机制(全走 NATS,无 HTTP):
- mcp-go/mcp-py 新增 list_tools 自省工具,能应答=在线 + 上报工具清单
- dispatcher 无端点 → 新增 NATS 心跳主题 sundynix.health.dispatcher
  (ServeHealth 应答 model/ready/uptime),网关用 bus.Ping 探
- 网关 GET /api/v1/admin/status 并发聚合四探针 + 各项延迟

mcp-go 重构:switch → map 注册表(buildRegistry),dispatch 与 list_tools
共用单一事实源,杜绝漂移;每个工具带中文名 + 作用描述。mcp-py 同样补元信息。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 16:38:10 +08:00
Blizzard 1674252d81 feat(memory): P1 长期记忆升级 —— 异步攒批 Consolidate + 软删 + importance/last_seen
把"逐轮盲写抽取"升级为 Mem0 式对账(方案见 memory_industry_analysis.md 落地节):

mcp-go:
- Profile 加 Importance(1~10, poignancy) + LastSeenAt(为 Generative Agents 读路径
  Score=w1·Relevance+w2·Recency+w3·Importance 铺路)。
- Upsert 收 importance + 每次置 last_seen(印证);新增 Delete(软删,BaseModel.DeletedAt
  已具备,失效不物删可审计)+ Touch;memory_upsert 透传 importance、新增 memory_delete 工具。

dispatcher:
- extractMemory → consolidateMemory:一次 LLM 调用同时做 抽取+对账,输出
  [{op:ADD|UPDATE|DELETE|NOOP,key,value,importance}];ADD/UPDATE→upsert、DELETE→软删;
  sanitizeOps 防幻删(DELETE 须命中已有)/夹 importance[1,10]/同key保末个/丢 NOOP。
- 攒批:每 3 轮(per-session 计数)才 consolidate 一次,省成本,对齐 ChatGPT 周期整理。
  从根上解决 exact-key 盲写的记忆腐烂。

验证:parseOps/sanitizeOps/parseProfile 纯逻辑单测;store 集成测试(真 PG)覆盖
importance/last_seen 写入 + 软删(live 0 / 物理 1);dispatcher -race 全过。
(注:完整多轮 LLM consolidate 未做实跑,属构造性验证 + 沿用已证 pool.Chat 模式。)

P2 待做:读路径按 Score(Recency+Importance) 排序/衰减/截断 + 桌面端记忆面板。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:27:16 +08:00
Blizzard 8f619c2a62 test(dispatcher): 引擎主链路集成测试(pool 抽接口 + 假替身端到端)
把 Orchestrator.pool 从 *llm.Pool 抽成 LLM 接口(Ready/ChatStream/StreamText/Chat),
*llm.Pool 天然满足、main 不变;从而可注入假模型做端到端测试,不依赖网络/Docker/LLM。

新增 integration_test.go(假 LLM/工具/sink/exec 替身):
- runGraph 分支路由:true/false 边标签精确选路(true 边故意列后)。
- runGraph 工具→agent:工具产出注入 agent 上下文。
- runGraph map fan-out:拆项 → 各章并行撰写 → 多章成稿。
- runGraph 输出护栏:流式 token 中疑似密钥被脱敏。
- handleReport:规划 → 分章撰写 → report_store 存源 → 流含标题/各章 + CompleteStream。

全部 go test -race 通过(修了测试替身 fakeExec 的并发追加竞态;生产 ExecSink 安全)。
至此引擎与报告主链路从"仅手动验证"升级为自动化端到端覆盖。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:51:39 +08:00
Blizzard 9c19bb44f1 feat(dispatcher): 长期偏好记忆抽取(补全记忆闭环)
memorize 的 TODO 落地:写回阶段(异步、离热路径)从本轮对话用 LLM 抽取用户
长期稳定偏好 → 与已有画像去重 → memory_upsert 登记。

- extractMemory:模型/工具不可用或输入过短则跳过;复用 llmCtx 超时;
  抽取 prompt 只取长期偏好、忽略一次性信息。
- 纯逻辑(可单测):parsePrefs(容忍 json 代码围栏)、parseProfile(把 memory_get
  渲染的"- 维度:值"解析回 map,兼容全/半角冒号)、filterNewPrefs(新增/变更才留,
  同批同 key 去重)。
- 单测覆盖三者;LLM 抽取调用沿用已验证的 pool.Chat 模式。

至此记忆闭环:召回(memory_get) + 历史写回 + 偏好自动抽取 全通。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 12:47:49 +08:00
Blizzard b7360439ab feat(dispatcher): 输出护栏 —— 发射层脱敏疑似密钥/令牌
补齐 Harness 输出侧:harness.RedactSecrets 识别并脱敏 sk-/AKIA/JWT/Bearer 等
疑似密钥令牌(纯逻辑 + 单测)。runAgent 在每个 token 分片发射前调用(流式无法回收
已发,故逐片脱敏),脱敏会累计进 b.answer(写回历史也是脱敏版);有命中则在
运行·观测打一条'已脱敏 N 处'轨迹。

注:跨分片的密钥可能漏(流式现实),逐片为最佳努力;生产可加滑窗缓冲增强。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:52:53 +08:00
Blizzard 718140239d fix(dispatcher): 报告 LLM 调用加单次超时上限,治偶发卡死
之前 writeSection/planOutline/planItems 的 pool.Chat 用无 deadline 的 ctx,个别
DeepSeek 流连接挂住会一直占着(曾累积把整篇卡死)。给每次 LLM 调用套 60s 超时
(llmCtx):超时即 cancel → 底层 http 请求中断 → Chat 返回错误 → 走兜底,不无限等。
happy path(约18s 完成)不变,仅加上限。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:47:46 +08:00
Blizzard 3ae009db38 feat(dispatcher): LLM 自动化评测落地(规则 + LLM-as-judge)+ 单测
Evaluator 此前是空桩(Score 恒返 0)且未接线。落地为真实自动化评测并接入:

- 规则评测(always-on,纯函数):空输出/过短/疑似拒答/重复啰嗦各扣分 → 0–1 分 + 标签。
- LLM-as-judge(模型就绪时):让模型对(输入,输出)按相关性/准确性/完整性 1–5 打分给理由,
  归一化后与规则分加权(0.4 规则 + 0.6 LLM);解析失败/无模型则回退纯规则分。
- 经注入 ready/chat 解耦 LLM 后端,便于单测(无需真实模型)。
- 接线:orchestrator 在答复产出后 `go o.evaluate(...)` 异步评分并记日志(off 热路径,
  不影响响应与流式);main.go 用 pool.Ready/pool.Chat 构造 Evaluator。

测试:规则各情形(正常/空/过短/拒答/重复)、纯规则模式、LLM-judge(带围栏 JSON 解析 +
归一化 + 加权)、坏 JSON 回退 —— 全过。

至此 Harness 三件:熔断降级  · 输入护栏  · LLM 自动化评测 (输出护栏待 emit 层)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:32:02 +08:00
Blizzard 31bf3e5907 feat(dispatcher): 熔断降级真三态状态机(弃用空桩)+ 单测
CircuitBreaker 此前是空桩(Allow 恒 true、Report 空操作),dispatcher 调 LLM/工具
无任何失败保护——今天就撞上 DeepSeek 流连接累积把报告卡死。改为真实三态熔断:

- Closed:正常放行;连续失败达阈值(默认5) → Open。
- Open:快速拒绝;冷却(默认10s)到点 → HalfOpen 放行少量探测(默认1)。
- HalfOpen:探测成功 → Closed 恢复;探测失败 → 重新 Open。
- sync.Mutex 并发安全(多任务 goroutine 共享);时钟可注入便于确定性测试。

orchestrator.Handle:熔断开启时不再静默丢弃任务,改为回流"服务繁忙"提示 +
CompleteStream 收尾,让客户端解阻不挂死。

测试(含 -race):达阈值断开、成功清零、半开恢复、探测失败重断、并发安全 —— 全过。
PROGRESS.md 勾掉熔断项。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:02:45 +08:00
Blizzard 9b0520e020 test(backend): 编排引擎/DSL/docx/报告导出 首批 Go 单测(19 用例,纯逻辑无依赖)
补齐核心后端逻辑的自动化测试,全部纯函数级、不依赖 docker/NATS/LLM,毫秒级跑完,
把"手动起全栈 curl 验证"变成 `go test`:

- dispatcher/internal/eino (graph_test.go)
  evalCondition(各运算符+refs/tools/answer/profile 关键字+兜底)、resolveOperand、
  aggregate(拼接/去重合并/默认/全空)、branchNode(真假边标签精确选路 + 无标签退回边序 +
  单边剪枝)、cstr/cbool/countLines/labelOf/targetsOf。
- dispatcher/internal/dsl (compile_test.go)
  Topo(线性序 + 有环不丢节点)、Compile(system/query/tools 抽取 + 无输入兜底 + 空图用原文)、
  ToolBinding(tool/retriever/非工具)、Parse(合法/非法)。
- mcp-go/internal/office (unioffice_test.go)
  RenderReport 产物为合法 docx(zip 三部件 + 标题/章节文本 + XML 转义)、escapeXML。
- mcp-go/internal/mcp (report_test.go)
  reportMarkdown(标题+多章 / 无标题)。
- gateway/internal/dsl (parser_test.go)
  ParseAndAssemble(task_ 前缀 + Graph 透传 + Meta 初始化 + 空/非法报错)。

`go test ./...` 各模块全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:13:13 +08:00
Blizzard 1dd6b0cce3 feat(report): 生成只出 Markdown 预览,导出时再渲染 Word/PDF/Markdown
把"渲染"从生成阶段解耦到导出阶段(导出时再处理):
- 生成阶段:报告正文按 Markdown 流式预览(前端 <Markdown> 已渲染),
  dispatcher 不再 eager 渲染 docx,改为经 mcp-go report_store 落盘报告源(title+sections JSON)。
- 导出阶段(按需现渲染):
  - GET /reports/:id/export?format=docx → mcp-go report_export 读源渲染 .docx;
  - ?format=md → 返回 Markdown 文本;
  - PDF → 前端把已渲染的 Markdown 送进打印视图出 PDF(CJK 零字体依赖)。
- 旧 /reports/:id/download 兼容保留(默认 docx)。

改动:
- contract: ReportSourcePath(id) = <id>.json。
- mcp-go: 新增 report_store / report_export 工具(report_render 保留给 Studio render 节点)。
- dispatcher: handleReport 末尾 renderReport → storeReport。
- gateway: DownloadReport → ExportReport(经 NATS 调 report_export)。
- 前端: ReportView 单个「Word」→「导出」组 Word/PDF/Markdown;
  desktop.printReportHtml 客户端打印;api.reportExportUrl。

实测(docker 全栈 + mcp-go + gateway + dispatcher + DeepSeek 真跑):
- 真实生成「绿茶的功效」18s 完成,report_store 落源(5章, 6280B) ✓
- export md 返回正确 Markdown(# 标题/## 小节/正文) ✓
- export docx 为合法「Microsoft Word 2007+」(含 document.xml/Content_Types) ✓
- 前端 tsc 干净 + 生产构建通过 ✓
(注:发现并修复一处环境问题——mcp-go 启动时若 Milvus 未起会阻塞在
 rag 初始化、永不订阅工具,导致所有 mcp-go 工具"no responders";起全栈后正常。
 报告生成在累积大量未完成 DeepSeek 流连接时会偶发卡顿,干净进程下正常。
 前端导出按钮的实时点击因 React 受控输入自动化限制未在预览中走通,非代码缺陷。)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:04:06 +08:00
Blizzard 1bd187874d feat(orchestration): Phase2 —— map 真并行 fan-out + branch 真/假边标签精确选路
Phase1 让引擎按图执行后,本轮补上两块:

1) map 并行 fan-out(dispatcher)
   - map 节点:planItems 把主题拆成 3–6 子项 → 复用 report 的 writeSections
     有界并发(4)逐项撰写 → 结构化 sections 存黑板 + 拼进 answer + 流式呈现。
   - 检索节点记下 owner 作用域库名(b.kb),供 map 各项并行检索复用。
   - render 节点优先用 map 产出的多章 sections 渲染,否则整段成稿当单章。

2) branch 真/假边标签(前端 + DSL + dispatcher)
   - TypedNode:分支节点渲染两个出口手柄 真(绿)/假(红),连线各带 sourceHandle。
   - exportDsl / TaskDsl:边导出携带 sourceHandle。
   - dispatcher dsl.Edge 增 SourceHandle;branchNode 优先按 true/false 标签精确
     选路,无标签的旧图退回"出边顺序"约定,向后兼容。

实测(gateway+dispatcher+DeepSeek 真跑):
- map:input→map→render,DeepSeek 拆出 5 章并行撰写(347–512字),trace 见
  section:0..4 并发 + 有界并发(section3 等槽);render 因 mcp-go 不在优雅降级 ✓
- branch 标签:把 true 边故意列第二位,条件真仍走 true 标签的分支A、假走分支B,
  证明按标签而非边序选路 ✓
- 桌面端:分支节点正确渲染 真/假 两手柄,无 console 报错 ✓

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:01:51 +08:00
Blizzard fd145b5852 feat(dispatcher): 编排引擎按图执行(拓扑+连线+分支剪枝),弃用线性拍平
旧 compileFlow 把 DSL 图拍平成线性 init→tool…→prompt→model,连线/分支/
memory/aggregate/render 节点全被忽略——"画得出、跑不全"。改为纯 Go 图解释器
(graph.go),按真实拓扑与连线执行,每种节点 kind 有真实行为:

- input     注入用户输入
- memory    按勾选注入画像/历史(无 memory 节点则沿用默认注入,不回归)
- retriever kb 按 owner 作用域 → kb_search 累计参考资料
- tool      调 MCP 工具,产出进黑板,失败降级不阻断
- agent     据黑板拼消息 → pool 流式回流 token,累计成稿
- aggregate 按策略合并参考资料(拼接/去重合并/摘要)
- render    把成稿经 report_render 渲染 docx
- branch    求值条件 + active-set 剪枝下游(边序约定 [true,false])
- map       占位(fan-out 暂串行,路线图 Phase 2)
- output    终端

全程逐节点点亮"运行·观测",token 流与记忆写回保持不变;报告 intent 走原专用
编排不动。compile.go 精简为只留 RunCtx/buildMessages/previewArgs。

实测(gateway+dispatcher+DeepSeek 实跑):
- input→agent→output 真实流式答复 ✓
- branch 条件 2>1 走分支A、1>2 走分支B(下游真被剪枝)✓
- memory 节点按勾选注入;exec 事件按新节点名(agent:a 等)回流 ✓
- 桌面端 Studio 载示例→运行:4节点3连线校验通过,检索节点 mcp-go 不在时
  优雅降级,agent 据空资料如实作答,输出/轨迹面板正常 ✓

路线图 Phase 2:map 真并行 fan-out + aggregate reduce 接上 report 那套;
前端给 branch 的边打 true/false 标签,使条件分支完全精确(当前靠出边顺序约定)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 11:42:29 +08:00
Blizzard 337d4d7619 feat(studio): 完善编排 —— 检索接本人知识库 + 真实模型下拉 + 模板/示例
把编排从"演示桩"接到真实平台:检索节点查本人 owner 隔离的知识库,节点下拉用真实数据。

- dispatcher:makeToolNode 用 task user_id 给检索类工具的 kb 加 owner 前缀("uid/kb"),
  编排里的「检索(RAG)」节点真正命中本人知识库(与隔离对齐)。
- 前端 StudioView:加 identity,载入 /kb/list 与 chat 模型作为「检索.kb」「Agent.model」下拉真值;
  Inspector 支持 dynamicOptions(无真值时提示去创建)。
- 编辑体验:示例(一键加载 输入→检索→Agent→输出 可运行图)/ 清空 / 模板名+保存(localStorage,
  含布局)/ 载入下拉;ReactFlow deleteKeyCode 支持 Del/Backspace 删节点。

验证:示例图运行 → gateway 发布任务 → dispatcher 编译 → mcp-go 日志 `tool=wiki_search
args=[kb:wt/default ...]`(kb 已按 owner 作用域)→ 命中本人库 → DeepSeek 流式作答;
底部抽屉 完成 ✓ · 工具调用 1。tsc+vite+dispatcher build 通过;重建 .app。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 16:14:54 +08:00
Blizzard cdc5b3a847 feat(observability): 执行可视化 — 节点级实时轨迹(运行·观测)
把任务执行做成可观测:Dispatcher 在每个节点/阶段发结构化 ExecEvent,
经独立 NATS 通道回流,前端逐节点点亮(状态/耗时/工具入参产出)。

- shared: contract.ExecEvent + ExecSubject(sundynix.exec.<id>,与 Token 流分流);
  bus.PublishExec/CompleteExec/SubscribeExec(core NATS,复用结束头)
- dispatcher: execTracer(自增 Seq 保序 + span 自动计耗时);
  Orchestrator 加 ExecSink;通用图(init 召回 / 各 tool 入参→产出 / prompt / model
  首token+token数)与报告编排(规划大纲 / 各章并行 start-end / 渲染)全程埋点
- gateway: SubscribeExec + GET /tasks/:id/exec SSE(与 token 流并行)
- desktop: streamExec + deriveNodes(按 node 归并 start/end/error/info);
  复用组件 ExecTrace(竖向轨道,按 kind 着色,运行中脉冲灯);
  新 RunsView(运行·观测:轨迹+输出双栏);BottomDrawer 轨迹/工具调用 tab 接真实数据;
  ReportView 加执行轨迹栏;左导航「运行」置就绪

实测:
- 报告任务 /exec:规划(2680ms,4章) → 4 章并行(seq 交错,各~7-8s 重叠=真并行,
  每章带 docs 知识库检索预览+成稿字数) → 渲染(docx 落盘)
- 通用图 /exec:tool:kb_search(678ms,入参→Milvus 产出) → prompt(2消息) →
  model(首token 860ms / 4 tokens)
- 浏览器(Preview):报告页执行轨迹逐节点点亮、章节带耗时/字数/检索片段,完成后下载 Word

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:29:28 +08:00
Blizzard ba8c6b3c43 feat(report): 报告生成端到端 — 规划→分章并行检索撰写→渲染真实 Word
- shared: 新增 intent=report 任务约定 + ReportPath(跨进程共享落盘目录,零配置对齐)
- dispatcher: handleReport 专用编排(DeepSeek 规划大纲 → 各章并行 RAG 检索+撰写
  → 汇聚 → report_render),Pool.Chat 非流式聚合;进度与正文经 Token 流实时回流
- mcp-go: 用标准库 archive/zip + OOXML 拼出真实可打开的 .docx(零额外依赖),
  report_render 工具落盘到共享目录;附 docx 有效性测试
- gateway: POST /reports 触发;GET /reports/:id/download 下发 Word
- desktop: 新增「报告」页(主题→实时编排进度→下载 Word),左导航置为就绪

实测:DeepSeek 生成 5 章报告 → 渲染 5KB docx → file 识别为 Microsoft Word 2007+
→ textutil 提取标题/各章正文完整。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:02:21 +08:00
Blizzard 21e5f6620d chore: fresh-clone 可直接运行审查与修复
确保 git clone 后零配置(除 LLM key)即可跑通后端链路。

- go.work: 移出 sundynix-desktop(Wails,//go:embed frontend/dist 需先 npm build,
  且 main 为桩),避免污染后端工作区构建;保留后端 4 模块
- mcp-py pyproject: 注释未 import 的 mcp/docker 重依赖(对应功能仍为桩),
  fresh pip install 只装实际用到的 nats-py + docx/openpyxl/pypdf,更快更稳
- README: 完整环境改为 fresh-clone 零配置运行指引(infra→4后端→2前端→控制台配模型)
- 审查结论: 无硬编码绝对路径; 各服务 env 默认全对齐 docker-compose; go.sum/
  package-lock 齐全; /tmp clone 实测: 4 后端模块 go build ✓、admin npm ci+build ✓、
  mcp-py 全新 venv 安装+导入 ✓; e2e PASS

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 17:13:55 +08:00
Blizzard 3b54e59ecf feat: embedding 配置搬上控制面 — 数据源页可视化配置 + 热更新
embedding 从 env 改为控制面驱动(持久化+可视化),复用 chat 模型同套范式:
配置控制面泛化为按 kind(chat/embedding),加 embedding kind。

- shared: 配置 subjects 泛化 sundynix.config.<kind>.get/.updated;bus 方法改 kind 参数
  (RequestConfig/ServeConfig/PublishConfigUpdated/SubscribeConfigUpdated)
- gateway: sundynix_model 加 kind 列(每 kind 唯一激活)+旧行回填 chat;admin 按 kind
  增删改/激活/列表,测试连接 embedding 走 POST /embeddings;main 按 kind ServeConfig;
  变更广播各 kind
- dispatcher: 取 chat 配置(kind 化)
- mcp-go: rag.Engine.SetEmbedding 热更新(RWMutex);main 取/订阅 embedding 控制面配置
  (覆盖 env)
- admin 控制台: api 按 kind;抽出复用 ModelManager;ModelsPage(chat)+新 DatasourcesPage
  (embedding + 向量/图库占位);routes 数据源页就绪
- 验证: 全模块 build✓ + e2e PASS + 控制台 npm build✓;live 全跑通——chat(DeepSeek 回填
  kind 仍工作);mcp-go 不带 EMBED env 启动→控制台配 embedding(百炼)→测试连接✓→激活
  →NATS 热更新 mcp-go→入库+语义检索'存向量的数据库'→Milvus;浏览器数据源页拉到激活配置

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 17:25:54 +08:00
Blizzard 71db0e295f 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>
2026-06-10 16:45:33 +08:00
Blizzard aa574a8cb2 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>
2026-06-10 16:34:38 +08:00
Blizzard 3c65189f30 feat: 配置控制面 + LLM Pool 接第三方在线 API (OpenAI 兼容)
后端从占位回显变为真实生成:管理员经控制面登记/激活模型,Gateway 经 NATS
下发,Dispatcher 热更新 LLM Pool,Eino 图用 OpenAI 兼容流式真实推理。

- shared: contract.ModelConfig(provider/base_url/api_key/model) + 配置 subjects;
  bus.RequestModelConfig/ServeModelConfig/Publish/Subscribe ModelConfigUpdated
- gateway: store.LLMModel→sundynix_model(AutoMigrate,唯一激活) + admin REST
  (GET/POST/active/delete/test models, api_key 脱敏) + main ServeModelConfig +
  变更广播; 路由 /api/v1/admin/models*
- dispatcher: llm.Pool OpenAI 兼容 SSE 流式客户端(ChatStream) + 热更新配置 +
  未配置则降级桩; poolModel.Ready()?真实流式:注入记忆的桩; main 取配置+订阅
- 开发期接在线 API 不拉本地模型(见 llm-provider-strategy memory)
- 验证: 4 模块 build✓ + e2e PASS; mock OpenAI 服务 live 跑通——登记/测试连接✓/
  激活→NATS 热更新→提交→真实 SSE 流出 mock 回复, mock 日志证明端点被调用且
  注入画像(老王)进了模型上下文

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:41:39 +08:00
Blizzard 4928ffc0f7 feat: 短期多轮历史接入 Eino 图 MessagesPlaceholder (⑨)
会话历史(Redis,易失,与长期画像分开)经 MCP 工具进出 Eino 图:
recall 召回历史填 MessagesPlaceholder,写回把本轮 user/assistant 落历史。

- mcp-go: internal/history(go-redis, sundynix:history:<session>, LPUSH+LTRIM 保留近20条,
  24h TTL) + 工具 history_get(返回JSON turns)/history_append; main 开 Redis(降级)
- dispatcher Eino: 模板加 MessagesPlaceholder('history'); recall 调 history_get→转 schema.Message;
  Handle 累积 answer; memorize 异步 history_append(user+assistant)
- shared: contract.MetaSessionID; gateway: SubmitTask 注入 Meta[session_id](X-Session-ID 头,缺省 default)
- demo.sh: 同会话两轮提交,验证第2轮召回第1轮历史
- 验证: 4 模块 build✓ + 3 e2e PASS; live 跑通——轮1=0轮历史→落库, 轮2 history_get 命中→注入

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 14:18:45 +08:00
Blizzard cbd130ecae feat: 第一张真实 Eino 图 + 偏好记忆(让模型知道是我)
dispatcher 不再手搓 pool.Stream,改用编译好的 Eino 图驱动;接入用户常驻画像,
推理前召回并注入 system prompt,实现个性化(架构'心脏'首次真跳)。

Eino 图(dispatcher/internal/eino): START→recall→prompt→model→END + 全局 State
- recall(Lambda): 取 Meta[user_id] → 调 MCP memory_get → ProcessState 写画像
- prompt(ChatTemplate): {profile} 注入 system,{query} 作 user
- model: poolModel 适配 LLM Pool 为 model.BaseChatModel(Generate+Stream, schema.Pipe)
- 写回: 流排空后异步 memorize(流式节点走 OnEndWithStreamOutput 非 OnEndFn)

记忆存储(mcp-go owns): GORM Profile→sundynix_user_profile(复合主键, AutoMigrate,
遵守前缀约定), 新工具 memory_get/memory_upsert, 连不上降级
Gateway: SubmitTask 注入 Meta[user_id](X-User-ID 头), PUT /api/v1/memory→memory_upsert
shared: contract.MetaUserID; llm.Pool 拆出 StreamText

验证: 4 模块 build✓ + 3 e2e PASS; live 跑通——PUT 偏好落 sundynix_user_profile,
带 X-User-ID 提交→Eino recall 召回→注入→SSE 流出含画像的个性化回答, writeback 触发

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 14:06:18 +08:00
Blizzard adc521f94d feat: 打通 Dispatcher→MCP 工具调用链路 (core NATS request-reply)
第 4 层 Dispatcher 经 NATS request-reply + 队列组同步调用第 5 层 MCP 工具,
工具不可用/超时即降级,不阻断主流程。

- shared/contract: ToolCall/ToolResult + sundynix.tools.go.* subject 约定 + ToolSubjectGo/Py
- shared/bus: CallTool(发起) / ServeTool(队列组订阅+应答)
- mcp-go: 接共享 bus,gateway 通配订阅按工具名分发(wiki_search/echo),main 优雅退出
- dispatcher: ToolCaller 接口 + Orchestrator.retrieveContext(调 wiki_search,超时3s降级)
- e2e: TestToolCallRoundTrip(PASS);demo.sh 加 mcp-go(就绪门避免启动竞态),live 跑通

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 11:31:58 +08:00
Blizzard c7a02c3905 feat: 初始化 sundynix-agentix 分层式 AI Agent 平台脚手架
5 层 + 1 条 NATS 零拷贝消息总线的 monorepo(Monolith First → Microservices Morph B)。
纵向主干(任务流 + Token 流回流)已真实跑通,横向各层能力为带注释的桩。

已贯通(real code):
- sundynix-shared: 共享契约 + JetStream/core NATS 真实收发(bus) + 内嵌 NATS(devnats) + e2e 测试
- sundynix-gateway: Gin 接入 + DSL 解析组装 + NATS Publish + SSE 流式输出
- sundynix-dispatcher: NATS 消费 + Eino Orchestrator 流式回流 + 熔断器 + LLM Pool 占位流式
- 链路: HTTP POST → DSL → sundynix.tasks.* → Dispatcher → Token 经 sundynix.streams.<id> 回流 → SSE
- 基础设施: docker-compose(nats/postgres/redis/neo4j/milvus) + Makefile(make demo/e2e)

待填(桩):
- Eino 图编排 compose.NewGraph、LLM Pool 接 vLLM/Ollama
- Gateway store 换真实 pgx/redis
- sundynix-mcp-go: Bleve+Milvus+Neo4j 混合检索 / UniOffice / 外部 API
- sundynix-mcp-py: gVisor 沙箱 / MinerU(PaddleOCR) / Docker 解释器
- sundynix-desktop: React Flow 画布 → DSL 导出 → SSE 展示
2026-06-10 11:00:29 +08:00