智能体开发实战02|Harness工程入门

上篇我们搭了第一个 Agent,60 行跑通了「思考→调用工具→生成回答」的循环。

但如果你真的拿去用几天,会发现一堆问题:

  • 搜到一半 API 超时,整个程序崩了
  • 模型连续调用工具 10 次不回答,Token 烧光
  • 搜索工具返回了乱码,Agent 开始胡说八道
  • 跑了一个小时,不知道中间发生了什么

这些问题的根因是:你的 Agent 只有『应用程序』级别的代码(调用模型→返回结果),没有『操作系统』级别的能力来管资源、管异常、管边界。

这个『操作系统』就是 Harness

今天这篇是系列第 02 篇,也是第二阶段的开门之作。我们从零给上篇的 Agent 装上 Harness,让它能扛住真实场景。

一、Harness 到底管什么

Harness 不是某个复杂框架,而是一组让你 Agent 可靠运行的工程机制。类比真实操作系统:

没有 Harness 的 Agent 就像没有操作系统的裸机------能通电,但随时蓝屏。

二、从第一行代码看问题

回顾上篇 Agent 的核心逻辑:

ini 复制代码
# ======================================================================
# [问题] 上篇代码没有任何保护措施
# ======================================================================

response = client.chat.completions.create(...)   # 网络超时?直接崩

msg = response.choices[0].message               # response 为 None?崩溃

if msg.tool_calls:
    result = web_search(args["query"])          # 搜索抛异常?直接崩
    # 没有最大循环限制 -> 模型可以无限调工具,Token 烧光
    # 没有日志 -> 跑完不知道它做了什么
    # 没有降级 -> 工具挂了,整个 Agent 也挂了

在实际场景中,上面每一行都可能崩。

三、Harness 核心组件实现

我们从最常用的 5 个组件开始,逐个加到 Agent 身上。

1. 重试机制(Retry)

网络抖动、API 限流是家常便饭。重试要带退避(Backoff),避免把服务器打炸:

python 复制代码
import time
import random


# ================================================================
# 组件 1: 重试机制(Retry)-- 网络抖动?给我再试一次
# ================================================================
def retry_on_fail(func, max_retries=3, base_delay=1):
    """
    带指数退避(Exponential Backoff)的重试包装器

    参数:
        func:        要执行的函数
        max_retries: 最大重试次数(默认 3 次)
        base_delay:  初始等待秒数(每次翻倍)

    效果:
        失败 1 次等 1s,失败 2 次等 2s,失败 3 次等 4s ...
    """
    for attempt in range(max_retries):
        try:
            return func()
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            delay = base_delay * (2 ** attempt) + random.uniform(0, 0.5)
            print(
                f"[attempt {attempt + 1}/{max_retries}] "
                f"failed: {e}, retry in {delay:.1f}s..."
            )
            time.sleep(delay)

2. 超时控制(Timeout)

模型或工具可能无响应,不能无限等下去。用 signal 或 Thread 加超时:

python 复制代码
import signal


# ================================================================
# 组件 2: 超时控制(Timeout)-- 不能无限等下去
# ================================================================
class TimeoutError(Exception):
    """自定义超时异常"""

    pass


def _timeout_handler(signum, frame):
    """SIGALRM 信号处理函数:直接抛异常"""
    raise TimeoutError("操作超时")


def with_timeout(func, timeout_sec=30):
    """
    给任意函数加超时保护(基于 SIGALRM)

    参数:
        func:        要执行的函数
        timeout_sec: 超时秒数(默认 30s)

    返回:
        函数执行结果,超时则返回 None
    """
    signal.signal(signal.SIGALRM, _timeout_handler)
    signal.alarm(timeout_sec)
    try:
        return func()
    except TimeoutError:
        print(f"[timeout] operation exceeded {timeout_sec}s, terminated")
        return None
    finally:
        signal.alarm(0)

3. 最大步数限制(Max Steps)

最容易被忽略的陷阱------模型可能陷入「思考→调工具→再思考→再调工具」的死循环。必须设硬上限:

python 复制代码
# ================================================================
# 组件 3: 最大步数限制(Max Steps)-- 防死循环的保险丝
# ================================================================
def run_with_step_limit(agent_func, max_steps=5):
    """
    限制 Agent 最大执行步数,防止陷入死循环

    参数:
        agent_func: Agent 单步执行函数,返回 {"done": bool, ...}
        max_steps:  最大允许步数(默认 5 步)

    返回:
        正常完成 -> Agent 结果
        超出限制 -> 错误字典 + 部分结果
    """
    last_result = None

    for step in range(max_steps):
        last_result = agent_func()
        if last_result and last_result.get("done"):
            return last_result

    print(f"[max_steps] reached limit ({max_steps}), force terminating")
    return {
        "error": "max_steps_exceeded",
        "partial": last_result,
    }

4. 日志追踪(Logging)

不知道 Agent 做了什么,等于没有调试能力。每步操作留下结构化记录:

python 复制代码
import json
from datetime import datetime


# ================================================================
# 组件 4: 日志追踪(Logging)-- 每一步都看得见
# ================================================================
class AgentLogger:
    """结构化 Agent 日志器,记录每一步操作"""

    def __init__(self):
        self.logs = []

    def log(self, event, detail):
        """
        记录一条日志。

        event:  事件类型(如 TOOL_CALL / MODEL_RESPONSE / TIMEOUT)
        detail: 事件详情(字符串或字典)
        """
        entry = {
            "timestamp": datetime.now().isoformat(),
            "event": event,
            "detail": detail,
        }
        self.logs.append(entry)

        if isinstance(detail, dict):
            detail_str = json.dumps(detail, ensure_ascii=False)
        else:
            detail_str = str(detail)
        print(f"[log] [{event}] {detail_str[:100]}")

    def dump(self):
        """导出完整日志(列表形式)"""
        return self.logs


# ===== 使用示例 =====
logger = AgentLogger()
logger.log(
    "TOOL_CALL",
    {"tool": "web_search", "query": "AI news 2026"},
)
logger.log(
    "MODEL_RESPONSE",
    {"tokens": 256, "finish_reason": "stop"},
)

5. 降级策略(Fallback)

核心工具挂了不等于整个 Agent 要崩。准备好降级方案:

python 复制代码
# ================================================================
# 组件 5: 降级策略(Fallback)-- 工具挂了,Agent 不能挂
# ================================================================
def safe_tool_call(func, fallback_result=None):
    """
    工具调用安全包装:重试 -> 失败 -> 降级

    参数:
        func:            要执行的工具函数
        fallback_result: 降级时的替代返回值(可选)

    执行链:
        func -> 重试(最多 3 次)-> 降级 -> 返回友好提示
    """
    try:
        return retry_on_fail(func)
    except Exception as e:
        print(f"[fallback] tool call failed ({e}), using fallback")
        if fallback_result is not None:
            return fallback_result
        return "(工具暂不可用,请稍后再试)"

四、组装:带 Harness 的 Agent

现在把上面 5 个组件装进上篇的 Agent,升级为带 OS 的版本:

python 复制代码
# ======================================================================
#  Harness Agent -- 完整可运行版本(带操作系统)
# ======================================================================
#  比上篇多了:重试、超时、步数限制、日志追踪、降级策略
#  复制后替换 API Key 即可运行
# ======================================================================

import json
import random
import requests
import signal
import time
from datetime import datetime

from openai import OpenAI


# ======================================================================
#  第一步:配置
# ======================================================================

client = OpenAI(
    api_key="your-api-key-here",
    base_url="https://api.deepseek.com/v1",
)
MODEL = "deepseek-chat"


# ======================================================================
#  第二步:装配 Harness 组件
# ======================================================================

# ---------- 组件 1: 日志追踪 ----------

class AgentLogger:
    """结构化日志器,记录 Agent 每一步操作"""

    def __init__(self):
        self.logs = []

    def log(self, event, detail):
        entry = {
            "timestamp": datetime.now().isoformat(),
            "event": event,
            "detail": detail,
        }
        self.logs.append(entry)
        print(f"[log] [{event}] {str(detail)[:80]}")

    def dump(self):
        """导出完整日志(列表形式)"""
        return self.logs


harness_log = AgentLogger()


# ---------- 组件 2: 重试机制 ----------

def with_retry(func, retries=3, delay=1):
    """指数退避重试:失败后等待时间呈指数增长"""
    for i in range(retries):
        try:
            return func()
        except Exception as e:
            if i == retries - 1:
                raise
            wait = delay * (2 ** i) + random.uniform(0, 0.5)
            harness_log.log(
                "RETRY",
                f"attempt {i + 1}: {e}, retry in {wait:.1f}s",
            )
            time.sleep(wait)


# ---------- 组件 3: 超时控制 ----------

class TimeoutError(Exception):
    """自定义超时异常"""

    pass


def _signal_handler(signum, frame):
    """SIGALRM 信号处理函数:直接抛异常"""
    raise TimeoutError("操作超时")


def with_timeout(func, sec=30):
    """SIGALRM 超时保护"""
    signal.signal(signal.SIGALRM, _signal_handler)
    signal.alarm(sec)
    try:
        return func()
    except TimeoutError:
        harness_log.log("TIMEOUT", f"exceeded {sec}s")
        return None
    finally:
        signal.alarm(0)


# ---------- 组件 4: 安全调用(重试 + 超时 + 降级) ----------

def safe_call(func, fallback="(工具暂不可用)"):
    """安全调用链:超时 -> 重试 -> 降级"""
    try:
        return with_timeout(lambda: with_retry(func))
    except Exception as e:
        harness_log.log("FALLBACK", str(e))
        return fallback


# ======================================================================
#  第三步:定义 Agent 可用工具
# ======================================================================

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "web_search",
            "description": "搜索互联网获取最新信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "搜索关键词",
                    },
                },
                "required": ["query"],
            },
        },
    },
]


def web_search(query):
    """执行互联网搜索,返回前 3 条结果摘要"""
    url = "https://api.bocha.cn/v1/web-search?query=" + query
    resp = requests.get(url, timeout=15)
    results = resp.json().get("results", [])
    lines = [
        f"{r['title']}: {r['snippet']}"
        for r in results[:3]
    ]
    return "\n".join(lines)


# ======================================================================
#  第四步:组装 Harness Agent
# ======================================================================

def run_agent(user_input, max_steps=5):
    """
    带 Harness 的 Agent 主循环。

    工作流程:
        用户输入
            -> 模型思考
                -> 是否调工具?
                    -> 是 -> 安全调用 -> 再思考
                    -> 否 -> 返回最终答案
    """
    messages = [
        {
            "role": "system",
            "content": "你是一个资讯助手,使用 web_search 获取最新信息。",
        },
        {
            "role": "user",
            "content": user_input,
        },
    ]

    for step in range(max_steps):
        harness_log.log("STEP", f"step {step + 1}/{max_steps}")

        # --- 安全调用模型(自动重试 + 超时) ---
        response = safe_call(
            lambda: client.chat.completions.create(
                model=MODEL,
                messages=messages,
                tools=TOOLS,
                tool_choice="auto",
            )
        )
        if response is None:
            return json.dumps(
                {"error": "模型调用失败"},
                ensure_ascii=False,
            )

        msg = response.choices[0].message
        messages.append(msg)
        harness_log.log(
            "FINISH_REASON",
            msg.finish_reason or "none",
        )

        if not msg.tool_calls:
            return msg.content

        for tc in msg.tool_calls:
            harness_log.log(
                "TOOL_CALL",
                f"{tc.function.name}({tc.function.arguments})",
            )
            if tc.function.name == "web_search":
                args = json.loads(tc.function.arguments)
                result = safe_call(
                    lambda: web_search(args["query"]),
                    fallback="搜索暂时不可用,请稍后再试",
                )
                messages.append(
                    {
                        "role": "tool",
                        "tool_call_id": tc.id,
                        "content": result,
                    }
                )

    harness_log.log("MAX_STEPS", f"reached {max_steps} steps limit")
    return "已达到最大执行步数,请简化问题后重试。"


# ======================================================================
#  第五步:运行!
# ======================================================================

if __name__ == "__main__":
    answer = run_agent("今天AI圈有什么大新闻?")

    print()
    print("=" * 50)
    print("Agent 回答:")
    print(answer)
    print()
    print("=" * 50)
    print("Harness 运行日志:")
    print(json.dumps(harness_log.dump(), ensure_ascii=False, indent=2))

运行效果

正常运行时输出一切如常。但当遇到 API 超时、工具报错、模型死循环时,Harness 会:

  • 自动重试(日志里看到 RETRY)
  • 超时中断(TIMEOUT 并返回降级结果)
  • 超过 5 步自动终止(MAX_STEPS)
  • 每一步都有日志可回溯

这就是『操作系统』级别的保障------你的 Agent 再也不会因为一次网络抖动就全盘崩溃。

五、Harness 组件全景图

至此你的 Agent 已经从『裸机』升级到了『带 OS 的电脑』------不会随便蓝屏了。

六、写在最后

很多团队踩过的坑:模型选最强的,Harness 写最弱的。结果最强模型跑在最差的系统上,体验还不如弱模型配好 Harness。

记住:

Harness 决定了 Agent 在生产环境下能存活多久,模型决定了它回答的质量上限。两者缺一不可。

今天加装了重试、超时、步数限制、降级策略、日志追踪 5 个组件。下一篇,我们解决让每个开发者肉疼的问题------Token 成本,第 03 篇见。

  • 第03篇: 上下文工程------吃透 Context,Token 成本降 80%
  • 第04篇: 工具调用------让 Agent 真正『动手干活』
  • 第05篇: Agent 记忆系统------从『转头就忘』到『过目不忘』

提示:📌 欢迎来到「智能体开发实战」第二阶段!

第一阶段我们用 60 行代码跑通了第一个 Agent,建立了直觉。从这篇开始,我们进入真正的工程世界------每个组件都是生产级的,每一行代码都能直接用。

今天这篇文章的代码不到 120 行,但给 Agent 装上了真正的『操作系统』。这些组件不是一次性的------你会反复使用它们,直到它们变成你的肌肉记忆。

想在生产环境偷懒?直接用 LangGraph / CrewAI 等框架也行。但理解底层原理,出了问题才知道去哪修。

下一篇:上下文工程,带你吃透 Context,把 Token 成本打下来!


提示 :本文由 码农大坚果 出品,欢迎转发分享,转载请注明出处。

参考: LangChain、Anthropic、花叔 Harness Engineering PDF、知识库 concepts/ai-agent-harness | 整理 by 码农大坚果

相关推荐
codefan※1 小时前
干掉幻觉实战:如何构建企业级知识图谱增强 RAG
人工智能·大模型·llm·知识图谱·neo4j·rag·graphrag
知识领航员1 小时前
30个AI音乐提示词|直接复制可用,覆盖6大风格
人工智能·adobe·chatgpt·prompt·aigc·音视频
ECT-OS-JiuHuaShan1 小时前
辩证函数,渡劫代谢:时势造英雄,英雄发神经
数据库·人工智能·机器学习
YOLO数据集集合1 小时前
YOLOv11+DeepSeek多技术融合电网缺陷巡检平台|绝缘子破损瓷瓶故障AI识别、前后端一体化电力运维管理系统落地开发
运维·人工智能·yolo
keyanbanyungong1 小时前
从选题到成文全流程写作辅助
人工智能
lg_cool_1 小时前
如何用AI处理图像
人工智能·计算机视觉·目标跟踪
zhongerzixunshi1 小时前
标准化能源管控,赋能企业双碳落地
大数据·人工智能·能源
LienJack1 小时前
Context Manager 新范式:Agent 的注意力操作系统
agent
知识浅谈1 小时前
人工智能日报 每日AI新闻(2026年6月3日):微软Agent生态、Google反诈AI与国产模型应用提速
人工智能·microsoft