为什么现在需要自己理解 Agent Harness?

我们最终要实现的是这样一条完整链路:

用户提出任务
     ↓
Flask API
     ↓
Agent Harness
     ↓
LLM 思考与决策
     ↓
选择 Tool
     ↓
执行 Tool
     ↓
获取 Observation
     ↓
继续决策
     ↓
完成任务

最终这个 Agent 不只是能“说”,而是能真正“做”:

  • 读取文件;
  • 修改代码;
  • 执行 Python;
  • 执行 Shell;
  • 分析报错;
  • 自动重试;
  • 保存会话;
  • 管理上下文;
  • 调用多个工具;
  • 记录完整执行轨迹;
  • 通过 Flask API 对外提供服务。

这里最值得注意的,是我们说的目标并不是“做一个聊天机器人”。

聊天机器人最典型的模式是:

用户输入
  ↓
LLM
  ↓
文本输出

而 Agent 的运行过程更像:

用户目标
  ↓
模型决定先做什么
  ↓
程序执行动作
  ↓
环境返回结果
  ↓
模型根据结果重新判断
  ↓
继续行动
  ↓
直到任务完成

这中间多出来的,不只是几次 API 调用,而是一套完整的运行基础设施。

我们把这套基础设施叫做 Agent Harness。

你可以暂时把它理解成“Agent 的身体、神经系统和安全边界”。LLM 更像决策组件,它负责理解任务、分析当前状态、选择下一步;但真正的文件访问、命令执行、权限控制、异常处理、日志记录、会话保存,全部需要 Harness 承担。

把这些角色放回同一套系统里看,分工会清楚很多:

图片[1]-为什么现在需要自己理解 Agent Harness?-大赛人网

这张图里最关键的不是模块数量,而是边界。LLM 只负责产生下一步决策,Tool Call 还要经过权限校验,才能由程序真正执行;执行环境返回的 Observation 又会进入下一轮决策。State、Context、Memory、Safety 和 Tracing 则一直托住整个循环,让它不只是“会想”,还能够连续、受控、可追踪地做事。

也正因为如此,这门课不会从“LangChain 某个类怎么调用”开始。

不是因为这些框架不好,而是因为如果我们一开始就把最重要的机制全部藏在框架后面,你很容易学会“怎么调用”,却不知道“为什么这样设计”。后面一旦遇到真实项目里的死循环、工具失败、上下文爆炸、权限风险、服务状态管理,你就会发现单纯记 API 很难帮你定位问题。

我们希望走另一条路线,先把 Agent 拆开,再把它重新装起来。

先用一个最小的 Python while 循环理解 Main Loop;再给它加 State;再让它真正调用 Tool;再处理 Tool Error;再加入 Context 和 Memory;再通过 Flask 暴露 API;然后加入 Streaming、SQLite、Middleware、Human Approval、Sandbox、SubAgent、Tracing 和 Evaluation。

到最后,你看到任何 Agent 框架,都会有一种很不一样的感觉,“哦,这一层其实是在帮我封装 Tool Registry。”

“这里其实是在保存 State。”

“这个 Executor 背后,本质上还是 Main Loop。”

这才是我们想达到的学习效果。

这一节就是整门课的地图。先把终点看清楚,后面的 30 多节才不会学成一堆散落的名词。

知识地图

这一节位于整门课程的最前面,它不追求把某个模块讲深,而是负责建立全局视角:我们最终要造什么、为什么要这样造、后面每一块知识会装到哪里。

图片[2]-为什么现在需要自己理解 Agent Harness?-大赛人网

学习路径:

图片[3]-为什么现在需要自己理解 Agent Harness?-大赛人网

核心小节一:先看终点 – 我们最后到底要造出怎样一套 Agent 系统?

先别管“Agent Harness”这个词听起来是不是有点抽象,我们直接从一个真实任务开始。

假设用户对系统说,“帮我检查这个 Flask 项目为什么启动失败,并给出修复建议。”

如果这是普通聊天模型,它最多只能根据你粘贴进去的代码做判断。

但我们最终的系统希望它能够自己完成一条工作链:

接收任务
  ↓
查看项目目录
  ↓
决定先读哪些文件
  ↓
读取 requirements.txt / pyproject.toml
  ↓
运行测试或启动命令
  ↓
观察报错
  ↓
根据报错继续搜索代码
  ↓
必要时修改文件
  ↓
再次执行验证
  ↓
输出最终结论与执行轨迹

注意,这里面出现了几个完全不同的角色。

第一类是 用户入口。

用户不应该直接调用一个 Python 函数,而是可以通过网页、CLI、IDE 插件、飞书机器人或其他系统来提交任务。为了让不同入口都能调用同一个 Agent,我们需要一个稳定的服务接口。这就是为什么课程后面会引入 Flask。

第二类是 LLM 决策。

模型需要理解:

  • 用户到底想解决什么;
  • 当前已经做了哪些事情;
  • 刚才的工具执行结果意味着什么;
  • 下一步最值得调用哪个工具;
  • 当前是否已经可以结束。

第三类是 Tool 执行。

LLM 不会真的打开文件,也不会真的运行 Python。它最多产生一个结构化意图:

{
  "name": "read_file",
  "arguments": {
    "path": "app.py"
  }
}

真正打开 app.py 的,是我们的 Python 程序。

第四类是 Observation 回传。

工具执行后可能得到:

FileNotFoundError: app.py

也可能得到文件内容:

from flask import Flask
import missing_package

这些结果要重新放回 Agent 的上下文里,让模型决定下一步。

第五类是 运行控制。

如果模型连续 30 次调用同一个工具怎么办?如果它想读取 ~/.ssh 怎么办?如果 Shell 命令超过 60 秒怎么办?如果要删除文件怎么办?这些都不能依赖模型“自觉”。

所以还需要:

Max Steps
Timeout
Path Validation
Permission
Middleware
Human Approval
Tracing

这就是 Agent Harness 真正开始变得“工程化”的地方。

我们可以先用一个纯 Python、完全不依赖真实 LLM 的程序,模拟最终系统最核心的形状:

from dataclasses import dataclass, field
from typing import Any


@dataclass
class AgentState:
    goal: str
    step: int = 0
    observations: list[str] = field(default_factory=list)
    finished: bool = False


def read_file(path: str) -> str:
    fake_files = {
        "app.py": "from flask import Flask\\nimport missing_package",
        "requirements.txt": "Flask==3.0.0",
    }
    return fake_files.get(path, "FILE_NOT_FOUND")


def decide_next_action(state: AgentState) -> dict[str, Any]:
    if state.step == 0:
        return {
            "action": "read_file",
            "arguments": {"path": "app.py"}
        }

    if state.step == 1:
        return {
            "action": "read_file",
            "arguments": {"path": "requirements.txt"}
        }

    return {
        "action": "finish",
        "answer": "app.py 导入了 missing_package,但 requirements.txt 没有声明该依赖。"
    }


def run_agent(goal: str) -> str:
    state = AgentState(goal=goal)

    while not state.finished and state.step < 10:
        decision = decide_next_action(state)

        if decision["action"] == "read_file":
            path = decision["arguments"]["path"]
            result = read_file(path)
            state.observations.append(f"读取 {path}:{result}")
            state.step += 1
            continue

        if decision["action"] == "finish":
            state.finished = True
            return decision["answer"]

    return "任务没有在最大步数内完成。"


if __name__ == "__main__":
    result = run_agent("检查项目为什么启动失败")
    print(result)

这段代码一点都“不智能”,因为 decide_next_action() 是我们写死的。

但非常重要,因为它已经让我们看到最终 Agent 的骨架:

State
  ↓
Decision
  ↓
Action
  ↓
Tool
  ↓
Observation
  ↓
State 更新
  ↓
下一轮 Decision

后面真正接入 LLM 时,我们替换的主要是:

decide_next_action(...)

让决策不再由固定 if/else 完成,而是由模型根据 Context 做出。

很多人第一次学 Agent 时,会把注意力全部放在模型上。其实真正难的往往不是“调用模型”,而是模型周围这些看起来不起眼的东西:

  • State 怎么保存;
  • Tool 怎么注册;
  • 参数怎么校验;
  • Observation 怎么回传;
  • Context 怎么组装;
  • 错误怎么恢复;
  • 什么时候结束;
  • 怎么限制权限;
  • 怎么对外提供服务。

这也是为什么我们把课程重点放在 Harness 上。

最终系统可以得到

如果课程最后只是得到:

agent.py

我们最终希望项目结构逐渐长成:

agent-harness/
├── app.py
├── api/
│   ├── chat.py
│   └── runs.py
├── agent/
│   ├── engine.py
│   ├── loop.py
│   ├── state.py
│   └── context.py
├── providers/
│   ├── base.py
│   └── provider.py
├── tools/
│   ├── registry.py
│   ├── file_tools.py
│   ├── shell_tools.py
│   └── python_tools.py
├── memory/
│   ├── working_memory.py
│   └── long_term_memory.py
├── middleware/
│   ├── safety.py
│   └── approval.py
├── tracing/
│   └── tracer.py
└── storage/
    └── db.py

这里容易踩一个小坑:这张目录图表达的是职责划分,不是要求最终代码必须逐字使用这些文件名。

学到后面,大家可能会看到 Main Loop 和 Engine 放在同一个文件里,也可能看到 Working Memory 与持久化接口放在同一个包里。判断结构是否合理时,我们看的是职责有没有边界,而不是目录名有没有和这张图一字不差。

配套项目采用“阶段项目 + 可复用源码包”的组织方式,实际会更接近:

agent_harness_course_projects/
├── projects/              # 01~12:每个阶段可以单独运行
├── src/agent_harness/     # 最终系统复用的核心实现
│   ├── core/              # Engine、State、Provider、运行边界
│   ├── tools/             # Tool Registry 与工作区工具
│   ├── context/           # Context、Session、Memory
│   ├── service/           # Flask API 与 Run Service
│   ├── safety/            # Middleware、Approval、Sandbox
│   ├── storage/           # SQLite、Trace 与 Artifact 持久化
│   ├── observability/     # Tracing、Metrics、Evaluation
│   ├── advanced/          # Plan、SubAgent、Skills、AGENTS.md
│   └── agents/            # Coding Agent 与 Data Analysis Agent
├── tests/                 # 自动化验证
├── run_demo.py            # 统一运行入口
└── start.sh               # 一键启动入口

比如概念图里的 agent/loop.py,在配套项目中主要由 core/engine.py 承担;file_tools.py、shell_tools.py、python_tools.py 的职责集中在 tools/workspace.py;api/chat.py 与 api/runs.py 对应的路由则由 service/flask_app.py 提供。后续章节引用某个“层”时,我们会同时关心它的职责和它在配套项目中的真实位置。

这不是为了显得“架构复杂”。

恰恰相反,是因为每个模块都应该有清晰职责。

如果 Flask 路由里直接写 300 行 Agent Loop,以后你想从 CLI 调用同一个 Agent,就会非常痛苦。如果 Tool 和安全检查混在一起,你以后增加新工具就容易漏掉权限控制。如果 Tracing 写死在每个函数里,代码会越来越难维护。

所以从第一天起,我们就会一直问一个问题,这段代码到底属于哪一层?

这也是学习 Harness 比单纯学某个 Agent 框架更有价值的地方。

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像-大赛人网
欢迎您留下宝贵的见解!
提交
头像-大赛人网

昵称

取消
昵称

    请登录后查看评论内容