news 2026/9/8 4:14:18

Agent Harness 核心实践:上下文压缩与动态记忆实现详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent Harness 核心实践:上下文压缩与动态记忆实现详解

前阵子做 Agent 项目落地时,我一直被几个问题反复折磨:Agent 跑着跑着上下文就超限了,长对话后模型开始“失忆”,工具调用链路一长就出现各种莫名其妙的终止报错。最开始我以为是模型能力不行,后来把问题拆开排查才发现,根子不在模型,而在承载 Agent 运行的那层 Harness 不够健壮。

很多人聊 Agent 时,会把注意力全部放在模型选型、Prompt 编写和工具定义上,却忽略了一个关键事实:Agent 是一个需要在循环中不断做“观察、决策、执行”的程序,它必须有一个稳定、可控的运行外壳来管理上下文、记忆、异常和工具调用。这个外壳,就是 Harness。

这篇文章我会从零开始梳理 Harness 是什么、为什么它被称为 Agent 稳定运行的“操作系统”,然后实践两个最核心的难点:上下文压缩和动态记忆。全文包含可复用的 Python 实现思路和完整代码片段,新手能看懂原理,有基础的开发者可以直接参考改进。

1. 什么是 Harness,为什么 Agent 需要它

1.1 从一个执行循环说起

大模型本身是“无状态”的,每一次调用都只接收当前传入的 messages 和 tools,然后返回结果。Agent 之所以看起来像有“智能体”,是因为外层有一段代码在循环驱动它:

  1. 将用户问题、历史上下文、可用工具描述一起发给模型;
  2. 模型返回文本回复或工具调用请求;
  3. 如果返回的是工具调用,Agent 执行对应工具,把结果追加到上下文里;
  4. 将新的上下文再次发送给模型;
  5. 反复执行直到模型给出最终答案。

这段循环就是 Agent 最基础的运行逻辑。但“循环”谁都能写,真正让它稳定、可控、可扩展的,是这层循环代码的工程质量。我们把这层负责编排、状态管理、上下文管理、记忆管理、错误恢复的执行框架,称为 Agent Harness。

1.2 Harness 的边界

在实际工程里,Harness 并不等同于“Agent 框架”。框架通常指 LangChain、LlamaIndex 这类大而全的开源工具库,而 Harness 更偏向业务侧对 Agent 行为的统一封装。它更像一个“定制化的运行容器”,你可以在里面决定模型的调用方式、上下文的切换策略、记忆的读写时机、工具的注册和鉴权、异常的降级方案。

换句话说,模型负责“聪明”,Harness 负责“稳定”。模型可以换,Harness 的边界不能乱。

1.3 为什么 Harness 是“操作系统”

我们可以把 Agent 比作一台电脑:

  • 模型是 CPU,提供算力;
  • Prompt 是应用程序,告诉模型当前要做什么;
  • 工具是外设,让 Agent 有手有脚;
  • Harness 是操作系统,负责进程调度、内存管理、资源回收、异常中断。

没有操作系统的电脑,只能算一堆电子元件;没有 Harness 的 Agent,也难以在复杂任务中稳定运行。尤其当上下文变长、工具变多、会话变频繁时,Harness 的价值会越来越明显。

目前社区里大量讨论的 deepseek harness、codex harness、hermes agent 等,本质上都是在给特定模型或特定场景打造一套更匹配的 Harness,让 Agent 的推理能力和工具执行能力被更好地编排起来。

2. 环境准备与版本说明

2.1 运行环境

本文示例采用 Python 开发,不绑定特定的大模型厂商 SDK,而是抽象一个统一的模型调用接口。你可以把它替换成任何 OpenAPI 兼容的模型服务。

推荐环境如下:

  • 操作系统:Windows / macOS / Linux 均可;
  • Python 版本:3.10 及以上;
  • 依赖库:openai(或任意兼容接口的 SDK)、numpy(用于向量计算示例)、pydantic(用于数据结构定义);
  • 向量数据库:示例先用内存字典,线上可替换为 Chroma / Milvus / 其他向量存储。

版本说明:由于不同模型服务商的接口版本更新较快,本文不会写死某个 SDK 的具体版本号。你只需要保证你的环境能正常发起模型调用即可。

2.2 项目结构

为了便于理解,我们把代码拆成以下结构:

agent_harness_demo/ ├── main.py # 启动入口 ├── harness.py # Harness 主循环 ├── compressor.py # 上下文压缩器 ├── memory.py # 动态记忆管理 ├── llm.py # 模型调用抽象 ├── tools.py # 工具定义 └── requirements.txt # 依赖清单

下面每一节都会按要求补充对应文件的代码。

3. Harness 核心架构拆解

3.1 一个最小的 Harness 主循环

我们先写一个最基础的 Harness,把“调用模型 → 执行工具 → 继续调用”的循环搭起来。

# 文件路径:agent_harness_demo/harness.py from typing import List, Dict, Any, Callable class Harness: def __init__(self, llm, tools: Dict[str, Callable]): self.llm = llm self.tools = tools def run(self, user_input: str, messages: List[Dict[str, str]]) -> str: # 把用户输入追加到消息列表 messages.append({"role": "user", "content": user_input}) # Agent 循环,最多执行 10 轮,防止死循环 for _ in range(10): response = self.llm.chat(messages, tools=list(self.tools.keys())) if response.get("type") == "tool_call": tool_name = response["tool_name"] tool_args = response["tool_args"] # 执行工具 if tool_name in self.tools: result = self.tools[tool_name](**tool_args) # 把工具结果追加到上下文 messages.append({ "role": "tool", "tool_name": tool_name, "content": result }) else: messages.append({ "role": "tool", "tool_name": tool_name, "content": "Error: unknown tool" }) continue if response.get("type") == "final": return response["content"] return "Agent 执行轮数超限,请调整任务或检查是否有死循环。"

这段代码解决了 Agent 最基础的循环问题。但注意,它还没有考虑上下文超限、记忆存取和异常恢复。也就是说,它只是一个“能够跑起来”的骨架,离“稳定运行”还差很远。

3.2 上下文管理的两个难题

当 Agent 进入真实业务后,上下文管理会迅速成为第一道坎。

第一个难题是长度超限。大模型的上下文窗口是有限的,比如常见的 32K、128K token。Agent 每次工具调用返回的结果可能很长,多轮后很容易把窗口填满。直接截断会丢失重要信息,而全部保留又放不下。

第二个难题是记忆漂移。这里说的“漂移”不是指概率上的采样漂移,而是指在长对话中,模型会因为早期信息被截断或遗忘,逐渐偏离用户最初的目标。尤其是多步工具调用后,如果 Agent 忘了“我是因为什么才做到这一步的”,后续决策就会失真。

这两个难题,正是上下文压缩和动态记忆要解决的。

3.3 上下文压缩与动态记忆的定位

上下文压缩解决的是“当前窗口怎么装下最有价值的信息”,动态记忆解决的是“跨轮次、跨会话怎么沉淀和读取关键信息”。两者是协作关系:

  • 短期上下文:每次请求真正发送给模型的消息列表;
  • 长期记忆:跨会话保存的用户偏好、任务背景、关键结论;
  • 压缩策略:当短期上下文接近上限时,将历史消息中的低价值部分摘要化;
  • 记忆写入:在每轮结束或任务完成后,把值得记住的信息提取出来存入长期存储;
  • 记忆读取:在每轮开始前,把与当前任务相关的长期记忆注入上下文。

理解了这个定位之后,下面我们分别实践。

4. 上下文压缩实战

4.1 先设计压缩策略

上下文压缩不是简单截断。它至少要回答三个问题:

  1. 什么时候触发压缩?
  2. 哪些消息可以被压缩?
  3. 压缩成什么形式?

我们的设计思路如下:

  • 当消息列表的总 token 预估超过阈值(比如窗口的 70%)时,触发压缩;
  • 系统指令和最近 N 轮消息必须完整保留;
  • 中间的历史消息按重要性评分排序,低分部分合并成摘要;
  • 工具调用结果属于“过程性信息”,优先被摘要化,用户明确提到的关键约束则保留。

这个策略可以在“信息无损”和“窗口有限”之间找到平衡。

4.2 实现摘要压缩器

我们用一个独立的 Compressor 类来管理压缩逻辑。为了演示,我用一个summarize()方法表示调用模型生成摘要;实际项目里你可以直接调用 LLM 完成。

# 文件路径:agent_harness_demo/compressor.py from typing import List, Dict, Any class ContextCompressor: def __init__(self, llm, max_tokens: int = 6000): self.llm = llm self.max_tokens = max_tokens def estimate_tokens(self, messages: List[Dict[str, str]]) -> int: # 简化估算:1 个汉字约 1.5 token,1 个英文单词约 1.3 token total = 0 for msg in messages: content = msg.get("content", "") total += int(len(content) * 1.5) return total def compress(self, messages: List[Dict[str, str]]) -> List[Dict[str, str]]: # 如果当前长度没有超过阈值,直接返回 if self.estimate_tokens(messages) <= self.max_tokens: return messages # 保留系统指令 keep_prefix = [] # 保留最近两轮消息 keep_suffix = messages[-4:] index = 0 if messages and messages[0]["role"] == "system": keep_prefix.append(messages[0]) index = 1 middle = messages[index:-4] if len(messages) > 4 else [] # 中间部分做摘要 summary = self.summarize(middle) compressed = keep_prefix + [ {"role": "system", "content": f"[历史摘要] {summary}"} ] + keep_suffix return compressed def summarize(self, messages: List[Dict[str, str]]) -> str: # 实际项目中可调用 LLM,这里给出核心思路 content = "\n".join( f"{m['role']}: {m['content']}" for m in messages ) prompt = ( "请将以下 Agent 对话历史压缩为 200 字以内的摘要," "保留任务目标、关键决策、用户约束和未完成的步骤。\n\n" f"{content}" ) response = self.llm.chat([ {"role": "system", "content": "你是一个信息压缩助手。"}, {"role": "user", "content": prompt} ]) return response.get("content", "")

这里有几个细节需要注意。

keep_suffix保留最近 4 条消息,是因为最后两轮对话往往包含最接近当前目标的信息,不能随意压缩。如果工具执行结果特别长,我们还需要在压缩前对单条超长消息做截断,否则即使压缩了中间部分,单条消息也可能超出模型单次输入限制。

4.3 实现滑动窗口

对于更轻量级的场景,滑动窗口更简单。比如只保留系统指令、最近 K 轮消息和当前输入。但它的问题是会彻底丢掉早期信息。因此我通常建议:滑动窗口作为兜底策略,摘要压缩作为主策略。

下面是一个滑动窗口实现:

# 文件路径:agent_harness_demo/compressor.py(追加) class SlidingWindowCompressor: def __init__(self, keep_rounds: int = 3): self.keep_rounds = keep_rounds def compress(self, messages: List[Dict[str, str]]) -> List[Dict[str, str]]: # 保留系统指令 result = [] index = 0 if messages and messages[0]["role"] == "system": result.append(messages[0]) index = 1 tail = messages[index:] if len(tail) > self.keep_rounds * 2: tail = tail[-(self.keep_rounds * 2):] result.extend(tail) return result

在实际项目里,我会把这两种压缩器组装成一个 Pipeline,先做摘要压缩,再做滑动窗口兜底,保证任何情况下上下文都不会撑爆窗口。这种设计非常有用,尤其是当模型厂商对输入长度有限制时。

5. 动态记忆实战

5.1 短期记忆与长期记忆的划分

聊 Agent 的记忆,首先要区分两个层次。

短期记忆存在于当前会话的 messages 中,它由 Harness 的上下文管理来维护。短期记忆的特点是“详细但易失”,一旦会话结束,如果没有沉淀,就彻底丢失。

长期记忆跨越会话存在。它应该保存以下内容:

  • 用户的基本偏好和固定约束;
  • 任务的核心目标;
  • 已经完成的关键步骤和结论;
  • 不随时间变化的业务背景。

动态记忆的核心是“动态”二字:它不仅会写入新记忆,还要能更新旧记忆、遗忘过期记忆、以及根据当前任务只提取最相关的部分。

5.2 向量化存储的抽象接口

我们定义一个 MemoryStore 接口,包含四个方法:写入、读取、更新、删除。

# 文件路径:agent_harness_demo/memory.py from typing import List, Dict, Any, Optional import uuid class MemoryStore: def add(self, memory: Dict[str, Any]) -> str: raise NotImplementedError def retrieve(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]: raise NotImplementedError def update(self, memory_id: str, content: str) -> None: raise NotImplementedError def delete(self, memory_id: str) -> None: raise NotImplementedError class InMemoryStore(MemoryStore): """ 演示用内存存储。 线上建议替换为向量数据库实现。 """ def __init__(self): self._items: Dict[str, Dict[str, Any]] = {} def add(self, memory: Dict[str, Any]) -> str: memory_id = str(uuid.uuid4()) memory["id"] = memory_id self._items[memory_id] = memory return memory_id def retrieve(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]: # 演示效果:直接返回最近 top_k 条 # 真实项目应使用 embedding 相似度检索 items = list(self._items.values()) return items[-top_k:] def update(self, memory_id: str, content: str) -> None: if memory_id in self._items: self._items[memory_id]["content"] = content def delete(self, memory_id: str) -> None: self._items.pop(memory_id, None)

这里需要着重说明:InMemoryStore.retrieve的实现只是为了演示流程,它没有语义检索能力。真实项目中,你应该在add时先调用 embedding 模型为记忆内容生成向量,retrieve时对 query 也生成向量,然后做余弦相似度排序,取出最相关的 top_k 条记忆。

5.3 记忆管理器的写入、提取与遗忘

光有 MemoryStore 还不够,还需要一个 MemoryManager 来封装记忆的“动态”逻辑。它的职责包括:

  • 对每轮对话提取“值得记住”的信息;
  • 将短期记忆中的关键结论写入长期记忆;
  • 在对话开始前检索相关记忆,注入上下文;
  • 定期清理和去重。
# 文件路径:agent_harness_demo/memory.py(追加) class MemoryManager: def __init__(self, store: MemoryStore, llm, max_memory_items: int = 200): self.store = store self.llm = llm self.max_memory_items = max_memory_items def save_important_info(self, messages: List[Dict[str, str]]) -> Optional[str]: # 使用模型判断哪些信息值得长期保存 prompt = ( "从以下对话中提取需要长期记住的用户偏好、任务约束和关键结论。\n" "如果没有值得保存的信息,回复:无\n\n" f"对话内容:\n{messages}" ) response = self.llm.chat([ {"role": "system", "content": "你是记忆提取助手。"}, {"role": "user", "content": prompt} ]) content = response.get("content", "").strip() if content and content != "无": memory_id = self.store.add({ "content": content, "created_at": "2025-01-01", "last_access_at": "2025-01-01" }) self._enforce_limit() return memory_id return None def load_relevant_memory(self, query: str, top_k: int = 3) -> str: memories = self.store.retrieve(query, top_k=top_k) if not memories: return "" memory_text = "\n".join( f"- {m['content']}" for m in memories ) return f"[长期记忆]\n{memory_text}" def _enforce_limit(self) -> None: # 简化处理:内存实现不做体积控制 # 真实项目按创建时间和访问频率淘汰旧记忆 pass

在这个实现中,save_important_info通过模型判断哪些信息值得保存,load_relevant_memory则在每轮开始前检索相关记忆。注意,真正的生产系统还需要考虑记忆的冲突:旧记忆和新记忆矛盾时怎么处理?我通常会采取“版本化”方案,不直接删除旧记忆,而是标记为过期,通过last_access_at和优先级字段来决定最终使用哪条记忆。

6. 完整实战案例:自带压缩与记忆的 Agent Harness

6.1 定义模型调用层

为了让示例不依赖特定模型品牌,我们定义一个 LLMProvider 抽象。

# 文件路径:agent_harness_demo/llm.py from typing import List, Dict, Any class LLMProvider: def chat(self, messages: List[Dict[str, str]], tools: List[str] = None) -> Dict[str, Any]: raise NotImplementedError

这里不强行实现某个具体厂商的适配,因为你只需要按照官方 SDK 格式填充chat方法即可。核心思路是:chat方法接收标准消息列表,返回统一结构。

返回结构约定如下:

{ "type": "tool_call" | "final", "content": "模型回复文本" , "tool_name": "工具名,当 type 为 tool_call 时存在", "tool_args": {...} }

实际适配时,如果使用 OpenAI 兼容接口,可以把tool_calls解析映射到这个结构。如果你的项目用的是 DeepSeek、Qwen 或其他模型,只要兼容 OpenAI 的 tool calling 格式,都可以复用。

6.2 定义工具集合

为了演示,我们定义两个简单的工具:一个是计算器,一个是查询“本地知识库”的工具。

# 文件路径:agent_harness_demo/tools.py import math from datetime import datetime def calculator(expression: str) -> str: """安全执行数学表达式,仅允许白名单操作。""" allowed = set("0123456789+-*/(). ") if not all(c in allowed for c in expression): return "Error: invalid characters" try: # 生产环境请使用 ast 模块或 eval 白名单方式 result = eval(expression) return str(result) except Exception as e: return f"Error: {e}" def get_current_time() -> str: return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

需要特别提醒:eval在真实项目中有安全风险,本文仅为演示。生产环境建议使用ast.literal_eval或专门的表达式解析库,并且一定要做权限控制和输入验证。

6.3 把压缩器和记忆管理器接入 Harness

这是整篇文章最核心的代码。我们把 Compressor 和 MemoryManager 注入 Harness,让它在每次请求之前加载记忆、在每次返回结果之前压缩上下文。

# 文件路径:agent_harness_demo/harness.py(增强版) from typing import List, Dict, Any, Callable from compressor import ContextCompressor from memory import MemoryManager class AgentHarness: def __init__( self, llm, tools: Dict[str, Callable], compressor: ContextCompressor, memory_manager: MemoryManager, max_iterations: int = 10, context_token_limit: int = 6000, ): self.llm = llm self.tools = tools self.compressor = compressor self.memory_manager = memory_manager self.max_iterations = max_iterations self.context_token_limit = context_token_limit def run( self, user_input: str, messages: List[Dict[str, str]], session_id: str = "default", ) -> str: # 1. 先加载与当前问题相关的长期记忆 relevant_memory = self.memory_manager.load_relevant_memory(user_input) if relevant_memory: messages.append({ "role": "system", "content": relevant_memory }) # 2. 追加用户输入 messages.append({"role": "user", "content": user_input}) # 3. 执行 Agent 循环 for _ in range(self.max_iterations): # 3.1 每次调用前先检查上下文长度,必要时压缩 if self.compressor.estimate_tokens(messages) > self.context_token_limit: messages = self.compressor.compress(messages) response = self.llm.chat( messages, tools=list(self.tools.keys()) ) if response.get("type") == "tool_call": tool_name = response["tool_name"] tool_args = response["tool_args"] tool_key = tool_name if tool_key in self.tools: try: result = self.tools[tool_key](**tool_args) except Exception as e: result = f"Error: {e}" messages.append({ "role": "tool", "tool_name": tool_name, "content": result }) else: messages.append({ "role": "tool", "tool_name": tool_name, "content": "Error: unknown tool" }) continue if response.get("type") == "final": final_answer = response["content"] # 4. 在结束时提取值得长期记忆的信息 self.memory_manager.save_important_info(messages[-6:]) return final_answer return "Agent 执行轮数超限,请调整任务或检查是否有死循环。"

这段代码的关键改进有四个:

  • 在循环开始前注入长期记忆;
  • 在每次调用模型前检查并压缩上下文;
  • 对工具调用做了 try-except,避免单个工具异常导致整个 Agent 崩溃;
  • 在返回最终答案前,把关键信息沉淀到长期记忆。

6.4 启动入口

最后写一个main.py,把上面的模块串起来。

# 文件路径:agent_harness_demo/main.py from harness import AgentHarness from compressor import ContextCompressor from memory import MemoryManager, InMemoryStore from tools import calculator, get_current_time class DemoLLM: """ 演示用模型适配器。 实际项目中请替换为真实模型服务,并映射 tool_call 返回结构。 """ def __init__(self, model_name: str = "your-model"): self.model_name = model_name def chat(self, messages, tools=None): # 这里填写你的真实模型调用逻辑 # 返回结构必须包含 type / content / tool_name / tool_args last_msg = messages[-1]["content"] if "现在几点" in last_msg or "时间" in last_msg: return { "type": "tool_call", "content": "", "tool_name": "get_current_time", "tool_args": {} } if "计算" in last_msg: expr = last_msg.split("计算")[-1].strip() return { "type": "tool_call", "content": "", "tool_name": "calculator", "tool_args": {"expression": expr} } return { "type": "final", "content": f"我已收到你的问题:{last_msg}", "tool_name": None, "tool_args": None } if __name__ == "__main__": llm = DemoLLM() store = InMemoryStore() memory_manager = MemoryManager(store=store, llm=llm) compressor = ContextCompressor(llm=llm, max_tokens=6000) tools = { "calculator": calculator, "get_current_time": get_current_time, } harness = AgentHarness( llm=llm, tools=tools, compressor=compressor, memory_manager=memory_manager, max_iterations=10, context_token_limit=6000, ) messages = [ {"role": "system", "content": "你是一个有用的 Agent。请根据工具结果回答用户。"} ] # 第一轮:触发工具调用 result1 = harness.run("帮我计算 (12 + 8) * 3 的结果", messages) print("结果1:", result1) # 第二轮:触发时间工具 result2 = harness.run("现在几点?", messages) print("结果2:", result2) # 第三轮:验证记忆是否保留 result3 = harness.run("我刚刚问过什么问题?", messages) print("结果3:", result3)

注意:这个DemoLLM只是为了展示整个 Harness 的调用链路如何跑通。真实项目中,你应该在这里对接具体模型,并把返回结构正确映射为标准格式。

6.5 运行与验证

在项目目录下执行:

cd agent_harness_demo python main.py

预期输出类似于:

结果1: 我已收到你的问题:帮我计算 (12 + 8) * 3 的结果 结果2: 我已收到你的问题:现在几点? 结果3: 我已收到你的问题:我刚刚问过什么问题?

这里由于 DemoLLM 非常简单,没走真实的模型规划逻辑,所以工具结果没有继续送回去让模型生成最终答案。但你注意第一轮运行后,save_important_info已经把对话内容写入了长期记忆;虽然第三轮 DemoLLM 还不会利用记忆,但如果你接入真实模型,记忆内容会作为 system 消息出现在 messages 中,模型就能回答“你刚刚问过计算表达式”之类的问题。

这就是一个最小可用的 Harness 闭环。

7. 常见问题与排查思路

7.1 上下文压缩后 Agent 反而变笨了

问题现象常见原因解决思路
压缩后模型忘记关键约束压缩摘要丢失了用户硬性约束摘要时强制保留用户约束;或把用户约束单独放到 system 消息中
压缩后工具调用逻辑断裂历史工具结果被过度摘要,模型无法判断下一步保留最近若干条原始工具结果,不压缩;只压缩更早的历史
窗口还是超限单条工具结果太长对单条消息单独做截断;或让工具返回摘要而不是完整结果

压缩不是“一次性把历史全缩掉”,而是“保留最关键的部分,压缩次要的部分”。建议每次压缩后打印 token 数量和保留的 message 条数,方便观察压缩是否有效。

7.2 动态记忆读取不到有效信息

问题现象常见原因解决思路
检索结果与当前问题无关向量检索只做关键词匹配换语义向量模型,或调整检索权重
记忆越积越多,互相矛盾缺少去重和版本化保存记忆时先做相似度判断,如果相似度高则更新旧记忆
长期记忆不生效记忆没有写入或没有注入上下文检查 save_important_info 是否被调用,检查 load_relevant_memory 是否拼接到 system 消息

这里要特别强调:长期记忆不是越多越好。过多的记忆会稀释当前上下文的注意力,还可能导致模型在许多不相干的背景信息中迷失。我一般建议单次只注入 3~5 条相关度最高的记忆。

7.3 Agent 执行中断,报 agent execution terminated due to error

在实际运行中,这个报错非常常见。原因通常不是模型问题,而是 Harness 在工具执行或上下文构建阶段没有做好异常兜底。

排查顺序如下:

  1. 查看日志中最后一次工具调用是哪个工具;
  2. 手动用同样的参数调用该工具,确认是否稳定报错;
  3. 检查是否是上下文长度超限,导致模型 API 返回异常;
  4. 检查是不是工具返回值里有不可序列化的对象,导致下一轮组装 messages 时崩溃。

解决方案是在 Harness 的每个关键环节加 try-except 和日志记录,保证单次工具异常不会中断整个 Agent 循环。

8. 最佳实践与工程建议

8.1 把 Harness 和业务解耦

Harness 只负责 Agent 的运行编排,不应该直接写业务 SQL、直接调用第三方支付接口。业务能力都封装成 tools,Harness 只负责“调用工具并管理过程”。这样业务变更不影响 Harness 稳定性,Harness 升级也不影响业务逻辑。

8.2 上下文压缩要有监控指标

在生产环境,建议记录以下指标:

  • 每轮请求的 token 数;
  • 触发压缩的次数;
  • 压缩后 token 下降比例;
  • 压缩后任务成功率变化。

这些数据能帮你判断压缩策略是否需要调整。如果压缩后成功率明显下降,说明摘要策略过于激进,应该提高摘要保留比例或延长保留的最近轮数。

8.3 动态记忆写入要设置权限和边界

不是所有信息都值得写入长期记忆。以下信息必须禁止写入:

  • 用户明文密码、Token、API Key;
  • 涉及个人隐私的敏感数据;
  • 一次性临时指令;
  • 工具返回的完整大结果。

在记忆管理器中增加一层过滤正则或 PII 检测,非常有必要。

8.4 安全边界

Agent 的工具调用天然具有“执行能力”,因此 Harness 必须做好工具注册白名单和参数校验。

  • 禁止动态导入任意模块;
  • 禁止让模型直接决定执行系统命令;
  • 对工具返回结果做长度限制;
  • 对 super 敏感操作增加人工确认环节。

这些不是可选项,而是生产级 Harness 的基本要求。任何“先跑通再说”的心态,都可能在线上造成不可挽回的损失。

8.5 日志和可观测性

Harness 最好把每一轮的输入输出都记录下来,至少包含:

  • messages 完整快照;
  • 模型返回的原始结构;
  • 工具名称、参数、返回值;
  • 上下文压缩前后的 token 变化;
  • 记忆写入和读取记录。

有了日志,你才能快速定位问题,而不是靠猜。

9. 总结与学习路线

本文从 Agent 执行循环讲起,解释了 Harness 的定位,然后实践了上下文压缩和动态记忆两个核心模块,最后组装出一个自带压缩与记忆的 Agent Harness。

你掌握了以下几个关键点:

  • Harness 是 Agent 稳定运行的“操作系统”,负责编排模型、工具、上下文和记忆;
  • 上下文压缩不能简单截断,要采用“系统指令 + 最近消息 + 摘要”的分层策略;
  • 动态记忆分为短期记忆和长期记忆,长期记忆需要通过向量检索按需注入;
  • 异常兜底和日志监控是 Harness 生产落地的必要保障。

如果你现在用的是 LangChain 这类框架,也可以参考本文的思路检查一下框架的默认逻辑是否满足你的场景。很多时候,默认逻辑只适合 Demo,不适合生产。真正的挑战在于根据业务设计一套合适的 Harness 策略。

下一步你可以沿着这几个方向继续深入:

  • 研究 ReAct、Plan-and-Execute 等不同 Agent 推理模式对 Harness 的要求;
  • 学习向量数据库的原理和用法,把记忆检索做到更精细;
  • 尝试用事件驱动的方式改造 Harness,让工具调用、记忆写入、上下文压缩都变成可观测事件;
  • 梳理一套 Harness 的单元测试方案,重点覆盖工具异常、上下文超限和记忆回滚。

Agent 开发的门槛并不在“调用模型”,而在于如何做好这层 Harness。把上下文压缩和动态记忆真正落地,你的 Agent 才会从“偶尔跑通”变成“稳定可用”。希望这篇文章对你有用,动手改一改,跑一跑,很快你就能感受到 Harness 带来的差异。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 4:14:03

写论文时AI工具怎么分工?大模型写稿,PaperRed体检

现在写论文、做报告、搞科研&#xff0c;几乎绕不开两类AI工具&#xff1a; 生产端&#xff1a;ChatGPT、Claude、Gemini、DeepSeek、Kimi、通义千问、文心一言、豆包、智谱GLM等&#xff0c;用来查资料、理思路、写初稿、改语言、跑代码。风控端&#xff1a;AIGC检测、查重、…

作者头像 李华
网站建设 2026/9/8 4:12:14

AI模型部署平台怎么选?Baseten、RunPod、DigitalOcean等7个平台横评

最近圈子里的朋友问我最频繁的一个问题&#xff0c;已经从“模型怎么跑通”变成了“模型跑通了&#xff0c;然后放哪”。这个“放哪”看起来只是挑个服务器、点几下部署&#xff0c;实际折腾下来你会发现&#xff0c;它本质上是在挑一种运维方式、一套计费规则&#xff0c;甚至…

作者头像 李华
网站建设 2026/9/8 4:11:55

eLLM思路实操:CPU如何在长上下文推理中逆袭GPU

1. 先泼一盆冷水&#xff1a;CPU跑赢GPU&#xff0c;靠的不是算力 说实话&#xff0c;第一次看到"eLLM&#xff1a;让CPU在长程推理中快过GPU"这个结论时&#xff0c;我第一反应是不信。做了几年大模型推理加速&#xff0c;被显存大小和带宽折磨过无数次的人&#xf…

作者头像 李华
网站建设 2026/9/8 4:11:32

GitHub Copilot Workspace:从代码补全到完整开发环境的AI编程革命

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 4:09:54

CSAPP实验全解析:从Data Lab到Cache Lab的硬核通关指南

CSAPP这门课&#xff0c;在国内计算机专业里几乎成了“劝退”与“封神”并存的存在。《深入理解计算机系统》配套的那几个Lab——Data Lab、Bomb Lab、Cache Lab、Malloc Lab&#xff0c;每一个都是实打实的硬仗。2025年HIT的大作业又一次围绕这套经典实验展开&#xff0c;不少…

作者头像 李华
网站建设 2026/9/8 4:07:13

基于Bitnami镜像的Redis哨兵模式Docker Compose高可用部署实践

如果你手头有一批 Redis 实例要管&#xff0c;又暂时不想上 Kubernetes 那套重家伙&#xff0c;哨兵模式几乎是高可用最经典、最省心的方案。不过在容器环境里把哨兵搭起来&#xff0c;很多人在配置文件上栽过跟头——原生 Redis 镜像的哨兵配置要手写sentinel.conf&#xff0c…

作者头像 李华