拆解11个Coding Agent:不用LangChain和向量检索,如何用grep与shell构建编程智能体 1. 拆开 11 个 coding agent 之后我看到的真实技术底牌前阵子我把市面上能拿到的 coding agent 挨个拆了一遍前前后后一共 11 个包括 Claude Code、Codex 这类大家天天在用的也有一些小众但设计思路很有意思的。拆完之后最大的感受不是“哇好复杂”而是“原来这么朴素”。这些 agent 没有一个用 LangChain 这类编排框架也没有一个用向量检索来索引代码库。这个结论刚出来的时候我自己都愣了一下因为过去两年整个行业都在讲 RAG、讲向量数据库、讲 agent 框架结果真正把产品做出来、做到能用的团队反而把这些东西全绕开了。这篇文章我想干的事很直接把这 11 个 coding agent 的共性设计拆给你看讲清楚它们为什么不用 LangChain、为什么不用向量检索那它们到底用什么来理解代码、定位文件、执行任务。我会把核心思路、关键实现、参数取舍、踩坑经验都摊开讲让你看完之后能自己动手复现一个最小可用的 coding agent而不是停留在“调个框架 API”的层面。适合谁看如果你正在做 AI 编程工具、想理解 agent 的真实工程实现、或者单纯好奇 Claude Code 这类产品内部是怎么跑的这篇都值得你花时间。如果你只是想找个框架套壳那可能会有点失望因为接下来讲的东西大部分是“反框架”的。先说结论省得你往下翻这 11 个 agent 的共性可以概括成三句话——用文件系统当记忆用 grep 和 glob 当检索用 shell 当执行器。听起来是不是特别土但就是这套土办法撑起了目前最好用的编程 agent。2. 为什么大家都不用 LangChain 和向量检索2.1 LangChain 的问题抽象层太厚调试成本太高我先说说 LangChain 为什么在这些项目里集体缺席。LangChain 的核心价值是提供一套统一的抽象把 LLM 调用、工具、记忆、链式编排都封装起来。听起来很美但实际用起来尤其是做 coding agent 这种需要精细控制每一步的场景问题就暴露了。第一个问题是抽象泄漏。LangChain 的 Chain、Agent、Tool 这些概念在简单场景下很顺手但一旦你要控制“模型先看哪个文件、看完之后决定下一步干什么、失败了怎么回退”这种细粒度逻辑就会发现框架的抽象和你的需求对不上。你要么绕过框架直接调底层 API要么写一堆自定义组件把框架的抽象再拆开两头不讨好。第二个问题是调试困难。coding agent 的运行过程是一长串的模型调用加工具调用中间任何一步出错都可能导致结果跑偏。LangChain 把很多细节藏在内部出错的时候你看到的是一层层包装过的异常定位问题要翻好几层源码。而这些 agent 的开发者普遍反映他们需要的是“每一步都看得见、改得动”而不是被框架托管。第三个问题是版本迭代快、破坏性变更多。做 coding agent 的团队往往需要长期维护框架的 API 一变整个项目就得跟着改。相比之下直接调模型厂商的 SDK虽然代码写得多一点但胜在稳定可控。提示这不是说 LangChain 不好而是它的定位和 coding agent 的需求不匹配。做简单 demo 用框架很爽做需要精细控制的生产级 agent直接写反而更省心。2.2 向量检索代码的致命缺陷粒度对不上再说向量检索。过去大家做代码问答第一反应就是把代码切块、embedding、存向量库、检索。但这 11 个 agent 没有一个这么干原因很实在。代码和自然语言文档不一样。一段代码的含义高度依赖上下文你把一个函数切成 500 token 的块去 embedding检索出来的往往是“看起来相似但实际不相关”的片段。比如你搜“用户认证”向量检索可能给你返回一堆名字里带 auth 的函数但真正需要的那段逻辑可能藏在一个叫handleRequest的函数里名字完全不沾边。更关键的是coding agent 需要的不是“相似片段”而是“精确的文件和行号”。它要能打开某个文件、读到某一行、然后修改它。向量检索返回的是一堆语义相近的文本块你还得反推这些块来自哪个文件的哪一段这个映射过程本身就容易出错。还有一个现实问题代码库是动态变化的。你刚建好索引用户改了几行代码索引就过期了。重新 embedding 整个代码库的成本不低而且有延迟。对于需要实时响应的 agent 来说这个延迟不可接受。所以这些 agent 的选择是放弃语义检索改用精确的文本搜索。grep 找关键词glob 找文件路径配合模型自己的理解能力效果反而更好。因为模型本身已经足够聪明你给它准确的搜索结果它能自己判断哪些相关、哪些不相关。2.3 那它们到底靠什么理解代码答案可能让你意外靠模型自己的上下文窗口加上精准的工具调用。这些 agent 的核心逻辑是模型不需要预先“理解”整个代码库它只需要在需要的时候用工具去查找、读取、修改。就像一个人类程序员你不需要背下整个项目的代码你只需要知道怎么用 grep 找函数、怎么用编辑器打开文件、怎么运行测试。具体来说它们给模型提供的工具通常包括这几类文件查找类glob 按模式匹配文件路径grep 按内容搜索文件读写类read 读文件内容write 写文件edit 做精确替换执行类bash 执行 shell 命令跑测试、跑构建、跑 git 操作辅助类列目录、查文件信息等模型拿到这些工具后会自己规划步骤先 grep 找到相关文件再 read 读取内容分析后 edit 修改最后 bash 跑测试验证。整个过程不需要向量检索也不需要复杂的编排框架就是一个“模型 工具循环”。这个设计的精妙之处在于它把“理解代码”这件事交给了模型本身而不是外部的检索系统。模型在读取真实代码的过程中形成理解而不是靠预先计算的向量相似度。这也是为什么这些 agent 在处理复杂任务时表现更好——它们看到的是真实的、完整的代码上下文而不是被切碎的片段。3. 核心架构拆解一个 coding agent 到底由什么组成3.1 主循环模型调用与工具执行的交替拆开这些 agent最核心的部分就是一个循环。伪代码大概长这样while not done: response model.call(messages, tools) if response.has_tool_call: result execute_tool(response.tool_call) messages.append(result) else: done True看起来简单到不像话但魔鬼在细节里。这个循环有几个关键设计点。第一是消息历史的管理。coding agent 跑一个任务可能涉及几十次模型调用消息历史会越来越长。如果不加控制很快就会超出上下文窗口。这些 agent 的处理方式通常是保留完整的工具调用记录但对工具返回的大块内容做截断或摘要。比如读一个 1000 行的文件不会把全文塞进历史而是只保留关键部分或者让模型自己决定读哪一段。第二是工具调用的错误处理。工具执行失败是常态——文件不存在、命令报错、权限不足。好的 agent 不会因为一次工具失败就崩溃而是把错误信息返回给模型让模型自己决定重试还是换方案。这个“错误也是信息”的设计是 agent 能自主解决问题的关键。第三是终止条件。什么时候算任务完成通常有两个信号模型不再调用工具认为任务结束或者达到了预设的最大轮数。最大轮数是必要的保护防止模型陷入死循环。3.2 工具设计少而精每个都要能独当一面这 11 个 agent 的工具集普遍很小通常不超过 10 个。这和很多人想象中“工具越多越强大”相反。工具少的好处是模型更容易记住每个工具的用途选择时不容易混淆。我统计了一下出现频率最高的工具是这几个工具用途出现频率read读取文件内容11/11write写入文件10/11edit精确替换文本9/11bash执行 shell 命令11/11grep按内容搜索10/11glob按路径模式查找9/11list列目录8/11read 和 bash 是全员必备。read 不用解释bash 之所以必备是因为它能覆盖太多场景跑测试、跑构建、git 操作、文件操作一个 bash 顶好几个专用工具。edit 这个工具值得单独说。它和 write 的区别是write 是覆盖整个文件edit 是做精确的字符串替换。为什么需要 edit因为模型改代码时往往只需要改几行用 write 重写整个文件既浪费 token 又容易引入意外改动。edit 让模型只输出要改的部分配合“旧字符串 - 新字符串”的替换逻辑精准且高效。注意edit 工具的设计有个坑就是“旧字符串”必须在文件中唯一。如果模型给的旧字符串在文件里出现多次替换就会出错。成熟的 agent 会要求模型提供足够的上下文来保证唯一性或者在替换失败时返回错误让模型重试。3.3 上下文管理怎么让模型记住该记的上下文管理是 coding agent 最容易被低估的部分。一个任务跑下来消息历史可能包含几十次工具调用和返回轻松超过十万 token。怎么在有限的上下文窗口里保留最关键的信息直接决定了 agent 能不能完成长任务。我观察到的策略主要有三种。第一种是滑动窗口加摘要。保留最近 N 轮完整对话更早的内容压缩成摘要。摘要通常由模型自己生成比如“之前已经读取了 A 文件确认了 B 函数的问题修改了 C 处代码”。这样既保留了关键信息又大幅压缩了 token。第二种是工具结果截断。读文件时如果文件很长只返回前若干行或匹配行附近的内容。grep 的结果也做数量限制比如最多返回 50 条匹配。这样避免单次工具调用就撑爆上下文。第三种是外部记忆。有些 agent 会把关键信息写到临时文件里需要时再读回来。这相当于用文件系统当扩展内存。比如模型发现了一个重要的配置项可以把它记到一个 scratch 文件里后面需要时再读。这三种策略往往组合使用。核心思路是上下文窗口是稀缺资源每一 token 都要花在刀刃上。3.4 提示词工程系统提示才是真正的“框架”如果说这些 agent 有什么“框架”那系统提示词就是它们的框架。系统提示里定义了模型的角色、可用工具、工作流程、注意事项。写得好的系统提示能让模型自主完成复杂任务写得差模型就会乱调工具、反复试错。我拆的这些 agent系统提示普遍有几个共同点。第一是明确的工作流程。比如“先理解需求再查找相关文件再制定修改计划再执行最后验证”。这个流程不是硬编码的而是通过提示词引导模型遵循。第二是工具使用的具体指导。不只是列出工具还告诉模型什么时候用哪个。比如“查找文件用 glob查找内容用 grep修改代码用 edit 而不是 write”。第三是约束和禁忌。比如“不要修改测试文件来让测试通过”、“不要删除看不懂的代码”、“修改前先读取文件确认内容”。这些约束能避免模型做出危险操作。第四是输出格式要求。比如要求模型在调用工具前先说明意图这样用户能看到 agent 的思考过程。系统提示的长度通常不短几千 token 很常见。但这是值得的因为它是 agent 行为的“宪法”写好了能省下大量调试时间。4. 实操复现从零搭一个最小可用的 coding agent4.1 环境准备与依赖选择说了这么多原理不如动手搭一个。我下面给的是一个最小可用版本能读文件、搜代码、改代码、跑命令。你可以基于它扩展。依赖方面我建议直接用模型厂商的官方 SDK不要上框架。以 Python 为例核心依赖就两个模型 SDK 和用于文件操作的库其实标准库就够。不需要向量数据库不需要 LangChain不需要任何编排框架。pip install openai # 或其他模型厂商的 SDK如果你用的是支持工具调用的模型SDK 里通常已经封装好了 function calling 的接口。我们直接用它。项目结构建议这样组织coding_agent/ main.py # 主循环 tools.py # 工具定义与实现 prompts.py # 系统提示词 config.py # 配置这个结构简单清晰每个文件职责单一方便调试和扩展。4.2 工具实现read、write、edit、bash、grep、glob先实现工具。每个工具就是一个函数接收参数返回字符串结果。关键是错误处理要做好任何异常都要捕获并返回可读的错误信息而不是让程序崩溃。import os import subprocess import glob as glob_module def tool_read(path, start_lineNone, end_lineNone): try: with open(path, r, encodingutf-8) as f: lines f.readlines() if start_line is not None: lines lines[start_line-1:end_line] # 加行号方便模型引用 result .join(f{i1}: {line} for i, line in enumerate(lines)) return result[:50000] # 截断保护 except Exception as e: return fError reading {path}: {e} def tool_write(path, content): try: os.makedirs(os.path.dirname(path), exist_okTrue) with open(path, w, encodingutf-8) as f: f.write(content) return fWritten {len(content)} chars to {path} except Exception as e: return fError writing {path}: {e} def tool_edit(path, old_str, new_str): try: with open(path, r, encodingutf-8) as f: content f.read() count content.count(old_str) if count 0: return fError: old_str not found in {path} if count 1: return fError: old_str appears {count} times, need unique match content content.replace(old_str, new_str, 1) with open(path, w, encodingutf-8) as f: f.write(content) return fEdited {path} except Exception as e: return fError editing {path}: {e} def tool_bash(command, timeout30): try: result subprocess.run( command, shellTrue, capture_outputTrue, textTrue, timeouttimeout ) output result.stdout result.stderr return output[:20000] if output else (no output) except subprocess.TimeoutExpired: return fError: command timed out after {timeout}s except Exception as e: return fError: {e} def tool_grep(pattern, path., includeNone): try: cmd fgrep -rn {pattern} {path} if include: cmd f --include{include} result subprocess.run(cmd, shellTrue, capture_outputTrue, textTrue) return result.stdout[:20000] or (no matches) except Exception as e: return fError: {e} def tool_glob(pattern): try: matches glob_module.glob(pattern, recursiveTrue) return \n.join(matches[:100]) or (no matches) except Exception as e: return fError: {e}这几个工具加起来不到 80 行但已经能覆盖 coding agent 的绝大部分需求。注意几个细节read 加了行号方便模型引用具体位置edit 检查了唯一性避免误替换bash 设了超时防止卡死所有输出都做了截断保护上下文。4.3 主循环实现把工具和模型串起来主循环的逻辑就是前面说的那个 while 循环。关键是把工具定义转换成模型能理解的格式然后处理模型的工具调用请求。import json from openai import OpenAI client OpenAI() TOOLS [ { type: function, function: { name: read, description: Read file content with line numbers, parameters: { type: object, properties: { path: {type: string}, start_line: {type: integer}, end_line: {type: integer} }, required: [path] } } }, # ... 其他工具定义 ] TOOL_MAP { read: tool_read, write: tool_write, edit: tool_edit, bash: tool_bash, grep: tool_grep, glob: tool_glob, } def run_agent(task, max_turns50): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: task} ] for turn in range(max_turns): response client.chat.completions.create( modelyour-model, messagesmessages, toolsTOOLS, tool_choiceauto ) msg response.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content for call in msg.tool_calls: fn TOOL_MAP.get(call.function.name) args json.loads(call.function.arguments) result fn(**args) if fn else fUnknown tool: {call.function.name} messages.append({ role: tool, tool_call_id: call.id, content: result }) return Max turns reached这个主循环大概 30 行但已经是一个能跑的 agent 了。你可以给它一个任务比如“找到项目里所有 TODO 注释并汇总”它会自己 grep、read、整理结果。4.4 系统提示词给 agent 立规矩系统提示词决定了 agent 的行为质量。我下面给一个精简版你可以根据需求调整。SYSTEM_PROMPT You are a coding agent. You help users with code tasks by reading, searching, and modifying files. Workflow: 1. Understand the task first 2. Search for relevant files using glob and grep 3. Read files to understand the code 4. Make changes using edit (preferred) or write 5. Verify changes by running tests or commands Tool usage rules: - Use glob to find files by path pattern - Use grep to find files by content - Use read before editing any file - Use edit for small changes, write for new files or full rewrites - Use bash to run tests, builds, and git commands Constraints: - Never modify test files to make tests pass - Never delete code you dont understand - Always read a file before editing it - If a command fails, read the error and adjust Be concise. Explain what youre doing before each tool call.这个提示词不长但把关键规则都覆盖了。实际项目中系统提示往往要长得多会针对具体场景加更多约束。但核心结构就是这样角色、流程、工具规则、约束。4.5 跑起来一个真实任务的完整执行记录我拿一个真实任务跑了一遍任务描述是“项目里有个utils.py里面的parse_config函数有 bug配置文件里的注释行会导致解析失败。找到并修复它。”Agent 的执行过程大致如下第一轮模型决定先找文件。调用glob(**/utils.py)返回src/utils.py。第二轮模型调用grep(parse_config, src/utils.py)定位到函数在第 45 行。第三轮模型调用read(src/utils.py, 40, 80)读取函数实现。发现代码用line.split()解析每一行没有跳过以#开头的注释行。第四轮模型调用edit把循环体改成先判断if line.strip().startswith(#) or not line.strip(): continue。第五轮模型调用bash(python -m pytest tests/test_utils.py)测试通过。第六轮模型输出总结“已修复 parse_config 函数添加了注释行和空行的跳过逻辑测试通过。”整个过程 6 轮没有向量检索没有框架就是 grep、read、edit、bash 四个工具的组合。这就是 coding agent 的真实工作方式。5. 常见问题与排查技巧实录5.1 模型不调用工具直接瞎编答案这是最常见的问题。模型看到任务后不调用工具去查证而是凭训练数据里的印象直接给答案。比如你问“项目里用了什么测试框架”它直接说“pytest”但其实项目用的是 unittest。解决办法是在系统提示里明确要求“必须先搜索再回答”并且在工具定义里把搜索工具的描述写清楚。另一个技巧是在用户消息里加一句“请先查看项目文件再回答”。实测下来加了这句之后模型调用工具的概率大幅提升。如果还是不行可以在主循环里加一个检查如果模型第一轮没有调用任何工具就给出答案就自动追加一条消息“请使用工具验证你的答案”强制它再跑一轮。5.2 edit 替换失败旧字符串找不到或不唯一edit 工具依赖模型提供的“旧字符串”在文件中精确匹配。失败通常有两种找不到或者找到多个。找不到的原因往往是模型记错了内容或者文件在读取之后被其他操作修改了。解决办法是让模型在 edit 之前重新 read 一次文件确保看到的是最新内容。有些 agent 会在 edit 失败时自动触发一次 read然后把最新内容返回给模型让它重试。不唯一的原因是模型给的旧字符串太短在文件里出现了多次。解决办法是在提示词里要求模型“提供足够的上下文确保旧字符串在文件中唯一”。通常多带几行上下文就能解决。实操心得edit 工具的错误信息要写得足够详细比如“old_str 在文件中出现 3 次分别在第 12、45、78 行”这样模型能快速调整。错误信息越具体模型自我修复的能力越强。5.3 上下文爆炸跑到一半就超限长任务跑到一半上下文超限是 coding agent 的经典问题。表现是模型开始胡言乱语或者工具调用格式出错。根本原因是消息历史太长。解决办法前面提过核心是三点工具结果截断、历史摘要、外部记忆。我实测下来工具结果截断的性价比最高实现简单效果明显。read 限制返回行数grep 限制匹配数量bash 限制输出长度这三个截断一加上下文压力立刻小很多。如果任务确实很长就需要历史摘要。我的做法是当消息数量超过阈值时把最早的一批消息交给模型总结成一段话然后用这段摘要替换掉原始消息。摘要里保留关键的文件路径、函数名、已做的修改丢弃具体的工具输出。5.4 模型陷入死循环反复调同一个工具有时候模型会卡在一个循环里比如反复 grep 同一个关键词或者反复 read 同一个文件。这通常是因为它没找到想要的信息又不知道换什么策略。防护措施有两个。一是设置最大轮数超过就强制终止。二是检测重复调用如果连续几轮调用了相同的工具和参数就注入一条提示“你已经调用过这个工具了请换一个策略”。实测这个提示很有效模型看到后会主动换思路。还有一个隐蔽的死循环是 edit 反复失败。模型改一次不成功再改一次还是不成功来回好几次。这种情况通常是旧字符串本身有问题比如包含了不可见字符。解决办法是让模型 read 出精确内容后再 edit或者干脆用 write 重写整个文件。5.5 常见问题速查表问题可能原因解决思路模型不调工具直接回答提示词约束不足系统提示强调先搜索加验证轮edit 找不到旧字符串文件已变更或模型记错edit 前重新 read失败时返回最新内容edit 匹配多个位置旧字符串太短要求提供更多上下文保证唯一上下文超限消息历史过长截断工具结果历史摘要外部记忆反复调同一工具模型卡住最大轮数限制重复检测加提示bash 命令超时命令卡死或耗时过长设置超时超时后返回错误让模型调整修改了不该改的文件约束不足系统提示明确禁止修改测试文件等6. 从这 11 个 agent 身上学到的设计原则拆完这 11 个 agent我最大的收获不是某个具体技巧而是一套设计哲学。这套哲学可以总结成几条原则不管你做什么类型的 agent都值得参考。第一条原则是用最简单的原语。文件系统、grep、shell这些都是存在了几十年的东西稳定、可靠、人人会用。与其发明新的抽象不如把老工具用好。模型足够聪明你给它简单的工具它能组合出复杂的行为。第二条原则是让模型做决策让代码做执行。Agent 的智能来自模型工程代码只负责把模型的决策落地。不要把业务逻辑硬编码在代码里而是通过提示词引导模型。这样 agent 的行为可以随模型升级而提升不需要改代码。第三条原则是错误是信息不是终点。工具失败、命令报错这些都应该返回给模型让它自己调整。一个健壮的 agent 不是不犯错而是能从错误中恢复。第四条原则是上下文是稀缺资源。每一 token 都要花在刀刃上。工具结果要截断历史要摘要无关信息要丢弃。上下文管理做得好不好直接决定 agent 能不能完成长任务。第五条原则是约束比能力更重要。给模型加能力容易加约束难。但真正让 agent 可用的往往是那些“不要做什么”的约束。不要改测试、不要删代码、不要瞎猜这些约束让 agent 的输出可信。这五条原则比任何框架都值钱。框架会过时原则不会。你把这五条吃透不管以后模型怎么升级、工具怎么变化你都能搭出好用的 agent。最后分享一个我自己的小习惯每次搭新 agent我都会先写系统提示词把工作流程和约束想清楚再去写代码。提示词写明白了代码往往很简单提示词没想清楚代码写再多也是白搭。这个顺序希望对你有用。