跳到主要内容

最小 Agent Harness 骨架

目录

目标

Vercel AI SDK 实现最小 Agent Harness

  • 编排环:maxSteps 限制多步 tool loop
  • 工具网关:Zod 校验 + mock 实现
  • 可观测:traceId 贯穿请求
  • 流式:前端可收 partial + tool 状态

对应理论:02-Harness核心组件与架构

项目结构(建议)

harness-min/
├── app/
│ ├── api/agent/route.ts # Harness 入口
│ └── page.tsx # 聊天 UI
├── lib/
│ ├── harness/
│ │ ├── trace.ts # traceId 生成与 log
│ │ └── tools.ts # tool 定义 + 执行
│ └── types.ts
└── package.json

1. trace 模块

// lib/harness/trace.ts
import { randomUUID } from "crypto";

export function createTraceId() {
return randomUUID();
}

export function logSpan(
traceId: string,
span: string,
data: Record<string, unknown>
) {
console.log(JSON.stringify({ ts: Date.now(), traceId, span, ...data }));
}

要点:入口 route.ts 生成一次 traceId,每个 onStepFinishstep span。

2. 工具定义(网关内)

// lib/harness/tools.ts
import { tool } from "ai";
import { z } from "zod";
import { logSpan } from "./trace";

export function buildTools(traceId: string) {
return {
getWeather: tool({
description: "查询城市天气",
parameters: z.object({
city: z.string().min(1).max(50),
}),
execute: async ({ city }) => {
logSpan(traceId, "tool.getWeather", { city });
// mock;生产接真实 API + 超时
return { city, tempC: 26, condition: "晴" };
},
}),
};
}

工具 只在 Harness 包内 execute——不要把密钥或 raw SQL 交给模型侧配置。

3. API Route:编排环

// app/api/agent/route.ts
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { createTraceId, logSpan } from "@/lib/harness/trace";
import { buildTools } from "@/lib/harness/tools";

export const maxDuration = 60;

export async function POST(req: Request) {
const traceId = createTraceId();
const { messages } = await req.json();

logSpan(traceId, "agent.start", { messageCount: messages.length });

const result = streamText({
model: openai("gpt-4o-mini"),
system: `你是助手。需要天气时必须调用 getWeather。回答简洁。traceId=${traceId}`,
messages,
tools: buildTools(traceId),
maxSteps: 5,
abortSignal: req.signal,
onStepFinish: ({ stepType, toolCalls, usage }) => {
logSpan(traceId, "agent.step", {
stepType,
toolNames: toolCalls?.map((t) => t.toolName),
usage,
});
},
});

return result.toDataStreamResponse({
headers: { "X-Trace-Id": traceId },
});
}

Harness 参数说明

参数作用
maxSteps模式 ① 边界约束
abortSignal用户取消
onStepFinish模式 ④ 运行时监测数据
X-Trace-Id前端/客服排障

4. 前端:useChat + 展示 trace

// app/page.tsx — 核心片段
"use client";
import { useChat } from "@ai-sdk/react";

export default function Page() {
const { messages, input, handleInputChange, handleSubmit, stop, data } =
useChat({ api: "/api/agent" });

return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}</strong>: {m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button type="submit">发送</button>
<button type="button" onClick={stop}>
停止
</button>
</form>
</div>
);
}

可从 AI-Chat 最小项目 复制流式 UI,把 endpoint 换成 /api/agent

5. 自测清单

用例预期
「北京天气怎样」getWeather,一步或多步后给答案
连问 3 个不同城市每请求独立 traceId,步数 ≤ 5
点击停止后端 log 出现 abort,无悬挂
故意问 20 轮观察 latency;为下一篇 context 预算埋伏笔

架构示意

下一步

02-加护栏与审批门(HITL) — 在网关加写操作审批与重复调用检测。

延伸阅读