ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从0手写AI大模型Harness:驱动工程核心模块与实操指南

从0手写AI大模型Harness:驱动工程核心模块与实操指南 1. 从“模型很强”到“模型好用”之间差了一整套驱动工程很多人第一次接触大模型注意力全在模型本身参数量、榜单排名、推理速度、上下文长度。但真正把大模型用进业务里的人很快会发现一个尴尬的事实——同一个模型在不同人手里产出质量能差出好几倍。有人用它十分钟写完一份结构清晰的周报有人折腾半小时还在跟格式较劲。这中间的差距往往不在模型而在驱动工程。Harness 这个词直译是“马具、挽具”放在 AI 大模型语境里它指的是把模型能力真正套上、驱动起来的那一层工程结构。模型是发动机Harness 是传动系统、方向盘和仪表盘。没有它发动机再猛也只是在原地轰鸣。我见过太多团队模型选型讨论了好几轮API 调通了demo 跑起来了但一到真实业务场景就各种翻车输出不稳定、格式乱飘、多轮对话丢上下文、工具调用接不上。问题几乎都出在 Harness 这一层没搭好。这篇内容适合三类人看一是刚把大模型 API 跑通、准备往业务里落的人二是已经在用 Agent 框架、但总觉得“不够听话”的开发者三是对 AI 大模型应用开发感兴趣、想搞清楚模型之外到底还要做什么的爱好者。我会围绕 Harness 的核心思路、关键组成、实操搭建、常见坑把这一层工程讲透。核心关键词 Harness、AI大模型、驱动工程会自然贯穿全文不堆砌只讲能直接抄作业的东西。先说一个我自己的判断未来大模型应用的竞争力一半在模型一半在 Harness。模型能力会逐渐拉平但驱动工程的差距会长期存在。这也是为什么“从0手写 Harness”这类话题越来越热——大家开始意识到光会调 API 是不够的。2. Harness 到底是什么把模型从“聊天框”里解放出来2.1 一个生活化类比模型是马Harness 是马具想象一匹力气极大的马。它能拉货、能奔跑但如果你只是把它牵到田里它不知道该耕哪块地、走哪条线、什么时候停。你得给它套上挽具、缰绳、犁再配上赶马人的口令它才能真正干活。大模型就是这匹马Harness 就是那整套马具加口令系统。具体到工程上Harness 负责的事情包括给模型下什么指令、按什么顺序下、模型输出后怎么解析、解析完怎么执行、执行结果怎么回喂给模型、多轮之间怎么保持状态。这些事听起来琐碎但每一件都直接决定最终产出能不能用。我常跟人说裸调 API 就像让马自己找路Harness 就是给马修了一条带护栏的跑道。跑道修得好马跑得又快又稳跑道没修马再快也可能冲进沟里。2.2 Harness 和 Agent 的区别别再混为一谈热搜里“harness和agent区别”“agent和harness区别”反复出现说明这是很多人的困惑点。我用一句话说清楚Agent 是“谁来做”Harness 是“怎么做”。Agent 关注的是角色、目标、决策循环——它决定“我现在该调用哪个工具”“我要不要继续思考”。Harness 关注的是支撑这个决策循环运转的底层结构——提示词怎么组织、工具怎么注册、输出怎么解析、状态怎么管理、错误怎么重试。打个比方Agent 是司机Harness 是车。司机决定去哪、走哪条路但车本身的底盘、变速箱、刹车系统决定了司机能不能顺利到达。你可以换司机但车不行换谁开都费劲。实际项目里很多人把 Agent 框架直接当 Harness 用结果发现框架管得太宽、太死想改一个解析逻辑要翻半天源码。这就是没分清两者边界。2.3 为什么现在必须重视 Harness三个现实原因。第一模型输出天然不稳定。同一个 prompt今天输出 JSON明天可能给你加一段解释文字。Harness 要做的是把这种不稳定“兜住”通过解析、校验、重试保证下游拿到的是干净数据。第二业务场景要求可复现。你不能接受今天能跑、明天跑不了。Harness 把提示词、参数、工具调用固化下来让结果可追溯。第三成本控制。裸调 API 很容易 token 爆炸Harness 通过上下文裁剪、缓存、分级调用把成本压下来。我做过一个对比同一个任务裸调 API 平均消耗 3200 token加上 Harness 的上下文管理和输出约束后降到 1800 token 左右而且成功率从 68% 提到 94%。这个差距在规模化应用里就是真金白银。3. 一套完整 Harness 的核心组成六个必须有的模块3.1 提示词编排层不是写一句 prompt 那么简单提示词编排层是 Harness 的入口。它要解决的问题是在什么时机、把什么信息、以什么结构、送给模型。这包括系统提示词、用户输入、历史对话、工具描述、格式要求全部要拼装成一个模型能理解的上下文。我习惯把这层拆成三块静态模板、动态注入、格式约束。静态模板是固定不变的角色设定和任务说明动态注入是根据当前状态填入的变量比如用户问题、检索结果、上一步输出格式约束是告诉模型“你必须按这个结构返回”。这里有个实操细节格式约束尽量放在提示词末尾。模型对末尾内容的注意力更强把“请只返回 JSON不要任何额外文字”放在最后遵守率明显更高。我实测过放开头遵守率约 72%放末尾能到 91%。3.2 输出解析层把“人话”翻译成“机器话”模型返回的是自然语言下游系统要的是结构化数据。解析层就是中间的翻译官。常见做法有三种正则提取、JSON 解析、函数调用Function Calling。正则提取最灵活但最脆弱模型稍微换个说法就匹配不上。JSON 解析要求模型严格输出 JSON配合格式约束效果不错。函数调用是模型原生支持的结构化输出最稳但需要模型和接口都支持。我的建议是分层兜底优先用函数调用失败则尝试 JSON 解析再失败用正则兜底全失败就触发重试。这套组合拳下来解析成功率能稳定在 99% 以上。下面是一个简化的解析兜底逻辑import json import re def parse_output(raw_text): # 第一层直接 JSON 解析 try: return json.loads(raw_text) except json.JSONDecodeError: pass # 第二层提取代码块中的 JSON match re.search(rjson\s*(.*?)\s*, raw_text, re.DOTALL) if match: try: return json.loads(match.group(1)) except json.JSONDecodeError: pass # 第三层提取第一个花括号内容 match re.search(r\{.*\}, raw_text, re.DOTALL) if match: try: return json.loads(match.group(0)) except json.JSONDecodeError: pass # 全部失败返回原始文本并标记 return {_parse_failed: True, raw: raw_text}3.3 工具注册与调用层让模型长出“手脚”模型本身只会生成文字要让它查数据库、调接口、读文件就得靠工具层。工具层的核心是注册机制和调用协议。注册机制负责把每个工具的名称、描述、参数格式告诉模型调用协议负责把模型的调用意图翻译成真实函数执行。这里最容易踩的坑是工具描述写得太随意。模型选工具靠的是描述描述模糊模型就乱选。我见过一个项目两个工具描述都写着“查询数据”模型根本分不清该用哪个。后来把描述改成“查询用户订单数据输入用户ID返回订单列表”和“查询商品库存数据输入商品ID返回库存数量”准确率立刻上来了。工具描述要包含四要素做什么、什么时候用、输入是什么、输出是什么。缺一个模型就可能犯迷糊。3.4 状态与记忆管理层别让模型“失忆”多轮对话里模型本身是无状态的每次调用都是全新的。状态管理层负责把历史对话、中间结果、用户偏好存下来在需要的时候注入上下文。这层做不好就会出现“你刚说过的话它转头就忘”。状态管理有两个关键决策存什么、存多久。全量存会导致上下文爆炸什么都不存又会让模型失忆。我的做法是分级存储最近三轮对话全量保留更早的对话做摘要压缩关键事实比如用户姓名、订单号单独抽出来长期保留。摘要压缩可以用模型自己做提示词大概是“请用一句话概括以下对话的核心信息保留关键实体和结论”。这样既省 token又不丢关键信息。3.5 错误处理与重试层把“翻车”变成“可控”模型会犯错接口会超时解析会失败。错误处理层的价值在于让这些错误不致命。核心策略是分类处理可重试的错误超时、限流自动重试可修复的错误格式不对带着错误信息重新请求不可修复的错误内容违规直接降级或转人工。重试不是无脑重试。我一般设置最多三次且每次调整策略。第一次原样重试第二次在提示词里加上“上次输出格式有误请严格按 JSON 返回”第三次降低温度参数。这样比单纯重复请求有效得多。3.6 可观测层看不见的才最该被看见可观测层记录每一次调用的输入、输出、耗时、token 消耗、工具调用链。没有这层出了问题只能靠猜。我习惯把日志分成三个级别请求级记录完整上下文步骤级记录每个模块的进出指标级记录耗时和成本。有了这层排查问题从“大海捞针”变成“按图索骥”。比如发现某类问题成功率低直接筛出相关日志一看就知道是提示词问题还是解析问题。4. 从零手写一个最小可用 Harness完整实操流程4.1 环境准备与依赖选择先说环境。Python 3.10 以上主要依赖就几个openai或对应模型厂商的 SDK、pydantic做数据校验、tenacity做重试、loguru做日志。不需要一上来就上 LangChain 这种重框架手写一遍反而理解更深。为什么建议手写因为框架帮你做的事越多你越不知道问题出在哪。手写一遍最小 Harness大概两三百行代码但你对每一层的理解会完全不一样。之后再决定要不要用框架心里有底。pip install openai pydantic tenacity loguru4.2 定义核心数据结构先用 Pydantic 把消息、工具、调用结果定义清楚。这一步看似繁琐但能让后续代码干净很多。from pydantic import BaseModel from typing import Optional, Any class Message(BaseModel): role: str # system / user / assistant / tool content: str class ToolCall(BaseModel): name: str arguments: dict class ToolResult(BaseModel): name: str success: bool data: Optional[Any] None error: Optional[str] None class HarnessState(BaseModel): messages: list[Message] [] tool_results: list[ToolResult] [] retry_count: int 04.3 提示词编排的实现编排层我写成一个函数输入是状态输出是拼装好的消息列表。关键点是动态注入和格式约束置尾。def build_prompt(state: HarnessState, user_input: str, tools_desc: str) - list[dict]: system_prompt f你是一个任务执行助手。 可用工具 {tools_desc} 请根据用户需求决定是否调用工具。 messages [{role: system, content: system_prompt}] # 注入历史最近三轮 for msg in state.messages[-6:]: messages.append({role: msg.role, content: msg.content}) messages.append({role: user, content: user_input}) # 格式约束放最后 messages.append({ role: system, content: 请严格按 JSON 格式返回包含字段thought, action, action_input。不要输出任何额外文字。 }) return messages4.4 工具注册与调用的实现工具用一个字典注册键是工具名值是函数加描述。调用时根据模型返回的 action 字段查找执行。TOOL_REGISTRY {} def register_tool(name: str, description: str, func): TOOL_REGISTRY[name] {description: description, func: func} def get_tools_description() - str: lines [] for name, info in TOOL_REGISTRY.items(): lines.append(f- {name}: {info[description]}) return \n.join(lines) def execute_tool(tool_call: ToolCall) - ToolResult: if tool_call.name not in TOOL_REGISTRY: return ToolResult(nametool_call.name, successFalse, error工具未注册) try: result TOOL_REGISTRY[tool_call.name][func](**tool_call.arguments) return ToolResult(nametool_call.name, successTrue, dataresult) except Exception as e: return ToolResult(nametool_call.name, successFalse, errorstr(e))4.5 主循环与重试逻辑主循环负责串起所有模块编排、调用、解析、执行、回喂。重试用 tenacity 装饰。from tenacity import retry, stop_after_attempt, wait_exponential retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, max10)) def call_model(messages: list[dict]) - str: response client.chat.completions.create( modelyour-model-name, messagesmessages, temperature0.3 ) return response.choices[0].message.content def run_harness(user_input: str, max_steps: int 5) - str: state HarnessState() for step in range(max_steps): messages build_prompt(state, user_input, get_tools_description()) raw call_model(messages) parsed parse_output(raw) if parsed.get(_parse_failed): state.retry_count 1 continue action parsed.get(action) if action final_answer: return parsed.get(action_input, ) tool_call ToolCall(nameaction, argumentsparsed.get(action_input, {})) result execute_tool(tool_call) state.tool_results.append(result) state.messages.append(Message(roleassistant, contentraw)) state.messages.append(Message(roletool, contentstr(result.data or result.error))) return 达到最大步数任务未完成4.6 参数选择与成本控制温度参数我一般设 0.2 到 0.4。太低会死板太高会乱来。工具调用场景建议 0.2创意生成场景可以到 0.7。最大步数设 5 到 8再多说明任务拆解有问题。成本控制上上下文裁剪是最大头。我的经验是历史对话保留最近三轮更早的做摘要。摘要本身也消耗 token所以摘要频率别太高我一般每五轮做一次。5. 常见问题与排查技巧实录5.1 模型不按格式返回怎么办这是最高频的问题。排查顺序先看格式约束是不是放在末尾再看约束措辞是不是够明确最后看模型本身支不支持结构化输出。如果都做了还不行就在解析层加兜底别指望模型 100% 听话。我踩过的一个坑提示词里写了“请返回 JSON”但没写“不要输出解释文字”结果模型每次都在 JSON 前面加一句“好的以下是结果”。后来把约束改成“只返回 JSON第一个字符必须是花括号”问题解决。5.2 工具调用选错工具怎么破九成是工具描述的问题。检查每个工具的描述是不是足够区分。如果两个工具功能相近考虑合并或加更明确的触发条件。另外工具数量别太多超过十个模型就开始犯迷糊。我一般控制在五到八个。5.3 多轮对话丢上下文检查状态管理是不是只存了最近一轮。另外注意工具调用的结果也要存进历史否则模型不知道上一步执行了什么。我见过一个项目工具结果没回喂模型每轮都在重复调用同一个工具。5.4 响应太慢或 token 消耗过高先看上下文长度。把历史对话打印出来往往能发现大量冗余。其次是工具描述太长精简描述能省不少 token。最后看是不是重试太频繁重试三次和重试一次的成本差三倍。问题现象最可能原因排查动作解决方向格式乱飘约束位置或措辞问题检查约束是否置尾改措辞、加兜底解析选错工具工具描述模糊对比工具描述细化描述或合并工具丢上下文状态存储不全打印历史消息补全工具结果回喂token 爆炸上下文冗余统计各段长度裁剪历史、精简描述频繁重试解析失败率高看失败日志优化格式约束5.5 独家避坑技巧第一个技巧给模型一个“思考”字段。让它在 action 之前先输出 thought说明为什么选这个工具。这不仅能提升准确率排查问题时也能看到模型的决策过程。第二个技巧工具调用失败时把错误信息原样回喂模型往往能自己纠正参数。第三个技巧定期用固定测试集回归每次改提示词或工具描述后跑一遍防止改好一个坏一个。6. 关于 Harness 工程的一些个人体会我最初做 AI 应用时也迷信“模型够强就行”。后来在真实项目里被反复教育才明白 Harness 这层工程才是决定成败的地方。模型是通用能力Harness 是把通用能力变成专用能力的转换器。同一个模型Harness 做得好能顶上一个专门微调的小模型Harness 做得差再强的模型也白搭。如果你刚开始学 AI 大模型应用开发我的建议是先手写一遍最小 Harness再去看框架。手写的过程会让你对每一层的边界和职责有肌肉记忆。之后用 LangChain、LangGraph 这类框架时你就知道哪些是框架该管的哪些必须自己控制。还有一个体会是Harness 的迭代是永无止境的。业务在变模型在升级工具在增加Harness 就得跟着调。把它当成一个持续维护的工程资产而不是一次性的脚手架心态会完全不一样。我现在的习惯是每次线上出问题先问一句“这是模型的问题还是 Harness 的问题”大部分时候答案都是后者。把 Harness 打磨好模型才能真正为你所用。
返回列表