最小 Agent Harness 骨架
目录
- 目标
- 项目结构(建议)
- 1. trace 模块
- 2. 工具定义(网关内)
- 3. API Route:编排环
- 4. 前端:useChat + 展示 trace
- 5. 自测清单
- 架构示意
- 下一步
- 延伸阅读
目标
用 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,每个 onStepFinish 打 step 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) — 在网关加写操作审批与重复调用检测。
延伸阅读
- Harness:介绍与定位
- AI-Chat 最小项目(流式 UI 可复用)