实现草图
下面是一个接近可实现的最小 Agent 草图。
Message
ts
type Message =
| { role: "user"; content: string }
| { role: "assistant"; content: AssistantBlock[] }
| { role: "tool"; toolUseId: string; content: string; isError?: boolean }
type AssistantBlock =
| { type: "text"; text: string }
| { type: "tool_use"; id: string; name: string; input: unknown }1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Tool
ts
type Tool<Input = unknown, Output = unknown> = {
name: string
description: string
readonly: boolean
schema: Schema<Input>
execute(input: Input, ctx: ToolContext): Promise<Output>
serialize(output: Output): string
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
AgentLoop
ts
async function* agentLoop(ctx: AgentContext) {
for (let turn = 0; turn < ctx.maxTurns; turn++) {
const response = await ctx.model.create({
messages: ctx.messages,
tools: ctx.tools.toSchemas(),
signal: ctx.abort.signal,
})
ctx.messages.push(response.message)
yield { type: "assistant", message: response.message }
const toolUses = collectToolUses(response.message)
if (toolUses.length === 0) {
return { reason: "complete" }
}
for (const toolUse of toolUses) {
const result = await executeToolUse(toolUse, ctx)
ctx.messages.push(result)
yield { type: "tool_result", message: result }
}
}
return { reason: "max_turns" }
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ToolExecutor
ts
async function executeToolUse(toolUse: ToolUse, ctx: AgentContext): Promise<Message> {
try {
const tool = ctx.tools.get(toolUse.name)
const input = tool.schema.parse(toolUse.input)
const decision = await ctx.permissions.check(tool, input, ctx)
if (decision.type !== "allow") {
return {
role: "tool",
toolUseId: toolUse.id,
content: decision.reason ?? "Permission denied",
isError: true,
}
}
const output = await tool.execute(input, ctx.toolContext)
return {
role: "tool",
toolUseId: toolUse.id,
content: tool.serialize(output),
}
} catch (error) {
return {
role: "tool",
toolUseId: toolUse.id,
content: error instanceof Error ? error.message : String(error),
isError: true,
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
第一版验收标准
第一版 Agent 能做到这些就够了:
- 用户输入能进入模型。
- 模型能调用至少一个工具。
- 工具结果能回到模型。
- 写操作会询问用户。
- 会话能写入 JSONL。
- 工具失败不会破坏下一轮模型调用。
尤其要保留两个不变量:
- 每个
tool_use.id必须有对应的tool_result.tool_use_id。 - 未知工具、权限拒绝、工具异常都要返回错误型
tool_result,不能直接让消息链断掉。