- 11.4.2:三类节点首次真进图 —— memory.recall/memory.write(loop/memory.go 召回与写入点)、 guardrail.input/output(流式主路径两侧 + agent.RunStep 静音路径)、orchestration.spawn (scheduler 每次派发/收束);新增 trace.ChildSpan 让不持有 Tracer 的叶子包也能开子 span - 11.4.3(设计先行,规格先写进 design/19 §1.1):tool 节点参数摘要 —— 放行表 argValueKeys + trace.IsSecretKey 统一判据(密钥类键不给值)+ 单值 80 rune / ≤24 条 + 出图前再清洗; 前端 NodeDetail 新增「参数摘要」区,未放行键显示「值未展示」 - 13.4.1:流式主路径输出侧护栏改后台 goroutine(脱离 context、30s 兜底、不阻塞回合), 输入侧仍在模型调用前同步拦下;拦截闭环到 guardrail 节点 + 审计事件 - 拆文件:scheduler.go 加 span 后 546 行 → 编排 span 助手拆出 scheduler_spans.go(回到 492) - 契约:无帧名/事件形状变化;graph_update 节点 props 新增**可选**键(tool args_keys/args_summary、 guardrail action/allow/elapsed_ms、memory action/scope/hits/…、orchestration subagent/persona), 文档同步由主会话统一落
63 lines
2.4 KiB
Go
63 lines
2.4 KiB
Go
package trace
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
)
|
||
|
||
// 本文件是 span 生产者侧的桥接层:它让不持有 *Tracer 句柄的叶子包
|
||
// (internal/runtime/agent 的护栏检查等)也能在当前 span 之下再开一个
|
||
// 子 span,从而把「尽可能深」的结构化追踪补齐(design/04 §9:LLM /
|
||
// 工具 / 编排 / 状态转移 / 子代理 / 记忆读写 / guardrail 全都要有生产者)。
|
||
//
|
||
// 图谱(graph*.go)与生产者(span 生产点)在这里分工:图构建器只负责把
|
||
// span 折成节点,生产者只负责如实记录发生了什么。两者都不需要对方存在
|
||
// —— 没有 tracer 时 ChildSpan 返回 nil,生产点照常跑完业务。
|
||
|
||
// ChildSpan 在当前 ctx 携带的 span 之下开一个具名字 span:新 span 复用其父
|
||
// span 的 Tracer(因此 Finish 时同样交给 Writer 落盘),ParentID 指向父
|
||
// span,返回的 ctx 把新 span 作为当前 span 继续往下传。
|
||
//
|
||
// ctx 未携带 span(未开追踪、或在根 span 之外调用)时返回 (ctx, nil):
|
||
// 调用方必须容忍 nil span(不要用它,也不要把 nil span 的 ctx 当真)。
|
||
// 空 ctx 按 context.Background() 处理。
|
||
func ChildSpan(ctx context.Context, name string, kind SpanKind) (context.Context, *Span) {
|
||
if ctx == nil {
|
||
ctx = context.Background()
|
||
}
|
||
parent := SpanFromContext(ctx)
|
||
if parent == nil {
|
||
return ctx, nil
|
||
}
|
||
parent.mu.RLock()
|
||
tracer := parent.tracer
|
||
parent.mu.RUnlock()
|
||
if tracer == nil {
|
||
return ctx, nil
|
||
}
|
||
span := tracer.newSpan(name, kind, parent)
|
||
return context.WithValue(ctx, spanKey{}, span), span
|
||
}
|
||
|
||
// FinishChild 关闭一个由 ChildSpan 开出的 span 并记录耗时与结果:span 为
|
||
// nil(未开追踪)时是空操作,因此生产点可以无条件调用它,不必在调用处
|
||
// 再写一遍 nil 判断。status 为 ok/error,err 为 nil 时代码不记录 error 属性。
|
||
func FinishChild(span *Span, status SpanStatus, err error) {
|
||
if span == nil {
|
||
return
|
||
}
|
||
if err != nil {
|
||
_ = span.SetProp("error", err.Error())
|
||
}
|
||
span.Finish(status)
|
||
}
|
||
|
||
// ElapsedMS 报告 span 自开始以来经过的毫秒数;nil span 或零 StartAt 返回 0。
|
||
// 它让叶子包在不依赖 loop 内部助手的情况下记录「耗时」属性。
|
||
func ElapsedMS(span *Span) int64 {
|
||
if span == nil || span.StartedAt.IsZero() {
|
||
return 0
|
||
}
|
||
return time.Since(span.StartedAt).Milliseconds()
|
||
}
|