Files
sundynix-agentix/sundynix-dispatcher/internal/eino/board_serde_test.go
T
Blizzard 5a1a994cc2 feat(hitl): 增量2a —— board 可序列化(compose checkpoint 的状态载体)
compose checkpoint 把图执行态(含 GenLocalState 的 board)序列化进 store,但
Eino 序列化器只认导出字段,而 board 字段全未导出 → 直接持久化会落成空、resume
丢全部状态。Eino 对同时实现 json.Marshaler+Unmarshaler 的类型改走自定义 JSON
(internal/serialization checkMarshaler),故给 *board 实现一对 JSON 方法映射到
导出 DTO,无需把 board 字段全导出(牵连几十处调用点)。

- board_serde.go: *board 的 MarshalJSON/UnmarshalJSON ↔ boardSnapshot;丢弃
  fatalErr(transient error,中断点必为 nil);schema.RegisterName[*board] 注册
  类型名供 checkpoint 的 State(any) 还原。
- 测试: 快照往返无损 + 编译期断言实现 json.Marshaler/Unmarshaler + fatalErr
  不被持久化。

零行为变更(纯新增)。go test ./... 全绿。
下一步 2b:审批节点 compose.Interrupt + 编译挂 checkpoint store + orchestrator
识别 InterruptInfo 置 waiting 并释放 goroutine。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 12:38:23 +08:00

65 lines
2.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package eino
import (
"encoding/json"
"reflect"
"testing"
"github.com/cloudwego/eino/schema"
)
// board 必须同时实现 json.Marshaler+UnmarshalerEino checkpoint 才会走自定义 JSON 路径
// 持久化未导出字段;任一缺失都会让序列化器回退到「只存导出字段」=空 board。
var (
_ json.Marshaler = (*board)(nil)
_ json.Unmarshaler = (*board)(nil)
)
// TestBoardSnapshotRoundtrip 钉死黑板快照往返:中断点的全部执行态都能无损还原(resume 不丢状态)。
func TestBoardSnapshotRoundtrip(t *testing.T) {
orig := &board{
uid: "u42",
sid: "s7",
query: "介绍杭州西湖",
profile: "用户偏好简洁",
history: []*schema.Message{schema.UserMessage("上一轮"), schema.AssistantMessage("上一轮回答", nil)},
kb: "u42/travel",
refs: []string{"西湖十景…", "苏堤春晓…"},
toolOut: []string{"[wiki] 杭州…"},
sections: []reportSection{
{Heading: "概况", Body: "西湖位于杭州。"},
},
answer: "西湖是…",
agentOut: []string{"草稿一", "草稿二"},
// fatalErr 故意置非空:快照应丢弃它(transient),不应影响往返。
fatalErr: errRejected,
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var got board
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("unmarshal: %v", err)
}
// 持久化字段逐一比对。
if got.uid != orig.uid || got.sid != orig.sid || got.query != orig.query ||
got.profile != orig.profile || got.kb != orig.kb || got.answer != orig.answer {
t.Fatalf("标量字段未还原: %+v", got)
}
if !reflect.DeepEqual(got.refs, orig.refs) || !reflect.DeepEqual(got.toolOut, orig.toolOut) ||
!reflect.DeepEqual(got.agentOut, orig.agentOut) || !reflect.DeepEqual(got.sections, orig.sections) {
t.Fatalf("切片字段未还原: %+v", got)
}
if len(got.history) != len(orig.history) || got.history[0].Content != "上一轮" {
t.Fatalf("history 未还原: %+v", got.history)
}
// fatalErr 是 transient,快照不持久化 → 还原后必为 nil。
if got.fatalErr != nil {
t.Fatalf("fatalErr 不应被持久化,got %v", got.fatalErr)
}
}