找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4262

积分

0

好友

560

主题
发表于 1 小时前 | 查看: 4| 回复: 0

讲真,期待 DeepSeek Harness 已经很久了。

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

DeepSeek员工Tianyi Cui在社交媒体发布的Agent Harness开源项目内测邀请信息

Model + Harness = Agent,这已经是公认的公式了。

模型负责推理,Harness 负责模型之外的一切:工具调用、记忆管理、上下文控制、桌面集成、MCP 协议、Skills 体系等等。

国内的许多大模型厂商也都有自己的 Harness,比如阿里的 Qoder,月之暗面的 Kimi Code,智谱的 Zcode。大家都很期待 DeepSeek 自家的 Harness。即便 DeepSeek API 接下来可能大幅涨价,我相信它的性价比依然会很高。

继续狠狠“斩杀”就对了。😄

从目前泄露的架构信息来看,DSH 支持 Sub-agent、KV Cache 智能复用、跨会话记忆持久化等特性。当然了,这些也是一个成熟 Agent 的标配。我自己做的 PaiCLI 终端 Agent 也同样集成了这些能力。

PaiCLI终端界面截图,展示了Agent工作流程和工具调用状态

代码已开源在 GitHub,Java/Go/Python/TypeScript 版本均已实现:https://github.com/itwanger/PaiCLI-Python

对大家来说,Agent 工程化的能力,正在成为大厂招人的硬性标准

如果你是一个愿意相信努力、相信过程、相信一步一个脚印,并坚信自己能在 AI 时代分一杯羹的人,那么接下来的硬核内容,希望能给你带来一些启发。

Harness Agent面试题列表,涵盖Agent架构、规划与执行、记忆与上下文三大类

(全文干货密集,系好安全带,我们出发~)

01、介绍一下自己的项目,整体架构和技术栈是什么?

“我主要做了两个开源实战项目:一个终端 Agent,一个 RAG 知识库。”

PaiCLI与派聪明架构总览图,展示终端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、Plan、Team三种Agent路径适用场景对比图

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

02、项目中 Agent 的完整流程是怎样的?

“以 ReAct 模式为例。”

ReAct完整执行流程图,核心思想是Thought-Action-Observation循环

“用户输入进来后,第一步是预处理,比如展开本地路径、解析图片引用。第二步是检索长期记忆,找出与当前输入相关的记忆,注入到系统提示词中。第三步是组装 Prompt,进行分层拼接——静态内容放在动态内容前面,这样可以充分利用大模型的Prompt Caching 来降低 API 的调用成本。”

“接着进入 ReAct 循环。每轮都要检查退出条件,比如 token 预算是否耗尽、是否有连续多次调用同一个工具、是否超过了迭代上限。通过检查后开始调用 LLM,如果返回了 Function Calling,就执行工具——多个无依赖的工具可以并行执行。然后再把工具的执行结果作为新消息追加到对话历史,发给 LLM 继续决策。”

“如果 LLM 没有返回工具调用,说明任务完成,就把输出格式化为 HTML 返回给用户。”

审批机制怎么设计的?

写操作审批流程决策树,展示了从工具调用请求到执行结果的完整安全校验流程

“像 write_fileexecute_command 这类工具有风险,执行前需要经过 HITL(Human-in-the-Loop,人机协作)审批。路径检查器会判断文件路径是否在允许的范围内,命令检查器会判断命令是否安全。审批策略分三档:auto(自动通过)、suggest(建议确认)、never(必须手动确认)。读操作则不需要审批,直接执行。”

03、项目过程中针对 Agent 做过哪些学习成长?具体怎么调优?

“印象最深的优化有两点。”

“第一,通过 Prompt Caching,也就是缓存命中来提高 Token 的利用率,最大程度降低成本。”

Prompt Caching命中策略图,强调稳定内容前置以提高缓存复用率

“PaiCLI 的 Prompt 共有 9 层,前四层是静态的,比如身份定义、人格定义、模式指令、审批策略。这些内容在整个会话期间都不变,放在提示词的最前面。因为 Prompt Caching 是按最长公共前缀命中的,前缀越稳定,缓存命中率就越高。”

“第二是上下文管理。拿短期记忆来说,当上下文达到阈值的 80% 时,我们会启动一个新的 SubAgent,进行摘要压缩。”

压缩后,再将压缩内容与最近三轮的完整信息一同发送给 LLM。为什么这么做?因为 LLM 的窗口都有上限,DeepSeek V4 是 1M,已经很大了,但一个长任务依然可能把它撑爆。摘要压缩是目前主流 Agent 普遍采用的解决方案。

Agent上下文与记忆管理图,分为短期记忆、摘要压缩、长期记忆三层

当然,为了避免摘要压缩丢失关键信息,我们会将一些事实性信息沉淀到长期记忆中。

长期记忆目前使用关键词匹配来召回最相关的信息,和摘要、最近 3 轮的完整信息一起组装到 Prompt,重新发给 LLM。

04、Agent 优化有哪些常见手段?

“第一,简单任务用小尺寸模型,复杂任务上大尺寸模型。”

比如意图识别,判断用户是要读文件还是执行命令,这种分类任务用小模型就够了,速度快、成本低。只有像任务规划和代码生成这类复杂推理场景,才需要大尺寸模型。平时我用 Claude Code 也是这个思路,日常用 Sonnet,复杂任务就切到 Opus。

Agent模型分级策略图,按任务难度匹配合适模型以兼顾效果、速度和成本

“第二,工具并行。”

Agent 经常需要同时调用多个工具,比如同时读取 3 个文件。如果这些操作没有前后依赖关系,就可以并行执行。

“第三,就是我前面提到的 Prompt Caching,这也是投入产出比最高的。”

为什么?

“因为 LLM 已经帮我们实现了缓存命中机制,我们只需要在调用 LLM 之前,把不变的内容放在 Prompt 最前面就行了,完全不用改架构。”

拿 DeepSeek V4 来说,缓存命中时,百万 Token 的输入价格只要 0.02 元,而没命中则需要 1 元,差了整整 50 倍。

DeepSeek V4 API价格表格,展示缓存命中与未命中的输入输出价格差异

05、基于项目设计一个场景,如果遇到类似问题应该怎么解决?

“比如,用户让 Agent 重构一个涉及多个文件的模块,并且改完后测试要全部通过。”

“这种场景下,就不能只用 ReAct 了。ReAct 的流程是观察、思考、行动。假如它改了文件 A,中间跑了一次测试,结果挂了。但这个‘挂’不是因为 A 改错了,而是因为 B、C、D 还没改。Agent 看到报错,可能会尝试去修复一个根本不该修的问题,结果越改越乱。”

ReAct与Plan模式对比图,展示多文件任务执行差异

“这种任务必须切换到 Plan 模式。规划器会首先读取所有相关文件,理解模块内部的依赖关系,然后生成一个任务图。”

“这个任务图是一个 DAG(有向无环图)。假设文件 A 定义了接口,B 和 C 分别实现了该接口,D 则依赖于 B 的输出。规划器生成的图大概长这样:

先读取所有文件(READ 类型,无依赖,可并行) → 修改 A 的接口定义 → 修改 B 和 C 的实现(互不依赖,可并行) → 修改 D(依赖 B) → 运行测试。

执行时按拓扑排序进行,每一轮找出所有入度为 0 的节点——也就是没有前置依赖的任务——将它们并行执行。这批任务完成后更新依赖图,再找出下一批可以并行的任务。”

DAG任务图与拓扑执行示意图,核心是有向无环图+拓扑排序+并行执行

改到一半发现计划有问题怎么办?

“比如,改到第 4 个文件时才发现,A 的接口变更还影响了一个之前漏掉的文件 E。”

“这时分两种情况处理。”

如果进度不到一半,说明规划阶段对模块的理解可能有较大偏差,这时应该带着错误信息,进行整体的重规划。

如果进度已经过半,已完成的工作量很大,推倒重来的成本太高。更好的做法是保留已完成的成果,追加 E 的修改任务,调整后续的依赖关系,然后继续执行。

增量重规划决策逻辑图,方案是已完成的不推倒,只调整受影响的部分

06、语义检索是如何实现的?

“派聪明用的是混合检索。”

混合检索完整流程图,展示从Query输入到输出Top-N结果的整个过程

“先说向量检索。用户的查询文本会通过 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 关键词检索。”

Elasticsearch双索引架构图,同一文档建立向量和倒排两类索引实现两路召回

“文档入库前会进行分块,分块策略直接决定了最终的检索质量。我们的分块大小是 512 个字符,相邻块之间有 100 个字符的重叠。这样做是因为一个完整的概念可能恰好被截断在两个块的边界上,重叠设计能确保边界处的语义不被切断。”

“分块逻辑是三级处理:先按双换行符切分段落,每个段落独立处理。如果某个段落超过 512 字符,就进一步按句子切分。切分完毕后,如果某一块不足 100 字符,就把它和前一块合并——太碎的块 Embedding 质量差,检索出来意义也不大。”

文档分块与父子层级示意图,小块负责精准召回,父块负责提供完整上下文

“此外,文档还维护了一层父子结构。子块就是这 512 字符的检索单元,用于精确匹配。父块则可以很大,最大到 1MB,差不多就是整篇文档的全文。当检索命中某个子块后,可以回溯到它的父块,获取更完整的上下文,让 LLM 在回答前能看到更多相关信息。对于大文件,我们会采用流式解析,防止一次性加载撑爆内存。”

什么情况下该换专门的向量数据库?

“主要看数据规模和检索 QPS。派聪明的知识库规模在几万到几十万文档这个级别,ES 的 HNSW 完全能够胜任。如果只是为了用而多部署一套 Milvus 或 Qdrant,反而要多维护一套集群、多做一份数据同步,收益不大。”

Elasticsearch与专用向量数据库对比图,选型取决于业务规模与现有基础设施

“但如果数据量达到了千万级,或者检索 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 不能为空,这些硬性约束模型是绕不过去的。”

Agent三层输出控制机制图,通过软约束、硬约束和人工审批逐层增强控制

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

MCP Schema清洗前后对比图,目标是保留约束但降低结构复杂度

“审批是最后一道防线。”

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

审批被用户拒绝后 Agent 怎么反应?

审批拒绝后的Agent决策流程图,强调拒绝是新的观察而非绕过授权的信号

“拒绝的结果会作为 tool_result 返回给模型,消息里会明确告知:‘这个操作被用户拒绝了’。”

模型看到这条信息后,会自行决定下一步行动。它可能换一种方式来达成目标,例如,如果用户拒绝了直接覆盖文件的操作,模型可能会改为先备份,再进行写入。

10、记忆模块是怎么设计的?

“PaiCLI 的记忆共分为三层。”

Agent三层记忆架构图,按作用域分层、按生命周期管理

短期记忆是会话级别的,只在当前会话中有效。底层实现是一个 LinkedHashMap,先进来的排在前面。它的 token 预算是上下文窗口的 45%,一旦超过,就从最旧的条目开始淘汰。”

长期记忆是持久化的,可以跨会话存活。它存储在 SQLite 里,按类型和作用域进行分类。在每轮调用 LLM 之前,系统会检索与当前输入相关的长期记忆条目,注入到系统提示词中。新存入的条目会进行去重检测,避免重复存储。”

项目记忆是开发者提前写好的规则文件,类似于 Claude Code 的 CLAUDE.md。它们会按优先级加载:PAI.md.paicli/PAI.mdPAI.local.md.paicli/PAI.local.md,后面的配置会覆盖前面的。”

11、多轮、多会话场景下 memory 如何处理?

“在多轮对话场景中,对话会越来越长,上下文窗口迟早会被塞满。PaiCLI 的策略是:保留最近 3 轮的完整对话不动,而更早的历史记录,则由 LLM 生成摘要来替换。摘要会重点保留用户的关键诉求、已完成的操作、达成的共识以及待办事项。”

“在多轮场景中,还有一个容易忽略的点,就是短期记忆的 token 预算。预算的上限是上下文窗口的 45%,超了就从最旧的条目开始淘汰。但淘汰不是直接删除,被淘汰的条目会先用 LLM 做一次压缩摘要,再用来替换,这样可以确保关键信息不会完全丢失。”

多轮多会话记忆处理流程图,会话内保连续,会话间靠持久化召回

“在多会话场景中,则主要依赖长期记忆的持久化能力。PaiCLI 的长期记忆存在 SQLite 里,新会话启动时,它会从磁盘加载,Agent 会记得你之前存过的偏好和规则。存入新条目时,会进行去重,确保同一条偏好不会被存两份。”

“需要注意的是,/clear 命令只会清除短期记忆和对话历史,长期记忆不会受到影响。因为用户说‘清一下上下文’,通常只是想换个话题重新开始,而不是想让 Agent 忘掉他所有的偏好习惯。”

/clear命令清除范围说明图,强调清当前会话,不误删长期资产

12、如果系统出现异常,整体的容错和异常处理机制怎么设计?

“Agent 系统出问题是常态:调 LLM 的 API 可能报错,工具执行可能失败,Agent 自身也可能跑进死循环。”

Agent容错机制分层架构图,在LLM层、工具层、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。这么预留的目的,是防止刚压缩完,下一秒又溢出了。

200K上下文窗口token分配与压缩触发机制图

“触发压缩后,第一步是扫描整个对话历史,找到所有 user 消息的位置索引。”

第二步,从最后一条 user 消息开始,往前数 3 条,这 3 轮完整的对话将被标记为‘保留区’。保留区内的所有消息——无论 userassistanttool_call 还是 tool_result——都原样不动。

第三步,将保留区之前的所有消息,发给 LLM 生成一份摘要,然后用这份结构化摘要替换掉之前那些冗长的原始消息。

上下文压缩三步流程,核心是先保护关键边界再压缩可替代历史

“这份摘要必须包含四类关键信息:用户的核心诉求、已完成的操作、双方达成的共识,以及待办的事项。”


这么说吧。

Harness 将成为未来五年的主旋律。除了模型,各大厂都在争先恐后地构建自己的 Harness。

当然,换个名字,叫 Agent 也行。

其实,所有的 Agent,本质上都是在做 Harness——让模型更好用,更贴合业务。

而 Harness 最核心的部分,就是上下文管理、Memory 管理、多 Agent 协作,以及提示词优化。

剩下的,大都是界面交互层面的工作了。

可以预见,拥有 Harness 实战经验的人才,未来一定会非常抢手。




上一篇:OpenAI 开源 Codex Security:Vibe Coding 项目必备的安全漏洞扫描工具
下一篇:Jeff Dean离开谷歌创办AI初创Discovery Loop,目标用AI全自动做实验
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-7 02:08 , Processed in 0.827805 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表