大模型刷题服务先把异常调用看清楚1. 模型响应变慢与 Token 消耗飙升的归因分析如果 Agent 会调用判题沙箱就要预先考虑重试失控一次请求可能在模型、工具和沙箱之间往返外层仍返回 200但耗时和 Token 已经失去控制。下面讨论的是设计场景不是线上事故复盘阈值应由自己的容量和成本预算确定。给传统后端服务做可观测性方法通常比较标准查慢 SQL、看 JVM 垃圾回收、盯 HTTP 状态码 5xx。但给大模型辅助算法训练系统做可观测性面临全新的挑战。大模型的输出具备非确定性。模型可能未返回报错200 OK但生成的 Python 解题代码包含语法错误或者 Prompt 中拼接了过多的历史刷题上下文撑爆了 Context Window又或者 Tool Calling 陷入死循环不停地请求判题机。如果只看外层 HTTP 响应时间很难定位内部细节。应把日志、指标和链路追踪放到模型调用与判题的边界上让每轮工具调用都关联到同一条请求。2. 三维指标体系设计防范 Token 异常与隐形超时在智能刷题场景下指标不能局限于常规的 QPS 和 CPU 利用率。需要建立一套专门针对 LLM 算法系统的监控指标体系。第一维是Token 吞吐与成本指标。必须精确统计llm_prompt_tokens_total和llm_completion_tokens_total。按题目类型动态规划、图论、双指针和用户阶段进行 Label 标记直观展现不同题目类型下的提示词开销。第二维是算法质量与效果指标。刷题系统的核心是辅助通过题目。需要统计algo_solution_pass_rate一次性通过率、algo_repair_attempts_count代码修复重试次数以及algo_compiler_error_ratio编译错误占比。若模型生成代码的通过率持续低于预设阈值系统即触发降级策略。第三维是链路耗时与背压指标。针对大模型推理耗时可以将整体延迟拆解为Prompt 组装耗时、TTFT首字延迟、Completion 生成耗时与沙箱判题耗时。package observability import ( context time github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus/promauto go.opentelemetry.io/otel go.opentelemetry.io/otel/attribute go.opentelemetry.io/otel/trace ) var ( // Token 消耗计数器 tokenUsageVec promauto.NewCounterVec( prometheus.CounterOpts{ Name: llm_token_usage_total, Help: Total number of tokens consumed in algorithm AI system, }, []string{model, type, problem_category}, ) // 链路延迟直方图 latencyHistogramVec promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: llm_request_duration_seconds, Help: Latency of LLM requests broken down by phase, Buckets: []float64{0.1, 0.5, 1.0, 2.5, 5.0, 10.0, 20.0}, }, []string{phase, status}, ) // 算法通过率指标 solutionPassVec promauto.NewCounterVec( prometheus.CounterOpts{ Name: algo_solution_verifications_total, Help: Total count of verification results from judge sandbox, }, []string{result_status, problem_difficulty}, ) ) type ObservedLLMClient struct { tracer trace.Tracer } func NewObservedLLMClient() *ObservedLLMClient { return ObservedLLMClient{ tracer: otel.Tracer(algo-ai-observability), } } // ExecuteAgentLoop 执行智能刷题链路并收集 Trace 与 Metrics func (c *ObservedLLMClient) ExecuteAgentLoop(ctx context.Context, problemID string, category string, difficulty string, prompt string) (string, error) { ctx, span : c.tracer.Start(ctx, ExecuteAgentLoop, trace.WithAttributes( attribute.String(problem.id, problemID), attribute.String(problem.category, category), )) defer span.End() start : time.Now() // 1. 模拟 Prompt 组装与发送 // 示例仅用于展示指标位置生产环境应使用供应商返回的 usage 或同一分词器计算。 promptTokens : len([]rune(prompt)) / 4 tokenUsageVec.WithLabelValues(gpt-4o, prompt, category).Add(float64(promptTokens)) // 2. 调用 LLM llmStart : time.Now() span.AddEvent(start_llm_call) // 假设 LLM 返回代码与结果 solutionCode, completionTokens, err : mockLLMCall(ctx, prompt) if err ! nil { span.RecordError(err) latencyHistogramVec.WithLabelValues(llm_call, error).Observe(time.Since(llmStart).Seconds()) return , fmt.Errorf(llm call failed: %w, err) } latencyHistogramVec.WithLabelValues(llm_call, success).Observe(time.Since(llmStart).Seconds()) tokenUsageVec.WithLabelValues(gpt-4o, completion, category).Add(float64(completionTokens)) // 3. 沙箱判题校验 judgeStart : time.Now() span.AddEvent(start_sandbox_judge) pass, judgeResult : mockSandboxJudge(solutionCode) latencyHistogramVec.WithLabelValues(sandbox_judge, judgeResult).Observe(time.Since(judgeStart).Seconds()) solutionPassVec.WithLabelValues(judgeResult, difficulty).Inc() span.SetAttributes( attribute.Bool(judge.pass, pass), attribute.String(judge.result, judgeResult), attribute.Int(tokens.total, promptTokenscompletionTokens), ) _ start // 生产环境写结构化日志避免打印题目或模型输出 return solutionCode, nil } func mockLLMCall(ctx context.Context, prompt string) (string, int, error) { time.Sleep(120 * time.Millisecond) // 模拟网络与推理延迟 return func twoSum(nums []int, target int) []int { ... }, 150, nil } func mockSandboxJudge(code string) (bool, string) { return true, Accepted }3. 把 Trace 接入 Tool Calling定位多轮调用的延迟只查看总延迟难以确定异常调用的阶段。大模型系统的复杂性集中在多轮 Tool Calling 上。例如 Agent 在识别到用户提交的代码可能存在超时风险时会依次调用analyze_complexity工具、generate_testcase工具和run_sandbox工具。这一长链条中任何一个环节滞留都会拖慢整体响应。借助 OpenTelemetry 的分布式链路追踪Distributed Tracing可以为每一轮 LLM 产生的 Tool Call 关联唯一的SpanID和TraceID。通过在 Trace 中记录tool.name、tool.args和tool.response_length等属性可以在可视化面板中准确排查各步骤的开销例如第 1 轮 LLM 生成耗时 1.1s第 2 轮复杂度分析耗时 0.05s第 3 轮提交沙箱判题耗时 5.2s。这样可以清晰暴露延迟升高的主因例如沙箱判题机处理死循环代码时等待超时释放资源的情况。4. 结构化日志收口规范非结构化文本输出系统初期若使用简单格式输出日志如log.Printf(Prompt: %s, Answer: %s, prompt, answer)在大规模运行下会导致日志存储快速膨胀并且增加日志检索与分析难度。LLM 调用的日志需要采用结构化格式Structured Logging。每条日志建议记录以下字段trace_id: 关联全链路。user_id: 用于异常调用识别。problem_id: 关联算法题目。prompt_tokenscompletion_tokens: 成本审计凭证。finish_reason: 模型停止原因stop正常length超长截断tool_calls工具调用。code_snippet_hash: 代码哈希避免打入完整代码导致存储过度膨胀。其中记录finish_reason: length尤为重要。当算法生成的代码因达到max_tokens限制而被截断时会导致后续编译失败。记录此字段可以帮助快速定位是否为截断问题避免盲目调试提示词。5. 可观测性闭环从告警触发到自动降级熔断建立可观测性的核心目的在于实现自动化的系统自愈与降级。通过在 Prometheus 中配置规则当sum(rate(llm_token_usage_total[5m]))异常升高且algo_solution_pass_rate降至预设阈值以下时触发告警并自动执行降级策略限制 Tool Calling 最大重试轮次从默认 5 轮缩减至 2 轮停止高成本死循环。模型动态降级将通用代码生成任务降级至响应更快的轻量级模型保障基础吞吐。启用预置解题 Cache对高频经典题目如两数之和、LRU 缓存直接检索 Vector DB 中的标准解题思路降低实时 LLM 调用量。告警触发后应先限制重试和排队再由值班人员确认是否切换模型或缓存。把动作写成可回滚的配置并定期用故障演练验证它是否真的能触发。