
1. 项目缘起与核心思路拆解1.1 一个真实痛点大模型做代码探索杀鸡用了牛刀日常开发里有一类操作特别频繁但又特别“不值得”动用顶级大模型翻代码库找某个函数的定义、追踪一个变量从哪来、搞清楚某个模块的调用链、定位一段配置到底在哪个文件里生效。这些活儿本质上属于代码探索code exploration不是让你从零写一个复杂算法也不是让你做架构设计而是“在已有的代码里找信息、理关系”。问题在于很多人习惯直接把这些任务丢给 Claude 这类顶级模型。结果就是一次简单的“这个函数在哪被调用”可能触发模型读取大量文件、反复推理token 消耗飞快响应也慢。你花的是顶级模型的价钱干的却是“代码搜索简单归纳”的活。Ototo 这个项目要解决的就是这件事——把代码探索这类任务从 Claude 等大模型卸载offload到一个更小的模型上。这个思路其实很朴素不是所有任务都需要最强的大脑。就像你不会为了查一个单词去请一位语言学教授查词典就够了。Ototo 的核心价值就是给“查词典”这类任务配一个便宜、快、够用的小模型把顶级模型省下来干真正需要它的活。1.2 为什么是“卸载”而不是“替代”这里要区分两个概念。替代是指小模型完全接管大模型的工作这在小模型能力不足时很容易翻车。卸载是指把任务分类把适合小模型的那部分分流出去大模型仍然保留处理复杂任务的能力。Ototo 走的是后者这个选择很关键。我自己的理解是代码探索任务有几个特征天然适合小模型输入输出结构化程度高问的是“X 在哪”“Y 调用了谁”答案往往是文件路径、行号、函数名不需要长篇创作。对世界知识依赖低不需要模型懂历史、懂文学、懂业务背景只需要它能读懂代码语法和基本逻辑。可验证性强答案对不对跳转到对应文件一看便知容错成本低。调用频次高这类操作一天可能几十上百次单次省一点累积起来非常可观。反过来真正需要 Claude 这种顶级模型的场景——复杂重构、跨模块架构设计、模糊需求拆解——本来就不该被这些琐碎探索挤占额度。把两类任务分开各用各的模型这才是 Ototo 的设计哲学。1.3 整体架构的合理推测虽然项目标题只给了一句话但基于“从 Claude 卸载代码探索到小模型”这个描述一个合理的架构大致包含这几块任务识别层判断当前请求属于“代码探索”还是“复杂任务”。这一步可能通过规则、关键词或者一个轻量分类器完成。路由层把识别出的探索类任务转发给小模型其余留给大模型。小模型执行层小模型配合代码检索工具比如基于 ripgrep、语法树解析的检索完成探索。结果回传层把探索结果整理后返回给用户或上层大模型。提示这种“路由分流”的模式在工程上非常常见核心难点不在模型本身而在“怎么准确判断一个任务该走哪条路”。判断错了要么小模型答不好要么大模型被浪费。2. 核心细节解析与实操要点2.1 小模型到底要选多大这是实操里第一个要拍板的问题。小模型不是越小越好太小了连代码语法都读不明白探索结果全是错的反而要人工返工。我的经验是代码探索类任务对模型的要求集中在三点能读懂多语言代码语法、能做基本的符号追踪、能输出结构化的文件位置信息。按这个标准参数量在 7B 到 14B 区间的代码专用模型通常是比较舒服的区间。再小处理稍复杂的调用链就容易断片再大就失去了“卸载”的成本优势还不如直接用大模型。具体选哪个要看你代码库的语言构成——如果主要是 Python、JavaScript 这类主流语言选择面很宽如果是比较小众的语言就得挑训练语料覆盖到的模型。这里有个容易被忽略的点小模型的上下文窗口。代码探索经常需要把多个文件片段塞进上下文窗口太小会频繁截断导致模型看不到完整信息。我建议至少保证 32K 的上下文能到 128K 更好。2.2 检索工具比模型本身更重要很多人做这类项目把精力全花在调模型上结果发现效果一般。其实代码探索的准确率一大半取决于检索层做得好不好。模型再聪明你给它喂的文件片段不对它也答不对。实操中我推荐这套组合ripgrep做全文快速检索找关键词、函数名、字符串出现位置速度极快。语法树解析tree-sitter 之类做结构化检索比如“找出所有调用 foo 函数的地方”比纯文本匹配准得多。符号索引提前建好函数、类、变量的定义位置索引查询时直接命中。小模型拿到这些工具返回的精准片段后只需要做“归纳和表达”难度大幅下降。这就好比给一个实习生配了一套好用的搜索工具他也能快速找到你要的东西。2.3 任务分流的判断逻辑分流判断是 Ototo 这类项目的“大脑”。判断错了整个卸载就失去意义。我总结了几条实用的判断规则任务特征建议路由理由包含“在哪”“谁调用”“定义位置”等词小模型典型探索任务需要跨多个模块做架构推理大模型探索层搞不定单文件内的语法问题小模型上下文小够用需要生成新代码或重构大模型创作类任务模糊的自然语言需求大模型需要理解意图注意不要指望一次判断就完美。实际部署时建议加一个“兜底机制”——小模型如果连续两次答非所问自动升级给大模型处理。这样既省成本又不会因为分流错误让用户体验崩掉。2.4 成本与延迟的实测账卸载的核心收益就是省钱和省时间。我按常见情况算一笔账假设一天有 200 次代码探索请求每次用大模型平均消耗 8000 token用小模型平均消耗 3000 token因为检索层已经帮忙缩小了范围。大模型按较高单价算小模型按低一个数量级算一天下来的成本差距可能是几十倍。延迟方面小模型推理速度快加上检索层并行单次响应能从几秒降到一秒以内。这个账不一定精确但方向是明确的高频、低难度的任务用小模型处理边际成本极低。这也是为什么“卸载”思路在工程上越来越受欢迎。3. 实操过程与核心环节实现3.1 环境准备与依赖安装先把基础环境搭起来。假设你在 Linux 或 macOS 上操作Windows 用户建议用 WSL能省掉很多路径和权限的麻烦。# 安装 ripgrep用于快速全文检索 # Ubuntu/Debian sudo apt-get install ripgrep # macOS brew install ripgrep # 安装 Python 依赖假设用 Python 做编排 pip install tree-sitter tree-sitter-languages小模型的部署方式有两种主流选择本地跑用 llama.cpp、ollama 之类或者调用小模型的 API。本地跑的好处是数据不出机器适合代码敏感的场景API 的好处是省去显卡和运维。我个人的建议是如果代码库不大、机器有张像样的显卡本地跑更划算如果团队协作、代码库巨大用 API 更省心。3.2 建立代码索引这一步是地基。索引建得好后面检索又快又准。import subprocess import json def build_symbol_index(repo_path): 用 ripgrep 快速提取函数和类定义位置 # 匹配常见的函数/类定义模式 pattern r(def |class |function |const \w \(|func ) result subprocess.run( [rg, --line-number, --no-heading, pattern, repo_path], capture_outputTrue, textTrue ) index {} for line in result.stdout.splitlines(): parts line.split(:, 2) if len(parts) 3: file_path, line_no, content parts index.setdefault(file_path, []).append({ line: int(line_no), content: content.strip() }) return index这段代码只是示意实际项目里你会想用 tree-sitter 做更精确的语法解析避免把注释里的“def”也匹配进来。索引建好后存成 JSON 或轻量数据库查询时直接读不用每次重新扫。3.3 检索与小模型协作的完整流程把整个流程串起来大概是这样的接收用户查询比如“parse_config这个函数在哪定义的被谁调用了”。分流判断识别出这是探索任务路由到小模型分支。检索执行先用符号索引查定义位置再用 ripgrep 查调用点。上下文组装把命中的文件片段、行号、周边代码拼成一段精简上下文。小模型归纳把上下文和问题一起给小模型让它输出结构化答案。结果返回整理成“定义在 X 文件第 N 行被 A、B、C 三处调用”这样的形式。def explore_code(query, index, small_model): # 第一步从查询里提取目标符号简化处理 target extract_symbol(query) # 第二步查定义 definitions lookup_definition(target, index) # 第三步查调用 calls subprocess.run( [rg, --line-number, target, .], capture_outputTrue, textTrue ).stdout # 第四步组装上下文 context f定义位置:\n{definitions}\n\n调用位置:\n{calls} # 第五步交给小模型归纳 prompt f根据以下代码信息回答问题{query}\n\n{context} answer small_model.generate(prompt) return answer这个流程的关键在于小模型拿到的不是整个代码库而是检索层已经筛过的精准片段。它的任务从“大海捞针”变成了“读几段代码然后总结”难度和 token 消耗都大幅下降。3.4 参数调优的几个关键点小模型跑起来后有几个参数值得反复调温度temperature代码探索要的是准确不是创意建议设到 0 到 0.2 之间越低越稳定。最大输出长度探索类答案通常不长设个 512 到 1024 token 就够避免模型啰嗦。检索返回条数ripgrep 命中太多时别一股脑全塞给模型按相关度排序取前 10 到 20 条太多反而干扰判断。上下文截断策略优先保留定义位置和最近的调用点远处的调用可以只给文件名和行号。实操心得我一开始把检索结果全塞给模型结果它经常被无关命中带偏。后来改成“定义优先、调用按距离排序取前若干条”准确率明显提升。检索层做减法比模型层做加法更有效。4. 常见问题与排查技巧实录4.1 小模型答非所问怎么办这是最常见的问题。排查顺序建议这样走先看检索结果对不对把喂给模型的上下文打印出来人工看一眼。如果检索本身就错了那问题不在模型。再看提示词是否清晰小模型对提示词比大模型敏感指令要短、要具体。别写“帮我分析一下”要写“回答这个函数定义在哪个文件第几行”。最后看模型能力是否够如果检索对、提示词也对还是答错那可能是模型太小考虑换大一号的。4.2 检索命中太多导致上下文爆炸大型代码库里一个常见函数名可能被调用几百次。这时候要有取舍策略情况处理方式命中少于 20 条全部保留命中 20 到 100 条定义全留调用按文件聚合每文件取前 3 条命中超过 100 条只留定义 调用最多的前 5 个文件命中上千条提示用户缩小范围或改用更精确的符号查询这个策略的核心是保证定义信息不丢调用信息按需裁剪。定义是回答问题的关键调用是补充。4.3 多语言代码库的处理一个仓库里混着 Python、Go、TypeScript 是常态。小模型如果只擅长一种语言跨语言探索就容易出错。我的做法是按文件扩展名分组每组用对应的语法解析规则建索引。检索时带上语言标记让模型知道这段代码是什么语言。如果小模型对某语言支持差那部分任务直接升级给大模型别硬撑。4.4 分流判断错误的兜底前面提过分流判断不可能百分百准。兜底机制我建议这样设计小模型返回结果后做一个简单的置信度检查如果答案里没有出现任何文件路径或行号大概率是没找到触发升级。记录每次分流的结果定期复盘哪些任务被误判用来优化判断规则。给用户一个手动“转大模型”的入口让用户自己决定。注意兜底机制会增加复杂度但它是保证体验的底线。宁可多花一点大模型的额度也不要让用户拿到错误答案还以为是对的。4.5 常见问题速查表现象可能原因解决方向答案里没有具体位置检索没命中或模型没读懂检查检索、简化提示词响应特别慢检索范围太大或模型太大缩小检索范围、换更小模型同一问题答案不稳定温度太高温度调到 0.1 以下跨文件调用漏掉索引没覆盖全重建索引、检查忽略规则小模型频繁升级到大模型分流规则太激进放宽探索任务判定条件5. 这套思路还能怎么扩展Ototo 这个项目本身聚焦在代码探索的卸载但这套“任务分流小模型处理高频低难任务”的思路其实可以迁移到很多场景。比如文档问答、日志分析、配置查询这些任务的共同点都是“高频、结构化、可验证”天然适合小模型。我个人的体会是未来工程上的主流不会是“一个模型打天下”而是多个模型各司其职用编排层把它们串起来。大模型负责需要深度思考和创造的部分小模型负责高频、确定性的部分检索工具负责提供精准素材。Ototo 做的正是这个方向上一个很具体的切口——从代码探索开始把这件事做扎实。如果你也在做类似的模型编排项目我的建议是先把检索层打磨好再调模型。检索层是地基模型是装修。地基不稳装修再漂亮也白搭。另外别一上来就追求全自动分流先做半自动——让用户或规则决定走哪条路跑顺了再逐步自动化。这样踩的坑会少很多。