大模型刷题服务,先把异常调用看清楚
2026/8/18 18:43:12 网站建设 项目流程

大模型刷题服务,先把异常调用看清楚

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_totalllm_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", promptTokens+completionTokens), ) _ = 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 关联唯一的SpanIDTraceID

通过在 Trace 中记录tool.nametool.argstool.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_tokens&completion_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降至预设阈值以下时,触发告警并自动执行降级策略:

  1. 限制 Tool Calling 最大重试轮次:从默认 5 轮缩减至 2 轮,停止高成本死循环。
  2. 模型动态降级:将通用代码生成任务降级至响应更快的轻量级模型,保障基础吞吐。
  3. 启用预置解题 Cache:对高频经典题目(如两数之和、LRU 缓存),直接检索 Vector DB 中的标准解题思路,降低实时 LLM 调用量。

告警触发后应先限制重试和排队,再由值班人员确认是否切换模型或缓存。把动作写成可回滚的配置,并定期用故障演练验证它是否真的能触发。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询