☰
Harness架构实战:一个人九个月20万行代码构建智能体系统
2026/9/30 5:46:25 网站建设 项目流程

1. 先搞清楚这个标题到底在说什么

一个人、九个月、20万行代码、每月40亿+ token——这几个数字摆在一起,任何一个写过代码的人都会先愣一下。20万行代码,如果按一个成熟工程师每天有效产出200行来算,需要1000个工作日,也就是接近三年。而这里只有九个月,还是一个人。更离谱的是每月40亿+ token的消耗量,这个量级意味着背后有一个持续运转、高频调用的智能体系统在支撑,而不是那种跑一次就停的脚本。

这个项目的核心,是围绕Harness 架构构建一款应用。Harness 这个词在当下的开发语境里,指的是一套把大模型能力、工具调用、上下文管理、任务编排串起来的运行骨架。你可以把它理解成一个"智能体的操作系统"——模型是CPU,Harness 是主板和总线,负责把记忆、工具、文件系统、外部服务全部接起来,让智能体能够长时间、多步骤地完成复杂任务。

关键词里出现的 Markdown、Agent、Claude Code、Obsidian,其实已经把这个项目的技术栈轮廓勾出来了:用 Markdown 作为知识载体和交互界面,用 Agent 作为执行主体,用 Claude Code 这类命令行智能体工具作为开发与运行环境,用 Obsidian 作为本地知识库和可视化层。这套组合不是随便凑的,它对应的是一个非常具体的需求场景——个人知识工作者或独立开发者,想要一个能长期陪伴、持续积累、自动干活的智能体系统。

这篇文章适合谁看?如果你正在琢磨怎么把大模型从"聊天玩具"变成"生产力工具",如果你对 Agent 开发、Harness 架构、本地知识库集成有兴趣,或者你只是好奇一个人怎么在九个月里堆出20万行代码还烧掉那么多 token,那这篇内容应该能给你一些实在的参考。我不会只讲概念,会把架构选型、token 消耗的构成、Markdown 作为核心载体的原因、以及实际开发中踩过的坑都摊开来说。

2. 为什么是 Harness 架构,而不是直接调 API

2.1 裸调 API 的天花板在哪里

很多人做 AI 应用的第一步,就是写个函数调一下模型接口,把用户输入塞进去,拿回输出展示出来。这个模式在简单问答场景下没问题,但一旦任务变复杂,问题就全冒出来了。

最直接的问题是上下文断裂。裸调 API 每次请求都是独立的,模型不记得上一轮干了什么。你可能会说,那就把历史消息都带上呗。但历史一长,token 消耗就爆炸,而且模型对超长上下文的注意力会稀释,早期关键信息容易被忽略。更麻烦的是,当任务需要调用外部工具——比如读文件、查数据库、执行命令——裸调 API 根本没有这套机制,你得自己在外层写一堆胶水代码来解析模型的意图、执行动作、再把结果塞回去。

还有一个隐性成本:状态管理。一个持续运行的应用,需要知道当前任务进行到哪一步、哪些文件被修改过、哪些工具调用失败了需要重试。这些状态如果全靠业务代码维护,很快就会变成一团乱麻。Harness 架构的价值,就是把这些脏活累活收拢到一个统一的运行时里。

2.2 Harness 到底管了什么

用生活化的类比来说,裸调 API 像是你每次要用车都临时去租一辆,还得自己加油、自己导航、自己处理违章。Harness 则是你自己的车库加司机加调度中心,车随时待命,路线自动规划,出了问题有人兜底。

具体到技术层面,一个 Harness 架构通常包含这几个核心模块:

  • 上下文管理器:决定每一轮给模型看什么、不看什么。它不是简单地把所有历史都塞进去,而是做摘要、做检索、做优先级排序。比如最近几轮对话保留原文,更早的内容压缩成摘要,相关的文件内容按需注入。
  • 工具注册与调度层:把文件读写、命令执行、网络请求、数据库查询等能力封装成模型可以调用的工具,并处理调用参数校验、超时、重试、结果格式化。
  • 任务编排引擎:支持多步骤任务的拆解与执行,能够根据上一步的结果决定下一步做什么,支持循环、条件分支、并行执行。
  • 持久化与状态存储:把会话状态、任务进度、工具调用记录落盘,保证进程重启后能恢复。
  • 安全与权限控制:限制智能体能碰哪些文件、能执行哪些命令,防止它把系统搞崩。

这五块里,任何一块自己手写都不难,难的是让它们协同工作,并且在长时间运行中保持稳定。20万行代码里,很大一部分就是在处理这些模块之间的边界情况和异常路径。

2.3 每月40亿 token 是怎么烧掉的

40亿 token 听起来吓人,但拆开看就合理了。假设系统每天运行16小时,一个月30天,总共480小时。40亿除以480,每小时约833万 token,每分钟约14万 token。这个量级意味着系统在持续进行模型调用,而不是偶尔跑一下。

消耗大头通常来自这几个方面:

消耗来源占比估算说明
上下文注入40%-50%每轮请求都要带上系统提示、工具定义、相关文件片段、历史摘要
工具调用往返20%-30%每次工具调用后要把结果送回模型继续推理
任务规划与反思15%-20%复杂任务需要模型先规划再执行,执行后还要检查结果
重试与纠错5%-10%工具调用失败、输出格式不对时的重试开销

关键洞察是:token 消耗的大头不是用户输入,而是系统为了维持智能体运转而注入的上下文。这也是为什么 Harness 架构里上下文管理器的设计直接决定了成本。一个优化不好的上下文管理器,可能让 token 消耗翻三倍。

3. Markdown 为什么成了这套系统的核心载体

3.1 Markdown 不只是标记语言

在这个项目里,Markdown 承担的角色远超"写文档的格式"。它同时是知识存储格式、交互界面、以及智能体的工作产物。这个选择背后有很实际的考量。

首先,Markdown 是纯文本,天然适合版本控制和差异对比。智能体修改了一个文件,你可以直接用 git diff 看到改了哪几行,而不像二进制格式那样两眼一抹黑。其次,Markdown 的结构化程度刚好——它有标题层级、列表、表格、代码块,足够让智能体理解内容的组织方式,又不像 XML 或 JSON 那样对格式要求苛刻,模型生成时不容易出错。

更重要的是,Markdown 是当下大模型最熟悉的格式之一。训练数据里海量的技术文档、README、笔记都是 Markdown,模型对它的语法和语义有很好的直觉。你让模型输出 Markdown 表格,它基本不会搞错;你让它输出某种自定义格式,它可能每次都要你纠正。

3.2 Obsidian 作为可视化与检索层

Obsidian 在这个架构里扮演的是"人机接口"的角色。智能体在后台读写 Markdown 文件,人在 Obsidian 里浏览、编辑、建立双链。这种分工的好处是,人不需要盯着智能体的每一步操作,只需要在知识库层面做审核和补充。

Obsidian 的双链和图谱功能,恰好补上了智能体不擅长的一块——跨文档的语义关联。智能体可以生成大量内容,但让它主动发现"这篇笔记和三个月前那篇有关系"并不容易。而 Obsidian 的链接机制和插件生态,可以让人或者辅助脚本来做这件事。

实际使用中,有几个细节值得注意:

  • 文件命名规范要统一。智能体读写文件时依赖路径,如果命名混乱,它很容易找不到目标文件或者创建重复文件。建议用日期前缀加主题的方式,比如2024-06-15-harness-architecture.md。
  • frontmatter 要结构化。在 Markdown 文件头部用 YAML 写元数据,比如标签、状态、关联项目,这样智能体可以通过解析 frontmatter 快速筛选文件,而不需要读全文。
  • 避免过深的文件夹嵌套。Obsidian 的搜索和双链在扁平结构下效率更高,智能体遍历文件时也不容易迷路。

3.3 Markdown 表格与数据交换的坑

热词里出现了"markdown表格转换excel"和"markdown表格复制",这说明实际使用中表格处理是个高频痛点。智能体生成 Markdown 表格很顺手,但要把表格数据导入 Excel 或者从 Excel 导出,就没那么直接了。

我试过几种方案,比较靠谱的是用 Python 的pandas做中转:

import pandas as pd from io import StringIO markdown_table = """ | 模块 | 职责 | 优先级 | |------|------|--------| | 上下文管理 | 控制注入内容 | 高 | | 工具调度 | 执行外部调用 | 高 | | 状态存储 | 持久化进度 | 中 | """ df = pd.read_csv(StringIO(markdown_table), sep="|", skipinitialspace=True) df = df.dropna(axis=1, how="all") df.columns = df.columns.str.strip() df = df.iloc[1:] print(df)

反过来,从 DataFrame 生成 Markdown 表格用df.to_markdown()就行,但要注意中文字符宽度对齐问题,在等宽字体下可能错位,这是 Markdown 表格的固有限制,不是代码的问题。

提示:如果表格里有竖线字符|,必须转义成\|,否则会破坏表格结构。智能体生成内容时经常忽略这一点,需要在后处理里做校验。

4. Agent 开发中的上下文管理与 token 优化

4.1 上下文窗口不是越大越好

很多人有个误区,觉得模型支持的上下文窗口越大,就把所有东西都塞进去。实际恰恰相反,上下文越长,模型对中间部分的注意力越弱,而且成本线性增长。在每月40亿 token 的规模下,上下文策略的微小调整都会带来巨大的成本差异。

我的做法是分层管理上下文:

  • 固定层:系统提示、工具定义、核心规则。这部分每轮都要带,但内容要精简到极致。工具定义能用一行说清楚就不用三行。
  • 近期层:最近几轮对话的原文。通常保留3到5轮,超过的压缩成摘要。
  • 检索层:根据当前任务,从知识库里检索相关文件片段注入。这里的关键是检索精度,宁可少注入也不要注入无关内容。
  • 摘要层:更早的历史压缩成一段话,放在上下文末尾作为背景。

这个分层策略的核心逻辑是:把 token 预算花在模型当前最需要的信息上。固定层和近期层保证行为一致性,检索层提供任务相关的具体知识,摘要层维持长期连贯性。

4.2 工具定义的瘦身技巧

工具定义是上下文里的常驻开销。如果你注册了20个工具,每个工具的描述加参数说明占200 token,那一轮就是4000 token,乘以每天成千上万次调用,成本非常可观。

几个实用的瘦身方法:

  • 合并同类工具。比如"读文件"和"写文件"可以合并成一个"文件操作"工具,用参数区分动作。这样描述只需要写一份。
  • 参数说明用简写。不需要每个参数都写完整句子,用关键词加类型就够了。模型理解能力足够强,不需要手把手的自然语言解释。
  • 动态加载工具。不是所有任务都需要所有工具。可以根据任务类型,只注入相关工具的定义。比如纯文本处理任务就不需要注入数据库查询工具。

我实测下来,经过瘦身的工具定义可以从平均4000 token 降到1200 token 左右,降幅超过70%,而且模型调用工具的准确率没有明显下降。

4.3 缓存与去重

在长时间运行的系统里,很多请求的上下文前缀是相同的。比如系统提示加工具定义这部分,每轮都一样。如果模型服务支持前缀缓存,这部分可以大幅降低成本。

即使不支持缓存,也可以在应用层做去重。比如把相同的文件内容片段做哈希,如果这一轮和上一轮注入的是同一份内容,就不重复发送,而是用引用代替。这需要模型服务支持某种形式的引用机制,或者你在提示里说明"以下内容与上轮相同,不再重复"。

另一个容易忽略的点是工具调用结果的精简。有些工具返回大量数据,比如读取一个长文件,直接把全文塞回模型会消耗大量 token。更好的做法是在工具层做预处理,只返回模型真正需要的部分,比如文件摘要、匹配到的段落、或者结构化后的关键信息。

5. 九个月20万行代码的工程实践

5.1 代码量背后的真实构成

20万行代码听起来很多,但拆开看,真正的手写业务逻辑可能只占一半,剩下的包括:

  • 自动生成的代码:比如从接口定义生成的类型声明、从配置生成的工具注册代码。
  • 测试代码:单元测试、集成测试、端到端测试。在智能体系统里,测试尤其重要,因为行为不确定性高。
  • 文档与注释:Markdown 文档、代码注释、架构说明。
  • 配置与脚本:部署脚本、数据迁移脚本、运维工具。

所以"20万行"这个数字,更多是说明项目的复杂度和迭代强度,而不是说每一行都是精雕细琢的手写逻辑。理解这一点,对评估自己的工作进度很重要——不要被大数字吓到,也不要盲目追求代码行数。

5.2 一个人怎么组织开发节奏

独立开发最大的挑战不是技术,而是注意力的分配。九个月里,如果什么都想做,最后什么都做不完。我的经验是采用"主线加支线"的模式:

主线是核心功能的可用版本,必须保持每周都有可运行的进展。支线是优化和扩展,在不影响主线的前提下推进。具体到每天,我会把时间分成三块:上午处理需要深度思考的架构和算法问题,下午写实现和调试,晚上做测试和文档。

还有一个关键习惯是每天结束前让系统跑一遍完整流程。智能体系统的很多问题只在长时间运行后才暴露,比如内存泄漏、状态不一致、工具调用超时累积。每天跑一遍,问题当天发现当天修,不会拖成顽疾。

5.3 版本控制与回滚策略

在快速迭代中,代码经常改坏。如果没有好的版本控制习惯,很容易陷入"改一个bug引入两个新bug"的困境。

我的做法是:

  • 小步提交。每完成一个可独立验证的小功能就提交一次,提交信息写清楚改了什么、为什么改。
  • 分支策略简单化。一个人开发不需要复杂的分支模型,主分支保持可用,新功能开短生命周期分支,合并前跑通测试。
  • 关键节点打标签。比如"第一个可用的工具调用版本""上下文管理重构完成",方便出问题时回退到已知稳定状态。

注意:智能体系统里,配置文件的变更和代码变更同样重要。建议把配置也纳入版本控制,并且每次变更配置后记录变更原因和预期影响。

6. 实际运行中踩过的坑与排查思路

6.1 工具调用失败的重试陷阱

智能体调用工具失败是常态,网络抖动、参数格式错误、目标服务不可用都会导致失败。最初我的做法是简单重试三次,但很快发现这会导致重复执行副作用操作。比如"发送邮件"这个工具,第一次调用其实成功了但返回超时,重试就会发第二封。

正确的做法是区分幂等操作和非幂等操作。读文件、查询数据库是幂等的,可以放心重试。写文件、发请求、执行命令是非幂等的,重试前必须确认上一次是否真的失败了。实现上可以给每个工具调用分配唯一ID,服务端记录已执行的ID,重试时先查记录。

6.2 上下文膨胀导致的性能衰减

系统运行几个小时后,响应越来越慢,token 消耗越来越高。排查后发现是上下文管理器没有正确清理过期内容。早期版本里,所有工具调用结果都保留在历史里,越积越多。

修复方案是给上下文加生命周期标记。每个注入的内容块标记类型和过期时间,比如工具调用结果保留最近5轮,文件内容保留到任务结束,系统提示永久保留。清理线程定期扫描并移除过期内容。这个改动让长时间运行时的平均 token 消耗下降了约35%。

6.3 状态不一致的排查链路

有一次系统重启后,任务进度显示混乱,有些已完成的任务被重新执行。排查过程是这样的:

  1. 先看日志,发现重启时状态文件写入了一半,JSON 格式不完整。
  2. 检查写入逻辑,发现没有做原子写入,直接覆盖原文件,写入过程中断电或崩溃就会损坏。
  3. 修复方案是先写临时文件,再原子重命名。同时增加状态文件的校验和,加载时验证完整性,损坏则回退到上一个备份。
  4. 进一步加固:状态变更时先写日志,再更新状态文件,重启时可以通过日志重放恢复。

这个坑的教训是:任何持久化操作都要考虑中断场景。在智能体系统里,状态就是一切,状态丢了,之前的工作全白费。

6.4 模型输出格式不稳定的应对

即使你明确要求模型输出 JSON,它偶尔还是会加个解释性前缀或者用 Markdown 代码块包起来。在自动化流程里,这会导致解析失败。

我的应对策略是宽容解析加严格校验。解析时先用正则提取可能的 JSON 片段,尝试多种常见格式。解析成功后,再用 schema 严格校验字段类型和必填项。校验失败则把错误信息返回给模型,让它重新生成。这个重试循环通常一到两次就能得到正确格式。

另外,在提示里给出具体的输出示例比抽象描述有效得多。与其说"输出 JSON 格式",不如直接给一个完整的示例,模型模仿示例的准确率明显更高。

7. 这套架构还能怎么扩展

7.1 多智能体协作的可能性

当前架构是单智能体为主,但 Harness 的分层设计天然支持扩展成多智能体。比如可以有一个"规划者"负责拆解任务,多个"执行者"分别处理不同子任务,一个"审核者"检查结果质量。它们共享同一个上下文存储和工具层,通过消息队列通信。

这种扩展的挑战在于协调开销。智能体之间的通信本身就要消耗 token,如果任务拆得太细,协调成本可能超过收益。我的建议是从简单场景开始,比如只把"代码生成"和"代码审查"拆成两个角色,验证效果后再逐步扩展。

7.2 与本地知识库的深度集成

Obsidian 目前主要作为文件存储和可视化层。进一步可以做的是语义检索集成。把知识库里的文档做向量化索引,智能体在需要时通过语义搜索找到相关笔记,而不是靠文件名匹配。这需要在 Harness 里增加一个检索工具,并维护索引的更新。

索引更新的时机很关键。实时更新成本高,定时批量更新又有延迟。折中方案是增量更新加定期全量重建。文件修改时标记为脏,后台线程定期处理脏文件,每周做一次全量重建保证一致性。

7.3 成本监控与预算控制

每月40亿 token 的规模,必须有成本监控。我建议在 Harness 里内置一个计量模块,记录每次调用的 token 消耗、模型类型、任务归属。基于这些数据可以做:

  • 实时预算告警:当日消耗超过阈值时通知。
  • 按任务归因:知道哪些任务最烧钱,优先优化。
  • 模型路由:简单任务用便宜模型,复杂任务用强模型,在质量和成本间找平衡。

这个模块本身不复杂,但需要从第一天就设计进去,后补会很痛苦,因为要改动所有调用点。

7.4 从个人工具到可分享产品的距离

如果想把这套东西分享给别人用,还有不少工作要做。配置要外置化,不能硬编码路径和密钥。安装流程要简化,最好一条命令搞定。文档要补齐,特别是常见问题的排查指南。最重要的是错误处理要友好,个人使用时可以看日志排查,给别人用就得有清晰的错误提示和恢复建议。

不过话说回来,这套架构的核心价值在于它验证了一个可能性:一个人借助合适的工具和架构,确实可以构建出相当复杂的智能体系统。20万行代码和40亿 token 的背后,是九个月里无数次的调试、重构和取舍。如果你也在走类似的路,希望这些经验能帮你少踩几个坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询