讲真,期待 DeepSeek Harness 已经很久了。
虽然 DeepSeek 对 Codex 和 Claude Code 都做了深度兼容,但官方原生的肯定更好用。毕竟每家的大模型 API,入参、出参都有差异,原生适配才是最顺滑的。

Model + Harness = Agent,这已经是公认的公式了。
模型负责推理,Harness 负责模型之外的一切:工具调用、记忆管理、上下文控制、桌面集成、MCP 协议、Skills 体系等等。
国内的许多大模型厂商也都有自己的 Harness,比如阿里的 Qoder,月之暗面的 Kimi Code,智谱的 Zcode。大家都很期待 DeepSeek 自家的 Harness。即便 DeepSeek API 接下来可能大幅涨价,我相信它的性价比依然会很高。
继续狠狠“斩杀”就对了。😄
从目前泄露的架构信息来看,DSH 支持 Sub-agent、KV Cache 智能复用、跨会话记忆持久化等特性。当然了,这些也是一个成熟 Agent 的标配。我自己做的 PaiCLI 终端 Agent 也同样集成了这些能力。

代码已开源在 GitHub,Java/Go/Python/TypeScript 版本均已实现:https://github.com/itwanger/PaiCLI-Python
对大家来说,Agent 工程化的能力,正在成为大厂招人的硬性标准。
如果你是一个愿意相信努力、相信过程、相信一步一个脚印,并坚信自己能在 AI 时代分一杯羹的人,那么接下来的硬核内容,希望能给你带来一些启发。

(全文干货密集,系好安全带,我们出发~)
01、介绍一下自己的项目,整体架构和技术栈是什么?
“我主要做了两个开源实战项目:一个终端 Agent,一个 RAG 知识库。”

“终端 Agent 叫 PaiCLI,对标 Claude Code。它有三种工作模式:ReAct 模式用于实时交互的短任务,Plan-and-Execute 模式用于需要分步规划的复杂任务,Multi-Agent Team 模式用于多角色协作。”
“RAG 知识库叫‘派聪明’,基于 Elasticsearch 做混合检索。向量检索用 KNN,关键词检索用 BM25,两者结合做召回和排序。上层则用了自定义的 ReAct 来支持工具调用和多轮对话。”
“技术栈方面,PaiCLI 是 Java 21 + OkHttp + SQLite + JLine,非常原生,没有用 Spring AI 这类框架。派聪明则是 Spring Boot + Elasticsearch 8.x + Embedding/LLM API + Redis。”
PaiCLI 为什么设计三个模式?
“因为不同的任务,需要不同的执行模式来适配。”

“举个例子,用户问‘这个方法是干什么用的’,ReAct 模式就能轻松搞定。但如果用户的指令是‘帮我重构这个模块,改完测试一下’,ReAct 模式就有些吃力了。这时需要用 Plan-and-Execute 模式,先拆解任务、编排依赖,然后分步执行。如果是更复杂的场景,比如需要多 Agent 协作,就可以上 Team 模式。”
02、项目中 Agent 的完整流程是怎样的?
“以 ReAct 模式为例。”

“用户输入进来后,第一步是预处理,比如展开本地路径、解析图片引用。第二步是检索长期记忆,找出与当前输入相关的记忆,注入到系统提示词中。第三步是组装 Prompt,进行分层拼接——静态内容放在动态内容前面,这样可以充分利用大模型的Prompt Caching 来降低 API 的调用成本。”
“接着进入 ReAct 循环。每轮都要检查退出条件,比如 token 预算是否耗尽、是否有连续多次调用同一个工具、是否超过了迭代上限。通过检查后开始调用 LLM,如果返回了 Function Calling,就执行工具——多个无依赖的工具可以并行执行。然后再把工具的执行结果作为新消息追加到对话历史,发给 LLM 继续决策。”
“如果 LLM 没有返回工具调用,说明任务完成,就把输出格式化为 HTML 返回给用户。”
审批机制怎么设计的?

“像 write_file、execute_command 这类工具有风险,执行前需要经过 HITL(Human-in-the-Loop,人机协作)审批。路径检查器会判断文件路径是否在允许的范围内,命令检查器会判断命令是否安全。审批策略分三档:auto(自动通过)、suggest(建议确认)、never(必须手动确认)。读操作则不需要审批,直接执行。”
03、项目过程中针对 Agent 做过哪些学习成长?具体怎么调优?
“印象最深的优化有两点。”
“第一,通过 Prompt Caching,也就是缓存命中来提高 Token 的利用率,最大程度降低成本。”

“PaiCLI 的 Prompt 共有 9 层,前四层是静态的,比如身份定义、人格定义、模式指令、审批策略。这些内容在整个会话期间都不变,放在提示词的最前面。因为 Prompt Caching 是按最长公共前缀命中的,前缀越稳定,缓存命中率就越高。”
“第二是上下文管理。拿短期记忆来说,当上下文达到阈值的 80% 时,我们会启动一个新的 SubAgent,进行摘要压缩。”
压缩后,再将压缩内容与最近三轮的完整信息一同发送给 LLM。为什么这么做?因为 LLM 的窗口都有上限,DeepSeek V4 是 1M,已经很大了,但一个长任务依然可能把它撑爆。摘要压缩是目前主流 Agent 普遍采用的解决方案。

当然,为了避免摘要压缩丢失关键信息,我们会将一些事实性信息沉淀到长期记忆中。
长期记忆目前使用关键词匹配来召回最相关的信息,和摘要、最近 3 轮的完整信息一起组装到 Prompt,重新发给 LLM。
04、Agent 优化有哪些常见手段?
“第一,简单任务用小尺寸模型,复杂任务上大尺寸模型。”
比如意图识别,判断用户是要读文件还是执行命令,这种分类任务用小模型就够了,速度快、成本低。只有像任务规划和代码生成这类复杂推理场景,才需要大尺寸模型。平时我用 Claude Code 也是这个思路,日常用 Sonnet,复杂任务就切到 Opus。

“第二,工具并行。”
Agent 经常需要同时调用多个工具,比如同时读取 3 个文件。如果这些操作没有前后依赖关系,就可以并行执行。
“第三,就是我前面提到的 Prompt Caching,这也是投入产出比最高的。”
为什么?
“因为 LLM 已经帮我们实现了缓存命中机制,我们只需要在调用 LLM 之前,把不变的内容放在 Prompt 最前面就行了,完全不用改架构。”
拿 DeepSeek V4 来说,缓存命中时,百万 Token 的输入价格只要 0.02 元,而没命中则需要 1 元,差了整整 50 倍。

05、基于项目设计一个场景,如果遇到类似问题应该怎么解决?
“比如,用户让 Agent 重构一个涉及多个文件的模块,并且改完后测试要全部通过。”
“这种场景下,就不能只用 ReAct 了。ReAct 的流程是观察、思考、行动。假如它改了文件 A,中间跑了一次测试,结果挂了。但这个‘挂’不是因为 A 改错了,而是因为 B、C、D 还没改。Agent 看到报错,可能会尝试去修复一个根本不该修的问题,结果越改越乱。”

“这种任务必须切换到 Plan 模式。规划器会首先读取所有相关文件,理解模块内部的依赖关系,然后生成一个任务图。”
“这个任务图是一个 DAG(有向无环图)。假设文件 A 定义了接口,B 和 C 分别实现了该接口,D 则依赖于 B 的输出。规划器生成的图大概长这样:
先读取所有文件(READ 类型,无依赖,可并行) → 修改 A 的接口定义 → 修改 B 和 C 的实现(互不依赖,可并行) → 修改 D(依赖 B) → 运行测试。
执行时按拓扑排序进行,每一轮找出所有入度为 0 的节点——也就是没有前置依赖的任务——将它们并行执行。这批任务完成后更新依赖图,再找出下一批可以并行的任务。”

改到一半发现计划有问题怎么办?
“比如,改到第 4 个文件时才发现,A 的接口变更还影响了一个之前漏掉的文件 E。”
“这时分两种情况处理。”
如果进度不到一半,说明规划阶段对模块的理解可能有较大偏差,这时应该带着错误信息,进行整体的重规划。
如果进度已经过半,已完成的工作量很大,推倒重来的成本太高。更好的做法是保留已完成的成果,追加 E 的修改任务,调整后续的依赖关系,然后继续执行。

06、语义检索是如何实现的?
“派聪明用的是混合检索。”

“先说向量检索。用户的查询文本会通过 text-embedding-v4 模型转成一个 2048 维的向量,然后在 Elasticsearch 里进行 KNN 搜索。为了保证召回率,我们的召回窗口设得比较大,是最终返回数量的 30 倍。比如,如果最终需要 5 条结果,KNN 会先召回 150 条候选。”
“在这 150 条候选集上,再用 BM25 进行二阶段重排序。对文本内容进行关键词匹配打分,KNN 的权重设为 0.2,BM25 的权重设为 1.0。这意味着,在最终的排序中,关键词精准匹配占据主导地位。”
“这样做的好处很明显:向量检索负责语义层面的广泛召回,不会漏掉表述不同但意思相近的内容;而 BM25 负责精确排序,确保关键词完全匹配的结果排在最前面。”
为什么用 BM25 做重排序?

“因为向量检索在匹配专有名词和缩写时不够精确。比如用户搜索‘MCP 协议’,向量可能会把‘RPC 协议’也召回来,因为语义上它们的确很接近。但用户想要的就是 MCP。BM25 能精准地将关键词完全匹配的结果排到最前面,这正好弥补了向量检索在精确匹配上的短板。”
07、向量数据库在项目中具体承担什么作用?
“派聪明没有单独部署向量数据库,Elasticsearch 同时承担了向量存储和全文检索的任务。”
“在 ES 里,每条文档有两个核心字段:vector 字段存储 2048 维的 Embedding 向量,ES 会使用 HNSW(Hierarchical Navigable Small World,分层可导航小世界图)算法为其构建索引,用于近似最近邻搜索。textContent 字段则存储原始文本,走 ES 自带的倒排索引,用于 BM25 关键词检索。”

“文档入库前会进行分块,分块策略直接决定了最终的检索质量。我们的分块大小是 512 个字符,相邻块之间有 100 个字符的重叠。这样做是因为一个完整的概念可能恰好被截断在两个块的边界上,重叠设计能确保边界处的语义不被切断。”
“分块逻辑是三级处理:先按双换行符切分段落,每个段落独立处理。如果某个段落超过 512 字符,就进一步按句子切分。切分完毕后,如果某一块不足 100 字符,就把它和前一块合并——太碎的块 Embedding 质量差,检索出来意义也不大。”

“此外,文档还维护了一层父子结构。子块就是这 512 字符的检索单元,用于精确匹配。父块则可以很大,最大到 1MB,差不多就是整篇文档的全文。当检索命中某个子块后,可以回溯到它的父块,获取更完整的上下文,让 LLM 在回答前能看到更多相关信息。对于大文件,我们会采用流式解析,防止一次性加载撑爆内存。”
什么情况下该换专门的向量数据库?
“主要看数据规模和检索 QPS。派聪明的知识库规模在几万到几十万文档这个级别,ES 的 HNSW 完全能够胜任。如果只是为了用而多部署一套 Milvus 或 Qdrant,反而要多维护一套集群、多做一份数据同步,收益不大。”

“但如果数据量达到了千万级,或者检索 QPS 要求非常高,ES 的 KNN 就可能成为瓶颈。HNSW 的内存开销会随着数据量线性增长,一个 2048 维的向量大约占 8KB,一千万条就是接近 80GB 的纯向量数据。到了这个量级,专用向量数据库的分布式系统检索和量化压缩能力,就有了用武之地。”
08、为什么选择 langchain、langgraph 这些框架?底层流程了解吗?
“PaiCLI 没有使用任何 AI 框架,全部是自研的。HTTP 层直接用 OkHttp 直连各家大模型的 API。”

为什么不用框架?
因为各家大模型的 API 规范不尽相同。例如,GLM 的 Prompt Caching 用的是 glm-prompt-cache 模式,DeepSeek 是 automatic-prefix-cache,而 Kimi 则是 moonshot-context-cache。
错误码的定义、重试策略、流式输出的格式也各有差异。框架通常会把底层的这些差异抹平,提供统一接口。但我的需求恰恰是,针对各家模型做精细化控制,比如最大化缓存命中率、准确计费、处理流式中断恢复等。在这些方面,用框架反而会成为一种限制。
不过,LangGraph 的底层原理我是了解的。
它的核心是围绕一个状态图(State Graph)展开的,节点是处理函数,边是条件路由。每个节点接收当前状态、执行操作、返回更新后的状态。其 Checkpoint 机制会保存每一步的状态快照,以此来支持断点恢复和回放。
什么场景下你会选框架?

“如果是为了快速验证一个想法,用框架跑通原型会比较快。但如果项目要上生产环境,要做多模型适配和精细化控制,自研就更为合适。更何况现在 AI Coding 的能力已经非常强了,自研的开发成本其实已经很低了。”
09、模型输出结果如何控制规则?
“Prompt 约束是基础。”
在系统提示词中,我们会定义好行为边界:哪些操作可以做、哪些禁止、输出格式怎样组织。这些规则在每一轮对话中都存在,模型一直能看到。但 Prompt 毕竟是自然语言,模型不一定会严格遵守,尤其是在上下文很长、注意力容易分散的时候。
“工具 Schema 则构成了第一层硬约束。每个工具在注册时都会附带一个 JSON Schema,明确定义了参数类型、必填项和取值范围。模型生成的工具调用参数,必须先通过 Schema 校验;如果不合规,就拒绝执行,并将错误消息返回给模型,让它重新生成。比如,write_file 工具的 file_path 必须是字符串、content 不能为空,这些硬性约束模型是绕不过去的。”

“对于 MCP 工具的 Schema,还需要额外做一步清洗。MCP Server 返回的 Schema 常常带有复杂嵌套、$ref 引用和 anyOf 分支。我们需要将这些展开、合并、拍平,把 anyOf 转化成最常见的类型,确保模型看到的是一个干净、易于理解的参数定义。”

“审批是最后一道防线。”
即使工具调用的参数完全合规,写操作在执行前,仍然需要过 HITL 审批这一关。路径检查器会拦截项目根目录以外的文件操作,命令检查器会拦截预设的危险命令。审批策略同样分三档:auto 自动通过、suggest 建议确认、never 必须手动确认,可以按工具的风险等级灵活配置。
审批被用户拒绝后 Agent 怎么反应?

“拒绝的结果会作为 tool_result 返回给模型,消息里会明确告知:‘这个操作被用户拒绝了’。”
模型看到这条信息后,会自行决定下一步行动。它可能换一种方式来达成目标,例如,如果用户拒绝了直接覆盖文件的操作,模型可能会改为先备份,再进行写入。
10、记忆模块是怎么设计的?
“PaiCLI 的记忆共分为三层。”

“短期记忆是会话级别的,只在当前会话中有效。底层实现是一个 LinkedHashMap,先进来的排在前面。它的 token 预算是上下文窗口的 45%,一旦超过,就从最旧的条目开始淘汰。”
“长期记忆是持久化的,可以跨会话存活。它存储在 SQLite 里,按类型和作用域进行分类。在每轮调用 LLM 之前,系统会检索与当前输入相关的长期记忆条目,注入到系统提示词中。新存入的条目会进行去重检测,避免重复存储。”
“项目记忆是开发者提前写好的规则文件,类似于 Claude Code 的 CLAUDE.md。它们会按优先级加载:PAI.md → .paicli/PAI.md → PAI.local.md → .paicli/PAI.local.md,后面的配置会覆盖前面的。”
11、多轮、多会话场景下 memory 如何处理?
“在多轮对话场景中,对话会越来越长,上下文窗口迟早会被塞满。PaiCLI 的策略是:保留最近 3 轮的完整对话不动,而更早的历史记录,则由 LLM 生成摘要来替换。摘要会重点保留用户的关键诉求、已完成的操作、达成的共识以及待办事项。”
“在多轮场景中,还有一个容易忽略的点,就是短期记忆的 token 预算。预算的上限是上下文窗口的 45%,超了就从最旧的条目开始淘汰。但淘汰不是直接删除,被淘汰的条目会先用 LLM 做一次压缩摘要,再用来替换,这样可以确保关键信息不会完全丢失。”

“在多会话场景中,则主要依赖长期记忆的持久化能力。PaiCLI 的长期记忆存在 SQLite 里,新会话启动时,它会从磁盘加载,Agent 会记得你之前存过的偏好和规则。存入新条目时,会进行去重,确保同一条偏好不会被存两份。”
“需要注意的是,/clear 命令只会清除短期记忆和对话历史,长期记忆不会受到影响。因为用户说‘清一下上下文’,通常只是想换个话题重新开始,而不是想让 Agent 忘掉他所有的偏好习惯。”

12、如果系统出现异常,整体的容错和异常处理机制怎么设计?
“Agent 系统出问题是常态:调 LLM 的 API 可能报错,工具执行可能失败,Agent 自身也可能跑进死循环。”

“在 LLM 层,我们的策略是重试 3 次,采用指数退避加上随机抖动。退避基数是 500 毫秒,上限为 30 秒。”
但并非所有错误都值得重试。我们只对特定错误码进行重试,比如 429(限流)、500/502/503/504(服务端异常)和 408(超时)。像 400(参数错误)、401(认证失败)这类错误,重试是毫无意义的,因为再试一次结果也一样。另外,如果响应头里带有 Retry-After 字段,我们会优先使用服务端建议的等待时间。
“在工具层,单个工具执行失败不会中断整个流程。失败的工具会返回一条错误消息,作为 tool_result 追加到对话历史中。LLM 看到这条错误信息后,可以自行决定是换一种方式重试,还是直接跳过。对于并行执行的多个工具,总超时时间设为 90 秒。”
“在 Agent 层,主要是防止死循环。一旦检测到连续进行相同的工具调用,就会强制退出。在 Plan 模式下,如果任务执行到中途失败了,会根据进度来决定策略:进度不到一半,就带着错误信息进行整体重规划;进度过半,则保留已完成的部分,继续向前推进。”
13、再给一个上下文处理相关的场景题,如何优化 context 管理?
“假设一个窗口大小为 200K 的 Agent,在处理一个大型项目时,对话进行到第 30 轮,上下文即将溢出。”
“到第 30 轮,对话历史加上工具调用的结果,总的 token 数很可能已经逼近了压缩触发阈值。”
对于 200K 的窗口,这个触发阈值大约是 167K。它的算法是:窗口大小减去两个预留量。min(20K, 窗口/4) 是预留给压缩后摘要的空间,min(13K, 窗口/8) 是预留给后续对话的空间,两个加起来大约 33K。这么预留的目的,是防止刚压缩完,下一秒又溢出了。

“触发压缩后,第一步是扫描整个对话历史,找到所有 user 消息的位置索引。”
第二步,从最后一条 user 消息开始,往前数 3 条,这 3 轮完整的对话将被标记为‘保留区’。保留区内的所有消息——无论 user、assistant、tool_call 还是 tool_result——都原样不动。
第三步,将保留区之前的所有消息,发给 LLM 生成一份摘要,然后用这份结构化摘要替换掉之前那些冗长的原始消息。

“这份摘要必须包含四类关键信息:用户的核心诉求、已完成的操作、双方达成的共识,以及待办的事项。”
这么说吧。
Harness 将成为未来五年的主旋律。除了模型,各大厂都在争先恐后地构建自己的 Harness。
当然,换个名字,叫 Agent 也行。
其实,所有的 Agent,本质上都是在做 Harness——让模型更好用,更贴合业务。
而 Harness 最核心的部分,就是上下文管理、Memory 管理、多 Agent 协作,以及提示词优化。
剩下的,大都是界面交互层面的工作了。
可以预见,拥有 Harness 实战经验的人才,未来一定会非常抢手。