2026年了,AI 应用开发已经从前两年的“调 API 跑 demo”进入到“把模型真正嵌进业务系统”的阶段。很多同学在 B 站刷到了 Harness 相关视频,看完觉得每个点都懂,但真要自己动手实现一套可控的 Agent 应用,还是会卡在工具编排、上下文管理、安全拦截这些细节上。网上关于 Harness 的讲解比较零散,有讲 CI/CD 的,有讲测试脚手架,也有讲大模型 Agent 运行时的,混在一起很容易看晕。
这篇文章会把 Harness 架构重新梳理成一套完整的学习路径:先搞清楚它到底解决什么问题,再拆解核心组件,然后从零实现一个轻量级 LLM Agent Harness,最后补充高频面试题和工程最佳实践。无论你是在准备大模型岗位面试,还是想把 DeepSeek、Codex 这类模型工具落地到实际项目,都可以直接参考这套思路。
1. 背景与核心概念
1.1 Harness 在不同技术语境下的含义
“Harness”这个词本身有“捆绑、控制、驾驶”的意思,在不同技术领域里有不同指向,刚接触时容易混淆。
- 在 CI/CD 领域,Harness 是一个持续交付平台产品,解决部署流水线、灰度发布、变更审批等问题。
- 在软件测试领域,Test Harness 通常指自动化测试的脚手架,负责启动被测系统、加载数据、执行用例并汇总结果。
- 在大模型应用开发领域,Harness 通常指围绕 LLM 和 Agent 打造的“运行时支撑层”,它把模型调用、工具注册、上下文管理、安全策略、日志观测等横切逻辑收拢在一起,让开发者不用每次从零搭一套轮子。
本文讨论的是第三种,也就是大模型应用与 Agent 开发中的 Harness 架构。你可能还听过 DeepSeek Harness、Codex Harness 这类社区项目,它们的命名逻辑也类似:在某个具体模型或 CLI 工具之上,再封装一层可编排、可扩展、可监控的运行框架。
1.2 为什么大模型应用开发需要 Harness
先看一个常见场景:你要做一个能查天气、能算数、能查数据库的智能助手。如果用最原始的方式写,通常要处理下面几类问题:
- 每次调用模型前,都要手动拼系统提示词、历史上下文、用户问题。
- 模型返回了工具调用指令,你要自己解析 JSON 参数,再写一堆 if-else 分发到不同函数。
- 工具执行结果要重新拼回对话,让模型继续推理,这个过程很容易出现字段格式错误。
- 用户连续对话几十轮后,上下文越来越长,Token 成本越来越高,需要想办法裁剪或压缩。
- 工具权限一旦配置不当,模型可能被诱导执行危险操作,比如删文件、改数据。
这些问题看起来不大,但每接入一个新业务场景就重复一遍,代码会迅速腐化。Harness 的价值就是把这层能力做成标准化的运行时组件:
- 对上层业务提供统一入口。
- 对下层模型提供兼容适配。
- 对工具函数提供注册和调度机制。
- 对安全和观测提供统一策略。
这也是为什么现在很多团队在招聘大模型应用开发工程师时,特别喜欢问 Harness、Agent 架构、工具调用循环相关的问题。因为面试官想确认的,不只是你会不会调 API,而是你有没有把工程问题系统化的能力。
1.3 本文会讲清楚哪些内容
读完本文,你会掌握以下内容:
- Harness 架构在 LLM Agent 开发中的定位和边界。
- 六大核心组件的职责与最小实现。
- 一个可运行的轻量级 Agent Harness 项目,包含模型接入、工具注册、上下文管理、安全拦截和主循环。
- 常见报错的排查思路,例如 tool_calls 解析失败、上下文超限、安全策略误伤。
- 大模型 Agent 方向的高频面试题与答题思路。
- 生产环境落地的安全、性能、成本建议。
2. 环境准备与版本说明
2.1 运行环境
本文示例使用 Python 编写,建议环境如下:
- 操作系统:Windows / macOS / Linux 均可,命令略有差异。
- Python 版本:3.10 或更高版本。
- 模型接口:需要有一个 OpenAI 兼容的模型服务接口,例如你使用的模型平台提供的 API。
- SDK:建议安装
openai库,版本以 1.x 为主。
需要说明的是,模型平台和 SDK 版本迭代比较快,不同版本的参数名和返回结构可能不同。如果后续官方接口有变化,请优先以你使用的模型平台文档和已安装 SDK 的实际定义为准,不要死记硬背下面的参数名。
2.2 项目结构规划
为了让代码更清晰,我们按模块拆分,最终目录结构如下:
my_harness/ ├── main.py ├── requirements.txt ├── harness/ │ ├── __init__.py │ ├── adapter.py │ ├── registry.py │ ├── memory.py │ ├── guardrails.py │ └── agent.py └── tools/ ├── __init__.py └── example_tools.pyadapter.py:模型接入层,负责封装模型 API 调用。registry.py:工具注册层,负责收集函数、生成 JSON Schema、按名称调用工具。memory.py:上下文管理层,负责保存用户与模型的对话记录。guardrails.py:安全策略层,负责在工具调用前做参数校验。agent.py:Agent 主循环,把所有组件串起来。
2.3 安装依赖
在项目根目录执行:
pip install openai python-dotenv如果网络环境特殊,需要配国内镜像源,可以用:
pip install openai python-dotenv -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后,建议在项目根目录创建.env文件,用来保存模型 API Key 和接口地址,不要把密钥写死在代码里。
LLM_API_KEY=你的_API_Key LLM_BASE_URL=https://你的模型服务地址 LLM_MODEL=deepseek-chat注意:.env文件要加入.gitignore,防止密钥被提交到 Git 仓库。
3. Harness 架构的核心组件拆解
一个相对完整的 LLM Agent Harness,核心组件可以拆成六层。下面逐层说明职责和最小实现思路。
3.1 模型接入层:把模型差异挡在外面
模型接入层(Model Adapter)是所有功能的地基。它的主要作用是把不同模型服务商的 API 差异封装起来,让上层代码只依赖一个统一的chat()方法。
实际开发中,你可能会遇到这样的问题:
- 今天用的是 DeepSeek,明天要切换到其他兼容模型。
- 同一个模型服务商又可能发布多个模型版本。
- 不同模型的请求参数、返回字段结构不完全一致。
如果业务代码里到处都是直接调用 SDK 的语句,切换模型时会非常痛苦。封装成 Adapter 之后,上层只需要传messages和可选的tools,不需要关心底层是哪个平台。
最小示例思路如下:
from openai import OpenAI class ModelAdapter: def __init__(self, api_key: str, base_url: str, model: str): self.client = OpenAI(api_key=api_key, base_url=base_url) self.model = model def chat(self, messages, tools=None): params = { "model": self.model, "messages": messages, } if tools: params["tools"] = tools params["tool_choice"] = "auto" response = self.client.chat.completions.create(**params) return response.choices[0].message3.2 工具注册层:把业务能力变成模型可调用的函数
大模型本身不能执行任意操作,它只能“提出要调用某个工具”。真正执行的仍然是业务代码。工具注册层(Tool Registry)负责两件事:
- 把 Python 函数转换成模型需要的函数描述,也就是 JSON Schema。
- 根据模型返回的工具名称和参数,找到对应函数并执行。
为什么不写死 if-else?因为工具一多,if-else 分发的代码会非常冗长,而且新增工具时容易漏改。Registry 的直观优势是:注册一个函数,自动生成 schema,自动按名称调度。
最小示例思路如下:
import inspect import json class ToolRegistry: def __init__(self): self._tools = {} def register(self, func): self._tools[func.__name__] = func return func def call(self, name: str, arguments_text: str): if name not in self._tools: raise ValueError(f"未知工具: {name}") args = json.loads(arguments_text) return self._tools[name](**args)3.3 上下文管理层:让模型记住关键信息又不超限
无状态模型本身没有记忆,多轮对话依赖我们把历史消息重新传给它。上下文管理层(Conversation Memory)负责历史消息的保存、拼装和裁剪。
在实际项目中,内存列表只是一种最简单的方案。更复杂的实现会有:
- 滑动窗口:只保留最近 N 轮消息。
- 摘要压缩:把超过窗口的历史信息用模型总结成摘要。
- 向量检索:从长历史中只取与当前问题相关的消息片段。
- 数据库持久化:把会话保存到 Redis 或 MySQL,支持用户跨会话恢复。
设计上下文管理层时,要特别关注“裁剪后不能破坏对话结构”。比如系统消息必须始终在消息列表最前面,工具调用和工具结果要成对出现,否则模型可能产生幻觉。
3.4 执行循环:Agent 的“大脑转动机制”
Agent 的执行循环是整个 Harness 的调度核心,它决定了模型和工具之间如何互动。标准循环通常是:
- 把当前消息列表发给模型。
- 判断模型返回值中是否存在
tool_calls字段。 - 如果没有工具调用,说明模型给出了最终答案,循环结束。
- 如果有工具调用,依次执行工具,并把工具结果以
role="tool"的消息追加回对话。 - 带着工具结果再次请求模型,继续步骤 2。
这个循环一般需要设置最大步数,防止模型陷入“反复调用工具”的死循环。生产环境中,最大步数通常控制在 5 到 10 步,同时配合超时和重试机制。
3.5 安全策略层:给工具调用加一道门禁
模型生成的工具调用不一定都是合理、安全的。比如用户通过提示词注入让 Agent 去执行DROP TABLE,或者删除生产环境关键文件。安全策略层(Guardrails)的作用,就是在执行工具前做参数校验、权限校验和风险拦截。
常见做法包括:
- 黑名单关键词匹配,例如
rm -rf、DROP TABLE。 - 强制工具参数只能来自结构化 JSON,不让模型直接拼字符串命令。
- 高权限操作必须经过人工审批接口。
- 工具执行环境使用沙箱或容器隔离。
- 全链路审计日志,记录谁在什么时间调用了什么工具。
安全策略是容易被新手忽略,但在面试和生产中极其重要的部分。
3.6 观测与日志层:让 Agent 行为可复现、可追踪
Agent 应用比普通接口更难排查问题,因为同一个用户问题,模型可能每次输出不同的工具调用路径。如果没有日志和链路追踪,出问题很难复现。
生产级 Harness 至少需要记录:
- 每次请求的消息列表。
- 模型返回的原始响应。
- 工具调用名称、参数、结果。
- 每轮循环的耗时和 Token 消耗。
- 安全拦截命中的策略。
有了这些数据,才能做回归测试、成本分析和故障复盘。
4. 完整实战案例:从零实现一个轻量级 Agent Harness
下面我们把上面的组件串起来,实现一个可以在本地运行的最小 Harness。示例中会注册两个工具:获取当前时间、两个整数相加。同时演示安全策略如何拦截高风险工具调用。
4.1 创建项目结构和依赖文件
先创建目录和依赖文件:
mkdir my_harness cd my_harness mkdir harness tools touch harness/__init__.py tools/__init__.pyrequirements.txt内容:
openai>=1.0 python-dotenv4.2 模型接入层实现
文件路径:my_harness/harness/adapter.py
import os from openai import OpenAI class ModelAdapter: """模型接入层:封装 OpenAI 兼容接口的调用。""" def __init__(self, api_key: str = None, base_url: str = None, model: str = None): self.client = OpenAI( api_key=api_key or os.getenv("LLM_API_KEY"), base_url=base_url or os.getenv("LLM_BASE_URL"), ) self.model = model or os.getenv("LLM_MODEL", "deepseek-chat") def chat(self, messages, tools=None): params = { "model": self.model, "messages": messages, } if tools: params["tools"] = tools params["tool_choice"] = "auto" response = self.client.chat.completions.create(**params) return response.choices[0].message这里要注意:api_key和base_url支持从构造参数传入,也支持从环境变量读取。环境变量方式更适合生产环境。
4.3 工具注册层实现
文件路径:my_harness/harness/registry.py
import inspect import json class ToolRegistry: """工具注册层:根据函数签名自动生成 schema,并支持按名称调用。""" def __init__(self): self._tools = {} def register(self, func): self._tools[func.__name__] = func return func def get_schemas(self): schemas = [] for name, func in self._tools.items(): schema = { "type": "function", "function": { "name": name, "description": (func.__doc__ or "").strip(), "parameters": { "type": "object", "properties": {}, "required": [], }, }, } params = schema["function"]["parameters"] sig = inspect.signature(func) for param_name, param in sig.parameters.items(): if param.annotation is int: params["properties"][param_name] = {"type": "integer"} else: params["properties"][param_name] = {"type": "string"} if param.default is inspect.Parameter.empty: params["required"].append(param_name) schemas.append(schema) return schemas def call(self, name: str, arguments_text: str): if name not in self._tools: raise ValueError(f"未知工具: {name}") args = json.loads(arguments_text) if arguments_text else {} return self._tools[name](**args)这个实现简化为只支持int和str类型,实际项目中你可以引入pydantic或手写更完整的类型映射,支持嵌套对象、数组、枚举等。
4.4 上下文管理与安全策略实现
文件路径:my_harness/harness/memory.py
class ConversationMemory: """上下文管理层:维护对话消息列表,并做简单的滑动窗口裁剪。""" def __init__(self, max_turns: int = 10): self.messages = [] self.max_turns = max_turns def reset(self): self.messages = [] def add_system(self, content: str): self.messages.append({"role": "system", "content": content}) def add_user(self, content: str): self.messages.append({"role": "user", "content": content}) self._trim() def add_assistant(self, content: str = None, tool_calls=None): msg = {"role": "assistant"} if content: msg["content"] = content if tool_calls: msg["tool_calls"] = tool_calls self.messages.append(msg) self._trim() def add_tool_result(self, tool_call_id: str, content: str): self.messages.append({ "role": "tool", "tool_call_id": tool_call_id, "content": content, }) self._trim() def get_messages(self): return self.messages def _trim(self): system_msgs = [m for m in self.messages if m["role"] == "system"] other_msgs = [m for m in self.messages if m["role"] != "system"] if len(other_msgs) > self.max_turns * 2: other_msgs = other_msgs[-(self.max_turns * 2):] self.messages = system_msgs + other_msgs文件路径:my_harness/harness/guardrails.py
SENSITIVE_WORDS = [ "rm -rf", "drop table", "truncate table", "delete from", ] def validate_tool_call(tool_name: str, arguments: dict) -> bool: """安全策略层:在工具执行前做参数风险校验。""" if tool_name in {"execute_shell", "execute_sql", "restart_service"}: raw = " ".join(str(value).lower() for value in arguments.values()) for word in SENSITIVE_WORDS: if word in raw: return False return True这里必须强调一下:上面的SENSITIVE_WORDS只是演示,实际项目的安全策略必须更完整。你还需要考虑用户身份权限、IP 白名单、敏感接口白名单、人工审批流程等因素,而不是只做关键词匹配。
4.5 Agent 主循环实现
文件路径:my_harness/harness/agent.py
import json from .adapter import ModelAdapter from .registry import ToolRegistry from .memory import ConversationMemory from .guardrails import validate_tool_call class Agent: """Agent 主循环:串联模型接入、工具调用、上下文管理和安全策略。""" def __init__( self, model_adapter: ModelAdapter, tool_registry: ToolRegistry, system_prompt: str = None, max_steps: int = 5, ): self.adapter = model_adapter self.tools = tool_registry self.memory = ConversationMemory() self.system_prompt = system_prompt or "你是一个有用的助手,可以调用工具完成任务。" self.memory.add_system(self.system_prompt) self.max_steps = max_steps def run(self, user_input: str): self.memory.add_user(user_input) for _ in range(self.max_steps): message = self.adapter.chat( self.memory.get_messages(), tools=self.tools.get_schemas(), ) # 情况一:模型没有要求调用工具,说明可以输出最终答案 if not message.tool_calls: self.memory.add_assistant(content=message.content) return message.content # 情况二:模型要求调用工具,先把 assistant 消息保存进历史 self.memory.add_assistant( tool_calls=[ { "id": tc.id, "type": "function", "function": { "name": tc.function.name, "arguments": tc.function.arguments, }, } for tc in message.tool_calls ] ) # 逐个执行工具调用 for tc in message.tool_calls: try: arguments = json.loads(tc.function.arguments) if tc.function.arguments else {} except json.JSONDecodeError: arguments = {} if not validate_tool_call(tc.function.name, arguments): self.memory.add_tool_result( tc.id, "该工具调用已被安全策略拦截,请换一种方式完成用户请求。", ) continue try: result = self.tools.call(tc.function.name, tc.function.arguments) self.memory.add_tool_result(tc.id, str(result)) except Exception as e: self.memory.add_tool_result(tc.id, f"工具执行失败: {e}") return "已达到最大执行步数,请简化问题或检查工具配置。"这个循环比较完整地展现了 Agent 的核心逻辑。在真实项目中,你可能还会加入“调用模型前先检查记忆是否需要压缩”“大模型返回内容过长时截断”“工具执行结果过大时只保留摘要”等处理。
4.6 工具函数与主程序入口
文件路径:my_harness/tools/example_tools.py
from datetime import datetime def get_current_time(): """返回当前服务器时间,格式为 YYYY-MM-DD HH:MM:SS。""" return datetime.now().strftime("%Y-%m-%d %H:%M:%S") def add(a: int, b: int): """对两个整数做加法运算。""" return a + b文件路径:my_harness/main.py
import os from dotenv import load_dotenv from harness.adapter import ModelAdapter from harness.registry import ToolRegistry from harness.agent import Agent from tools.example_tools import get_current_time, add def main(): load_dotenv() registry = ToolRegistry() registry.register(get_current_time) registry.register(add) adapter = ModelAdapter( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL"), model=os.getenv("LLM_MODEL"), ) agent = Agent( model_adapter=adapter, tool_registry=registry, system_prompt="你是一个会调用工具的助手。" "如果需要获取当前时间,请调用 get_current_time;" "如果需要计算两个整数之和,请调用 add。", ) print("Harness Agent 已启动,输入 exit 退出。") while True: user_input = input("你:") if user_input.strip().lower() in {"exit", "quit"}: break answer = agent.run(user_input) print("助手:", answer) if __name__ == "__main__": main()4.7 运行与验证
在项目根目录执行:
python main.py如果你配置好了.env中的接口地址和 Key,并且模型支持函数调用,控制台会进入交互模式。比如输入:
你:现在几点了?模型很可能返回一个tool_calls,要求调用get_current_time,然后 Harness 执行工具并把时间返回给模型,最终模型把结果整理成自然语言返回:
助手:当前时间是 2026-01-15 10:24:33。如果输入:
你:计算 12 + 30模型会调用add(12, 30),最终输出42。
这里需要说明:由于不同模型和不同版本的 API 行为可能存在差异,如果你发现模型始终不调用工具,建议先检查system_prompt是否明确描述了工具的使用场景,再检查工具 schema 是否生成正确。
5. 常见问题与排查思路
5.1 常见问题速查表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 模型不调用工具 | system prompt 没有说明工具用途,或模型本身不支持函数调用 | 优化提示词,确认所选模型支持 tool calling |
解析tool_calls报错 | SDK 版本不同,message 对象结构有差异 | 打印原始响应,以实际 SDK 返回结构为准 |
| 工具参数 JSON 解析失败 | 模型返回的 arguments 不是严格 JSON,可能被截断 | 增加异常捕获,尝试二次修复或提示模型重试 |
| 上下文越来越长,费用涨得很快 | 没有做历史消息裁剪或摘要压缩 | 使用滑动窗口、摘要压缩、向量检索 |
| 工具执行结果很大,模型反而回答不好 | 工具结果塞入上下文太多,干扰模型注意力 | 对工具结果做截断、摘要或转成结构化数据 |
| 安全策略误伤正常请求 | 黑名单匹配太粗糙 | 改为白名单校验,完善参数结构约束和权限模型 |
5.2 模型返回异常时的打印调试法
遇到任何tool_calls解析问题,第一步不是猜原因,而是把模型的原始返回结构打印出来。例如在adapter.chat()方法中临时加一行:
print(response.choices[0].message)然后对比你当前使用的 SDK 版本返回的字段结构,确认tool_calls是列表还是对象、arguments是字符串还是字典。版本差异导致的字段变化,是这类问题最常见的来源。
5.3 工具调用成功但最终回答不理想
这种情况通常不是 Harness 循环的问题,而是工具返回结果的表达方式不对。比如工具返回的是一个很长的 Python dict,直接str(result)塞回对话,模型可能难以理解关键信息。更推荐的做法是:
- 让工具返回简洁的摘要性文本。
- 把 JSON 数据整理成表格或 key-value 形式。
- 特别重要的指标放在最前面。
5.4 安全拦截导致 Agent 无法完成任务
如果你在validate_tool_call中拦截了某个工具调用,Agent 会把拦截结果回传给模型,模型通常会自动调整策略。但要注意,过于严格的策略可能导致 Agent 频繁“绕路”,影响体验。建议在开发阶段开启详细日志,把每次拦截命中的策略名称、参数内容、触发原因都记录下来,方便调整规则。
6. 面试技巧与高频面试题
6.1 面试官问 Harness 时,到底在考察什么
大模型应用开发岗位的面试中,Harness 类问题通常不是背概念,而是考察三件事:
- 你有没有真正写过 Agent 应用,对模型交互循环是否熟悉。
- 你能不能把工程化问题拆解成可设计的组件。
- 你有没有安全意识,能不能应对提示词注入、权限滥用这些真实风险。
所以回答问题的时候,不要只背定义,最好结合项目经历讲。比如“我在项目里负责了工具注册层,遇到某个参数类型映射问题,最终通过扩展 schema 生成器解决”,这种表达远胜于“工具注册层就是注册函数的”。
6.2 高频面试题与参考作答
面试题 1:什么是 Harness 架构?它解决什么问题?
参考回答:Harness 是围绕大模型应用的运行时支撑层,把模型接入、工具编排、上下文管理、安全策略和观测日志等能力收拢成统一框架。它解决的核心问题是:让开发者更关注业务逻辑,而不是每次都重复处理模型调用和工具调用的细节。
面试题 2:请描述一次完整的 Agent 工具调用循环。
参考回答:用户问题进入后,先写入上下文管理器;然后模型接入层携带消息和工具 schema 发起请求;模型返回结果后判断是否存在tool_calls;如果存在,把 assistant 消息保存到历史,再逐个执行工具并回填tool消息;然后带着新消息再次请求模型;直到模型返回最终文本或无工具调用为止。整个过程需要设置最大步数,防止无限循环。
面试题 3:上下文超出模型 Token 限制时你会怎么处理?
参考回答:优先做消息裁剪,只保留最近 N 轮;更早但重要的历史可以生成摘要放回系统提示词;如果业务涉及大量知识库内容,则用向量检索只取与当前问题相关的片段。另外,对工具返回结果也要做截断,因为工具结果过大同样是 Token 消耗的大头。
面试题 4:模型返回的 tool_calls 参数不是合法 JSON,你会怎么处理?
参考回答:第一步是异常捕获,避免整个 Agent 崩溃;第二步是尝试从原始文本中提取 JSON 片段,例如用正则截取第一个{到最后一个};如果仍失败,可以让模型重新生成参数,或者把错误信息回传给模型提示其修正;同时记录日志,用于后续分析是哪类问题导致生成的参数不完整。
面试题 5:如何防止 Agent 被提示词注入后执行危险操作?
参考回答:核心原则是最小权限。第一,可执行的工具必须提前注册,不允许模型动态生成任意命令;第二,工具参数必须结构化,避免让模型直接拼接 shell 命令或 SQL;第三,对高危操作设置人工审批;第四,执行环境使用沙箱隔离;第五,全链路审计日志,确保任何可疑操作可追溯。安全是系统工程,不能只靠关键词黑名单。
面试题 6:Harness 如何支持多模型切换?
参考回答:通过模型接入层抽象统一接口,内部封装不同模型服务商的 API 差异。业务代码只依赖统一的chat()/embed()方法,切换模型时只需修改配置或调整 Adapter 实现。生产环境中还可以做模型降级策略,当主模型超时或不可用时,自动切换到备用模型。
面试题 7:如何评估一个 Agent Harness 的好坏?
参考回答:可以从功能、性能、成本、稳定性四个维度评估。功能上考察工具调用成功率、复杂任务完成率;性能上关注端到端延迟和工具执行耗时;成本上关注 Token 消耗和 API 调用次数;稳定性上关注错误恢复率、人工干预率、重复调用率。另外还需要有回归测试集,避免模型版本升级后行为退化。
6.3 面试中展示深度的技巧
面试时如果想在 Harness 话题上拿到加分,不要停留在“我会用 LangChain”这个层面。可以主动聊下面这些细节:
- 你如何处理工具调用的错误重试。
- 你怎么保证工具返回结果和模型历史消息的配对关系。
- 你在生产环境中怎么控制并发和限流。
- 你在日志中记录了哪些字段,方便事后复盘。
- 你有没有遇到过模型版本升级导致工具调用行为变化的情况。
这些内容不需要每个都精通,但只要你能讲出自己在真实项目里踩过的坑和解决方案,面试官对工程能力的判断会明显提高。
7. 最佳实践与工程建议
7.1 安全边界设计要前置
Harness 一旦接入真实业务,安全就不是可选项。这里给出几条容易落地的建议:
- 工具权限要按用户维度隔离,不能所有登录用户都能调用所有工具。
- 工具注册时给每个工具标记风险等级,例如只读、可写、高风险。
- 高危工具调用必须走审批接口,或者需要二级确认。
- 生产环境禁止把完整 API Key 放入前端代码、日志或 Git 历史。
- 定期检查审计日志,发现异常请求要及时复盘。
- 在进行任何删除、更新、重启类操作前,先在测试环境验证,并做好备份。
7.2 配置管理要规范
不要把所有配置散落在代码里。建议把模型地址、模型名称、超时时间、重试次数、会话窗口大小、安全策略开关都放到统一配置中心或.env文件中。不同环境(开发、测试、生产)使用不同的配置项,并确保生产环境的敏感配置有严格的访问控制。
7.3 成本和性能优化
大模型应用的资源消耗比传统接口更高,所以在 Harness 设计中要提前考虑成本控制:
- 设置单用户单会话的最大轮数。
- 对长文本工具结果做截断,而不是原样回传。
- 使用缓存减少重复请求,例如相同问题在短时间内可以命中缓存。
- 合理设置
temperature,避免模型在成本无关的问题上过度发散。 - 对模型调用做聚合统计,每天关注 Token 消耗和失败率变化。
7.4 测试、可观测性和持续迭代
Harness 的核心是模型驱动的动态行为,比传统代码更难测试,但也更需要测试。建议维护一个 Golden Set,也就是一批带预期结果的测试问题,每次调整提示词、升级模型或修改工具逻辑后,都跑一遍回归。
可观测性方面,线上环境建议对每个 Agent 请求生成一个 Trace ID,从用户输入到模型响应、工具调用、安全策略命中结果,全链路串起来。这样即使某个问题只在特定上下文下出现,也能通过日志复现。
7.5 学习路线建议
如果你想往大模型应用开发方向深入,可以参考下面的路线:
- 第一阶段:掌握基础 HTTP 调用和 OpenAI 兼容接口,理解消息结构。
- 第二阶段:动手实现函数调用工具,理解 tool_calls 的交互循环。
- 第三阶段:把工具注册、上下文管理、安全策略拆成组件,形成自己的 Harness。
- 第四阶段:研究消息队列、向量数据库、任务队列等外部依赖,把 Harness 扩展成多 Agent 协作系统。
- 第五阶段:关注模型评估、成本管理、线上监控和压测,把 Harness 做成可运营的工程产品。
学习 Harness 架构,关键不是收藏多少资料,而是亲自动手把一个小循环跑通。只要你理解了模型与工具之间的那几轮交互,再去看各种 Agent 框架源码,会轻松很多。建议你直接拿本文的示例项目改一改,加上一个自己的业务工具,比如查询订单、查天气、操作数据库,跑一遍完整的工具调用链路,比看十篇架构分析更有用。