从零到一实现 nano-agent(四):工具系统设计
从零到一实现 nano-agent(四):工具系统基础
前言
工具系统是 AI Agent 与外部世界交互的核心。通过工具调用(Function Calling),LLM 可以读取文件、执行命令、搜索代码。本章将实现一个类型安全的工具系统,使用 Zod 进行参数验证,并自动生成 JSON Schema。
技术亮点
| 技术点 | 难度 | 面试价值 | 本文覆盖 |
|---|---|---|---|
| 工具接口设计 | ⭐⭐⭐ | API 设计能力 | ✅ |
| Zod 参数验证 | ⭐⭐⭐ | 类型安全实践 | ✅ |
| JSON Schema 生成 | ⭐⭐⭐ | 元编程能力 | ✅ |
| 工具注册表模式 | ⭐⭐ | 设计模式 | ✅ |
面试考点
- 如何设计一个可扩展的工具系统?
- Zod 如何实现类型推导和运行时验证?
- 如何将 TypeScript 类型转换为 JSON Schema?
设计思路:为什么需要类型安全的工具系统?
问题背景
AI Agent 的工具调用本质上是 LLM 决定调用哪个函数,并传递参数。但这里存在两个核心问题:
┌─────────────────────────────────────────────────────────────────────┐
│ 工具调用的两个核心问题 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 问题 1:LLM 怎么知道有哪些工具?怎么传参数? │
│ │
│ 解决方案:JSON Schema │
│ - 工具定义包含 name, description, input_schema │
│ - LLM 根据 schema 生成符合格式的 JSON 参数 │
│ │
│ 问题 2:Agent 如何验证 LLM 生成的参数是否正确? │
│ │
│ 方案 A:不验证,直接传给工具函数 │
│ - 风险:LLM 可能生成错误参数,导致运行时错误 │
│ │
│ 方案 B:手写验证逻辑 │
│ - 问题:代码冗余,容易出错 │
│ │
│ 方案 C:Zod 自动验证 + 类型推导 │
│ - 优点:一套定义,同时用于验证和类型推导 │
│ │
└─────────────────────────────────────────────────────────────────────┘
为什么选择 Zod?
Zod 的核心优势:
// 1. 定义 Schema(运行时验证)
const ReadFileSchema = z.object({
path: z.string().describe("文件路径"),
limit: z.number().optional().describe("行数限制"),
})
// 2. 自动推导 TypeScript 类型(编译时类型检查)
type ReadFileParams = z.infer<typeof ReadFileSchema>
// 等价于:{ path: string; limit?: number }
// 3. 自动生成 JSON Schema(给 LLM 使用)
const jsonSchema = zodToJsonSchema(ReadFileSchema)
// 输出:
// {
// type: "object",
// properties: {
// path: { type: "string", description: "文件路径" },
// limit: { type: "number", description: "行数限制" }
// },
// required: ["path"]
// }
一套定义,三处使用:
- 运行时验证 - 确保 LLM 生成的参数格式正确
- 编译时类型检查 - 开发时 IDE 自动补全和错误提示
- JSON Schema 生成 - 告诉 LLM 如何调用工具
工具注册表模式的作用
不使用注册表:
┌─────────┐
│ Agent │───▶ 需要知道所有工具的实现细节
└─────────┘
使用注册表:
┌─────────┐ ┌─────────────┐ ┌──────────────┐
│ Agent │────▶│ Registry │────▶│ Tool: read │
│ │ │ (统一入口) │ ├──────────────┤
│ get() │ │ list() │────▶│ Tool: write │
│ execute()│ │ execute() │ ├──────────────┤
└─────────┘ └─────────────┘ │ Tool: bash │
└──────────────┘
好处:
1. Agent 不需要知道工具实现细节
2. 工具可以动态注册和注销
3. 统一的工具发现机制
4. 便于实现权限控制
方案对比:工具系统设计
方案一:直接函数调用
// 最简单的方式,直接在 Agent 中调用函数
async function readFile(path: string) { ... }
async function writeFile(path: string, content: string) { ... }
// Agent 直接调用
const content = await readFile("/src/index.ts")
优点:简单直接
缺点:LLM 无法知道这些函数的存在和参数格式
结论:不适用于 AI Agent
方案二:手动 JSON Schema 定义
const tools = [
{
name: "read_file",
description: "读取文件内容",
parameters: {
type: "object",
properties: {
path: { type: "string", description: "文件路径" }
},
required: ["path"]
}
}
]
// 手动验证参数
function validateParams(toolName: string, params: any) {
const schema = tools.find(t => t.name === toolName).parameters
// 手写验证逻辑...
}
优点:完全控制
缺点:手动维护,容易出错,无类型安全
结论:不推荐
方案三:装饰器模式(Python 风格)
@tool
def read_file(path: str, limit: int = None):
"""读取文件内容"""
pass
# 自动生成 JSON Schema
优点:简洁优雅
缺点:TypeScript 装饰器支持有限,需要额外配置
结论:Python 项目推荐,TypeScript 项目不太适用
方案四:Zod + 注册表(本文方案)
const readTool = defineTool({
name: "read",
description: "读取文件内容",
parameters: z.object({
path: z.string(),
limit: z.number().optional(),
}),
execute: async (params) => { ... }
})
toolRegistry.register(readTool)
优点:类型安全、自动验证、自动生成 Schema
缺点:需要学习 Zod 语法
结论:TypeScript 项目推荐方案
常见陷阱与解决方案
陷阱一:Zod 验证失败时错误信息不友好
问题描述:
const schema = z.object({ path: z.string() })
schema.parse({ path: 123 }) // 抛出 ZodError,信息难以理解
// Error: Expected string, received number at "path"
解决方案:自定义错误消息
const schema = z.object({
path: z.string({
required_error: "path 是必填项",
invalid_type_error: "path 必须是字符串",
}),
})
// 或者使用 .refine 自定义验证
const schema = z.object({
path: z.string().refine(
(val) => val.startsWith("/"),
{ message: "path 必须是绝对路径" }
),
})
陷阱二:可选参数的默认值处理
问题描述:
const schema = z.object({
limit: z.number().optional(), // limit 可能是 undefined
})
// 工具函数期望有默认值
function read(path: string, limit: number = 100) { ... }
// 需要手动处理 undefined
const params = schema.parse(input)
read(params.path, params.limit ?? 100) // 手动提供默认值
解决方案:使用 Zod 的 default
const schema = z.object({
limit: z.number().default(100), // 自动提供默认值
})
const params = schema.parse({ path: "/test" })
// params.limit 自动为 100
陷阱三:JSON Schema 生成遗漏 description
问题描述:
const schema = z.object({
path: z.string(), // 没有 description
})
// 生成的 JSON Schema 也没有 description
// LLM 不知道这个参数是什么意思
解决方案:始终添加 describe
const schema = z.object({
path: z.string().describe("要读取的文件绝对路径"),
limit: z.number().optional().describe("最大读取行数,默认全部"),
})
// 生成的 JSON Schema 包含 description
// LLM 能理解参数含义,生成更准确的参数
陷阱四:工具执行异常没有正确传递给 LLM
问题描述:
async function execute(name: string, params: any) {
const tool = registry.get(name)
const result = await tool.execute(params) // 如果抛出异常?
return result
}
解决方案:捕获异常,返回错误结果
async function execute(name: string, params: any): Promise<ToolResult> {
try {
const tool = registry.get(name)
const validated = tool.parameters.parse(params)
return await tool.execute(validated)
} catch (error) {
// 返回错误信息,让 LLM 知道发生了什么
return {
title: `Error: ${name}`,
output: `Error: ${error instanceof Error ? error.message : String(error)}`,
metadata: { error: true },
}
}
}
陷阱五:忘记在 ToolResult 中提供足够的上下文
问题描述:
// 返回的信息太简略
return {
title: "Read file",
output: "content...", // LLM 不知道是哪个文件
}
解决方案:提供丰富的元数据
return {
title: `Read: ${absolutePath}`,
output: content,
metadata: {
path: absolutePath,
lines: lines.length,
size: content.length,
truncated: lines.length > limit,
},
}
// LLM 可以根据 metadata 理解结果
工具系统架构
整体设计
┌─────────────────────────────────────────────────────────────┐
│ Tool System │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Tool Registry │ │
│ │ - register(tool) │ │
│ │ - get(name) → Tool │ │
│ │ - list() → Tool[] │ │
│ │ - toLLMTools() → LLMToolDefinition[] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Tool Interface │ │
│ │ - name: string │ │
│ │ - description: string │ │
│ │ - parameters: ZodSchema │ │
│ │ - execute(params, ctx) → Promise<ToolResult> │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ read │ │ write │ │ bash │ │
│ │ Tool │ │ Tool │ │ Tool │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ └───────────────────┼───────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Tool Context │ │
│ │ - sessionId: string │ │
│ │ - messageId: string │ │
│ │ - workingDirectory: string │ │
│ │ - abortSignal: AbortSignal │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
工具调用流程
┌─────────────────────────────────────────────────────────────┐
│ Tool Call Flow │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. LLM 返回 tool_call │
│ { │
│ name: "read", │
│ input: { path: "/src/index.ts" } │
│ } │
│ │
│ 2. Agent 获取工具定义 │
│ tool = registry.get("read") │
│ │
│ 3. Zod 参数验证 │
│ validatedParams = tool.parameters.parse(input) │
│ │
│ 4. 执行工具 │
│ result = await tool.execute(validatedParams, ctx) │
│ │
│ 5. 返回结果 │
│ { │
│ title: "Read: /src/index.ts", │
│ metadata: { path: "...", lines: 100 }, │
│ output: "file content..." │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘
工具接口定义
核心类型
// src/tool/tool.ts
import z from "zod"
/**
* 工具执行上下文
*/
export interface ToolContext {
sessionId: string // 会话 ID
messageId: string // 消息 ID
workingDirectory: string // 工作目录
abortSignal: AbortSignal // 中止信号
}
/**
* 工具执行结果
*/
export interface ToolResult {
title: string // 结果标题
metadata: Record<string, unknown> // 元数据
output: string // 输出内容
}
/**
* 工具定义
*/
export interface ToolDefinition<P extends z.ZodType = z.ZodType> {
name: string // 工具名称
description: string // 工具描述
parameters: P // 参数 Schema
execute: (params: z.infer<P>, ctx: ToolContext) => Promise<ToolResult>
}
/**
* 工具定义辅助函数
* 提供类型推导
*/
export function defineTool<P extends z.ZodType>(
config: ToolDefinition<P>
): ToolDefinition<P> {
return config
}
Zod 到 JSON Schema 转换
// src/tool/tool.ts (续)
/**
* 将 Zod Schema 转换为 JSON Schema
* 用于 LLM Function Calling
*/
export function zodToJsonSchema(schema: z.ZodType): Record<string, unknown> {
const def = (schema as any)._def
// 对象类型
if (def.typeName === "ZodObject") {
const properties: Record<string, unknown> = {}
const required: string[] = []
for (const [key, value] of Object.entries(def.shape())) {
properties[key] = zodToJsonSchema(value as z.ZodType)
if ((value as any)._def.typeName !== "ZodOptional") {
required.push(key)
}
}
return {
type: "object",
properties,
required: required.length > 0 ? required : undefined,
additionalProperties: false,
}
}
// 字符串类型
if (def.typeName === "ZodString") {
return {
type: "string",
description: def.description
}
}
// 数字类型
if (def.typeName === "ZodNumber") {
return {
type: "number",
description: def.description
}
}
// 布尔类型
if (def.typeName === "ZodBoolean") {
return {
type: "boolean",
description: def.description
}
}
// 数组类型
if (def.typeName === "ZodArray") {
return {
type: "array",
items: zodToJsonSchema(def.type),
description: def.description,
}
}
// 可选类型
if (def.typeName === "ZodOptional") {
return zodToJsonSchema(def.innerType)
}
// 默认返回空对象
return {}
}
工具注册表
// src/tool/registry.ts
import z from "zod"
import type { ToolDefinition, ToolResult, ToolContext } from "./tool"
import { zodToJsonSchema } from "./tool"
/**
* 工具注册表
*/
class ToolRegistry {
private tools = new Map<string, ToolDefinition>()
/**
* 注册工具
*/
register(tool: ToolDefinition): void {
this.tools.set(tool.name, tool)
}
/**
* 获取工具
*/
get(name: string): ToolDefinition | undefined {
return this.tools.get(name)
}
/**
* 列出所有工具
*/
list(): ToolDefinition[] {
return Array.from(this.tools.values())
}
/**
* 转换为 LLM 工具定义格式
*/
toLLMTools(): Array<{
name: string
description: string
input_schema: Record<string, unknown>
}> {
return this.list().map(tool => ({
name: tool.name,
description: tool.description,
input_schema: zodToJsonSchema(tool.parameters),
}))
}
/**
* 执行工具
*/
async execute(
name: string,
params: Record<string, unknown>,
ctx: ToolContext
): Promise<ToolResult> {
const tool = this.tools.get(name)
if (!tool) {
throw new Error(`Unknown tool: ${name}`)
}
// Zod 参数验证
const validatedParams = tool.parameters.parse(params)
// 执行工具
return tool.execute(validatedParams, ctx)
}
}
// 单例实例
export const toolRegistry = new ToolRegistry()
基础工具实现
read 工具
// src/tool/read.ts
import z from "zod"
import { defineTool, ToolContext, ToolResult } from "./tool"
import { readFile, stat } from "fs/promises"
import { existsSync } from "fs"
import { resolve } from "path"
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
const Parameters = z.object({
path: z.string().describe("The absolute path to the file to read"),
offset: z.number().optional().describe("Line number to start reading from"),
limit: z.number().optional().describe("Maximum number of lines to read"),
})
export const readTool = defineTool({
name: "read",
description: `Read the contents of a file. Supports text files. The path must be absolute.`,
parameters: Parameters,
execute: async (params, ctx: ToolContext): Promise<ToolResult> => {
const absolutePath = resolve(params.path)
// 安全检查:路径必须在工作目录内
if (!absolutePath.startsWith(ctx.workingDirectory)) {
throw new Error(`Path must be within working directory: ${ctx.workingDirectory}`)
}
// 检查文件是否存在
if (!existsSync(absolutePath)) {
throw new Error(`File not found: ${absolutePath}`)
}
// 检查文件大小
const fileStat = await stat(absolutePath)
if (fileStat.size > MAX_FILE_SIZE) {
throw new Error(`File too large: ${fileStat.size} bytes (max: ${MAX_FILE_SIZE})`)
}
// 读取文件内容
const content = await readFile(absolutePath, "utf-8")
const lines = content.split("\n")
// 分页处理
let offset = params.offset ?? 0
let limit = params.limit ?? lines.length
const selectedLines = lines.slice(offset, offset + limit)
const numberedLines = selectedLines.map((line, i) =>
`${String(offset + i + 1).padStart(6, " ")}\t${line}`
).join("\n")
const header = offset > 0 || limit < lines.length
? `[Lines ${offset + 1}-${Math.min(offset + limit, lines.length)} of ${lines.length}]\n`
: `[${lines.length} lines]\n`
return {
title: `Read: ${absolutePath}`,
metadata: { path: absolutePath, lines: lines.length },
output: header + numberedLines,
}
},
})
write 工具
// src/tool/write.ts
import z from "zod"
import { defineTool, ToolContext, ToolResult } from "./tool"
import { writeFile, mkdir } from "fs/promises"
import { dirname, resolve } from "path"
const Parameters = z.object({
path: z.string().describe("The absolute path to write the file to"),
content: z.string().describe("The content to write to the file"),
})
export const writeTool = defineTool({
name: "write",
description: `Write content to a file. Creates the file if it doesn't exist, overwrites if it does. The path must be absolute.`,
parameters: Parameters,
execute: async (params, ctx: ToolContext): Promise<ToolResult> => {
const absolutePath = resolve(params.path)
// 安全检查
if (!absolutePath.startsWith(ctx.workingDirectory)) {
throw new Error(`Path must be within working directory: ${ctx.workingDirectory}`)
}
// 确保目录存在
await mkdir(dirname(absolutePath), { recursive: true })
// 写入文件
await writeFile(absolutePath, params.content, "utf-8")
return {
title: `Write: ${absolutePath}`,
metadata: {
path: absolutePath,
size: params.content.length,
lines: params.content.split("\n").length,
},
output: `Successfully wrote ${params.content.length} bytes to ${absolutePath}`,
}
},
})
edit 工具
// src/tool/edit.ts
import z from "zod"
import { defineTool, ToolContext, ToolResult } from "./tool"
import { readFile, writeFile } from "fs/promises"
import { existsSync } from "fs"
import { resolve } from "path"
const Parameters = z.object({
path: z.string().describe("The absolute path to the file to edit"),
old_string: z.string().describe("The exact text to replace"),
new_string: z.string().describe("The text to replace it with"),
})
export const editTool = defineTool({
name: "edit",
description: `Make precise edits to a file by replacing specific text. The old_string must match exactly.`,
parameters: Parameters,
execute: async (params, ctx: ToolContext): Promise<ToolResult> => {
const absolutePath = resolve(params.path)
// 安全检查
if (!absolutePath.startsWith(ctx.workingDirectory)) {
throw new Error(`Path must be within working directory: ${ctx.workingDirectory}`)
}
if (!existsSync(absolutePath)) {
throw new Error(`File not found: ${absolutePath}`)
}
// 读取文件
const content = await readFile(absolutePath, "utf-8")
// 查找并替换
const index = content.indexOf(params.old_string)
if (index === -1) {
throw new Error(`Could not find the text to replace in ${absolutePath}`)
}
// 检查是否唯一
const secondIndex = content.indexOf(params.old_string, index + 1)
if (secondIndex !== -1) {
throw new Error(`Found multiple occurrences of the text to replace. Please provide more context.`)
}
// 执行替换
const newContent = content.slice(0, index) + params.new_string + content.slice(index + params.old_string.length)
// 写入文件
await writeFile(absolutePath, newContent, "utf-8")
return {
title: `Edit: ${absolutePath}`,
metadata: {
path: absolutePath,
replacements: 1,
},
output: `Successfully replaced text in ${absolutePath}`,
}
},
})
bash 工具
// src/tool/bash.ts
import z from "zod"
import { defineTool, ToolContext, ToolResult } from "./tool"
import { spawn } from "child_process"
const DEFAULT_TIMEOUT = 120000 // 2分钟
const Parameters = z.object({
command: z.string().describe("The shell command to execute"),
timeout: z.number().optional().describe("Timeout in milliseconds (default: 120000)"),
})
export const bashTool = defineTool({
name: "bash",
description: `Execute a shell command. Use with caution as it can modify the filesystem.`,
parameters: Parameters,
execute: async (params, ctx: ToolContext): Promise<ToolResult> => {
const timeout = params.timeout ?? DEFAULT_TIMEOUT
const startTime = Date.now()
return new Promise((resolve, reject) => {
const proc = spawn(params.command, [], {
cwd: ctx.workingDirectory,
shell: true,
timeout,
})
let stdout = ""
let stderr = ""
proc.stdout.on("data", (data) => {
stdout += data.toString()
})
proc.stderr.on("data", (data) => {
stderr += data.toString()
})
proc.on("close", (code) => {
const duration = Date.now() - startTime
resolve({
title: `Bash: ${params.command.slice(0, 50)}...`,
metadata: {
command: params.command,
exitCode: code,
duration,
},
output: [
`Exit Code: ${code}`,
`Duration: ${duration}ms`,
"",
"STDOUT:",
stdout || "(empty)",
"",
"STDERR:",
stderr || "(empty)",
].join("\n"),
})
})
proc.on("error", (err) => {
reject(err)
})
// 支持中止
ctx.abortSignal.addEventListener("abort", () => {
proc.kill()
reject(new Error("Command was aborted"))
})
})
},
})
glob 工具
// src/tool/glob.ts
import z from "zod"
import { defineTool, ToolContext, ToolResult } from "./tool"
import { glob as globAsync } from "glob"
import { stat } from "fs/promises"
const Parameters = z.object({
pattern: z.string().describe("The glob pattern to match files against"),
path: z.string().optional().describe("The directory to search in (default: working directory)"),
})
export const globTool = defineTool({
name: "glob",
description: `Find files matching a glob pattern. Results are sorted by modification time (newest first).`,
parameters: Parameters,
execute: async (params, ctx: ToolContext): Promise<ToolResult> => {
const searchPath = params.path ? resolve(params.path) : ctx.workingDirectory
// 安全检查
if (!searchPath.startsWith(ctx.workingDirectory)) {
throw new Error(`Path must be within working directory: ${ctx.workingDirectory}`)
}
// 执行 glob 搜索
const files = await globAsync(params.pattern, {
cwd: searchPath,
absolute: true,
nodir: true,
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
})
// 按修改时间排序
const filesWithStats = await Promise.all(
files.map(async (file) => {
try {
const s = await stat(file)
return { file, mtime: s.mtimeMs }
} catch {
return { file, mtime: 0 }
}
})
)
filesWithStats.sort((a, b) => b.mtime - a.mtime)
const output = filesWithStats
.map((f, i) => `${i + 1}. ${f.file}`)
.join("\n")
return {
title: `Glob: ${params.pattern}`,
metadata: {
pattern: params.pattern,
path: searchPath,
count: files.length,
},
output: output || "No files found",
}
},
})
工具初始化
// src/tool/index.ts
import { toolRegistry } from './registry'
import { readTool } from './read'
import { writeTool } from './write'
import { editTool } from './edit'
import { bashTool } from './bash'
import { globTool } from './glob'
import { grepTool } from './grep'
import { batchTool } from './batch'
import { taskTool } from './task'
import { skillTool } from './skill'
// 导出类型
export * from './tool'
export { toolRegistry }
/**
* 初始化所有工具
*/
export function initializeTools(): void {
// 基础工具
toolRegistry.register(readTool)
toolRegistry.register(writeTool)
toolRegistry.register(editTool)
toolRegistry.register(bashTool)
toolRegistry.register(globTool)
toolRegistry.register(grepTool)
// 高级工具
toolRegistry.register(batchTool)
toolRegistry.register(taskTool)
toolRegistry.register(skillTool)
}
工具使用示例
生成 LLM 工具定义
import { toolRegistry } from './tool'
// 获取所有工具的 LLM 格式定义
const llmTools = toolRegistry.toLLMTools()
console.log(JSON.stringify(llmTools[0], null, 2))
// 输出:
// {
// "name": "read",
// "description": "Read the contents of a file...",
// "input_schema": {
// "type": "object",
// "properties": {
// "path": { "type": "string", "description": "The absolute path..." },
// "offset": { "type": "number", "description": "Line number..." },
// "limit": { "type": "number", "description": "Maximum number..." }
// },
// "required": ["path"],
// "additionalProperties": false
// }
// }
执行工具调用
import { toolRegistry } from './tool'
const ctx: ToolContext = {
sessionId: 'session-123',
messageId: 'msg-456',
workingDirectory: '/Users/example/project',
abortSignal: new AbortController().signal,
}
// 执行 read 工具
const result = await toolRegistry.execute('read', {
path: '/Users/example/project/src/index.ts',
limit: 50,
}, ctx)
console.log(result.output)
小结
本章实现了工具系统的基础架构,包括:
- 工具接口 - 统一的工具定义和执行接口
- Zod 参数验证 - 类型安全的参数定义和验证
- JSON Schema 生成 - 自动生成 LLM Function Calling 格式
- 基础工具 - read、write、edit、bash、glob 实现
关键要点:
- Zod 提供了类型推导和运行时验证的双重保障
- 工具注册表模式实现了工具的统一管理和发现
- 工具执行上下文提供了必要的环境信息和控制能力
- 安全检查确保工具只能操作工作目录内的文件
下一章我们将实现 Agent 核心循环,将 Provider 和 Tool 系统整合起来,实现完整的 ReAct 循环。
参考资料
相关文章
从零到一实现 nano-agent(五):Agent 循环与对话管理
实现 AI Agent 的核心 ReAct 循环,处理工具调用、消息状态管理和流式响应,构建完整的 Agent 执行引擎。
从零到一实现 nano-agent(三):Provider 抽象与多模型支持
实现多 LLM 提供商的统一接入,支持流式响应、工具调用和成本计算,构建 Provider 抽象层和注册表。
从零到一实现 nano-agent(一):项目概述与架构设计
深入分析 AI 编程助手的核心架构,设计 nano-agent 项目的技术选型和核心模块,为构建生产级 AI Coding Agent 奠定基础。