ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

一个人九个月20万行代码:Harness架构如何让AI Agent稳定运行

一个人九个月20万行代码:Harness架构如何让AI Agent稳定运行 1. 这个项目到底在造什么先把标题拆开看一个人、九个月、20万行代码、每月40亿 token最终产物是一款 Harness 架构应用。这几个数字里最容易被误读的是“20万行代码”——很多人第一反应是“这人是不是在堆屎山”。但如果你真正做过 Agent 类产品就会明白在一个以 Harness 为核心的应用里20万行并不夸张因为它的本质不是“一个功能”而是一套让模型稳定干活的运行底座。Harness 这个词在 Agent 语境下指的是包裹在模型外面的那一层“挽具”。模型本身是野马能跑但方向不定Harness 负责给它套上缰绳、规划路线、提供工具、校验结果、失败重试。你平时用的 Claude Code、各种本地 Agent 框架本质上都是 Harness 的不同实现形态。这个项目要做的就是一个人从零搭出一套完整的 Harness 架构应用并且让它能长期、稳定、低成本地跑下去。它解决的核心问题很具体如何让一个 AI Agent 在真实任务里不跑偏、不中断、不烧钱烧到失控。适合谁来参考三类人最该看一是正在做 Agent 开发、被“执行到一半报错终止”折磨的工程师二是想把 Obsidian、Markdown 笔记体系和 AI 能力打通的知识工作者三是想理解 Harness 架构到底怎么落地、而不是停留在概念层面的产品和技术负责人。下面我按实际搭建顺序把这套东西拆开讲。2. 整体架构设计与思路拆解2.1 为什么选 Harness 而不是直接调 API很多人做 Agent 的第一版都是写个 while 循环把用户输入拼进 prompt调模型拿到结果判断要不要调工具再拼回去。这个方案在 demo 阶段没问题但一上真实任务就崩。原因有三个上下文会无限膨胀、工具调用失败没有兜底、模型输出格式不稳定。Harness 架构的价值就在于把这三点全部工程化。它把“模型调用”降级成一个可替换的组件把真正复杂的逻辑放在外层任务如何拆解、上下文如何裁剪、工具如何注册与调度、失败如何重试、结果如何校验。这个项目选择 Harness 路线本质上是承认了一个现实——模型能力会变但 Harness 的稳定性必须自己掌控。我自己的经验是直接调 API 的 Agent 在单轮任务里能跑通但连续跑两小时必然出问题。Harness 的意义就是让“连续跑两小时”变成常态而不是奇迹。这也是为什么标题里强调“九个月”和“20万行”——时间几乎全花在 Harness 的健壮性上而不是模型本身。2.2 核心模块划分与职责边界一套完整的 Harness 应用我习惯拆成五层这个项目的结构基本也符合这个划分层级职责关键设计点接入层接收任务、管理会话会话隔离、超时控制编排层任务拆解、步骤调度状态机、可中断恢复执行层调用模型与工具重试、降级、限流记忆层上下文与长期存储裁剪策略、检索增强观测层日志、指标、回放全链路追踪这五层里最容易被低估的是观测层。一个人做项目没有团队帮你盯线上出问题时唯一的依靠就是日志和回放。这个项目每月烧 40 亿 token如果没有细粒度的 token 消耗追踪根本不知道钱花在哪。所以观测层不是“锦上添花”而是“活下去的前提”。2.3 技术选型背后的取舍逻辑选型上这个项目明显偏向“可控”而非“省事”。Markdown 作为中间表示格式Obsidian 作为知识库载体Claude Code 作为参考实现和部分能力来源这套组合的逻辑是所有数据都以纯文本形式落地任何环节都可被人直接阅读和修改。为什么不用数据库存中间状态因为 Agent 的中间状态是调试的重灾区存成 Markdown 文件你可以直接用 Obsidian 打开看甚至手动改一行再继续跑。这种“人可介入”的能力在单人开发场景下价值极高。我踩过的坑就是早期把状态存进 SQLite结果调试时得写查询语句才能看清 Agent 到底在想什么效率极低。换成 Markdown 文件后调试速度至少快三倍。3. 核心细节解析与实操要点3.1 Markdown 作为 Agent 的通用语言这个项目里 Markdown 不只是文档格式而是 Agent 之间、Agent 与人之间的通信协议。任务描述用 Markdown工具返回用 Markdown记忆存储用 Markdown最终产出还是 Markdown。这么做的好处是模型对 Markdown 的理解极其稳定几乎不会出现格式解析失败。但 Markdown 有几个坑必须提前处理。第一是换行不同解析器对单换行和双换行的处理不一致Agent 生成的内容经常因为换行问题导致结构错乱。我的做法是统一强制双换行分段并在 Harness 里加一层规范化处理。第二是表格模型生成的 Markdown 表格经常列数对不齐需要写校验逻辑列数不符就触发重生成。第三是数学公式如果任务涉及公式必须明确约定用$...$还是$$...$$否则渲染出来一团糟。提示在 Harness 的 prompt 里把 Markdown 规范写成硬性约束比事后修复便宜得多。我实测下来前置约束能让格式错误率下降八成以上。3.2 上下文管理与 token 消耗控制每月 40 亿 token平均下来每天一亿多。这个量级如果不做上下文管理成本会失控。核心策略是分层记忆短期上下文只保留当前任务相关的最近若干轮长期记忆压缩成摘要存进 Obsidian需要时再检索回来。具体做法是给每个任务维护一个“工作区”目录里面分context.md、memory.md、output.md。context.md只放当前活跃内容超过阈值就触发摘要压缩把旧内容合并进memory.md。这个阈值我建议按 token 数设而不是按轮数因为不同轮次长度差异太大。实测把阈值设在模型上下文窗口的 60% 左右比较稳留出足够空间给工具返回和模型输出。另一个省 token 的关键是工具返回的裁剪。很多工具比如读文件、搜索返回的内容远超实际需要Harness 必须在工具层做截断和摘要而不是把原始结果全塞回上下文。这一块做得好token 消耗能直接砍掉一半。3.3 工具注册与失败重试机制Harness 的工具系统必须支持动态注册因为 Agent 的任务类型是开放的。这个项目里工具以插件形式存在每个工具声明自己的名称、参数 schema、超时时间和重试策略。注册时做参数校验调用时做超时控制失败时按策略重试。重试策略不能一刀切。读文件失败可以立即重试网络类工具要退避重试而模型调用失败往往需要换 prompt 或降级模型。我整理了一张常见失败与处理对照表失败类型典型表现处理策略格式解析失败输出不是合法 JSON/Markdown追加格式提醒后重试一次工具超时执行超过设定时限退避重试最多三次上下文超限提示 token 超出触发压缩后重试模型拒答返回空或拒绝换表述重试或降级循环调用反复调同一工具检测重复后强制中断注意重试一定要有上限和熔断否则一个坏任务能把你的 token 额度烧穿。我给每个任务设了总重试预算用完就标记失败并落盘绝不无限重试。4. 实操过程与核心环节实现4.1 从零搭建 Harness 骨架第一步是搭最小可运行骨架一个主循环能接收任务、调模型、解析输出、执行工具、写回结果。这个骨架不需要任何花哨功能先让它能跑通一个“读文件并总结”的任务。代码结构上我建议把模型调用、工具执行、上下文管理分成三个独立模块主循环只负责编排。# 骨架示意非完整实现 class Harness: def __init__(self, model, tools, memory): self.model model self.tools tools self.memory memory def run(self, task): self.memory.init(task) while not self.memory.done(): ctx self.memory.build_context() resp self.model.invoke(ctx) action self.parse(resp) if action.type tool: result self.execute_with_retry(action) self.memory.append(result) else: self.memory.finish(action.content) return self.memory.output()这个骨架跑通后再逐步加东西加重试、加压缩、加观测、加插件。顺序很重要先保证能跑再保证跑得稳最后才是跑得省。很多人一上来就追求架构完美结果骨架都没跑通就卡在细节里。4.2 接入 Obsidian 作为知识底座Obsidian 在这个项目里承担两个角色一是长期记忆的存储二是人工审阅和干预的界面。Harness 把压缩后的记忆写成 Markdown 文件放进 Obsidian 库你可以随时打开看 Agent 记住了什么、忘了什么。接入方式很直接Harness 通过文件系统读写 Obsidian 库目录不需要任何插件。关键是目录结构要设计好我推荐按任务类型/日期/任务ID分层每个任务目录里放context.md、memory.md、output.md、log.md。这样在 Obsidian 里用文件树就能快速定位任何一次执行。如果你想把 Zotero 的笔记导入 Obsidian 一起用思路是先把 Zotero 导出为 Markdown再按同样的目录规范放进库。这样 Agent 检索记忆时能同时覆盖你的个人笔记和任务记忆效果比纯任务记忆好很多。4.3 参考 Claude Code 的交互模式Claude Code 的交互设计值得借鉴的地方在于它把“人在回路”做得很自然。Agent 执行到关键节点会停下来问而不是一路跑到底。这个项目里我也加了类似的确认点涉及写文件、删文件、调用外部工具时Harness 会先输出计划等确认后再执行。实现上就是在主循环里加一个“确认门”。当 action 被判定为高风险时不直接执行而是把计划写进pending.md然后暂停。你审阅后手动改一个标志位Harness 下次启动时继续。这个机制在单人开发里特别有用因为它给了你一个“随时刹车”的能力避免 Agent 半夜跑飞。4.4 观测与成本追踪的落地每月 40 亿 token必须精确知道花在哪。我在 Harness 里给每次模型调用都记一条日志时间、任务 ID、输入 token、输出 token、模型名、耗时。这些日志汇总后能直接算出每个任务类型的平均成本。# 日志格式示意 2024-01-01T10:00:00 taskabc123 in1200 out800 modelxxx cost0.012有了这个你就能做成本优化哪个任务类型最烧钱、哪个 prompt 最啰嗦、哪次重试最浪费一目了然。我实测下来光是把最烧钱的前三个任务类型优化一遍月度 token 消耗就能降三成。观测不是目的观测是为了优化。5. 常见问题与排查技巧实录5.1 Agent 执行中途报错终止怎么办“agent execution terminated due to error”是这类项目最常见的报错。排查顺序我固定为四步先看日志最后一条工具调用是什么再看上下文是否超限然后看模型返回是否为空最后看是否有未捕获异常。大部分情况下问题出在工具返回格式不符合预期导致解析失败后没有兜底。解决办法是在工具层加 schema 校验校验不过就返回结构化错误让模型自己决定下一步而不是直接抛异常终止整个任务。这个改动能让任务成功率提升非常明显。5.2 插件加载失败与依赖问题“harness failed to load plugins”通常有两个原因插件声明的依赖没装或者插件入口文件路径不对。排查时先单独跑插件加载逻辑把异常打出来不要被 Harness 的封装吞掉。我的经验是给插件加载加一个“隔离模式”单个插件加载失败不影响其他插件只记录警告。这样即使某个插件有问题Harness 主体还能跑。另外插件版本要锁定避免某次更新后接口不兼容导致全线崩溃。5.3 上下文膨胀与循环调用Agent 反复调同一个工具、或者上下文越滚越大是 Harness 必须处理的两大顽疾。前者用重复检测解决记录最近若干次工具调用发现高度重复就强制中断并让模型换策略。后者用压缩解决上下文超过阈值就触发摘要把旧内容压成一段话。提示压缩时一定要保留“当前任务目标”和“已确认的关键结论”这两样丢了Agent 就会失忆重来。我一般把这两块单独标记压缩时强制保留。5.4 常见问题速查表现象可能原因快速处理任务跑一半停住确认门未通过检查 pending.mdtoken 消耗异常高上下文未压缩调低压缩阈值输出格式错乱Markdown 规范未约束前置格式约束工具调用失败参数 schema 不符加校验与错误返回记忆丢失压缩过度保留目标与结论成本失控重试无上限设重试预算与熔断6. 一个人做这类项目的真实体会九个月、20万行、每月40亿 token这些数字背后其实是一个很朴素的道理Agent 产品的难点从来不在模型而在模型外面那层 Harness。模型能力再强没有稳定的编排、记忆、工具和观测它就是个玩具。反过来Harness 做扎实了哪怕模型换一代你的应用也能平滑迁移。我个人在实际操作中的体会是单人做这种项目最大的敌人不是技术难度而是“想一次做完美”。我早期花了大量时间设计完美架构结果三个月没跑通一个完整任务。后来改成“先跑通再优化”进度才起来。所以如果你也在做类似的东西我的建议是先把最小骨架跑起来哪怕它很丑、很慢、很费 token跑通之后再一项一项优化。能跑起来的丑东西永远比跑不起来的完美设计有价值。最后再分享一个小技巧把每次失败的任务都存下来定期回看。你会发现失败模式高度重复修掉前三个高频失败模式任务成功率就能翻倍。这比盲目加功能有用得多。
返回列表