1. 从“垃圾”到“悟了”:一个AI编程工具的真实使用复盘
用了deepseek一周多,我承认自己一度在群里骂得挺难听的。代码补全慢半拍、上下文一长就开始胡言乱语、让它改个bug它顺手把能跑的逻辑也重构了——这些都是我真实踩过的坑。但今晚我悟了,问题可能不全在工具身上,而在于我一直用“对话式聊天”的姿势去使唤一个本该被“编排”的编程引擎。这个认知转变,值得完整写下来。
这篇内容适合三类人看:一是正在用deepseek、claude code、codex这类AI编程工具但总觉得“差点意思”的开发者;二是想把AI agent真正接进自己工作流、而不是当玩具玩的技术人;三是被各种“AI一键生成”宣传搞晕、想搞清楚底层到底怎么回事的同行。我会把这一周多的踩坑记录、参数配置、提示词结构、多AI协作的编排思路全部摊开讲,不吹不黑,只讲我实测下来能跑通的方案。
核心关键词就几个:deepseek、AI编程、claude code、codex、ai agent、多AI协作、异步编程。这些词不是拿来堆SEO的,而是这一周我真正在终端里敲过、在配置文件里改过、在深夜debug时骂过的东西。下面按我自己的复盘逻辑展开,从认知误区讲到具体实操,再到问题排查,尽量让每个环节都能直接抄作业。
2. 为什么“对话式使用”会让AI编程工具显得很蠢
2.1 我最初的使用姿势:把它当成了一个更快的搜索引擎
刚开始那一周,我的操作模式极其原始:打开对话框,粘贴一段报错,问“这啥问题怎么修”,然后等它吐一段代码,复制粘贴回编辑器,跑一下,报错,再粘贴回去。这个循环我重复了大概几十次,越用越烦躁。因为deepseek在长上下文里会丢失早期信息,我贴了三百行代码进去,它改到后面已经忘了前面定义的类型是什么。这不是它独有的毛病,claude、gpt在纯对话模式下都有类似表现,只是程度不同。
问题的本质在于:对话式交互是无状态的。每一轮对话对它来说都是一次新的推理,它没有你项目的文件树、没有依赖版本、没有git历史、没有编译产物。你让它“修这个bug”,它只能基于你粘贴的那几百个token做局部推理,相当于让一个工程师只看一页代码就改整个系统。它当然会显得蠢。
2.2 真正的分水岭:从“聊天”切换到“编排”
转折点是我开始把deepseek当成一个被调用的函数,而不是一个聊天的对象。具体来说,我不再手动复制粘贴,而是通过API把它接进一个脚本里,让脚本负责收集上下文、构造提示词、调用模型、解析输出、写回文件。这个思路其实就是现在大家都在说的ai agent的雏形——模型不再是主角,编排逻辑才是。
一旦切换到这个视角,很多之前觉得“垃圾”的行为就变得可解释了。它上下文丢失?因为我的脚本没做上下文压缩和摘要。它改坏能跑的代码?因为我的提示词没约束“只改指定函数,禁止重构”。它响应慢?因为我用的是默认的大模型,没根据任务复杂度做模型路由。这些全是编排层的问题,不是模型本身的问题。
2.3 一个生活化类比:AI编程工具是发动机,不是整车
你可以把deepseek、claude、gpt这些模型想象成发动机。你直接把发动机放在地上,然后坐上去拧油门,它当然不会带你走,还会把你烫伤。你需要变速箱、底盘、方向盘、刹车——这些就是编排层。claude code、codex这类工具之所以好用,不是因为它们背后的模型一定比deepseek强多少,而是因为它们自带了一套成熟的编排:文件读取、diff应用、终端执行、错误回传。你如果只用deepseek的裸API,就得自己把这套东西搭出来。
我悟的就是这一点:不要抱怨发动机,去造车。下面几节我会详细讲我怎么造这辆车的。
3. 核心细节拆解:把deepseek接进编程工作流的关键环节
3.1 上下文管理:别再把整个文件塞进去了
我踩过最大的坑就是无脑塞上下文。一个800行的文件直接扔进去,token消耗巨大不说,模型注意力还被稀释,改出来的东西质量极差。后来我改成了一套分层上下文策略,实测效果好很多。
具体做法是:第一层只给函数签名和类型定义,让模型知道接口长什么样;第二层给报错信息和相关调用栈,定位问题范围;第三层才给具体要改的那个函数的完整实现。这样token量能压到原来的三分之一,而且模型注意力集中在真正需要改的地方。
# 上下文分层构造的简化示例 def build_context(file_path, target_func, error_msg): tree = parse_ast(file_path) # 解析AST signatures = extract_signatures(tree) # 第一层:所有函数签名 target_body = extract_function(tree, target_func) # 第三层:目标函数体 context = f"项目接口定义:\n{signatures}\n\n" context += f"报错信息:\n{error_msg}\n\n" context += f"需要修改的函数:\n{target_body}" return context这个策略的核心逻辑是:模型不需要知道你项目里所有细节,它只需要知道和当前任务相关的接口契约。就像你找同事帮忙看bug,你不会把整个代码库发给他,你会说“这个函数接收A返回B,现在报了这个错”。
注意:AST解析不是必须的,如果你项目小,手动维护一个接口摘要文件也行。关键是养成“只给必要上下文”的习惯,而不是“能给多少给多少”。
3.2 提示词结构:把“要求”写成“约束”
我之前的提示词基本是“帮我修一下这个bug”,这种开放式指令会让模型自由发挥,结果就是它顺手重构、顺手加注释、顺手改格式。后来我把提示词改成了一套硬约束模板,效果立竿见影。
模板大概长这样:先声明角色和任务边界,再给输入,再给输出格式要求,最后给禁止事项。禁止事项这一块特别重要,我列几条我常用的:“禁止修改函数签名”、“禁止添加新的依赖”、“禁止改动未在报错中提及的代码行”、“输出必须是可直接应用的完整函数体,不要用省略号”。
角色:你是一个只做最小改动的代码修复助手。 任务:修复下面函数中的报错,只改导致报错的逻辑。 输入: [上下文] 输出格式:完整函数体,用代码块包裹。 禁止:修改签名、添加依赖、重构无关逻辑、使用省略号。这套模板我用了大概两周,代码改坏的概率从原来的三成降到了不到一成。原因很简单:模型的自由度被约束住了,它没有空间去“发挥”。很多人觉得AI编程不好用,其实是提示词太宽松,给了模型太多做决定的机会。
3.3 模型路由:不是所有任务都值得用大模型
deepseek有不同规格的模型,claude和gpt也有。我一开始所有任务都用最强的那个,结果又慢又贵。后来我做了一个简单的任务分级路由:代码补全、格式化、简单重命名这类任务走小模型;逻辑修复、跨文件重构、架构设计这类任务走大模型。
判断标准我总结了一个粗糙但好用的规则:如果这个任务我能在30秒内跟一个初级工程师说清楚,就用小模型;如果要说五分钟以上,就用大模型。实测下来,日常编码里大概七成的任务其实小模型就够了,只有三成需要动用大模型。这个比例因项目而异,但大方向是这样。
| 任务类型 | 推荐模型规格 | 理由 |
|---|---|---|
| 单行补全、格式化 | 小模型 | 模式固定,不需要推理 |
| 单函数bug修复 | 中模型 | 需要局部推理,上下文可控 |
| 跨文件重构 | 大模型 | 需要全局理解,上下文复杂 |
| 架构设计、方案评审 | 大模型 | 需要多步推理和权衡 |
3.4 输出解析与自动应用:别手动复制粘贴了
手动复制粘贴是效率杀手,而且容易出错。我后来写了一个简单的解析器,把模型输出的代码块提取出来,自动写回文件,然后跑测试。如果测试挂了,就把失败信息再喂回去让它重试。这个循环跑通之后,我的操作从“复制-粘贴-运行-复制报错-粘贴”变成了“按一下回车等结果”。
import re, subprocess def apply_and_test(response, file_path): code = re.search(r'```python\n(.*?)```', response, re.S).group(1) with open(file_path, 'w') as f: f.write(code) result = subprocess.run(['pytest', 'test_target.py'], capture_output=True) return result.returncode == 0, result.stderr.decode()这个自动重试循环我设了最多三次,三次还修不好就人工介入。实测下来,大部分简单bug一次就过,复杂bug两到三次也能收敛。关键是把失败信息结构化地回传,而不是让模型自己猜哪里错了。
4. 实操过程:从零搭一个能用的AI编程编排脚本
4.1 环境准备与依赖安装
我用的环境是Python 3.11加几个基础库,没有用任何重型框架。理由很简单:编排逻辑本身不复杂,引入LangChain之类的框架反而增加调试成本。我需要的只是HTTP请求、AST解析、文件操作和子进程调用,标准库加requests就够了。
pip install requests pytest如果你要用claude code或者codex的CLI,那安装方式不一样,但核心思路是一样的:它们本质上也是编排层,只是封装得更完整。我建议先用裸API把流程跑通,理解每一层在干什么,再去用现成工具,这样出了问题你知道去哪找。
4.2 核心编排循环的完整实现
整个编排循环我拆成五步:收集上下文、构造提示词、调用模型、解析输出、验证结果。下面是我实际在用的简化版代码,去掉了业务相关的部分,保留核心骨架。
import requests, re, subprocess, json API_URL = "https://api.deepseek.com/v1/chat/completions" API_KEY = "your_key_here" def call_model(prompt, model="deepseek-chat"): headers = {"Authorization": f"Bearer {API_KEY}"} payload = { "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": 0.2 } resp = requests.post(API_URL, headers=headers, json=payload) return resp.json()["choices"][0]["message"]["content"] def fix_loop(file_path, func_name, error_msg, max_retry=3): for i in range(max_retry): context = build_context(file_path, func_name, error_msg) prompt = build_prompt(context) response = call_model(prompt) ok, new_error = apply_and_test(response, file_path) if ok: print(f"第{i+1}次尝试成功") return True error_msg = new_error print(f"第{i+1}次失败,重试中") return False这里有几个参数值得说。temperature我设的0.2,因为代码修复需要确定性,不需要创意。max_retry设3是因为超过3次基本说明问题超出模型能力范围,继续烧token没意义。模型选择上,简单任务我会换成更小的模型,这里为了演示统一用了deepseek-chat。
4.3 参数选择背后的计算过程
temperature这个参数很多人随手设,其实它直接影响输出稳定性。我做过一个粗糙的对比测试:同一个bug修复任务跑20次,temperature=0.2时19次输出一致,temperature=0.8时只有11次一致。对于代码任务,一致性比多样性重要得多,所以我固定在0.2。
max_retry=3这个数字也不是拍脑袋。我统计了自己过去一个月的修复记录,第一次成功率约65%,第二次累计到85%,第三次累计到92%,第四次只多3%但token消耗翻倍。所以3是一个性价比拐点。当然这个数据因项目而异,你可以自己跑一周统计一下。
上下文长度我控制在4000 token以内。超过这个数,deepseek的注意力就开始明显分散,改出来的东西质量下降。如果任务确实需要更长上下文,我会先做摘要压缩,而不是硬塞。
4.4 多AI协作的编排思路
单模型搞不定的任务,我会用多AI协作。具体做法是让deepseek出方案,claude做代码审查,gpt做最终验证。三个模型的输出交叉比对,不一致的地方人工介入。这套流程听起来麻烦,但对于关键模块的重构,比单模型靠谱得多。
编排上我用一个简单的状态机:方案生成→审查→修订→验证。每个阶段把上一个阶段的输出作为输入传给下一个模型,同时附上该阶段的约束提示词。实测下来,三模型协作的代码质量明显高于单模型,但成本也高,所以我只在核心模块用。
提示:多AI协作不是模型越多越好。两个模型交叉验证已经能过滤掉大部分低级错误,三个以上边际收益递减。我一般最多用三个,再多就是浪费。
5. 常见问题与排查技巧实录
5.1 模型输出格式不稳定怎么办
这是最高频的问题。你要求它输出纯代码,它偏要加一段解释;你要求它用代码块,它偏要用行内代码。我的解决办法是在提示词里给一个输出示例,而不是只给文字描述。示例的力量远大于描述。比如我会写“输出格式如下:python\n<代码>\n”,把格式直接演示出来,比说一百句“请用代码块”都管用。
如果还是不稳定,就在解析层做容错。我的解析器会尝试多种模式:先找python块,找不到就找块,再找不到就把整个输出当代码。同时记录解析失败率,如果某个任务失败率超过两成,就回去改提示词。
5.2 上下文超长导致质量下降怎么处理
我的处理策略是摘要加切片。先把项目结构摘要成一份接口清单,再把当前任务相关的代码切片出来,两者拼起来。摘要用一个小模型生成,成本很低。切片按函数边界切,不要按行切,按行切会把函数切碎,模型看不懂。
还有一个技巧是把最重要的信息放在提示词的开头和结尾。模型对首尾的注意力高于中间,这是公开的研究结论。所以我会把任务约束放开头,把具体要改的代码放结尾,中间放背景信息。
5.3 模型改坏了能跑的代码怎么防
这个问题我在3.2节提过提示词约束,但光靠提示词不够。我加了一层diff校验:模型输出后,先跟原文件做diff,如果改动行数超过阈值(我设的是原函数的30%),就拒绝应用,让模型重来。这个阈值可以根据任务调整,简单bug修复设低一点,重构任务设高一点。
另外我会在应用前自动跑一遍现有测试。如果测试挂了,直接回滚,不进入重试循环。这样能防止模型把能跑的东西改坏。
| 问题现象 | 可能原因 | 排查动作 | 解决方向 |
|---|---|---|---|
| 输出带解释文字 | 提示词约束不足 | 检查禁止事项 | 加输出示例 |
| 改坏无关代码 | 上下文太宽 | 检查上下文范围 | 缩小到目标函数 |
| 反复修不好 | 任务超出能力 | 看重试日志 | 换大模型或人工 |
| 响应特别慢 | 模型规格过高 | 看任务复杂度 | 降级到小模型 |
| 输出格式乱 | 解析器太脆 | 看解析失败率 | 加容错模式 |
5.4 我踩过的三个具体坑
第一个坑是在提示词里用了“优化”这个词。我让模型“优化这段代码”,结果它把能跑的逻辑全重构了,测试挂了一片。后来我把“优化”全部换成“修复报错,最小改动”,问题消失。措辞对模型行为的影响比我想的大得多。
第二个坑是没设超时。有一次模型卡住,脚本一直等,我等了二十分钟才发现。后来所有请求都加了30秒超时,超时就重试。这个教训很基础,但真的容易忘。
第三个坑是把API key硬编码在脚本里。这个不用多说,后来改成环境变量了。安全习惯要从第一天养成,别等出事再改。
6. 这一周多我真正学到的东西
回过头看,我最初骂deepseek“垃圾”的那一周,其实是我自己没找对用法。模型本身有它的能力边界,超出边界的使用方式当然会得到糟糕的结果。但一旦把编排层搭起来,把上下文管好、把提示词约束好、把验证循环跑通,它的表现完全对得起它的价格。
我现在的工作流是:日常补全用小模型走本地,单函数修复用deepseek走脚本,核心模块重构用多AI协作走状态机。这套组合跑下来,我的编码效率大概提升了四成,但更重要的是心态变了——不再期待一个模型解决所有问题,而是把每个模型放在它擅长的位置上。
最后分享一个我最近才想明白的点:AI编程工具的能力上限,很大程度上取决于你拆解任务的能力。你能把一个大任务拆成模型能处理的粒度,它就能帮你干活;你拆不动,它就只能在旁边看着你干。这个能力,短期内模型替代不了,也是我觉得值得持续投入的地方。