semble:让Agent精准检索代码,大幅降低token成本的语义搜索工具 之前在做一个面向仓库级代码问答的 Agent 小工具时我遇到了一个非常现实的问题模型上下文窗口有限token 费用却在飞速上涨。最开始的想法很简单把整个仓库的代码文件全部拼进 prompt让模型自己找答案。结果项目还没跑通账单先让人清醒了。后来我把思路换成了“先检索、后生成”让 Agent 先去代码库里定位相关片段再把片段喂给模型。这一改效果立竿见影token 消耗大幅下降回答准确率也上去了。而帮我实现这个思路的核心工具就是本文要聊的 semble。这篇文章会围绕 semble 展开从它解决的问题、核心原理到安装配置、实际使用方式和与 Agent 集成的思路最后给出常见问题排查和工程落地建议。适合正在做 AI Agent、代码问答、代码审查工具或者想降低 LLM 调用成本的开发者阅读。学完本文你能理解语义代码搜索的基本思路能独立搭建一个代码检索服务也知道怎么把它接入自己的 Agent 流程。1. 背景与核心概念1.1 Agent 处理代码时的 token 困境现在很多开发者喜欢用 Agent 来写代码、查 bug、做代码评审。Agent 的工作方式通常是把“上下文”交给大模型你给它一堆文件它基于这些文件给出回答。问题也出在这里一个中大型项目的代码量非常大动辄几千个文件、几十万行代码。如果全部塞进上下文会遇到三类问题。第一是成本问题。大模型按 token 计费代码文本的 token 密度很高一个 500 行的文件可能就有 4000 到 6000 个 token。塞 100 个文件就是几十万 token一次调用成本就很可观。第二是窗口问题。即使是支持长上下文的大模型也有长度上限。上下文中塞下整个仓库后模型的有效注意力会被稀释它很难从大量无关代码里找出真正相关的几行。第三是响应速度。输入越长首字返回越慢用户体验明显变差。所以核心矛盾是Agent 需要理解代码但不需要理解所有代码。它只需要“恰好相关”的那一小段。而这句话正是 semble 这类代码搜索工具存在的价值。1.2 什么是代码搜索为什么它适合 Agent代码搜索并不是新概念。传统 IDE 里的文本搜索、正则搜索、基于文件名的搜索都属于代码搜索。它们的局限在于只能按字面匹配。你搜“用户登录失败”如果代码里没有这个中文字符串就什么都搜不到。但代码的真实描述可能是validateCredentials、checkPassword或failedToLogin字面搜索无法建立这种语义联系。semble 的思路是构建“语义代码搜索”。它通过解析代码结构和语义特征把代码片段转换成可检索的向量表示。当你用自然语言提问“这段代码在哪里校验用户密码”它能返回与问题语义最接近的函数和代码块而不是只匹配关键词。这种能力正好符合 Agent 的需求Agent 收到的用户问题通常是自然语言它需要从仓库中定位对应实现。另外代码搜索对 token 的节省是结构性的。传统方法把整个仓库送入模型token 消耗是 O(仓库大小)。使用代码搜索后流程变成先检索出 top-k 个代码片段再把片段送入模型token 消耗变成了 O(片段大小)。在仓库很大的情况下省下的 token 数量非常可观这也是很多团队把代码搜索作为 Agent 基础设施的重要原因。1.3 semble 是什么semble 是一个面向代码仓库的搜索工具在 GitHub 上已经有 6k Star 左右的热度。它不是一个完整的 Agent 框架而是 Agent 和代码仓库之间的“检索层”。你可以把它理解成一个本地的代码 AI 搜索引擎它先扫描一个代码仓库建立索引之后你用自然语言提问它会返回候选代码文件、函数位置和相似度排名。之所以在标题里强调“让 Agent 只看该看的那段代码”是因为 semble 的设计目标不是替代大模型而是给大模型做“信息筛选”。它不负责生成答案只负责回答“相关代码在哪里”。这样 Agent 就能把有限的上下文预算全部花在与问题相关的代码片段上。有一点需要提前说明semble 这类工具处于快速迭代阶段具体 CLI 命令、索引目录参数、配置文件格式可能随版本变化。本文会给出通用用法和示例但你在实际安装时请以官方仓库 README 和当前版本文档为准。2. 环境准备与安装2.1 环境要求semble 的运行环境比较轻量通常只需要一个现代操作系统和一个 Python 运行时。常见的组合是操作系统macOS、Linux、Windows建议使用 WSL2运行环境Python 3.9 或更高版本包管理工具pip 或 uv磁盘空间索引会占用额外空间小项目几百 MB 足够大仓库需要预留更多网络首次安装依赖时需要联网如果采用本地模型做嵌入后续可以离线使用如果你的机器上有多个 Python 版本建议使用虚拟环境隔离依赖避免和系统 Python 冲突。2.2 安装 semble安装方式很简单可以用 pip 直接安装。下面以虚拟环境为例。# 创建虚拟环境 python -m venv .venv # 激活虚拟环境macOS / Linux source .venv/bin/activate # 激活虚拟环境Windows # .venv\Scripts\activate # 安装 semble pip install semble安装完成后可以用下面的命令验证是否安装成功semble --version如果输出版本号说明安装成功。需要注意的是不同版本之间的 CLI 可能有差异有些版本使用semble作为主命令有些版本可能使用python -m semble。遇到命令不存在时优先查看官方 README。2.3 项目结构与配置准备为了方便演示我先创建一个简单的示例仓库。这里用一个小型 Python 项目作为搜索对象它模拟了一个支持用户注册、登录、订单查询的 API 服务。mkdir demo-project cd demo-project mkdir app touch app/__init__.py touch app/auth.py touch app/order.py touch app/user.py后续我会往这些文件里写入代码再让 semble 建立索引并执行搜索。如果你已经有现成的仓库可以跳过这一节直接用你自己的代码库测试。3. 核心工作原理拆解3.1 语义搜索的总体流程semble 的工作流程可以分成四个阶段扫描代码、索引构建、查询匹配、结果排序。扫描代码阶段semble 会遍历指定目录按文件类型过滤出代码文件同时会忽略.git、node_modules、dist、__pycache__之类的目录和二进制文件。索引构建阶段它会解析每个代码文件的结构提取函数、类、变量等符号并为代码块生成向量表示。查询匹配阶段它把你输入的自然语言转换成同样的向量空间计算相似度。结果排序阶段它综合相似度、代码结构等信号返回最相关的文件路径、函数名和代码片段。这个过程的本质是检索增强生成RAG思路在代码领域的应用。大模型擅长生成但不知道你的私有代码长什么样semble 负责把私有代码中与问题相关的部分找出来交给大模型。3.2 为什么能省 token省 token 的关键在于“片段级检索”而不是“文件级填充”。在传统方案里你可能会把一个文件整体加入上下文因为无法确定哪个函数是模型需要看的。而 semble 会把检索单位拆到函数、类或代码块级别返回的每个结果都包含“哪个文件、哪个函数、代码片段”。举个例子一个auth.py文件有 300 行其中有登录、注册、token 刷新三个逻辑。如果按文件整体送给模型不管用户问的是登录还是 token 刷新模型都得读完 300 行。使用 semble 后你问“token 刷新逻辑在哪”它只返回对应的那个函数可能只有 30 行。这就是数量级上的 token 差异。嵌套在 Agent 流程里节省就更明显。Agent 通常需要多轮检索每一轮都只带着相关片段去调用模型而不是反复把同一个大文件塞进上下文。累计下来省 99% 的 token 不是夸张的说法它在“整库投喂 vs 片段投喂”的对比中是真实存在的数量级差距。3.3 混合检索不只是向量单纯靠向量相似度做代码搜索有一个弱点向量适合理解语义但对函数名、类型、文件路径等精确信息不敏感。比如你搜def create_user向量可能不够精确。因此semble 这类工具通常会采用“混合检索”策略一方面用符号解析提取函数名、类名、参数等信息另一方面用向量表示捕捉语义。最终排序时把两类信号合并。这种设计的工程价值在于它能同时应对“模糊的自然语言问题”和“精确的符号查找”。对于 Agent 场景自然语言问题更多所以向量部分权重往往更高但对于开发者手动搜索精确符号匹配也很重要。3.4 常见误区有一个误区是把代码搜索当成“代码生成器”。semble 不写代码不解释代码它只负责定位。真正回答问题和生成补丁的是 Agent 中的大模型。另一个误区是认为索引建立后代码变更会自动同步。很多实现里索引是一段时间内代码快照的映射代码变动后需要重新索引或增量更新否则搜索结果会过期。最后一个误区是大仓库直接一把梭。索引整个巨型仓库会消耗较多时间和资源更合理的做法是按模块或按目录分别索引。4. 实战用 semble 构建代码检索服务4.1 准备示例代码先往示例项目里写入代码用来说明检索效果。下面创建app/auth.py# 文件路径demo-project/app/auth.py import hashlib import time import secrets def hash_password(password: str) - str: 使用加盐方式对密码进行哈希处理。 salt secrets.token_hex(16) digest hashlib.sha256((salt password).encode(utf-8)).hexdigest() return f{salt}${digest} def verify_password(password: str, stored: str) - bool: 校验密码是否与存储的哈希值匹配。 salt, digest stored.split($) new_digest hashlib.sha256((salt password).encode(utf-8)).hexdigest() return new_digest digest def generate_token(user_id: int) - str: 生成一个简单的用户令牌。 raw f{user_id}:{time.time()} return secrets.token_urlsafe(16) . raw def refresh_token(old_token: str, user_id: int) - str: 根据旧令牌为用户刷新新令牌。 return generate_token(user_id)再创建app/order.py# 文件路径demo-project/app/order.py from datetime import datetime class Order: def __init__(self, order_id: str, user_id: int, amount: float): self.order_id order_id self.user_id user_id self.amount amount self.created_at datetime.utcnow() def create_order(user_id: int, amount: float) - Order: 为用户创建一笔新订单。 order_id fORD-{user_id}-{amount} return Order(order_id, user_id, amount) def get_user_orders(user_id: int, orders: list[Order]) - list[Order]: 返回指定用户的所有订单。 return [o for o in orders if o.user_id user_id] def calculate_total_amount(orders: list[Order]) - float: 计算订单列表的总金额。 return sum(o.amount for o in orders)最后创建app/user.py# 文件路径demo-project/app/user.py from app.auth import hash_password, verify_password from app.order import Order, create_order class User: def __init__(self, user_id: int, name: str, password: str): self.user_id user_id self.name name self.password_hash hash_password(password) def check_password(self, password: str) - bool: return verify_password(password, self.password_hash) def register_user(user_id: int, name: str, password: str) - User: 注册一个新用户。 return User(user_id, name, password)4.2 建立索引代码准备好后进入项目根目录执行索引命令。假设当前版本的看似 CLI 语法如下具体命令要以你安装的版本为准cd demo-project semble index --path . --name demo-index有些版本会把索引保存在默认缓存目录也支持--output指定位置。索引过程会输出扫描到的文件数、函数数等信息。如果命令格式不一致执行semble --help查看当前版本的可用子命令。这里需要说明的是索引的建立速度取决于仓库文件数量和机器性能。小项目几秒就能完成大仓库可能需要几分钟到几十分钟。首次建索引时工具还需要下载或加载嵌入模型耗时会更长一些。4.3 执行语义搜索索引建立完成后就可以进行搜索了。下面测试一个自然语言问题semble search 校验用户密码的函数 --index demo-index预期结果应该会返回app/auth.py中的verify_password和app/user.py中的check_password并包含文件路径、函数名、相似度分值以及代码片段预览。再试一个订单相关的问题semble search 计算订单总金额 --index demo-index预期结果应该返回app/order.py中的calculate_total_amount。这种“问题语义到代码实现”的映射正是 Agent 需要的检索能力。如果用传统文本搜索搜“订单总金额”很可能因为代码里没有这个中文变量名而一无所获。4.4 通过 Python 调用除了命令行semble 通常还提供编程接口方便集成到自己的工具链中。下面给一个通用思路具体类名和函数名按实际版本调整# 文件路径scripts/search_code.py from semble import CodeSearchClient client CodeSearchClient() client.load_index(demo-index) result client.search(检查密码是否正确, top_k3) for item in result: print(item.file_path) print(item.symbol_name) print(item.score) print(item.snippet) print( * 40)这段代码的作用是加载本地索引执行搜索并打印最相关的三个结果。如果你的 Agent 是用 Python 写的可以在内部调用这个客户端把返回的片段拼入 prompt。4.5 与 Agent 集成的完整思路下面的伪代码展示了如何把 semble 作为 Agent 的“代码检索工具”。它不直接调用大模型而是先拿到相关代码再让大模型基于代码回答from semble import CodeSearchClient from openai import OpenAI client CodeSearchClient() client.load_index(demo-index) # 假设这是一个 LangChain / 自研 Agent 的工具函数 def search_code(question: str) - str: 工具函数根据问题返回相关代码片段。 results client.search(question, top_k5) fragments [] for item in results: fragments.append( f文件: {item.file_path}\n f符号: {item.symbol_name}\n fpython\n{item.snippet}\n ) return \n---\n.join(fragments) def ask_agent(question: str) - str: Agent 主流程检索代码 - 组装 prompt - 调用大模型。 code_context search_code(question) prompt f 请根据下面的代码片段回答问题。 问题: {question} 相关代码: {code_context} response OpenAI().chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], ) return response.choices[0].message.content if __name__ __main__: print(ask_agent(刷新 token 的逻辑是什么))关键思路是code_context里只有检索到的片段而不是整个仓库。这样的 prompt 短、有效、省 token模型也不需要从噪声里挑信息。如果你用的是 LangChain可以把search_code包装成 Tool 对象让 Agent 自主调用。4.6 结果说明使用上述流程后你会看到几个明显变化prompt 里的代码量从几十万 token 降到几千 token模型的回答更聚焦响应速度更快费用也随之下降。当然前提是检索准确。如果 semble 返回的片段和问题无关大模型再强也答不对。所以下文会专门讨论如何提升检索质量。5. 常见问题与排查思路5.1 问题排查表问题现象常见原因解决思路安装后命令找不到Python 环境不一致或未激活虚拟环境激活虚拟环境后重试检查 PATH索引扫描不到代码默认忽略规则匹配了你的目录检查忽略配置必要时强制指定文件类型搜索结果完全不相关索引过期或嵌入模型未正确加载重新索引确认模型加载成功搜索速度慢仓库过大或索引未增量更新分模块建索引定期增量更新结果片段太长top_k 参数过大或片段切分粒度过粗调小 top_k按函数级别返回内存占用高向量全部加载在内存中调整批量加载配置或使用持久化索引代码更新后搜不到新内容没有重新运行索引建立 CI 或钩子代码变更后自动更新索引5.2 检索不准怎么调检索不准是最影响使用体验的问题。通常从几个方向调整。第一检查问题描述是否包含领域术语。代码里的函数名往往和业务词汇相关问题里提到的词越具体检索越好。第二调整 top_k 大小。top_k 太小可能漏掉正确结果top_k 太大又会把噪声带进 prompt建议先设置一个中等值再根据实际命中情况微调。第三查看返回结果的 score 分布。如果正确结果和噪声结果的分值非常接近说明业务问题本身不够清晰需要拆分问题。第四确认索引是否覆盖了正确的分支或目录。很多团队的仓库有多个长期分支如果你的 Agent 只索引了主分支而线上代码在 release 分支搜索结果自然对不上。第五针对特定语言可以检查代码解析是否支持。不同语言的语法差异较大工具对不同语言的支持程度也不同遇到 Go、Rust、Java 等语言时要确认版本支持情况。5.3 token 优化相关排查有读者可能会遇到“我用上代码搜索了但 token 还是很多”的情况。这通常不是搜索工具的问题而是 prompt 组装策略的问题。比如把每个文件的完整内容都拼接进上下文或者把多个大函数的代码片段一次性塞给模型都会让 token 迅速膨胀。建议做法是在把检索结果送入模型之前先做一层裁剪只保留每个片段的签名、关键行和核心逻辑再按 token 数估算工具检查总长度超过阈值就降低 top_k。另外在 Agent 多轮对话中历史消息也会占 token要定期裁剪不重要的历史消息避免“检索省下来”的 token 又被对话历史吃回去。6. 最佳实践与工程建议6.1 把代码搜索当作 Agent 的独立基础设施不要把 semble 集成成一次性脚本。在工程上可以把它作为独立服务部署提供稳定的检索接口。这样多个 Agent、多个工具可以共用同一套索引避免每个 Agent 各建一份索引造成资源浪费。独立服务还方便控制并发、做缓存和监控查询量。搭建时建议遵循以下原则索引构建和检索分离索引由 CI 流程负责定期更新检索服务只读索引检索接口使用 HTTP 或 gRPC 暴露内部再调用搜索客户端所有请求记录日志方便回溯“Agent 到底检索了什么”这对调试回答不准确的问题非常重要。6.2 索引与代码库版本管理代码搜索最怕的是“索引和仓库不一致”。代码每天都在变索引如果停留在几天前Agent 就是“拿着旧地图找新路”。建议把索引构建接入 CI/CD每次合并到主分支后自动触发索引更新。如果仓库非常大可以按模块构建多个索引哪个模块变更就更新哪个索引。索引本身也存在版本管理问题。搜索服务的版本升级、嵌入模型更换都会影响向量空间导致旧索引失效。建议在索引元数据里记录模型版本和索引时间当模型版本变化时强制重建索引。6.3 安全边界与敏感信息处理这里要特别提醒代码搜索工具建立索引时会把仓库代码保存为本地索引文件。如果你的仓库包含密钥、内网地址、客户数据等敏感信息一定要防止索引文件泄露。措施包括在扫描时排除包含敏感配置的目录或文件类型对索引文件设置权限不要提交到公共仓库检索服务需要鉴权至少使用 token 认证敏感仓库和普通仓库使用不同的索引实例隔离。如果在公司内部使用建议由安全团队评审索引存储和检索接口的权限设计。这同样适用于 Agent 的 token 使用。Agent 的鉴权 token、API key 不应出现在检索结果或日志中。很多 Agent 开发中出现过 “token exchange failed”“access token could not be refreshed” 之类的鉴权异常根本原因往往是密钥管理不规范、token 过期未续签。最佳做法是把密钥放在环境变量或密钥管理服务中并对它们做自动轮换。6.4 检索质量的度量与监控引入代码搜索后不要只兴奋于 token 下降还要持续度量检索质量。简单的方法是从测试集里抽出 20 到 50 个典型问题人工确认每个问题的正确代码位置然后定期跑到这些用例上看搜索结果是否命中预期文件和函数。指标可以用“检索命中率”正确文件或函数出现在前 5 个结果中的比例。命中率低于 80% 时需要检查索引更新、模型配置或问题写法。这个指标比单次 demo 效果好得多也能在索引模型升级时快速判断是否回退。6.5 节省 token 的工程组合拳semble 解决的是“从仓库里找代码”的问题但整体 token 优化还需要其他手段配合。常用组合拳包括设置 prompt 压缩只保留关键代码行对代码片段做去重避免同一个函数在多个文件里重复出现在 Agent 中开启结果缓存同一个问题短时间内不重复检索对模型做分级调用简单问题用低成本模型复杂问题再调用能力更强的模型。其中结果缓存很实用。用户往往会把同一个问题反复提交给 Agent带上缓存后不仅能省检索开销还能省模型调用费。缓存键建议用“问题文本 仓库版本号”拼接保证代码更新后缓存不会失效。6.6 从单体仓库到多语言仓库如果你的仓库是大型多语言仓库建议先做两层拆分按平台拆分比如前端仓库、后端仓库、算法仓库分开索引按代码类型拆分比如业务代码、测试代码、脚手架代码分开处理。Agent 在回答用户问题时可以根据问题的领域词选择对应索引避免跨语言检索带来的噪声。多语言场景下还要注意语言差异。Python 的def、Go 的func、Java 的class在符号解析上的表现不同检索器对不同语言的支持度也不同。在推广到全团队之前先用高频语言的小仓库做验证再逐步覆盖其他语言。7. 下一步可以怎么学如果你对 semble 的用法已经基本掌握下一步可以从三个方向继续深入。第一个方向是了解语义检索的底层技术学习向量嵌入模型在代码领域的应用包括代码 token 化、函数嵌入、相似度计算等内容。第二个方向是把代码搜索和 Agent 编排框架结合起来学习工具调用、多步推理、结果记忆等设计模式。第三个方向是在团队里落地工程化能力把索引构建、检索服务、监控告警做成一套完整的基础设施。就个人练手而言我不建议一上来就拿几千个文件的大仓库做实验。先创建一个 10 到 20 个文件的小项目跑通“建索引、搜索、接入 Agent”的流程再逐步扩大范围。等你在小仓库上验证了 token 节省效果再评估是否值得在大仓库上投入索引构建和运维成本。这样不容易被复杂环境干扰也能更快建立对工具能力的直觉。最后想说的是代码搜索这类工具本质上是给 Agent 装上“定向查找代码的能力”。它不会取代 Agent也不会取代大模型它做的是更基础、更不起眼、但极其重要的事情把海量代码变成可检索、可定位、可喂给模型的精确片段。谈起 Agent 开发大家关注很多的是模型调度、工具调用、记忆结构。但真正让 Agent 在真实代码仓库里发挥作用的关键往往是你如何选择和投喂上下文。用 semblent 这类工具把上下文质量提上去token 和费用问题自然会得到改善。