第 10 章:自定义 Agent——BaseAgent 与 BaseInstalledAgent
第 10 章:自定义 Agent——BaseAgent 与 BaseInstalledAgent
预集成的 42 个 agent 覆盖不到你的被测对象时,Harbor 提供两条自定义路径:让 Harbor 把 agent CLI 装进任务环境里跑(Installed),或在 Harbor 进程内自己驱动 agent 循环(External)。本章讲解两条路径的选择依据、基类模板的逐段实现,以及如何注册使用、如何把输出轨迹对接回评测体系。
两条自定义路径:Installed 还是 External
自定义 agent 是指在 Harbor 内置注册表之外开发的集成——可以是本地试验品,也可以是不打算上游化的私有集成。选择哪条路径,取决于 agent 循环跑在哪里:
| 类型 | 运行方式 | 参考实现 |
|---|---|---|
| Installed agent | Harbor 在任务环境内部安装并运行 agent CLI | Claude Code |
| External agent | agent 循环运行在 Harbor 进程中,通过 BaseEnvironment 控制任务环境 |
Terminus-2 |
flowchart LR
subgraph I[Installed agent 路径]
A1[Harbor 宿主进程] -- 安装/下发指令 --> A2[任务环境内部
agent CLI 执行] --> A3[日志同步回 Harbor]
end
subgraph E[External agent 路径]
B1[Harbor 进程内的
agent 循环 + LLM 调用] -- environment.exec / 文件传输 --> B2[任务环境
只作为工具执行地]
end官方给出的经验法则很明确:优先选 BaseInstalledAgent,绝大多数 agent 集成都遵循这一设计;只有当 agent 循环必须留在任务环境之外时才用 BaseAgent。原因不难理解——Installed 路径下 agent 的依赖、日志、轨迹都天然落在任务环境内,隔离性与可复现性最好,Harbor 只需要负责"装"和"跑"两个动作。
BaseInstalledAgent 模式:环境安装与运行逻辑分离
BaseInstalledAgent 把集成拆成两个钩子:install() 负责把 agent 装进任务环境,run() 负责在环境内执行它。完整模板如下:
import shlex
from harbor.agents.installed.base import (
BaseInstalledAgent,
with_prompt_template,
)
from harbor.environments.base import BaseEnvironment
from harbor.models.agent.context import AgentContext
class MyAgent(BaseInstalledAgent):
@staticmethod
def name() -> str:
return "my-agent"
async def install(self, environment: BaseEnvironment) -> None:
await self.exec_as_agent(
environment,
command="pip install my-agent",
)
@with_prompt_template
async def run(
self,
instruction: str,
environment: BaseEnvironment,
context: AgentContext,
) -> None:
await self.exec_as_agent(
environment,
command=f"my-agent {shlex.quote(instruction)}",
)逐段拆解:
name():返回集成名。虽然是自定义 agent,不进入按名注册表,但名字仍会出现在日志与结果目录中。install():只做安装。基类提供两个执行助手——exec_as_root用于安装系统级软件包(如apt-get),exec_as_agent用于用户级的安装与执行。安装与运行分离,意味着换任务环境时安装逻辑可以按需重放。run():真正执行被测 agent。@with_prompt_template装饰器负责把任务指令按模板渲染后传入;shlex.quote对指令做 shell 转义,防止特殊字符注入命令行。context: AgentContext:agent 运行上下文,是向 Harbor 回传信息的通道(见下文轨迹对接)。
模板中未出现但同样重要的是 populate_context_post_run 钩子:在 agent 日志同步回 Harbor 之后调用,用于解析用量(usage)或轨迹(trajectory)数据并写入上下文。你的 agent 只要会落盘日志,就能通过这个钩子把 token 消耗、轨迹文件提取出来。
BaseAgent 模式:agent 循环留在 Harbor 进程内
当 agent 需要直接访问模型 API 凭据、复杂运行时,或其循环根本无法打包成 CLI 时,用 BaseAgent:
from harbor.agents.base import BaseAgent
from harbor.environments.base import BaseEnvironment
from harbor.models.agent.context import AgentContext
class MyAgent(BaseAgent):
@staticmethod
def name() -> str:
return "my-agent"
def version(self) -> str | None:
return "1.0.0"
async def setup(self, environment: BaseEnvironment) -> None:
pass
async def run(
self,
instruction: str,
environment: BaseEnvironment,
context: AgentContext,
) -> None:
# 调用你的模型,并通过 environment.exec(...) 作用于环境。
pass与 Installed 路径相比有三个差异:
- 多了
setup()与version():setup()在运行前对环境做准备工作(可以留空),version()返回集成的版本号,便于结果溯源。 - 没有
install():因为 agent 本体就跑在 Harbor 进程里,任务环境只是它操作的"外设"。 - 直接持有
environment:通过environment.exec(...)执行命令、通过文件传输方法读写环境内容,agent 循环(LLM 调用、工具决策)全部在 Harbor 侧完成。
注意安全边界:External 路径下模型凭据留在 Harbor 进程内,不进入任务环境;但 agent 循环与任务环境同宿主,隔离性弱于 Installed 路径。
注册与在评测中使用自定义 agent
自定义 agent 不注册名字,使用时把 模块路径:类名 传给 --agent(-a)即可,前提是该模块可以从 Harbor 进程中导入(同一 Python 环境已安装):
harbor run \
-t hello-world/hello-world \
-a examples.agents.marker_agent:MarkerAgent其他旗标与预集成 agent 完全一致:
--model(-m):agent 接受模型名时传入;--agent-kwarg(--ak):其他构造参数;--agent-env(--ae):环境变量。
官方仓库提供了完整的 BaseAgent 参考实现 examples/agents/marker_agent.py,动手前先读它能少走很多弯路。
输出轨迹如何对接
自定义 agent 产出的轨迹要进入 Harbor 的结果体系,关键在两点(与第 11 章的 ATIF 规范衔接):
- 落盘位置:把轨迹写成合法的 ATIF JSON,保存到
self.logs_dir / "trajectory.json"。下载 trial 结果时,它的路径是agent/trajectory.json,结果查看器的 Trajectory 页签会直接渲染这个文件。 - 能力声明:只有当你的 agent 确实会写出合法的
trajectory.json时,才声明AgentCapabilities(atif=True)。声明与实际不符,下游的加载、渲染、校验都会出问题。
两者的衔接动作发生在 populate_context_post_run:日志同步完成后,在这里解析 agent 落盘的原生日志,转换(或直接校验)成 ATIF 轨迹并写入上下文。写完后建议用 Harbor 自带的校验器过一遍:
uv run python -m harbor.utils.trajectory_validator path/to/trajectory.json校验通过的自定义轨迹与预集成 agent 的轨迹完全同权:可以被结果查看器渲染,也可以被支持加载 ATIF 轨迹的 agent(如 claude-code、codex)用作新会话的初始上下文。
本章小结
- 自定义 agent 分 Installed(Harbor 在任务环境内安装并运行 CLI)与 External(agent 循环在 Harbor 进程内通过
BaseEnvironment操作环境)两条路径。 - 经验法则:优先
BaseInstalledAgent,仅当 agent 循环必须留在环境外时才用BaseAgent。 BaseInstalledAgent的核心是install()与run()两个钩子:exec_as_root装系统包,exec_as_agent做用户级安装与执行,@with_prompt_template渲染指令。populate_context_post_run在日志同步后触发,是解析用量与轨迹数据的标准位置。BaseAgent通过setup()/run()/version()组织代码,直接调用模型并以environment.exec(...)作用于环境。- 使用时传
模块路径:类名给-a,模块必须可从 Harbor 进程导入;-m、--ak、--ae与预集成 agent 用法一致。 - 轨迹对接三件套:写入
self.logs_dir / "trajectory.json"、如实声明AgentCapabilities(atif=True)、用 trajectory_validator 校验。
延伸阅读
- Custom agents——本章对应的官方页面
- ATIF——轨迹格式规范(见本书第 11 章)
- marker_agent.py——官方完整示例实现
- BaseEnvironment——External 路径操作环境的接口定义
- Loading trajectories——轨迹的加载与复用