主人 2026-10-03 拍板「注释一律中文」;本模块按长期分批协议(一个模块一次派工)执行。 注释行数 1329 → 1329 逐行 1:1(含中文 33 → 1304);行尾注释 5 条一并译。 校验:机械校验(非注释改动行)= 空;独立 Go AST 程序(剥注释复渲)证明 44/44 文件代码全等; 全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。 gofmt:capacity_loop_test.go / flow.go 改动前即未格式化,按铁律保留原样未动。
117 lines
4.5 KiB
Go
117 lines
4.5 KiB
Go
package loop
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/agent"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/persist"
|
||
)
|
||
|
||
// checkpointStore 是循环持久化持久快照并恢复被中断
|
||
// run 所需的最小表面(design §1 durable execution)。
|
||
// 生产环境由 *persist.Store 满足;测试替换为假实现,以在
|
||
// 没有真实数据库的情况下断言恢复路径。
|
||
type checkpointStore interface {
|
||
// Save 插入或更新一个 checkpoint。
|
||
Save(ctx context.Context, cp *persist.Checkpoint) error
|
||
// LoadLatest 返回 agentID 最近更新的 checkpoint;
|
||
// 该 agent 没有时返回 ErrNotFound。
|
||
LoadLatest(ctx context.Context, agentID string) (*persist.Checkpoint, error)
|
||
}
|
||
|
||
// checkpointState 是每步持久化的持久快照。携带消息
|
||
// 窗口与步索引,使长任务能从最新 checkpoint 恢复而不
|
||
// 丢失上下文(design §1 durable execution)。
|
||
type checkpointState struct {
|
||
Step int `json:"step"`
|
||
Messages []model.Message `json:"messages"`
|
||
StopReason string `json:"stop_reason,omitempty"`
|
||
}
|
||
|
||
// saveCheckpoint 在 step 处持久化一个快照。它绝不让 run 失败:
|
||
// store 为 nil 时为空操作,marshal/save 出错则记录日志。
|
||
func (l *Loop) saveCheckpoint(ctx context.Context, sessionID string, step int, messages []model.Message, stopReason string) {
|
||
if l.checkpoint == nil {
|
||
return
|
||
}
|
||
state := checkpointState{
|
||
Step: step,
|
||
Messages: messages,
|
||
StopReason: stopReason,
|
||
}
|
||
buf, err := json.Marshal(state)
|
||
if err != nil {
|
||
l.log.Warn("checkpoint marshal", "step", step, "error", err)
|
||
return
|
||
}
|
||
cp := &persist.Checkpoint{
|
||
ID: fmt.Sprintf("%s:%d", sessionID, step),
|
||
AgentID: sessionID,
|
||
Step: step,
|
||
State: buf,
|
||
}
|
||
if err := l.checkpoint.Save(ctx, cp); err != nil {
|
||
l.log.Warn("checkpoint save", "step", step, "error", err)
|
||
}
|
||
}
|
||
|
||
// loadCheckpoint 从该会话最新的持久 checkpoint 恢复一个被中断的
|
||
// run(design §1 durable execution)。它返回恢复后的消息
|
||
// 窗口、下一个要运行的循环步(checkpoint 步 + 1),以及
|
||
// 是否找到了可恢复的 checkpoint。不存在 checkpoint,或最新的一个
|
||
// 反映的是已完成/被阻塞的 run 时,它返回调用方的消息并置步为 1,
|
||
// 使循环从新开始。
|
||
//
|
||
// 恢复是幂等的:checkpoint 精确保存了上一个已完成步结束时
|
||
// 的消息窗口(assistant 工具调用及其结果已融合在内),
|
||
// 因此从下一步恢复会由历史重建模型
|
||
// 上下文,绝不重跑已完成步的工具调用,也不
|
||
// 重新生成已完成的答案。加载是只读的 —— 恢复绝不删除
|
||
// checkpoint,因此每一步的快照始终可查询。
|
||
func (l *Loop) loadCheckpoint(ctx context.Context, sessionID string, messages []model.Message) ([]model.Message, int, bool) {
|
||
if l.checkpoint == nil {
|
||
return messages, 1, false
|
||
}
|
||
cp, err := l.checkpoint.LoadLatest(ctx, sessionID)
|
||
if err != nil {
|
||
if !errors.Is(err, persist.ErrNotFound) {
|
||
l.log.Warn("checkpoint load", "session", sessionID, "error", err)
|
||
}
|
||
return messages, 1, false
|
||
}
|
||
st := checkpointState{}
|
||
if err := json.Unmarshal(cp.State, &st); err != nil {
|
||
l.log.Warn("checkpoint unmarshal", "session", sessionID, "step", cp.Step, "error", err)
|
||
return messages, 1, false
|
||
}
|
||
if !resumableStopReason(st.StopReason) {
|
||
l.log.Debug("checkpoint not resumable", "session", sessionID, "step", cp.Step, "stop_reason", st.StopReason)
|
||
return messages, 1, false
|
||
}
|
||
l.log.Info("checkpoint resumed", "session", sessionID, "step", cp.Step, "next_step", cp.Step+1, "messages", len(st.Messages))
|
||
return st.Messages, cp.Step + 1, true
|
||
}
|
||
|
||
// resumableStopReason 报告最后一个步以上述 stop reason 结束的
|
||
// checkpoint 是否代表一个值得恢复的在途 run。只有
|
||
// 工具循环中途的步才真正算「被中断」:循环已把工具结果
|
||
// 并入窗口并即将继续,因此从下一步接上
|
||
// 会重建上下文而不重复工作。携带最终答案、
|
||
// 预算耗尽、安全网终止或护栏拒绝的 checkpoint 是
|
||
// 已完成/被阻塞的 run —— 恢复它会重新生成已产出的答案
|
||
// (重复副作用)—— 因此这些一律落到全新开始(打开
|
||
// 真正新会话的调用方使用不同的 sessionID,本就错过
|
||
// LoadLatest)。
|
||
func resumableStopReason(stopReason string) bool {
|
||
switch stopReason {
|
||
case "", agent.StopReasonToolCalls:
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|