package loop import ( "context" "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/trace" ) // stepOutcome 是循环对单个模型步的内部视图,由 // agent 的 *agent.StepResult(非流式)或流式路径构建的等价物 // 归一化而来。 type stepOutcome struct { final bool text string toolCalls []agent.ToolCall stopReason string // modelStopReason 是 provider 对该模型调用的原始 stop/finish // 原因(Anthropic max_tokens / end_turn / tool_use),记录在 llm_call // span 上,使即使该步本身正常结束,截断也可见。 modelStopReason string // model 是服务该调用的路由模型,在流式路径上可知 //(该路径自行构建请求),在 agent 路径上为空。它 // 记录在 llm_call span 的日志事件上。 model string } // step 运行单次模型调用。配置了 OnStream 回调时,它 // 直接驱动适配器的 ChatStream,把增量串给 OnStream 并 // 应用循环的护栏;否则委托给 agent 的 RunStep, // 由后者运行 agent 自身的护栏并记录自己的 CallRecord。整个 // 调用被包在 llm_call span 中,使 M9 面板能看到每次 LLM 调用。 func (l *Loop) step(ctx context.Context, sessionID string, messages []model.Message) (*stepOutcome, error) { _, llmSpan := l.span(ctx, "llm.call", trace.SpanKindLLM) var ( out *stepOutcome err error ) // 静音(内部自主流程)绕过流式:它既不流向 // UI,也不付流式路径的额外开销。正常聊天则流式。 if l.onStream != nil && !isMutedBroadcast(ctx) { out, err = l.streamStep(ctx, messages) } else { out, err = l.agentStep(ctx, sessionID, messages) } if err != nil { l.spanProp(llmSpan, "error", err.Error()) l.finishSpan(llmSpan, trace.SpanStatusError) return nil, fmt.Errorf("loop: model step: %w", err) } l.spanProp(llmSpan, "stop_reason", out.stopReason) if out.modelStopReason != "" { l.spanProp(llmSpan, "model_stop_reason", out.modelStopReason) } l.spanProp(llmSpan, "tool_calls", len(out.toolCalls)) l.recordLLMDone(llmSpan, out, l.agentFor(ctx)) l.finishSpan(llmSpan, trace.SpanStatusOK) return out, nil } // agentStep 把非流式步委托给该 turn 的 agent,由后者运行输入 // 与输出护栏、调用模型并解码所有工具调用。 func (l *Loop) agentStep(ctx context.Context, sessionID string, messages []model.Message) (*stepOutcome, error) { sr, err := l.agentFor(ctx).RunStep(ctx, sessionID, messages) if err != nil { return nil, fmt.Errorf("loop: agent step: %w", err) } return &stepOutcome{ final: sr.Final, text: sr.Text, toolCalls: sr.ToolCalls, stopReason: sr.StopReason, modelStopReason: sr.ModelStopReason, }, nil }