ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

DeepSeek Harness:从零构建可控的AI Agent执行框架

2026/8/31 5:45:53 拓冰建站 浏览量
DeepSeek Harness:从零构建可控的AI Agent执行框架 最近围绕 AI Agent 开发的讨论越来越密集无论是开源社区的仓库热度还是各大云厂商推出的 agent 平台都指向同一个方向让大模型从“聊天”走向“任务执行”。在这个背景下DeepSeek 相关生态里出现了一个高频词——harness。很多人把它理解为某个单一工具或某个仓库但更准确地说harness 代表的是一种把大模型封装进可控自动化流程的工程模式。本文就以“DeepSeek Harness”为切入点从概念、原理、环境准备、最小实现到常见报错排查完整走一遍 Agent 开发的核心路径。无论你是刚接触 Agent 的初学者还是已经写过多个自动化脚本的开发者这篇文章都会给你一套能落地的思路和代码。1. 背景DeepSeek Harness 与 Agent 开发为何被频繁提起1.1 从 AI 编码助手到 Agent 开发过去两年里大模型应用大致经历了三个阶段。第一阶段是对话机器人模型只负责生成文本用户手动复制粘贴到其他地方继续操作。第二阶段是 AI 编码助手模型被集成到 IDE 中能补全代码、解释报错、生成单元测试但它仍然没有“主动执行”的能力。第三阶段就是现在正在发生的 Agent 开发阶段模型不再只是给建议而是可以自己决定调用哪些工具、读取哪些文件、执行哪些命令并根据执行结果继续调整策略。DeepSeek 模型在开源社区中之所以受欢迎一方面是因为推理能力表现不错另一方面是模型权重开放开发者可以本地部署也可以直接调用官方 API。这些特性为 Agent 开发提供了很好的基础因为 Agent 场景通常需要大量调用、频繁实验成本可控和部署灵活是硬性要求。于是当社区中出现类似 DeepSeek Harness 的概念时很多人会把它和“Agent 开发革命”联系起来。这里的 harness 字面意思是“线束”或“操控装置”在软件工程里可以理解为“外部控制层”。放到 Agent 场景中harness 就是一套把大模型、工具、上下文、错误处理、日志追踪组织起来的运行时骨架。1.2 Harness 和 Agent 的边界在哪里很多人容易把 harness 和 agent 混在一起其实它们的分工是不同的。Agent 通常指代具备推理能力的那部分它的核心是模型本身。模型根据用户目标和当前上下文决定下一步要做什么比如调用一个计算器工具或者从本地文件里读一段配置。Agent 的“智能”来自模型权重和提示词设计。Harness 则是包裹在 Agent 外面的工程壳。它负责接收用户任务维护对话历史提供工具注册表限制模型的最大迭代次数捕获异常把执行过程记录下来并在必要时做重试或回退。换句话说Agent 负责“思考”harness 负责“保障”。用户任务 - Harness控制循环 - Agent模型决策 | v 工具执行结果 | v Harness 继续组织下一步模型调用在没有 harness 的情况下我们只能让模型生成一段文本然后人工判断结果。有了 harness模型可以在受限环境里自主行动完成一个包含多个步骤的任务。这也是为什么很多 Agent 项目强调“harness 设计比模型选型更影响稳定性”。1.3 看待“打破 GitHub 记录”的理性姿势关于“DeepSeek Harness 打破 GitHub 记录”的说法不同渠道的统计口径并不一致比如 star 增长数、clone 数量、issue 讨论热度都可能影响结论。对开发者而言与其关注某个数字不如关注它背后所代表的设计思路。GitHub 上的热度是“关注度”指标不代表代码质量也不等于生产可用。真正让一个 Agent 项目有价值的地方在于它是否把复杂环境差异封装好是否提供清晰可扩展的工具接口是否具有完善的错误恢复和日志体系。这些工程能力才是 Agent 开发从 demo 走向生产的关键。所以本文不试图给某个具体仓库“带货”而是从 harness 的通用原理出发用 DeepSeek 模型和最小代码实现一套可运行、可扩展的 Agent harness。读完以后你就能够理解那些热门项目的主要设计动机也能在自己的业务里搭建属于你的 agent 执行框架。2. 环境准备与方案选型2.1 两种主流调用方式做 DeepSeek 方向的 Agent 开发首先要确定模型从哪里来。目前主流方式有两种。第一种是调用 DeepSeek 开放平台的 API。这种方式优点是部署成本低、并发稳定不需要自己准备 GPU。缺点是每次调用按 tokens 计费长任务和多次迭代会产生费用而且数据会经过外部服务对数据安全要求高的场景需要谨慎。第二种是本地部署模型。DeepSeek 的权重是开放的很多量化后的模型可以跑在消费级显卡甚至纯 CPU 环境下。本地部署的优点是隐私性更强调用成本边际递减适合高频实验。缺点是初始化门槛较高硬件不同导致推理速度和效果差异很大模型版本管理也需要自己维护。在 Agent 开发初期我更建议先用 API 方式把逻辑跑通确认工具调用和任务流程没问题后再根据预算和隐私需求切换到本地部署。这样能减少变量定位问题也更容易。2.2 基础环境要求本文示例使用 Python 3.10 或更高版本操作系统不限Windows、macOS、Linux 都可以运行。OpenAI 官方 Python SDK 提升了对接兼容性因为很多模型服务都提供 OpenAI 兼容接口DeepSeek API 也走这套模式。需要安装的 Python 依赖如下openai python-dotenv requests为了避免污染系统环境推荐创建虚拟环境python -m venv venv source venv/bin/activateWindows 下激活命令是venv\Scripts\activate然后安装依赖pip install openai python-dotenv requests如果你所在网络无法直接访问官方 PyPI可以按当前网络环境选择可用的 Python 包索引源但不要使用来路不明的安装脚本。2.3 示例项目结构为了让后续代码更清晰我们规划这样一个目录结构deepseek-harness-demo/ ├── .env ├── requirements.txt ├── src/ │ ├── __init__.py │ ├── llm_client.py │ ├── tools.py │ ├── harness.py │ └── main.py其中llm_client.py负责封装模型调用。tools.py负责定义可供 Agent 使用的工具。harness.py是核心执行循环。main.py是命令行入口。这个结构简单但足够体现 harness 的核心思路模型调用、工具集合、控制逻辑三层分离。3. Harness 的核心原理拆解3.1 Agent 运行循环一个最小可用的 Agent 循环通常包含以下步骤把用户任务和系统提示词组装成请求。调用模型得到回复。解析回复判断模型是给出最终答案还是请求调用工具。如果是工具调用执行对应的工具函数拿到结果。把工具结果追加到对话历史中再一次调用模型。重复上述步骤直到模型给出最终答案或达到最大迭代次数。这个循环在学术和工程领域有很多名字比如 ReAct、Tool Calling、Function Calling。核心思想一致让模型在“推理”和“行动”之间交替前进。需要注意的是模型本身是“无状态”的。它每次接收的都是完整上下文。因此 harness 必须负责保存历史消息包括用户消息、助手消息、工具结果消息。没有上下文管理Agent 就会忘记自己前面做了什么。3.2 工具注册与调用协议工具是 Agent 改变世界的手段。为了让模型知道有哪些工具可用我们需要把每个工具的“元信息”传给模型包括工具名称、功能描述、参数结构。在实现时可以采用一个简单的注册表TOOL_REGISTRY {}每个工具通过装饰器注册进去。模型在推理时如果觉得需要执行某个工具就按照提示词要求的 JSON 格式返回。harness 解析出工具名和参数后从注册表里找到对应函数并执行。这种动态注册的好处是扩展性很强。业务侧新增一个能力只需要实现一个普通函数然后加上注册装饰器模型就能在下一轮调度中感知并调用它。3.3 上下文管理与终止条件对话历史如果无限增长最终会超过模型的上下文窗口。因此 harness 需要设定上下文策略只保留与当前任务相关的核心历史。对工具返回的大段文本做截断。可以设置历史消息的最大条数超出的部分做摘要。设置最大迭代次数防止模型陷入死循环。终止条件一般有两种模型显式返回最终答案或者达到迭代次数上限。后一种情况说明模型的行动链没有收敛harness 应该给出明确提示而不是静默返回一个不完整结果。3.4 错误恢复机制真实环境下工具执行可能失败模型返回的 JSON 可能无法解析API 可能超时。一个健壮的 harness 必须考虑这些异常。常见的错误恢复策略有对工具调用代码使用 try-except把异常信息转成文本回填给模型继续推理。对模型输出解析失败时重新请求模型并在提示词中强调格式要求。对 API 超时做有限次数重试重试间隔采用退避策略。设置全局超时时间避免任务一直挂起。这些策略看起来简单但在 Agent 工程中非常关键。错误恢复做得好不好直接决定了 Agent 是“偶尔能用”还是“稳定可用”。4. 实战基于 DeepSeek 接口实现一个最小 Harness4.1 配置环境变量在项目根目录创建.env文件DEEPSEEK_API_KEY你的API密钥 DEEPSEEK_BASE_URLhttps://api.deepseek.com DEEPSEEK_MODELdeepseek-chat如果你使用本地部署的 OpenAI 兼容服务比如基于 vLLM 或 Ollama 启动的服务可以把DEEPSEEK_BASE_URL改为本地服务地址DEEPSEEK_MODEL改为本机模型名称。这样上层代码不需要改动。注意.env文件不要提交到 Git 仓库。生产环境建议通过配置中心或 K8s Secret 注入环境变量。4.2 封装模型客户端创建src/llm_client.pyimport os from openai import OpenAI class LLMClient: 统一封装大模型调用兼容 OpenAI 协议接口。 def __init__(self): api_key os.getenv(DEEPSEEK_API_KEY, ) base_url os.getenv(DEEPSEEK_BASE_URL, https://api.deepseek.com) self.model os.getenv(DEEPSEEK_MODEL, deepseek-chat) self.client OpenAI(api_keyapi_key, base_urlbase_url) def chat(self, messages, temperature0.7): 发送对话消息返回文本内容。 response self.client.chat.completions.create( modelself.model, messagesmessages, temperaturetemperature, ) return response.choices[0].message.content这里核心点是复用 OpenAI SDK。只要模型服务兼容 OpenAI 协议这段代码就可以在 API 服务和本地服务之间无缝切换。4.3 设计工具注册表创建src/tools.pyimport datetime import json import subprocess from pathlib import Path TOOL_REGISTRY {} def register_tool(name, description, parameters): 工具注册装饰器。 def decorator(func): TOOL_REGISTRY[name] { function: func, description: description, parameters: parameters, } return func return decorator register_tool( get_current_time, 获取当前系统时间返回格式为 YYYY-MM-DD HH:MM:SS, {type: object, properties: {}, required: []}, ) def get_current_time(**kwargs): return datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) register_tool( read_file, 读取本地文本文件内容path 参数为文件绝对路径, { type: object, properties: { path: {type: string, description: 文件绝对路径} }, required: [path], }, ) def read_file(path): file_path Path(path) if not file_path.exists(): raise FileNotFoundError(f文件不存在: {path}) if file_path.stat().st_size 4096: return 文件过大只允许读取 4KB 以内的文件 return file_path.read_text(encodingutf-8) register_tool( run_shell_command, 执行 shell 命令返回执行结果。注意该工具仅用于测试环境生产环境必须严格控制权限。, { type: object, properties: { command: {type: string, description: shell 命令} }, required: [command], }, ) def run_shell_command(command): result subprocess.run( command, shellTrue, capture_outputTrue, textTrue, timeout10, ) if result.returncode ! 0: raise RuntimeError(f命令执行失败: {result.stderr}) return result.stdout.strip() def get_tool_system_prompt(): 根据注册表生成系统提示词中的工具描述。 tools_desc [] for name, meta in TOOL_REGISTRY.items(): tools_desc.append( f- {name}: {meta[description]}参数 JSON Schema: {json.dumps(meta[parameters], ensure_asciiFalse)} ) return \n.join(tools_desc) def execute_tool(name, args): 执行工具把错误转为文本返回避免中断 Agent 循环。 if name not in TOOL_REGISTRY: return fERROR: 未知工具 {name} try: return TOOL_REGISTRY[name][function](**args) except Exception as e: return fERROR: 工具执行失败请更换方案或修正参数。异常信息: {str(e)}这里把工具执行异常捕获住并转成文本回传给模型。这个设计非常重要它让模型具备基于错误信息自我纠正的机会而不是整个 Agent 直接崩溃。4.4 实现 ReAct 循环创建src/harness.pyimport json from llm_client import LLMClient from tools import get_tool_system_prompt, execute_tool, TOOL_REGISTRY SYSTEM_PROMPT 你是一个运行在 Agent harness 中的 AI 助手。 你可以使用以下工具来帮助用户完成任务 {tools} 当需要使用工具时严格输出如下 JSON 格式 {{type: tool_call, tool: 工具名, parameters: {{参数名: 参数值}}}} 当已经可以给出最终答案时输出 {{type: final, answer: 你的最终回答}} 注意你的输出必须是一个合法的 JSON 对象不能包含其他文字。 class Harness: def __init__(self, max_iterations6): self.client LLMClient() self.max_iterations max_iterations self.history [] def run(self, user_task): system_content SYSTEM_PROMPT.format( toolsget_tool_system_prompt() ) self.history [ {role: system, content: system_content}, {role: user, content: user_task}, ] for step in range(1, self.max_iterations 1): print(f\n[Step {step}] 正在调用模型...) model_output self.client.chat(self.history) try: parsed json.loads(model_output) except json.JSONDecodeError: # 模型输出不合法要求它重新输出 self.history.append({role: assistant, content: model_output}) self.history.append({ role: user, content: 你的输出不是合法 JSON请只输出要求的 JSON 格式。, }) continue if parsed.get(type) final: return parsed[answer] if parsed.get(type) tool_call: tool_name parsed.get(tool, ) parameters parsed.get(parameters, {}) print(f[Step {step}] 调用工具: {tool_name}参数: {parameters}) result execute_tool(tool_name, parameters) print(f[Step {step}] 工具返回: {result[:200]}) self.history.append({role: assistant, content: model_output}) self.history.append({ role: user, content: ( f工具 {tool_name} 执行结果如下\n{result}\n 请根据结果继续思考。如果已经完成任务请直接返回 final 结果。 ), }) else: self.history.append({role: assistant, content: model_output}) self.history.append({ role: user, content: 未识别的输出类型请只输出 tool_call 或 final 两种 JSON。, }) return f已达到最大迭代次数 {self.max_iterations}任务未能收敛。请拆分任务后重试。4.5 编写命令行入口创建src/main.pyimport os import sys from dotenv import load_dotenv from harness import Harness load_dotenv() def main(): if len(sys.argv) 2: print(用法: python main.py 你的任务描述) return task sys.argv[1] harness Harness(max_iterations6) answer harness.run(task) print(\n最终回答, answer) if __name__ __main__: main()运行命令cd src python main.py 现在是几点预期输出会经过几个步骤[Step 1] 正在调用模型... [Step 1] 调用工具: get_current_time参数: {} [Step 1] 工具返回: 2025-05-18 14:30:25 [Step 2] 正在调用模型... 最终回答 当前时间是 2025-05-18 14:30:25。这个最小示例已经具备 Agent harness 的完整骨架。你可以在tools.py中继续增加自定义工具比如查询数据库、调用内部接口、处理 Excel 文件等模型会自动学会在任务需要时调用它们。5. 本地部署 DeepSeek 模型的 Harness 接入5.1 本地推理服务的选择很多人既想体验 Agent 开发又不希望每次调用都产生 API 费用于是会选择本地部署 DeepSeek 模型。常见的本地推理工具有 vLLM、Ollama、llama.cpp 等。它们都对外提供服务有的兼容 OpenAI 接口有的需要额外适配层。在实践中最省力的方式是选择支持 OpenAI 兼容接口的服务。这样我们上一节写的LLMClient完全不需要改动只需要调整.env配置。5.2 切换 base_url假设你在本机的11434端口启用了 Ollama并且已经拉取了合适的模型那么.env可以这样配置DEEPSEEK_API_KEYnot-needed-for-local DEEPSEEK_BASE_URLhttp://localhost:11434/v1 DEEPSEEK_MODELdeepseek-r1:7b这里需要注意Ollama 的 OpenAI 兼容地址通常是http://localhost:11434/v1。模型名称必须和本地模型库中的实际名称一致不同版本标签不同。本地部署受硬件性能影响较大建议选择量化版本否则推理速度会非常慢。如果你的本地推理工具不是 OpenAI 兼容协议需要写一个适配客户端类实现相同的chat(messages)方法替换掉LLMClient。这也是我们把客户端独立封装的价值。5.3 本地模型与 API 模型的效果差异本地部署通常不会使用满血版本而是量化后的中小模型。这意味着推理能力尤其是复杂工具调度能力会有所下降。Agent 任务越复杂本地小模型的失败率越高。建议在业务中采用混合架构简单的任务由本地模型处理复杂的推理、多步工具调度、敏感逻辑判断由 API 模型兜底。harness 层面可以根据任务类型切换不同的模型。6. 常见问题与排查思路6.1 Agent execution terminated due to error这个问题在不少 Agent 框架中都会出现。报错本身包含两个信息执行已经终止原因是 error。真正要排查的是 error 前面的真实异常。常见原因包括工具函数中抛出了未捕获异常。模型连续多轮输出的 JSON 格式不合法。API 调用超时。上下文过长超过了模型窗口。排查建议按顺序来看日志中第一次出现 error 的位置。确认是模型调用报错还是工具执行报错。如果是工具执行报错把错误信息回传给模型让它重新选择参数或换一个工具。如果是模型输出解析失败检查提示词中的格式要求。不要盲目增加重试次数先找到根因。在 harness 层面建议对每一次工具调用和模型调用都打印耗时、输入摘要、输出摘要形成可追踪的链路日志。6.2 API 返回 401 错误原因基本是 API Key 无效。可能的情况是.env中配置的密钥带了空格或者使用了本地部署服务的 key 去请求远程 API又或者密钥已过期。排查步骤打印环境变量的长度排除读取失败。确认配置文件在项目根目录并且load_dotenv()在创建客户端之前执行。在服务商控制台重新生成密钥后更新环境变量。6.3 模型输出的 JSON 无法解析这是 Agent 开发中非常常见的问题主要原因有两个。一是模型本身没有严格遵守指令二是提示词中没有给出明确的错误恢复指令。解决方案在系统提示词中增加一段说明要求模型只输出 JSON解析失败时把解析异常信息作为用户消息回传给模型提示它修正输出。我们的 harness 示例已经实现了这个策略。更稳妥的方案是使用模型厂商提供的 function calling / tools 参数让 API 自己完成结构化输出而不是依赖模型手写 JSON。如果模型支持原生 function calling建议优先使用。6.4 上下文超长Agent 多轮调用工具后历史消息会累积得很长尤其是读取文件、查询数据库这类工具返回值很大。当超过上下文窗口时API 会返回报错。解决方案包括在工具返回前做内容截断比如只返回前 200 个字符。对历史消息做滑动窗口只保留最近几轮。使用大模型的摘要能力把旧对话压缩成摘要。设置更小的最大迭代次数从设计上避免上下文爆炸。6.5 一定要设置最大迭代次数没有最大迭代次数的 Agent 是危险的。模型可能由于错误反馈陷入循环反复调用同一个工具导致 API 费用飙升或服务资源被打满。我们的 harness 已经实现了max_iterations。生产环境建议设置为 5 到 10同时设置整体任务超时时间。如果发现很多任务都无法在限制次数内完成优先优化工具设计和提示词而不是简单调大次数。7. 最佳实践与工程建议7.1 工具函数必须控制安全边界Agent 最大的风险来源是工具权限。如果允许 Agent 执行任意 shell 命令那么一次提示词注入攻击或一次模型幻觉就可能造成严重事故。在工程上至少要做到禁止 Agent 直接访问生产数据库。shell 命令执行必须白名单化只允许预定义的命令集合。文件读写限定在指定的临时目录。所有敏感操作需要人工审批后执行。工具函数内做参数校验不信任模型输出的参数。7.2 日志与可观测性Agent 是异步多步执行的系统日志决定你能否定位问题。建议每轮循环都输出步数。模型传入的完整消息条数。解析出的工具名和参数。工具执行耗时。工具返回结果截断。是否触发重试。如果使用 Langfuse、LangSmith 等可观测性平台可以把 tracing 信息接入其中。即使不使用这些平台也要自己设计结构化的日志格式。{ trace_id: xxx, step: 2, event: tool_call, tool_name: read_file, cost_ms: 35 }7.3 配置管理不要把 API Key、模型名称、迭代次数、超时时间写死在代码里。使用环境变量、配置文件或配置中心统一管理。AGENT_MAX_ITERATIONS6 AGENT_TIMEOUT_SECONDS120 TOOL_SHELL_ENABLEDfalse TOOL_SAFE_TEMP_DIR/tmp/agent-workspace配置项之间要区分环境本地开发、测试环境、生产环境使用不同的配置组。7.4 成本与延迟控制Agent 多次调用模型成本不是单次请求的成本而是整个任务链路的总成本。控制成本可以从三个维度入手模型层面简单任务使用轻量模型复杂任务才使用强推理模型。上下文层面减小每轮请求的 tokens能截断就截断。迭代层面减少无效重试加入容错逻辑避免模型反复试探错误路径。延迟同理一个 6 步任务如果每步耗时 10 秒整体就是 1 分钟。对用户来说这个感知很明显。建议提供流式输出或任务进度反馈避免用户以为程序卡死。7.5 CLI 通用规范如果你打算把 Agent 封装成命令行工具可以参照社区中逐渐形成的 CLI 约定子命令统一agent run、agent build、agent test。支持--model、--max-iterations、--verbose等通用参数。输出结果支持 JSON 格式便于脚本调用。退出码语义化0 表示成功1 表示任务失败2 表示参数错误3 表示超时。这样封装出来的工具更容易集成到 CI/CD 流水线中。7.6 从 Demo 到生产的差距很多人写好一个 Agent demo 后发现生产环境问题很多。差距主要体现在demo 不需要考虑并发生产需要。demo 只服务一个用户生产可能有多个租户。demo 不会遇到模型限流生产需要做熔断降级。demo 不需要审计生产需要完整留痕。如果要把 Agent 能力发布到生产环境建议先做小流量的灰度持续观察任务成功率、平均迭代轮数、工具的失败率。在数据沉淀充分之前不要贸然开放全量。8. 后续学习建议如果你想深入 Agent 开发接下来的学习顺序可以这样安排。第一研究 ReAct 论文和 Tool Calling 的官方文档理解模型是如何从文本中结构化输出工具调用意图的。第二阅读几个主流 Agent 框架的源码观察它们如何实现工具注册、错误重试、上下文管理。第三分类练习工具开发比如读写文件、请求 HTTP 接口、操作数据库、执行外部命令。第四做任务评估准备一组标准问题集衡量 Agent 的成功率和耗时变化。第五关注模型能力的更新当新版本模型发布时优先验证它在工具调用稳定性上的表现。Agent 开发并不会止步于“让模型调用一个工具”。真正难的是让一个由多步决策组成的任务链稳定地完成并能够在出错时自动恢复。harness 模式的本质就是在模型能力和复杂业务之间铺上一层可控、可观测、可干预的工程底座。如果你按照本文的思路成功跑通了自己的第一个 DeepSeek Harness 项目下一步可以尝试接入更多业务工具把它变成真正能处理日常事务的自动化助手。遇到问题不要急着堆代码先看日志再改提示词最后才改逻辑。这套排查顺序能帮你省下大量时间。