17.2 实验二:为 CLI 工具添加 Tool Use 能力
Last updated
Last updated
import { z } from "zod"
import { tool } from "ai"
import * as fs from "fs"
import { execSync } from "child_process"
// 工具一:文件读取
const readFileTool = tool({
description: "读取指定路径的文件内容。用于查看代码、配置文件等。",
parameters: z.object({
filePath: z.string().describe("要读取的文件的绝对路径或相对路径"),
}),
execute: async ({ filePath }) => {
try {
const content = fs.readFileSync(filePath, "utf-8")
return `文件内容 (${filePath}):\n${content}`
} catch (e: any) {
return `错误:无法读取文件 ${filePath}: ${e.message}`
}
},
})
// 工具二:文件写入
const writeFileTool = tool({
description: "将内容写入指定路径的文件。如果文件不存在则创建,存在则覆盖。",
parameters: z.object({
filePath: z.string().describe("要写入的文件路径"),
content: z.string().describe("要写入的文件内容"),
}),
execute: async ({ filePath, content }) => {
try {
fs.writeFileSync(filePath, content, "utf-8")
return `成功写入文件: ${filePath} (${content.length} 字符)`
} catch (e: any) {
return `错误:无法写入文件 ${filePath}: ${e.message}`
}
},
})
// 工具三:命令执行
const bashTool = tool({
description: "在 Shell 中执行命令。用于运行代码、安装依赖、查看目录等。",
parameters: z.object({
command: z.string().describe("要执行的 Shell 命令"),
}),
execute: async ({ command }) => {
try {
const output = execSync(command, {
encoding: "utf-8",
timeout: 30000, // 30 秒超时
maxBuffer: 1024 * 1024, // 1MB 输出上限
})
return `命令输出:\n${output}`
} catch (e: any) {
return `命令执行失败:\n${e.stderr || e.message}`
}
},
})const result = streamText({
model,
system: `你是一个 AI 编程助手,可以读取文件、写入文件和执行命令。
规则:
1. 需要查看文件内容时,使用 readFile 工具
2. 需要创建或修改文件时,使用 writeFile 工具
3. 需要执行命令时,使用 bash 工具
4. 在修改文件之前,先读取文件了解当前内容
5. 如果任务完成,直接告诉用户结果`,
messages,
tools: {
readFile: readFileTool,
writeFile: writeFileTool,
bash: bashTool,
},
maxSteps: 10, // 最多允许 10 轮工具调用
})async function chat(userInput: string) {
messages.push({ role: "user", content: userInput })
const result = streamText({
model,
system: `你是一个 AI 编程助手...(同上)`,
messages,
tools: {
readFile: readFileTool,
writeFile: writeFileTool,
bash: bashTool,
},
maxSteps: 10,
})
let fullResponse = ""
process.stdout.write("\nAssistant: ")
// 处理流式输出——包括文本和工具调用
for await (const part of result.fullStream) {
switch (part.type) {
case "text-delta":
// 文本块
process.stdout.write(part.textDelta)
fullResponse += part.textDelta
break
case "tool-call":
// LLM 决定调用工具
console.log(`\n [工具调用] ${part.toolName}(${
JSON.stringify(part.args)
})`)
break
case "tool-result":
// 工具执行完成
const preview = String(part.result).slice(0, 200)
console.log(` [工具结果] ${preview}${
String(part.result).length > 200 ? "..." : ""
}`)
break
case "step-finish":
// 一轮工具调用完成,LLM 继续思考
break
}
}
console.log("\n")
messages.push({ role: "assistant", content: fullResponse })
}bun run index.tsYou: 帮我创建一个 hello.py 文件,写一个简单的 Flask 服务器