ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

单人九个月二十万行代码:Harness架构与AI Agent开发实践

单人九个月二十万行代码:Harness架构与AI Agent开发实践 1. 先搞清楚这个项目到底在做什么一个人九个月二十万行代码每个月消耗四十亿以上的 token最终交付的是一款基于 Harness 架构的应用。这几个数字摆在一起任何一个写过代码的人都会先愣一下。二十万行代码是什么概念一个中等规模的商业项目五到八人的团队做一年大概也就是这个量级。而这里是一个人九个月。更夸张的是每个月四十亿 token 的消耗量这意味着整个开发过程高度依赖大模型辅助不是偶尔问几句而是把 AI 当成了日常开发的基础设施。我第一眼看到这个标题的时候脑子里冒出来的问题不是“怎么做到的”而是“这到底是一个什么样的应用需要这么重的架构”。Harness 这个词在 DevOps 领域原本指的是持续交付平台但在 AI 应用开发的语境下它更多指的是一种“编排层”或者“驱动层”的架构模式——把模型调用、工具执行、上下文管理、状态流转这些东西统一收拢到一个可配置、可扩展的框架里。简单说Harness 架构解决的核心问题是当你的应用需要频繁调用大模型、需要管理复杂的上下文、需要串联多个工具和步骤时怎么让这套东西跑得稳、改得动、扩得开。这个项目之所以值得拆解不是因为数字好看而是因为它代表了一种正在成型的开发范式。一个人加上足够强的 AI 工具链能不能撑起一个原本需要团队才能完成的项目这个问题的答案藏在这九个月的每一个技术决策里。适合读这篇内容的人包括正在做 AI Agent 开发的工程师、想了解 Harness 架构落地细节的技术负责人、以及那些对“单人重项目”模式好奇的独立开发者。不管你现在用的是 Claude Code、DeepSeek Harness 还是自己搭的 Agent 框架这里面的很多经验都能直接参考。2. 为什么选 Harness 架构而不是直接写业务代码2.1 从“调 API”到“搭骨架”的思维转变很多人做 AI 应用的第一反应是写个函数调模型 API拿到结果处理一下完事。这个思路在 demo 阶段没问题但一旦业务逻辑超过三个步骤就会开始失控。你会发现自己在一堆 if-else 里处理模型返回的各种格式在多个文件里重复写几乎一样的 prompt 拼接逻辑在每次模型升级后手动改十几处调用点。这就是典型的“没有架构”的开发方式。Harness 架构的核心思路是把“模型调用”这件事从业务代码里抽出来变成一个独立的、可配置的层。你可以把它想象成汽车的底盘发动机、变速箱、悬挂、转向这些系统都挂在底盘上底盘本身不参与具体行驶但它决定了这辆车能不能跑得稳、能不能换发动机、能不能加装新功能。在这个项目里Harness 层负责的事情包括统一管理模型调用的入口和出口、维护对话上下文和状态、调度工具函数的执行顺序、处理错误重试和降级、记录每次调用的 token 消耗和耗时。为什么这个选择很关键因为二十万行代码的项目如果模型调用散落在各处后期根本没法维护。我见过太多项目前期图快直接在业务逻辑里写client.chat.completions.create(...)等到要换模型、要加缓存、要做限流的时候发现要改上百个地方。Harness 架构把这个成本前置了前期多花时间搭骨架后期每一次变更都只改一个地方。2.2 四十亿 token 背后的成本账和效率账每个月四十亿 token这个数字需要拆开看。假设按主流模型的定价输入和输出混合计算每百万 token 的成本在几块到几十块之间浮动。四十亿 token 意味着每个月的模型调用成本可能在一个相当可观的区间。但这里的关键不是花了多少钱而是这些 token 花在了哪里。根据我对类似项目的观察token 消耗的大头通常集中在几个地方代码生成和补全、代码审查和重构建议、文档生成、测试用例生成、以及 Agent 在执行任务时的多轮推理。其中 Agent 的多轮推理是最容易失控的因为每一次工具调用、每一次上下文重新加载、每一次错误重试都在烧 token。如果 Harness 层没有做好上下文压缩和缓存token 消耗会呈指数级增长。这个项目能做到每个月四十亿 token 的稳定消耗说明 Harness 层在上下文管理上做了不少优化。我推测其中至少包括对历史对话做滑动窗口或摘要压缩、对重复的系统提示做缓存、对工具调用的结果做结构化存储而不是全部塞回上下文、以及对不同任务类型设置不同的 token 预算上限。这些优化单独看都不复杂但组合在一起能把 token 的有效利用率提升好几倍。2.3 单人项目的架构选型逻辑一个人做项目最大的约束不是技术能力而是注意力和时间。你不可能像团队那样分工一个人写架构、一个人写业务、一个人写测试。所以单人项目的架构选型必须遵循一个原则前期多花时间做抽象后期少花时间做重复。Harness 架构恰好符合这个原则。虽然搭骨架的阶段会慢一些但一旦骨架搭好后面写业务逻辑就是填空。而且因为所有模型调用都走统一入口调试的时候只需要在一个地方加日志优化的时候只需要在一个地方改参数。对于一个人维护二十万行代码来说这种集中式的管理方式是必须的。另一个考虑是工具链的整合。这个项目的热词里出现了 Claude Code、Obsidian、Markdown、Agent 这些关键词说明整个开发过程是高度工具化的。Harness 层不仅要管模型调用还要管这些工具之间的数据流转。比如用 Claude Code 生成代码用 Obsidian 管理笔记和文档用 Markdown 作为中间格式做转换。这些工具各自有各自的接口和数据格式Harness 层要做的就是把它们粘在一起让数据能顺畅流动。3. 核心模块拆解与关键实现细节3.1 上下文管理让模型记住该记的忘掉该忘的上下文管理是 Harness 架构里最核心也最容易被低估的模块。很多人以为上下文管理就是“把历史消息塞进 messages 数组”但实际做起来远不止这么简单。在一个二十万行代码的项目里Agent 需要记住的东西包括当前正在处理的文件、之前修改过的相关文件、项目的整体架构约定、用户的偏好设置、以及当前任务的进度状态。如果把这些全部塞进上下文token 消耗会爆炸而且模型的有效注意力会被稀释。所以上下文管理模块需要做几件事分层存储把上下文分成“必须每次都带的”如系统提示和核心约定、“按需加载的”如相关文件内容、“可摘要的”如历史对话动态裁剪根据当前任务类型和模型窗口大小决定加载哪些内容缓存复用对不变的部分做缓存避免重复计算。我在类似项目里的做法是给每个上下文片段打上标签和优先级Harness 层根据当前任务的元数据来决定加载策略。比如代码生成任务优先加载相关文件和技术栈约定文档生成任务优先加载项目结构和已有文档。这个策略需要不断调优但一旦跑通token 的有效利用率能提升百分之三十以上。3.2 工具调度Agent 的手和脚怎么协调Agent 和普通聊天机器人的最大区别在于Agent 能调用工具。在这个项目里工具可能包括文件读写、代码执行、搜索、Markdown 解析、Obsidian 笔记操作、以及各种格式转换。工具调度模块要解决的问题是什么时候调用哪个工具、调用参数怎么传、调用结果怎么处理、失败了怎么重试。这里面的坑非常多。第一个坑是工具描述的准确性。模型是根据工具的自然语言描述来决定调不调、怎么调的。如果描述写得模糊模型就会乱调。我见过一个案例工具描述里写“读取文件”模型就以为可以读任何路径结果传了一个不存在的路径整个流程卡住。后来改成“读取项目目录下的指定文件路径必须是相对路径”问题就少了很多。第二个坑是工具调用的并发控制。有些工具可以并行调用有些必须串行。比如同时读多个文件没问题但同时写同一个文件就会冲突。Harness 层需要维护一个依赖图决定哪些调用可以并行、哪些必须排队。这个逻辑如果放在业务代码里很快就会乱成一团。第三个坑是错误处理和降级。工具调用失败是常态网络超时、文件不存在、格式不对各种情况都有。Harness 层需要定义一套统一的错误处理策略哪些错误可以重试、重试几次、重试间隔多少、重试失败后是降级还是终止。这套策略必须是可配置的因为不同工具、不同任务对错误的容忍度不一样。3.3 Markdown 作为中间格式的利与弊这个项目的热词里 Markdown 出现的频率很高说明它在整个架构里扮演了重要角色。Markdown 的好处很明显纯文本、易读易写、工具支持广泛、版本控制友好。用 Markdown 作为中间格式意味着模型生成的內容、Obsidian 里的笔记、项目文档、甚至部分配置都可以用同一种格式表示和转换。但 Markdown 也有它的坑。第一个坑是格式一致性。不同工具生成的 Markdown 在细节上会有差异比如换行方式、列表缩进、表格对齐、代码块标记。如果不做统一处理后续的解析和转换就会出问题。我的做法是在 Harness 层加一个 Markdown 规范化模块所有进入系统的 Markdown 先过一遍格式化统一换行符、统一列表符号、统一代码块语言标记。第二个坑是表格和数学公式的处理。Markdown 的表格语法在不同解析器下表现不一致数学公式更是需要额外的渲染支持。如果项目里涉及这些内容Harness 层需要集成相应的解析和渲染工具并且在存储时保留原始格式避免转换过程中丢失信息。第三个坑是与 Obsidian 的同步。Obsidian 有自己的 Markdown 扩展语法比如双链、标签、frontmatter。如果项目需要和 Obsidian 双向同步Harness 层要能识别和处理这些扩展语法不能简单地当普通 Markdown 处理。我建议的做法是在 Harness 层定义一套内部的 Markdown 方言进入系统时把各种来源的 Markdown 转换成内部方言输出时再转换成目标格式。3.4 Agent 执行循环的设计要点Agent 的执行循环是整个 Harness 架构的心脏。一个典型的循环包括接收任务、规划步骤、调用工具、观察结果、更新状态、决定下一步、直到任务完成或达到终止条件。这个循环看起来简单但实际实现时要考虑很多细节。首先是循环终止条件。除了任务完成还要考虑最大步数限制、超时限制、连续失败次数限制、token 预算限制。这些限制必须同时生效任何一个触发都要能优雅地终止循环并返回当前状态。我见过一些项目只设了最大步数结果 Agent 在某个步骤上反复重试烧了大量 token 才停下来。其次是状态持久化。Agent 的执行可能跨越很长时间中间可能因为各种原因中断。Harness 层需要把每一步的状态持久化这样中断后可以从上次的位置恢复而不是从头开始。这个功能在长任务场景下特别重要能省下大量重复计算。最后是可观测性。Agent 在循环里做了什么决策、调了什么工具、花了多少 token、耗时多少这些信息必须完整记录。没有可观测性调试 Agent 就是盲人摸象。我的做法是在 Harness 层加一个事件总线每个关键节点都发一个事件然后统一收集和分析。这些事件数据后来成了优化 token 消耗和提升执行效率的主要依据。4. 实操流程从零搭建一个可运行的 Harness 骨架4.1 环境准备与工具链配置在开始写代码之前先把工具链配好。这个项目的热词里提到了 Claude Code、DeepSeek Harness、Obsidian、VS Code 这些工具说明整个开发环境是围绕这些工具搭建的。我的建议是先把核心工具装好再考虑集成。第一步是确定模型调用层。你可以用 Claude Code 作为主要的代码生成工具用 DeepSeek Harness 作为 Agent 执行框架或者两者结合。关键是确定一个统一的模型调用入口所有模型请求都走这个入口。这个入口需要处理 API 密钥管理、请求重试、速率限制、token 计数这些基础功能。第二步是配置开发环境。VS Code 加上 Claude Code 插件是目前比较顺手的组合。如果你用 Obsidian 管理笔记和文档需要配置好 vault 路径和同步策略。Markdown 相关的插件比如 Markdown Preview Enhanced、数学公式插件也建议提前装好避免后期格式转换时出问题。第三步是初始化项目结构。一个典型的 Harness 项目结构包括harness/目录放核心框架代码tools/目录放工具实现agents/目录放 Agent 定义configs/目录放配置文件docs/目录放文档和笔记。这个结构不是固定的但核心思路是把框架、工具、业务逻辑分开避免混在一起。4.2 核心模块的代码实现思路Harness 核心模块的实现可以从一个简单的Harness类开始。这个类需要维护几个关键属性模型客户端、工具注册表、上下文管理器、事件总线、配置对象。对外暴露的方法包括register_tool()注册工具、run_agent()执行 Agent、load_context()加载上下文、save_state()保存状态。工具注册表的实现可以用一个字典键是工具名称值是工具的描述、参数 schema、执行函数。描述和 schema 会传给模型让模型知道有哪些工具可用、怎么调用。执行函数是实际干活的代码接收参数、返回结果。这里要注意的是执行函数的返回值必须是可序列化的因为要存到上下文里。上下文管理器的实现稍微复杂一些。我建议用一个分层结构最底层是原始消息列表中间层是摘要和索引最上层是当前任务的上下文视图。加载上下文时根据任务类型从各层抽取内容组装成最终的 messages 数组。这个组装过程要记录日志方便后续分析哪些内容被加载了、哪些被裁剪了。事件总线的实现可以用简单的发布订阅模式。每个关键节点调用emit(event_type, payload)订阅者可以是日志模块、监控模块、或者调试工具。这个设计的好处是你可以在不修改核心代码的情况下加各种观测和分析功能。4.3 与 Obsidian 和 Markdown 工具的集成如果你的项目需要和 Obsidian 集成核心要做的事情是读取 Obsidian vault 里的 Markdown 文件、解析 frontmatter 和双链、把处理结果写回 vault。这里的关键是路径管理和格式转换。路径管理方面Obsidian vault 是一个目录树Harness 层需要知道哪些文件是相关的、哪些可以忽略。我的做法是在配置文件里定义几个目录inbox/放待处理的笔记projects/放项目相关文档archive/放已完成的内容。Agent 只操作这些目录下的文件避免误改其他内容。格式转换方面Obsidian 的 Markdown 有一些扩展语法比如[[双链]]、#标签、---frontmatter---。如果 Harness 层需要把这些内容传给模型处理要么先转换成标准 Markdown要么在 prompt 里说明这些语法的含义。我倾向于后者因为转换过程中容易丢失信息。在 prompt 里加一段说明告诉模型这些语法的用途模型通常能正确处理。写回 vault 的时候要注意冲突处理。如果 Agent 修改了一个文件而用户同时也在 Obsidian 里编辑可能会冲突。我的做法是Agent 写文件前先检查文件的修改时间如果和上次读取时不一致就提示用户手动合并而不是直接覆盖。4.4 参数配置与性能调优Harness 架构的性能调优主要围绕几个参数上下文窗口大小、最大工具调用次数、重试策略、并发度、缓存策略。这些参数没有万能值需要根据具体任务和模型来调。上下文窗口大小决定了每次请求能带多少历史信息。设得太小模型会丢失重要上下文设得太大token 消耗高且模型注意力分散。我的经验值是对于代码生成任务窗口设在模型最大窗口的百分之六十到七十比较合适留出空间给工具调用结果和模型输出。最大工具调用次数决定了 Agent 在一个任务里最多能调多少次工具。设得太小复杂任务做不完设得太大出问题时烧 token 多。我通常从二十次开始根据任务复杂度调整。对于代码修改类任务二十次通常够用对于需要大量搜索和验证的任务可能要设到五十次。重试策略要区分错误类型。网络超时和速率限制可以重试参数错误和权限错误不应该重试。重试间隔建议用指数退避第一次等一秒第二次等两秒第三次等四秒最多重试三次。这样既能应对临时故障又不会在永久性错误上浪费时间。并发度主要影响工具调用的效率。读操作可以高并发写操作要串行。我的做法是给每个工具打上readonly或write标签Harness 层根据标签决定并发策略。只读工具可以同时跑十个写工具一次只跑一个。5. 踩过的坑和常见问题排查5.1 Token 消耗失控的几种典型场景Token 消耗失控是 Harness 项目最常见的问题。我总结了几种典型场景和对应的排查方法。第一种是上下文无限增长。Agent 在循环里不断把工具调用结果塞回上下文导致每次请求的 token 数越来越大。排查方法是看每次请求的 token 计数如果呈线性或指数增长就是这个问题。解决方法是加一个上下文压缩策略比如只保留最近 N 轮的工具调用结果更早的做摘要。第二种是重复加载相同内容。比如每次工具调用前都重新读取同一个文件或者每次都把完整的系统提示重新传一遍。排查方法是看请求内容里有没有重复的片段。解决方法是加缓存对不变的内容做缓存避免重复传输。第三种是模型陷入循环。Agent 在某个步骤上反复尝试同一个操作每次都失败但每次都重试。排查方法是看工具调用日志如果同一个工具被连续调用多次且参数相同就是这个问题。解决方法是在 Harness 层加一个循环检测机制连续三次相同调用就强制终止并报错。第四种是输出 token 过多。模型生成了大量无用内容比如重复的解释、冗长的代码注释。排查方法是看输出 token 的占比。解决方法是在 prompt 里明确要求简洁输出或者设置最大输出 token 限制。5.2 Agent 执行中断的排查思路Agent 执行中断的原因很多排查时要按顺序检查。先看错误信息如果是模型返回的错误检查 API 密钥、配额、模型名称是否正确。如果是工具执行错误检查工具的参数和运行环境。如果是超时检查网络和模型响应时间。一个常见的问题是agent execution terminated due to error这个错误信息很模糊需要看更详细的日志。我的做法是在 Harness 层捕获所有异常记录完整的堆栈和上下文然后统一格式化输出。这样排查时能看到具体是哪一步、哪个工具、什么参数出的问题。另一个常见问题是harness failed to load plugins这通常是插件路径配置错误或者依赖缺失。检查插件目录是否存在、插件文件是否完整、依赖是否安装。如果是动态加载的插件还要检查加载顺序有些插件依赖其他插件先加载。还有一种情况是 Agent 执行到一半卡住没有报错也没有进展。这通常是模型在等待某个永远不会返回的结果或者工具调用陷入了死锁。排查方法是看事件日志找到最后一个成功的事件然后检查它之后的步骤。如果是工具调用卡住检查工具的并发控制逻辑看是不是有锁没释放。5.3 常见问题速查表问题现象可能原因排查方法解决方案Token 消耗突然飙升上下文无限增长或模型陷入循环查看每次请求的 token 计数和工具调用日志加上下文压缩和循环检测Agent 执行中断无报错工具调用死锁或等待超时查看事件日志最后一条记录检查并发控制和超时设置工具调用参数错误工具描述不清晰或模型理解偏差查看工具调用日志中的参数优化工具描述和参数 schemaMarkdown 格式混乱不同来源格式不一致对比输入和输出的 Markdown加规范化模块统一格式Obsidian 同步冲突文件被同时修改检查文件修改时间加冲突检测和手动合并提示模型输出质量下降上下文过长或 prompt 漂移对比不同时期的请求内容压缩上下文和固定 prompt 模板插件加载失败路径错误或依赖缺失检查插件目录和依赖列表修正路径和安装依赖执行速度变慢并发度低或缓存失效查看工具调用耗时和缓存命中率调整并发策略和缓存配置5.4 几个容易被忽略的实操心得第一个心得是给每个工具加超时。不管工具本身多快都要设一个超时时间。我见过一个案例一个文件读取工具因为网络文件系统的问题卡了十分钟整个 Agent 就停在那里。后来给所有工具加了三十秒超时问题就再没出现过。第二个心得是日志要分级。不是所有日志都同等重要调试时看全部日志会被淹没。我的做法是分三级ERROR 级别记录所有异常INFO 级别记录关键决策和工具调用DEBUG 级别记录详细的上下文和参数。平时只看 ERROR 和 INFO排查问题时再开 DEBUG。第三个心得是定期清理缓存和临时文件。Harness 项目跑久了缓存和临时文件会占大量空间而且可能包含过期数据。我建议加一个定时任务每天清理一次超过七天的缓存和临时文件。这个习惯能避免很多莫名其妙的问题。第四个心得是版本控制要包括配置文件。很多人只把代码纳入版本控制配置文件放在本地。结果换台机器或者重装系统后配置全丢了。我的做法是把所有配置文件都纳入版本控制敏感信息用环境变量替代。这样任何时候都能快速恢复开发环境。6. 这套架构还能怎么扩展Harness 架构搭好之后扩展性其实很好。你可以加新的工具只要实现统一的工具接口注册到工具注册表就行。你可以加新的 Agent定义不同的执行策略和工具集。你可以加新的模型后端只要适配统一的模型调用接口。这种可扩展性正是当初选择 Harness 架构的原因。从 token 消耗的角度看后续优化的空间主要在上下文压缩和缓存策略上。我试过用摘要模型对历史对话做压缩效果不错能把上下文长度减少百分之四十左右同时保留关键信息。缓存方面对系统提示和常用工具描述做缓存能省下不少重复传输的 token。从功能扩展的角度看可以加的东西包括多 Agent 协作、任务队列和调度、结果验证和自动修复、以及与更多外部工具的集成。这些扩展不需要改动核心架构只需要在现有框架上叠加模块。这也是 Harness 架构的价值所在它不解决具体业务问题但它让解决业务问题变得更容易。我个人在实际操作中的体会是Harness 架构的前期投入确实比直接写业务代码大大概多花百分之三十到四十的时间。但一旦骨架搭好后面每加一个新功能、每换一个模型、每接一个新工具省下的时间远超前期投入。对于一个人维护的大项目来说这种投入产出比是划算的。如果你也在做类似的事情建议先把 Harness 层做扎实后面会轻松很多。
返回列表