主人 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 改动前即未格式化,按铁律保留原样未动。
121 lines
3.8 KiB
Go
121 lines
3.8 KiB
Go
package loop
|
||
|
||
import (
|
||
"sort"
|
||
"time"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/agent"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
|
||
)
|
||
|
||
// 记录在 span 上的日志事件类型,使 flow 图的节点 log[] 展示
|
||
// 每一步发生了什么(design/19 §1 节点实时日志)。仅当该步真正
|
||
// 完成时才追加事件,因此日志绝不宣告未运行的工作。
|
||
const (
|
||
// EventLLMDone 在模型调用返回后记录到 llm_call span 上。
|
||
EventLLMDone = "llm.done"
|
||
// EventToolDone 在工具调用落定后记录到 tool_call span 上。
|
||
EventToolDone = "tool.done"
|
||
)
|
||
|
||
// maxLogSummaryRunes 在发布前限制日志取值的长度。图在
|
||
// 输出时还会再次脱敏与截断;这里只是让单个 span 保持小。
|
||
const maxLogSummaryRunes = 240
|
||
|
||
// recordLLMDone 把一次已完成模型调用的脱敏摘要追加到其
|
||
// span:服务该 turn 的模型、两个 stop reason、请求了多少工具调用
|
||
// 以及该调用耗时。不记录任何提示词或补全文本,
|
||
// 因此 llm 节点的日志绝不携带对话内容。ag 是该 turn 的 agent
|
||
// (按角色池条目),因此多角色进程上报的是实际
|
||
// 服务该调用的模型,而不是默认角色的模型。
|
||
func (l *Loop) recordLLMDone(span *trace.Span, out *stepOutcome, ag *agent.Agent) {
|
||
if span == nil || out == nil {
|
||
return
|
||
}
|
||
props := map[string]any{
|
||
"stop_reason": out.stopReason,
|
||
"tool_calls": len(out.toolCalls),
|
||
"elapsed_ms": elapsedMS(span),
|
||
}
|
||
if m := modelName(out, ag); m != "" {
|
||
props["model"] = m
|
||
}
|
||
if out.modelStopReason != "" {
|
||
props["model_stop_reason"] = out.modelStopReason
|
||
}
|
||
_ = span.Record(trace.Event{Type: EventLLMDone, Props: props})
|
||
}
|
||
|
||
// recordToolDone 把一次工具调用的脱敏摘要追加到其 span:工具
|
||
// 名、是否成功、参数名(绝不记录其值)、失败时的
|
||
// 错误种类,以及截断后的结果摘要。参数值被
|
||
// 刻意省略 —— 工具参数正是凭据、消息正文
|
||
// 或含机密的文件路径会出现的地方 —— 且这里记录的一切
|
||
// 在发布前都会由图再次清洗。
|
||
func (l *Loop) recordToolDone(span *trace.Span, tc agent.ToolCall, out ToolOutcome) {
|
||
if span == nil {
|
||
return
|
||
}
|
||
props := map[string]any{
|
||
"tool": tc.Name,
|
||
"ok": out.Err == nil,
|
||
"args_keys": argKeys(tc.Args),
|
||
"elapsed_ms": elapsedMS(span),
|
||
}
|
||
if out.Err != nil {
|
||
if out.ErrKind != "" {
|
||
props["error_kind"] = out.ErrKind
|
||
}
|
||
props["error"] = trimSummary(out.Err.Error())
|
||
} else {
|
||
props["result_summary"] = trimSummary(formatToolResult(out.Result))
|
||
}
|
||
_ = span.Record(trace.Event{Type: EventToolDone, Props: props})
|
||
}
|
||
|
||
// modelName 报告服务某步的模型:流式路径知道时用路由出的
|
||
// 请求模型,否则用该 turn 的 agent 配置的模型。两者
|
||
// 都不可用时为空。
|
||
func modelName(out *stepOutcome, ag *agent.Agent) string {
|
||
if out.model != "" {
|
||
return out.model
|
||
}
|
||
if ag == nil {
|
||
return ""
|
||
}
|
||
return ag.Model()
|
||
}
|
||
|
||
// elapsedMS 报告 span 迄今已打开多久。span 的 Duration 仅由
|
||
// Finish 打上,因此在 Finish 之前记录的日志事件在此测量它。
|
||
func elapsedMS(span *trace.Span) int64 {
|
||
if span == nil || span.StartedAt.IsZero() {
|
||
return 0
|
||
}
|
||
return time.Since(span.StartedAt).Milliseconds()
|
||
}
|
||
|
||
// argKeys 按排序返回工具调用的参数名。只记录
|
||
// 参数名;值留在 trace 之外(见 recordToolDone)。
|
||
func argKeys(args map[string]any) []string {
|
||
if len(args) == 0 {
|
||
return nil
|
||
}
|
||
keys := make([]string, 0, len(args))
|
||
for k := range args {
|
||
keys = append(keys, k)
|
||
}
|
||
sort.Strings(keys)
|
||
return keys
|
||
}
|
||
|
||
// trimSummary 把日志取值缩短到 maxLogSummaryRunes 个 rune,
|
||
// 且不切开多字节 rune。
|
||
func trimSummary(s string) string {
|
||
r := []rune(s)
|
||
if len(r) <= maxLogSummaryRunes {
|
||
return s
|
||
}
|
||
return string(r[:maxLogSummaryRunes]) + "…"
|
||
}
|