跳到正文
阿里雲開發者社區 AI·· 2 小時前AI 評分56

Claude Code 召回應帶多少條?調整 top-k 和閾值的實踐記錄

召回该带多少条:在 Claude Code 里调 top-k 和阈值的一段记录

AI 導讀

作者在 Claude Code 的程式碼倉庫工作流中接入 ctxdb,以 top-k 和 threshold 調整記憶召回,並把硬約束與候選資訊分開組裝。任務目標、規則及執行狀態固定優先注入,個人經驗用 memory search 查找,團隊規範則透過 kb search 查閲原文;top-k 設定返回上限,threshold 用於篩除低分結果。

正文

2026-10-23 5

版权

版权声明:

本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议》和 《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

简介: 本文作者分享了在中等规模代码仓库中使用Claude Code时,因上下文过载导致规则误用(如错误码前缀混淆)的实践反思。为优化Agent提示工程,他将上下文分为“硬约束”(优先固定注入)与“候选信息”(按决策价值检索),并引入`ctxdb`工具,通过`memory search`与`kb search`分离私有经验与团队规范,精细调控`--top-k`和`--threshold`参数,兼顾召回精度与执行可靠性。

我现在写的服务放在一个中等规模的仓库里,几十个模块,三四个人轮流改。半年前开始让 Claude Code 承担大部分重复性的编码,比如加接口、补测试、按约定改错误码。用得越久,我越常碰到同一类问题:它手里的东西太多了。仓库说明、我写的约定笔记、之前几轮会话里交代过的偏好,全都往窗口里塞,到了干活的时候,它反而抓不住当前这一步该遵守的那条规则。有一次它给新接口用了旧的错误码前缀,那套前缀团队早就换掉了,只是旧说明和新说明同时躺在笔记里,它挑了先出现的那一份。

问题不大,改一行就好。可它让我开始认真对待一件之前一直敷衍的事:一次任务到底该给 Agent 带多少上下文,带哪些,按什么顺序。

自己先写了一个组包脚本

我最早的做法很朴素:笔记切块,向量检索取 top-k,按分数拼进提示词,窗口还有余量就再多放几条。模型窗口越来越大,我当时觉得多带一点总比漏掉强。

后来才意识到窗口大小只是天花板,约束预算的是调用延迟、单次成本、模型在长输入里分配注意力的方式,以及任务本身的复杂度。"多带一点"惹出来的麻烦也更难排查:重复内容把关键约束挤到中间,旧版规则和新规则并排出现,模型看起来每条都读了,执行时却按其中一条走。

于是我把上下文分成两类。一类是硬约束:任务目标、必须遵守的规则、当前执行状态、工具调用的边界。它们和我这句话未必语义相似,可一旦缺了,Agent 就不知道自己能做什么。这部分最先放,不参与相关性排序。另一类才是候选信息,按决策价值挑,默认只放摘要,需要核验时再拉原文。

下面是我当时写的示意伪代码,只表达分配顺序,和任何产品接口无关:

# 示意:我自己的组包顺序,非任何产品 API
budget = model_budget(task)            # 由延迟、成本、任务类型共同决定
pack   = []
pack += hard_constraints(task)         # 身份、权限、目标、规则、执行状态
budget -= tokens(pack)
candidates = dedupe_by_source(retrieve_within_acl(task))  # 先鉴权再召回
for item in rank_by_decision_value(candidates, task):
    if item.kind == "skill" and not task.will_act:
        continue                       # 不打算动手时不带操作步骤
    if not fits(item.summary, budget): break
    pack.append(item.summary)          # 默认先放摘要级内容
# 需要核验的结论,再按引用回拉原文

这个脚本跑了一段时间,有几处一直让我不舒服。

rank_by_decision_value 是我最没把握的部分。相似度高的内容不一定对决策有用,关系隔得远的信息反倒可能是关键前置条件,我只按任务类型写了几条粗规则。

按来源去重也只做到了一半。同一条约定可能同时出现在仓库说明、我的笔记和某次会话的摘要里,我能识别同一文件的重复,识别不了"换了种说法的同一条规则"。

会话之间的记忆完全靠我手动维护。哪条偏好该记下来、什么时候过期,都得我自己改笔记。笔记越长,召回越乱,这又回到了起点。

查记忆方案时看到的帮助文档

记忆这一处,让我开始找现成的 Agent 记忆方案。对比了几种做法之后,我读到一份 Agent Context 的帮助文档,命令行工具叫 ctxdb。

我关心的只有一件事:它在召回这一侧给了调用方哪些控制手段,能不能替掉我那段脚本里最脆弱的部分。接入只花了一条命令,按文档执行 ctxdb setup --agent claude,它会配置凭证、Hooks 和 Skill。

memory search 的三个参数

帮助文档里 memory search 的写法是这样的:

ctxdb memory search "<q>" --top-k=<n> --threshold=0.4
ctxdb memory search "<q>" --knowledge

返回的是 results,每条有 id、memory 和 score。我花时间最多的是 --top-k 和 --threshold 这两个参数的配合。

--top-k 好理解,就是最多拿几条。--threshold 默认是 0.4,分数低于它的不返回。这两个参数放在一起,其实就是我组包脚本里"取多少"和"够不够格"两个判断,只是从应用层挪到了检索层。

挪到检索层有一个我事先没想到的好处。以前我的脚本先拿一大批候选,再在本地按分数截断,召回回来的那批内容其实已经进了日志和中间变量,只是没进窗口。现在低于阈值的内容根本不回来,我要排查的东西少了一截。

Agent Context 的检索是语义加关键词的混合检索,多路召回之后再融合打分,所以 score 反映的是融合后的结果。

我拿仓库里几类常见任务的提问去试,同一句话分别用不同的 top-k 和阈值跑,观察返回条数和内容的变化。几轮下来我形成了一个粗浅的印象:top-k 管上限,噪声多少主要看阈值。阈值调低,一些只是沾边的记忆会进来,比如我在另一个模块里交代过的日志习惯;阈值调高,返回的条数少了,偶尔会把一条措辞和问题差得比较远、但确实相关的约定挡在外面。

问错误码规范这种精确问题,和问"这个模块的测试一般怎么写"这种宽泛问题,适合的阈值明显不一样。我现在的做法是先用默认值,发现漏召回再按任务类型往下放,没有把握的时候宁可让 top-k 小一点、阈值低一点,再自己看一眼返回内容。

--knowledge 是另一个用得多的开关。加上之后,按我的理解,一次查询会把知识库里的相关内容和记忆一起带回来,省得我分两次调用再自己合并。对 Agent 来说少一次工具调用,对我来说少一段合并逻辑。只是两类结果混在一起之后,我还是想在组包时把"此前的经验"和"团队的规则"分开标注,免得模型把一条私人偏好当成团队约定。

kb search 单独拿出来用

需要原文和出处的时候,我直接用知识库检索:

ctxdb kb search "<q>" --kb=<name> --top-k=6

它返回 chunks,每个 chunk 有 content 和 score。我让 Claude Code 在改动说明里写"依据某某约定"时,要求它只引用 chunk 里的内容。这和我自己脚本里的做法其实一致:第一轮放摘要级内容和引用,需要核实时再回拉原文。

预算现在怎么分

接入之后,我的组包顺序没有大改,改的是每一段由谁来做。

硬约束仍然由我自己放,任务目标、当前状态和动作边界不交给任何检索。权限我依旧放在检索之前处理,先全量召回、再在提示词里叮嘱"某类数据不要用"这种做法我不接受,模型看到了就是看到了。

私有经验交给 memory search,top-k 设得比较小,阈值按任务类型调。团队约定和规则走 kb search,拿 chunk 原文。任务真要执行操作时才放操作步骤。任务执行到一半变了方向,预算重新分一次。

我那段 dedupe_by_source 还留着,因为跨记忆和知识库的重复仍然可能出现:同一条约定,我在会话里说过一次,被提炼进了记忆,团队文档里又写了一次。两份都进窗口,证据一份没多。我的规则是知识库原文优先,记忆里的那条只在知识库里查不到时才用。

为了让 Claude Code 自己分清什么时候查哪一边,我在仓库说明里加了几行约定:问"之前怎么做的、我偏好什么"时用 memory search;问"团队规定是什么、哪份文档写的"时用 kb search;需要在提交说明里写依据时,只能引用 chunk 原文。几行字解决不了所有情况,但它在拿不准时会先查知识库,这个倾向比以前什么都往记忆里找要稳一些。

衡量时我只看自己任务集上的现象:这次它有没有按当前的错误码前缀写,引用的约定能不能在 chunk 里找到原文,出错时是缺了上下文还是被上下文干扰。那次错误码的问题,换了这套做法之后我在类似任务里没再见到,样本不多,我不打算把它说成稳定结论。

压缩时我仍然不让碰的两类内容

召回量收下来以后,剩下的预算问题主要在压缩。能砍的我砍得很干脆:重复内容、和当前动作无关的段落、已经结束的中间过程。长会话我会让 Claude Code 归纳成"当前状态加关键决策",原始记录另存,不进窗口。

入库做得好,后面要砍的就少。帮助文档里提到知识入库可以走 AI 质量评审,按逻辑性、噪音比、矛盾度打分,必要时智能重写,入库还是驳回由人决定。把一部分整理工作前移到写入侧,这个方向我认同。

有两类内容我不交给任何压缩或改写环节。

一类是带权利义务措辞的条款。"可以""应当""不得"只差几个字,改写时很容易被统一成一句温和的建议。比如"不得在接口层直接返回数据库错误"被概括成"建议对数据库错误做封装",读起来意思差不多,执行时的约束力完全不同。这类内容我宁愿放原句,哪怕多占一些 Token。

另一类是做法的适用条件。如果只留操作步骤、删掉"什么情况下才适用",Agent 拿到的是一套看起来完整的方法,却可能在错误的场景里执行它。这种错误比缺上下文更隐蔽,因为输出读起来很专业,审代码的人也容易放过去。

所以知识库里的这两类文档,我在提交评审时会特别看一眼重写结果,有改动措辞的就驳回,保留原文入库。这一步现在只能靠人看,我还没想到自动检查的办法。

少带不一定好

有几类任务我反而会主动放宽预算:比较两个版本的接口差异,追查某个约定当初为什么这么定,在一份长设计文档里找前后矛盾。这时候过度裁剪会直接破坏证据链。我会把 kb search 的 top-k 放大,或者分成先读取、再核验的两步,否则结论没法复查。

反过来,日常的小改动我会尽量收紧。加一个字段、补一个测试,Agent 需要的是这个模块的约定和测试命令,整份架构说明带进来只会分散注意力。

接下来准备做的事

下一步我打算把仓库里最常见的十几类任务各写一句代表性提问,固定下来,换阈值时整批重跑,看哪类任务在什么区间开始漏召回或混进噪声。这件事以前做不了,因为召回逻辑和组包逻辑缠在我自己的脚本里,现在检索侧有了明确的参数,至少可以单独测。跑的时候 memory search 和 kb search 分开记,我想知道 Agent Context 的两条检索在同一句提问下各自更适合多大的 top-k。

还有两件事没想清楚。

Hooks 的自动召回算一件。setup 之后,每次提交 prompt 都会用 prompt 作查询自动召回,注入 <recalled-memories>。这条路径用的 top-k 和阈值是多少、能不能按任务调,我在帮助文档里没找到明确说明,也可能是我没读仔细。如果不能调,我手动调出来的那套参数就只对显式调用的 memory search 有效,两条召回路径会各管各的。

另一件是应用层要不要再排一次。检索侧已经做过混合检索和融合排序,我按任务再重排一遍会更贴近当前动作,可两套排序叠在一起,出了问题很难说清是哪一层的判断。我目前只在应用层做过滤和去重,不做重排。等那批固定提问跑出几轮结果,再决定要不要加。

來源:阿里雲開發者社區 AI · developer.aliyun.com