刷题 Agent 的快与省怎么量:固定题集、并发闸门和取消语义
刷题助手的一次请求可能同时带着题目、用户代码、报错和系统提示。先用请求指纹去重,再记录缓存命中、模型调用和取消结果;缓存、限流与降级只是控制入口,不替模型判断答案。
先把观测条件说清楚
评估前应固定模型、提示词版本、题目集、并发模型和缓存预热状态。至少记录:缓存命中率、模型调用次数、输入/输出 Token、TTFT、总耗时、超时和限流响应数。不要把一次压测中的具体数值外推到其他模型或流量。
# 以固定请求集压测;rate 与 duration 应按环境容量设置 vegeta attack -targets=targets.txt -rate=RATE -duration=DURATION | vegeta report curl -s http://127.0.0.1:9090/metrics | rg 'llm_request|cache_hit|token'请求路径
精确缓存的键应包含题目 ID、题目版本、代码、提问内容和提示词版本。语义缓存只能用于经过人工或程序验证的答案,并应限定在同一道题、同一任务类型内;相似度阈值需要用标注集校准。未命中的请求进入有界队列,由并发上限保护下游 API。批处理只适合供应商接口支持且各子请求能独立解析的场景。
一个有取消语义的并发闸门
type Gate struct { sem chan struct{} } func NewGate(maxConcurrent int) *Gate { return &Gate{sem: make(chan struct{}, maxConcurrent)} } func (g *Gate) WithPermit(ctx context.Context, fn func(context.Context) (string, error)) (string, error) { select { case g.sem <- struct{}{}: defer func() { <-g.sem }() return fn(ctx) case <-ctx.Done(): return "", ctx.Err() } }实际调用还应设置请求超时、区分可重试错误与参数错误,并为 429/5xx 使用带抖动的退避。缓存中不要保存含敏感代码或用户标识的数据,除非已有明确的保留策略。
如何判断是否值得保留
用同一请求集分别运行“无缓存”和“启用策略”的测试,比较模型调用量、各分位耗时和回答正确性。缓存命中后的速度没有意义,前提是复用的答案仍适用于当前代码和问题。若命中答案经常不相关,应收紧键或停止语义复用。