
做 AI 辅助开发半年多我总结出最磨人的一件事不是不会写 Prompt而是同一个动作要反复做。比如代码审查每次都要打开编辑器选中代码复制到 DeepSeek 网页贴一句“帮我审查这段代码重点关注并发安全和异常处理”再把结果复制回来。一天下来光是这套动作就重复十几次。后来我干脆写了一个 DeepSeek Harness 插件把这些反复跑的操作固化成面板入口——点一下就执行同时也注册成 Agent 工具让自动化流程能直接调用。这篇文章把完整的思路、架构和踩坑过程都记录下来适合正在做 AI 工具链、IDE 插件或者 Agent 开发的读者参考。1. 为什么要做这个插件从“复制粘贴重复劳动”到“操作固化”1.1 我遇到的实际痛点先还原一下我开发时的真实场景。我负责一个中等规模的服务端项目模块多接口复杂。日常工作里至少有四类操作频率极高代码审查、生成单元测试、分析报错日志、根据需求生成接口文档。这四件事没有一件是难到做不了的但每一件都极其琐碎。拿生成单元测试来说我通常的做法是先定位到目标文件把函数主体复制出来再找出依赖的 mock 对象拼一段 Prompt 给大模型最后把生成的测试代码粘贴进测试文件并手动修格式。这个过程每次至少五分钟而且枯燥到让人怀疑人生。更要命的是不同文件、不同场景下Prompt 的措辞需要微调每次都要重新组织背景信息否则模型给的答案质量飘忽不定。我观察到一个规律这些操作本质上是“固定输入 固定处理逻辑 固定输出格式”。代码审查的输入是当前选中的代码输出是一份带优先级的问题清单日志分析的输入是日志文本和错误关键字输出是根因判断和排查建议。既然输入输出都这么明确那把它固化成工具就完全可行。1.2 Harness 到底在 harness 什么“Harness”这个词在工程里经常出现有时候翻译成“套具”有时候翻译成“驾乘”但核心意思都相通把散乱的动力源和需要驱动的部件连接起来让整体可靠运转。放到 AI 辅助开发里Harness 就是一套连接“大模型能力”和“具体开发场景”的中间层。我理解的 Harness 工程核心动作是把人反复执行的“操作习惯”固化下来。它跟普通脚本不一样。脚本只是把命令跑一遍但 Harness 会重点关注上下文怎么收集、Prompt 模板怎么渲染、结果怎么回流到工作流里。换句话说脚本是一行命令Harness 是一个带有输入输出协议的运行单元。这个思路其实是从 Claude Code 插件生态里得到的启发。社区里很多高手做插件第一件事就是定义“这个工具接收什么参数、返回什么结构”然后再考虑 UI 和触发方式。我写 DeepSeek Harness 插件时也完全沿用了这个思路先定义工具能力再把工具挂到面板上。1.3 面板入口和 Agent 工具的关系这里需要说清楚两个东西的区别因为评论区经常有人问“那到底是面板还是 Agent 工具”。我的答案是两者可以是同一套能力的两个出口。面板入口面向人。你打开插件侧边栏有一排按钮选中一段代码点击“代码审查”结果出现在输出区域这是给人用的交互方式。Agent 工具面向程序。你的 Agent 框架在跑自动化流程时通过函数调用机制唤起同一个“代码审查”能力把代码作为参数传进去拿到结构化结果继续决策这是给机器用的交互方式。关键是底层不要重复实现两套逻辑。我最初犯过这个错误面板里写了一份审查逻辑Agent 工具又写了一份结果两边 Prompt 细节不一致输出质量也不同。后来统一成一份操作定义面板和 Agent 都去调同一个执行函数保证一致性也大幅降低了维护成本。2. 架构设计与技术选型2.1 三层结构面板层、编排层、模型层整个插件的架构我分成了三层。第一层是面板层负责跟用户交互。它需要拿到当前编辑器的选中内容、当前文件路径、用户输入的补充参数然后把任务请求交给下层。面板本身不做任何 AI 相关的计算只做状态流转。第二层是编排层这是插件的核心。它负责把“操作”变成一个可执行的工作流包含三步上下文收集、Prompt 渲染、结果后处理。上下文收集就是从当前项目里提取相关信息比如选中的代码、相关文件、报错日志Prompt 渲染就是按照每个操作预设的模板把上下文填充进去结果后处理则是把模型返回的内容清洗、切片、或者转成结构化数据。第三层是模型层封装 DeepSeek API 调用。这一层要处理的细节比较多包括鉴权、消息拼装、流式读取、超时重试、或者函数调用时的工具消息解析。很多插件写一半就烂尾就是因为这三层逻辑搅在一起改一个地方崩一片。分层之后每一层都能单独测试和替换我实测下来调试效率高很多。2.2 为什么选了 DeepSeek API 而不是本地模型这个选择我纠结过一段时间。当时我手头有一台能做推理的显卡也尝试过本地部署开源模型。但最终放弃了本地方案原因很现实开发体验差部署维护成本太高。首先是负载问题。我在写代码的时候插件会被高频触发尤其是批量审查文件时本地模型动辄占满显存导致整个系统卡顿比不用 AI 还影响效率。其次是版本升级成本开源模型版本迭代快每次升级都要重新处理量化、推理框架兼容性纯粹是给自己找活干。DeepSeek API 解决了我最在意的两个问题稳定性和成本。API 调用无需关心硬件并发也扛得住。价格方面deepseek-chat 模型的输入输出定价比很多主流商业模型便宜得多日常开发场景的调用频率完全在可接受范围内。更重要的是它支持 function calling这让后续把操作固化成 Agent 工具成为可能。如果你当前也想做类似插件我建议直接走 API 方案把精力放在插件逻辑本身。2.3 工具定义让 Agent 理解“可用的能力”Agent 要使用插件里的能力必须让模型知道这个工具存在、什么时候调、传什么参数。这个过程靠的就是工具定义或者说函数调用的 Schema 描述。我用的是 OpenAI 兼容的工具定义格式DeepSeek 的 API 也支持这个协议。每个工具定义包含四要素工具名称、工具描述、参数结构、必填参数。不要把这几项当摆设尤其是描述字段它直接决定了模型在什么时机下会调用这个工具。描述写得越具体调用准确率越高。举个例子如果描述里只写“审查代码”模型可能在用户闲聊时也触发调用。但如果写成“当用户选中一段代码并要求检查质量问题、安全隐患或逻辑错误时使用输入为完整代码文本”模型就会在语义匹配时更加明确。参数结构也要尽量细化把每个字段的类型、含义都写清楚这样模型才能正确地从上下文里提取参数。3. 核心实现与实操拆解3.1 插件骨架manifest 与生命周期写插件的第一步是搭骨架。一个合格的插件必须具备三个基础要素元信息描述、初始化逻辑、资源清理逻辑。元信息描述通常放在 manifest 文件里声明插件名称、版本、依赖、以及它注册了哪些面板入口和哪些 Agent 工具。这个文件写不好后面很容易出现加载即失败的问题。这是我用的 manifest 简化结构{ name: deepseek-harness, version: 0.1.0, description: 把常见 AI 辅助操作固化为面板入口和 Agent 工具, panels: [ { id: code-review, title: 代码审查, command: deepseek-harness.codeReview } ], agentTools: [ { name: review_code, entry: tools/code-review.js } ] }生命周期上插件要处理三个事件启用时初始化配置、读密钥、检查网络连通性任务执行中处理单个操作停用时释放资源。很多插件开发者只关心任务执行忽略了初始化和清理结果就是每次重启编辑器都要重新配置或者退出时留下残留进程。建议在启用阶段就把 API 密钥、默认模型、超时时间这些参数全部加载并缓存起来执行时直接取用。3.2 注册面板入口和操作命令面板入口最朴素的做法就是把每个操作注册成一个命令。以代码审查为例命令触发时需要做三件事获取编辑器当前选中文本、把文本传给编排层、展示结果。在 VS Code 插件体系里获取选中文本有现成的 API。核心代码如下import * as vscode from vscode; export function activate(context: vscode.ExtensionContext) { const disposable vscode.commands.registerCommand(deepseek-harness.codeReview, async () { const editor vscode.window.activeTextEditor; const selection editor?.selection; const selectedText editor?.document.getText(selection); const result await runHarnessOperation(code_review, { code: selectedText, language: editor?.document.languageId }); const panel vscode.window.createOutputPanel(DeepSeek Review); panel.append(result); }); context.subscriptions.push(disposable); }这里有一个细节值得注意传给编排层的数据结构尽量保持稳定我统一用的是“操作名 参数对象”的模式。操作名对应 manifest 里的 id参数对象则根据每个操作单独定义。这样后期扩展新操作时只需要新增一个处理入口不需要改面板注册逻辑。面板入口不仅仅是侧边栏按钮。我建议同时把它绑到右键菜单和快捷键上。实际使用中鼠标右键唤起审查比侧边栏点按钮更快快捷键则适合高频操作。这三个入口共享同一个命令不会增加额外维护成本。3.3 封装 DeepSeek 调用流式输出、超时与重试模型层的封装质量直接决定了插件的体感。我最开始是把网络请求写死在每个操作里后来抽成了一个 requestDeepSeek 函数统一处理鉴权、消息拼装、流式输出和异常兜底。调用 DeepSeek 的 API 基本和 OpenAI SDK 一致base_url 指向 DeepSeek 的接口地址。这里给一个最精简的流式调用示例from openai import OpenAI client OpenAI( api_keysk-your-key, base_urlhttps://api.deepseek.com ) stream client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是一个资深后端开发工程师专门负责代码审查。}, {role: user, content: user_prompt} ], streamTrue, temperature0.3 ) for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: yield chunk.choices[0].delta.content流式输出是必须的。如果不流式一个长回答可能要等十几秒用户会以为插件卡死了。有了流式面板上文字逐字出现交互反馈感强很多体验完全不一样。超时和重试我单独说一下。DeepSeek API 偶尔会因为服务端负载问题变慢我遇到过几次请求挂起的情况。建议给每次请求设置一个可配置的超时时间默认 60 秒。同时对于网络类的瞬时错误也就是连接失败、请求超时这类加上重试机制最多重试三次每次间隔递增。但是注意不要在业务逻辑错误时重试比如鉴权失败、参数格式错误这类重试多少次都没意义只会浪费额度。3.4 把操作固化成 Agent 工具固定面板之后第二步是把它暴露出 Agent 工具接口。这一步的关键在于学会使用 function calling 机制。当你把工具列表传给 DeepSeek API 后模型会根据对话内容决定是否调用某个工具。如果决定调用返回的内容里会包含 tool_calls 字段结构里携带工具名称和参数。你要做的是把工具真实执行后的结果再作为一条工具消息回传给模型让它继续生成最终回复。仍然以代码审查为例工具定义如下{ type: function, function: { name: review_code, description: 对指定代码执行安全、性能和逻辑审查返回问题列表和改进建议。, parameters: { type: object, properties: { code: { type: string, description: 要审查的完整代码内容 }, language: { type: string, description: 代码语言如 Python、TypeScript } }, required: [code, language] } } }当 Agent 框架接收到模型的 tool_calls 之后执行函数并返回结果{ role: tool, tool_call_id: call_xxx, content: [{\level\:\error\,\issue\:\未处理空指针异常\,\suggestion\:\...\}] }这里有一个非常容易踩的坑tool_call_id 必须原样返回不能自己生成。如果 id 对不上API 会直接报错并且整轮对话会中断。我第一次对接时用了一个新生成的 uuid 当 tool_call_id结果被这个低级错误卡了一个多小时。另一个值得强调的点是工具结果要尽量结构化。如果直接返回大段自然语言模型下一步的决策质量会打折。建议在工具内部做一层结构化处理比如把审查结果组织成 JSON 数组带有 level、issue、suggestion 这些字段再交给模型去总结。3.5 实操记录一个“日志分析专家”工具的完整过程前面讲的偏通用这里放一个实战记录。我的项目里需要频繁分析线上报错日志于是我固化了“日志分析专家”这个工具整个过程比较典型。第一步是明确输入。日志分析需要的输入有三个原始日志文本、应用类型、以及可选的错误关键字。我在工具定义里让模型自动提取这三个参数。为了让模型更好理解我还在描述里强调“当日志中包含堆栈信息或异常关键字时使用”。第二步是设计 Prompt 模板。系统提示词里我写了这样的逻辑先识别日志中的异常类型再定位到业务代码层最后给出根因判断和排查顺序。模板里我把日志放在占位符位置同时要求输出格式固定为三部分异常现象描述、根因推断、下一步排查建议。这个步骤很关键模板写得好输出就不会天马行空。第三步是连通 Agent。我把这个工具注册到 Agent 工具列表后测试了一个真实场景Agent 在自动化脚本中检测到接口报错调起日志分析工具把堆栈信息传过去模型返回根因是数据库连接池耗尽接着 Agent 自动生成了一个工单并附上扩容建议。整个链路跑通的那一下我确实感觉到“操作固化”的价值以前要靠人肉复制日志、分析、写结论现在全自动完成。4. 常见问题与排查技巧实录4.1 Harness 插件加载失败的几个原因“Harness failed to load plugins”是我在开发时几乎每天都会见到的报错。最常见的根因有三个manifest 文件格式错误、依赖模块缺失、插件路径配置不对。manifest 文件格式错误很容易犯比如 JSON 里多了个尾逗号或者某个必须字段漏写了。插件宿主在加载时不会给你任何提示只会给一个模糊的加载失败。我的建议是写一个独立的 schema 校验函数在开发阶段每次构建时自动校验 manifest而不是等到运行时才暴露。尾逗号这种错误在 JSON 标准里本来就是非法的很多编辑器默认配置的 formatter 反而会帮你加进去一定注意。依赖模块缺失这个坑也很隐蔽。插件里如果引用了某个 npm 包但没有在插件自身的本地依赖里声明而是依赖宿主环境里的包那换一台机器跑就会加载失败。处理办法是把所有运行时依赖都显式声明在插件目录的 package.json 里不要相信全局安装。路径配置问题通常出现在插件入口文件被移动了位置但 manifest 里写的还是旧路径。检查思路很简单打开宿主环境的日志确认它到底尝试加载哪个文件然后对比实际路径。建议插件项目从一开始就保持固定目录结构不要随意调整。4.2 Agent 工具调用报错的排查思路Agent 工具调用报错九成以上和函数调用协议有关。最常见的是 tool_call_id 不匹配我在前面已经说过。这里再说另外两个高频问题。第一个问题是参数 Schema 与实际传入类型不符。比如你在工具定义里声明 code 字段是字符串但模型在实际调用时可能传了一个对象进去或者漏了必填字段。排查方法是打印原始 tool_calls 参数看模型到底生成了什么。有时候模型会把 JSON 字符串序列化成了对象再序列化一次导致双重编码函数里就需要做一次兼容处理。标准做法是先反序列化成字符串再强转一次保证拿到的字段是预期类型。第二个问题是工具结果超长。有些操作返回内容动辄几十 KB直接塞进工具消息里模型上下文很容易爆掉。我的方案是给工具结果加一个截断策略保留结构化摘要内容细节存到临时文件把文件路径作为结果返回。这样既保留了信息又控制了 token 占用。还有一个容易被忽略的问题是并发调用。Agent 在一次对话里可能同时触发多个工具调用插件侧要做好并发控制。实测下来同时跑两个耗时操作没问题同时跑五六个的话一是 API 配额可能撑不住二是面板输出区域会乱。我的做法是给每个操作加一个队列限制最大并发数为二其余排队执行。4.3 上下文超限与成本控制上下文超限是这类插件必然遇到的问题。代码审查时一个文件可能就几千行日志分析时日志文本动辄上 MB直接全部塞给模型要么超限要么费用飙升。我统计过成本最高的一天因为反复调试一个超长日志分析消耗了几百万 token算下来花费虽然不至于破产但也让我警觉起来。现在我的做法是先做上下文裁剪对超长文本按行采样保留头部栈信息、尾部异常信息中间部分摘要化。对于代码文件则是按函数粒度切分只把选中的函数及其直接调用链发过去。缓存也是控制成本的重要手段。同一个文件在参数没有变化的情况下审查结果是稳定的完全没必要每次都重新调用模型。我加了一个基于文件内容哈希的缓存层命中缓存时直接读取上次结果并给面板打上“缓存结果”的标记。实际运行下来缓存命中了大约四成请求效果非常明显。4.4 与编辑器/IDE 集成的避坑如果你也想把类似插件扩展到其他编辑器这里有几个通用建议。首先是获取选中内容的时机问题。编辑器在失焦状态下选中内容可能读取不到所以要在命令触发的那一瞬间立即获取并存储不要等到异步流程中间再去取。其次是文件路径的跨平台兼容。Windows 下路径分隔符是反斜杠Mac/Linux 是正斜杠路径拼接如果不做归一化同一个操作在另一台机器上就会出问题。建议用编辑器提供的路径 API不要手工拼路径。最后是项目级上下文的收集。别小看这一步很多操作需要读取项目中相关文件的内容比如查找调用方、读取配置文件。如果直接按文件系统遍历项目目录可能会把 node_modules 这种大目录也扫描进去又慢又费 token。一定要做忽略列表过滤至少把 node_modules、dist、.git 这些目录排除掉。实测下来加过滤后上下文收集时间从十几秒降到一两秒体验提升是质变级别的。从最开始手动复制粘贴 Prompt到现在面板点一下、Agent 自动调度这个过程给我最大的感触是AI 时代真正耗时的不是大模型本身而是大模型和开发环境之间的“最后一公里”。把高频操作固化成一个可以程序化调用的工具让模型随时用得上比不停调 Prompt 有效得多。现在我正打算把面板入口做成可视化工作流编辑器让非深度用户也能自己拖拽配置操作相当于把 Harness 插件再往前推一步。如果你也在做类似的东西建议从最小的操作固化开始先跑通一个完整链路再慢慢扩展工具集。