首页 / 资讯中心 / 文章详情

CoreCoder源码精读系列:逐文件拆解agent.py、llm.py、context.py,看懂生产级Agent的每个设计决策

CoreCoder源码精读系列:逐文件拆解agent.py、llm.py、context.py,看懂生产级Agent的每个设计决策 ★ FEATURED ARTICLE
【免费下载链接】CoreCoderMinimal AI coding agent (~1,000 lines of Python) inspired by Claude Code. Works with any LLM. Think NanoGPT for coding agents. Formerly NanoCoder.项目地址https://gitcode.com/gh_mirrors/co/CoreCoder点击查看免费下载CoreCoder 是一个极简单文件视角的 AI coding agent 开源项目用约 1,322 行引擎代码把生产级编码 Agent 的核心机制诚实地写了出来。本篇源码精读逐文件拆解引擎的三个心脏文件agent.py、llm.py、context.py讲清楚每一处设计决策背后的为什么主循环如何兜底、LLM 调用如何重试与记账、有限上下文窗口里如何扛住长任务。读完你能看懂一个 coding agent 的完整骨架。三个文件的分工agent 主循环全景图在钻进代码之前先建立一张地图。整个引擎就是三块拼图文件规模职责一句话本质agent.py240 行主循环 并行工具执行问模型 → 跑工具 → 回填结果 → 再问llm.py349 行模型接口 重试 成本引擎最大文件全是脏活context.py220 行上下文三层压缩窗口将满时从最便宜的地方让起数据流一句话用户消息进agent.py的循环 →llm.py把消息流式发给模型 → 模型要工具就执行、结果回填 → 历史太长时context.py分层压缩 → 直到模型不再要工具、吐出答案为止。想边读边断点五分钟先跑起来git clone https://gitcode.com/gh_mirrors/co/CoreCoder cd CoreCoder pip install -e . # 任意 OpenAI 兼容模型都能接只需两个环境变量 export OPENAI_API_KEYsk-... OPENAI_BASE_URLhttps://api.deepseek.com CORECODER_MODELdeepseek-chat corecoderagent.py240行主循环只有二十行其余全是护栏主循环骨架问模型、跑工具、再问打开 agent.py核心就是chat()方法完整实现 L70-L124骨架长这样def chat(self, user_input): self.messages.append({role: user, content: user_input}) self.context.maybe_compress(self.messages, self.llm) for _ in range(self.max_rounds): # 默认 50 轮硬上限 resp self.llm.chat(self._full_messages(), self._tool_schemas()) if not resp.tool_calls: # 模型不再要工具 self.messages.append(resp.message) return resp.content # - 任务完成交还答案 self.messages.append(resp.message) results run_tools(resp.tool_calls) # 多个则并行执行 self.messages tool_replies(results) # 结果回填进入下一轮 return (reached maximum tool-call rounds)值得停一下的细节模型自己决定何时收手。没有任何外部规则判断任务完成了没有它读完文件、跑完测试、觉得够了就只回一段文本。这种让模型自己判断收敛是所有 agent 的共同假设——你不在写 if-else你在和一个会自己拿主意的东西协作。刹车设计为什么用for而不是while(true)循环写成for _ in range(self.max_rounds)默认 50 轮。这不是风格偏好是最便宜的保险模型可能陷入读文件→发现不对→再读→还是不对的死循环没有上限就会一直烧 token。真撞上限循环克制地返回一句(reached maximum tool-call rounds)把控制权交还给你。系统提示词每轮重新拼装_full_messages()不是直接返回缓存的 system prompt而是每一轮都重新拼plan 模式的追加指令、agent 自己维护的 TODO 任务列表都在每次请求前现渲染。好处是两点——会话中途切换/plan下一个请求立即生效模型看到的任务清单永远是当前状态而不是一份埋在旧工具输出里的过期副本。两段式 try区分参数填错与工具自己炸了_exec_tool里藏着一个很值得偷的工程判断try: inspect.signature(tool.execute).bind(**tc.arguments) # 先只做参数绑定 except TypeError as e: return fError: bad arguments for {tc.name}: {e} try: return tool.execute(**tc.arguments) except Exception as e: return fError executing {tc.name}: {e}如果直接调用TypeError分不清是模型给的参数对不上签名它的错该让它改还是工具内部 bug和参数无关。bind()不执行函数只校验实参能否绑定形参先把参数问题单独拎出来判。模型拿到一句精确的错误反馈下一轮就能自我修正拿到误导性的反馈会朝错误方向越改越远。CtrlC 的半截状态给中断补上占位回复OpenAI 兼容协议有条硬约束assistant 消息里每个tool_calls必须有tool_call_id一一配对的tool回复否则下一次请求直接被 API 拒绝。而用户随时可能 CtrlC正好打在模型返回了一批工具调用、工具还没跑完的瞬间。_answer_pending_tool_calls的处理给每个还没拿到回复的调用补一条[interrupted]占位让历史重新合法再抛出异常。这样中断之后还能接着聊会话不被弄脏。这是 demo 和可交付 agent 的距离之一——循环不仅要处理正常走完还要处理在任意一步被掐断。并行执行线程池干活主线程问路模型一次返回多个工具调用时_exec_tools_parallel用 8 线程的线程池并发执行。两个容易忽略的设计hooks 和权限确认全部在主线程提前结算。如果放到池子里多个 worker 会在同一个终端上交错弹出一串允许吗体验灾难。被追踪的 cwd 是线程局部的worker 拿不到会话当前目录所以要显式传入某个 worker 里cd之后主线程按调用顺序把目录变化合并回来让一批并行命令的行为等价于顺序执行。️ Plan 模式一个布尔量压过--yes_permit里plan 模式优先于一切同意层——连脚本用的--yes都压不过。开启期间所有写操作当场被拒拒绝信息作为普通工具结果喂回模型告诉它只用只读工具继续调研然后给出编号计划。用户输入approve后才放行执行。机械上就是Agent上的一个布尔量加一个拒绝分支主循环和权限层都不用动不想配 API key 也能体验这套流程仓库自带离线演示 examples/plan_hooks_demo.py。llm.py349行引擎最大的文件全在做脏活调用模型本身不难难的是流式响应会把每个工具调用的参数撕成碎片、provider 会给你半个 JSON 或 null 的 usage 字段、429 和超时要退避重试而 4xx 该直接抛。llm.py整篇就是在处理这些。一个 OpenAI 兼容层接住全世界DeepSeek、Qwen、Kimi、GLM、本地 Ollama 都暴露 OpenAI 兼容端点所以LLM直接复用 openai SDK换 provider 只是换base_url和 key 两个环境变量。对不提供兼容端点的AWS Bedrock、Google Vertex 等LiteLLM子类走统一接口路由到 100 家设CORECODER_PROVIDERlitellm即可。错误三分法重试、适配、抛出两层重试结构把错误分成三种命运错误类型命运429 / 超时 / 连接断开 / 5xx指数退避重试2^attempt最多 3 次400 且报错文本点名了参数方言适配后重试见下其他 4xx直接抛不浪费重试次数其中方言适配是容易被忽略的巧思新版 OpenAI 模型只认max_completion_tokens且不接受自定义 temperature_adapt_rejected_param解析 400 报错里被引号点名的参数自动改名或丢弃还有一层兜底是某些服务器会拒掉stream_options扩展字段那就丢一次再发。注意引号匹配是关键——max_completion_tokens的报错文本里也含max_tokens不加引号限定就会误触发转换。另一个设计流式中途中断时重试的是整个请求而非连接。因为此时工具还没执行整包重发是安全的已经流式显示出的半截文本会被重新生成不会产生副作用。碎片化工具调用的拼接流式协议下一个tool_call的 id、函数名、参数 JSON 会分散在多个 chunk 里。_drain用一个tc_map按index归位for tc_delta in delta.tool_calls: idx tc_delta.index if idx not in tc_map: tc_map[idx] {id: , name: , args: } if tc_delta.id: tc_map[idx][id] tc_delta.id if tc_delta.function: if tc_delta.function.name: tc_map[idx][name] tc_delta.function.name if tc_delta.function.arguments: tc_map[idx][args] tc_delta.function.arguments # 逐片累加整条流走完后才json.loads完整参数串解析失败兜底为空 dict 而不是崩溃。思考模型deepseek-reasoner、kimi 系的reasoning_content也在这里单独接住只用于展示永不进对话历史因为很多 provider 把它发回去会直接拒绝请求。 诚实记账可覆盖的价格表_PRICING内建了 GPT、Claude、DeepSeek、Qwen、Kimi 每百万 token 的 (输入, 输出) 价格_load_pricing允许用~/.corecoder/pricing.json覆盖任一条目——新模型上市或调价不用等发版。/tokens命令背后就是这张表加上累计 token 数。context.py220行用有限窗口扛住长任务上下文窗口是 agent 的硬约束。核心问题不是满了怎么办而是满之前先让掉什么。先算对账token 估算要懂中文[ _approx_tokens](https://link.gitcode.com/i/07f5e66ce423fb4cfb1541fdf8e4f1e5#L28-L35)没有拍脑袋按每 3 字符 1 token而是分类估算CJK 字符约 1.5 字符/token汉字信息密度高符号密集的代码约 2.8普通英文散文约 3.4。按固定 3 字符估算中文会话的真实体积会被读成一半压缩触发就会严重偏晚——等发现满了窗口已经爆了。50% / 70% / 90% 三层递进从最便宜的让起阈值定义在 L51-L54maybe_compress按层触发触发点动作成本50%把超过 1500 字符的工具输出就地剪成前 3 行 后 3 行 省略标记纯机械0 次模型调用70%LLM 把较旧的轮次总结成一段最近 8 条原文保留1 次模型调用90%紧急压缩只留总结 最近 4 条1 次模型调用设计哲学是廉价的先让一刀切的截断往往会扔掉长任务最依赖的早期决策分层让掉最不值钱的部分让重要信息活得更久。第 2、3 层的总结提示词还专门要求保留文件路径、关键决策、遇到的错误、当前任务状态丢弃冗长命令输出和代码清单总结失败比如没配 llm时还有纯正则的兜底——从历史里抠出文件路径和 error 行拼一段摘要_extract_key_info。必须后退的边界孤儿 tool 消息_safe_split是全文最容易被忽视、删掉一定出 bug 的函数split max(0, len(messages) - keep_recent) while split 0 and messages[split].get(role) tool: split - 1切分点如果恰好落在一批tool结果上这些结果就会和产生它们的 assistanttool_calls消息分离——孤儿 tool 消息OpenAI 兼容 API 见到必拒。所以边界要一路后退直到落回tool消息之前。压缩和中断agent.py 里的回填都撞在同一条协议约束上这条线守不住整个会话立刻报错。可以带走的设计决策清单把这 800 行读完后真正能搬进你自己项目的是这十条判断循环必须带硬上限——for range(N)比while(true)便宜且安全得多错误是普通返回值——工具炸了、被拒了都变成文本喂回模型循环从不被外围杀死参数错和内部错要分开报——精确的错误反馈是模型自我修正的前提半截状态必须回填——任何每个 tool_call 要有配对回复的系统中断处理都是必修课确认在主线程执行在 worker——交互类操作放进线程池必乱重试只给 5xx 和瞬时错误4xx 直接抛——用重试次数掩盖真正的错误是最贵的 bug流式工具调用按 index 拼接——参数 JSON 会在碎片里跨 chunk 到达价格表内建 本地文件覆盖——记账功能不需要等发版就能跟上市场token 估算按内容类型加权——中英混排场景下每 3 字符 1 token是失真的压缩分层且切分点必须避开 tool 消息——先让最便宜的保住最关键的想继续深入仓库自带 8 篇双语源码精读系列本篇拆解的每个细节在对应篇目里都有更完整的展开系列总目录、主循环篇、LLM 与成本篇、上下文篇。赞分享【免费下载链接】CoreCoderMinimal AI coding agent (~1,000 lines of Python) inspired by Claude Code. Works with any LLM. Think NanoGPT for coding agents. Formerly NanoCoder.项目地址https://gitcode.com/gh_mirrors/co/CoreCoder点击查看免费下载相关推荐如何5分钟看懂CoreCoder1300行Python拆解Claude Code级编码Agent的全部奥秘如何5分钟看懂CoreCoder1300行Python拆解Claude Code级编码Agent的全部奥秘 想读懂一个 AI 编码 Agent 的内部构造却D2Admin源码结构完全解读10分钟看懂每个目录的设计思路D2Admin源码结构完全解读10分钟看懂每个目录的设计思路 D2Admin 是一款完全开源免费的 Vue 后台管理系统前端整合方案An elegant d前端企业应用minbpe代码注释全解析读懂每个函数背后的设计哲学minbpe代码注释全解析读懂每个函数背后的设计哲学 引言为什么选择minbpe作为BPE学习范本 你是否在学习Byte Pair Encoding字节NLP大模型创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站