在前端开发领域,随着 AI 技术的快速发展,我经常探索如何将 AI 集成到日常开发流程中。今天,我想分享一个实际案例:使用 LangChain 框架构建一个 AI 代理,通过自定义工具来自动化创建和管理一个 React TodoList 应用。这个代理可以处理文件读写、命令执行和目录操作等任务,让开发过程更高效、智能。
LangChain 是一个强大的开源框架,用于构建基于大型语言模型(LLM)的应用。它允许我们定义“工具”(Tools),这些工具可以被 LLM 调用来执行具体操作,从而实现代理(Agent)式的交互。代理的核心在于循环迭代:LLM 思考、规划、使用工具、调整,直到任务完成。这比单纯的提示工程更智能,能处理复杂、多步任务。
在本文中,我将基于提供的代码实例逐步讲解。首先介绍四个核心工具的实现,然后说明如何将它们与 OpenAI 模型绑定,形成一个代理。最后,通过一个完整示例演示如何自动化创建 React TodoList 应用,包括项目初始化、代码编写、样式添加、动画实现和依赖安装。代码中有些小错误我会指正,以确保分享的知识准确可靠。所有示例都使用 Node.js 环境,依赖如 @langchain/core/tools、zod 等。
核心工具的实现
代理需要工具来与外部世界交互。我们定义了四个工具:读取文件、写入文件、执行命令和列出目录。这些工具使用 LangChain 的 tool 函数创建,每个工具包括异步执行逻辑、名称、描述和 Zod schema 用于参数验证。
首先是读取文件工具(readFileTool):
JavaScript
const readFileTool = tool(
async ({filePath}) => {
try {
const content = await fs.readFile(filePath, 'utf-8');
console.log(`[工具调用] read_file("${filePath}") 成功读取 ${content.length} 字节`);
return `文件内容:\n${content}`;
} catch (error) {
console.log(`工具调用 read_file("${filePath}") 失败:${error.message}`);
return `错误:${error.message}`;
}
},
{
name: 'read_file',
description: '读取指定文件的内容',
schema: z.object({
filePath: z.string().describe('文件路径')
})
}
);
这个工具使用 Node.js 的 fs/promises 读取文件内容,返回 UTF-8 编码的字符串。如果失败,返回错误消息。日志记录有助于调试。
接下来是写入文件工具(writeFileTool):
JavaScript
const writeFileTool = tool(
async ({filePath, content}) => {
try {
const dir = path.dirname(filePath);
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(filePath, content, 'utf-8');
console.log(`[工具调用] write_file("${filePath}") 成功写入 ${content.length} 字节`);
return `文件写入成功: ${filePath}`;
} catch (error) {
console.log(`工具调用 write_file("${filePath}") 失败:${error.message}`);
return `写入文件失败:${error.message}`;
}
},
{
name: "write_file",
description: '向指定路径写入文件内容,自动创建目录',
schema: z.object({
filePath: z.string().describe('文件路径'),
content: z.string().describe('要写入的文件内容')
})
}
);
这个工具先创建目录(使用 recursive: true 确保嵌套目录),然后写入内容。
第三个是执行命令工具(executeCommandTool),代码中名为 executeCommanTool,有拼写错误,应改为 executeCommandTool:
JavaScript
const executeCommandTool = tool(
async ({command, workingDirectory}) => {
const cwd = workingDirectory || process.cwd();
console.log(`[工具调用] execute_command("${command}", 在目录 ${cwd} 执行命令`);
return new Promise((resolve, reject) => {
const [cmd, ...args] = command.split(' ');
const child = spawn(cmd, args, {
cwd,
stdio: 'inherit',
shell: true
});
let errorMsg = '';
child.on('error', (error) => {
errorMsg = error.message;
});
child.on('close', (code) => {
if (code === 0) {
console.log(`[工具调用] execute_command("${command}") 命令执行成功,子进程退出`);
const cwdInfo = workingDirectory ?
`\n\n重要提示:命令在目录"${workingDirectory}"中执行成功。
如果需要在这个项目目录中继续执行命令,请使用 workingDirectory
"${workingDirectory}" 参数,不要使用 cd 命令`
: ``;
resolve(`命令执行成功, ${command} ${cwdInfo}`);
} else {
if (errorMsg) {
console.error(`错误:${errorMsg}`);
}
reject(`命令执行失败,退出码:${code}`);
}
});
});
},
{
name: 'execute_command',
description: '执行系统命令,支持指定工作目录,实时显示输出',
schema: z.object({
command: z.string().describe('要执行的命令'),
workingDirectory: z.string().optional().describe('指定工作目录,默认当前目录')
})
}
);
这个工具使用 child_process.spawn 执行命令,支持 workingDirectory 参数,避免手动 cd。Promise 结构处理异步,成功时返回提示信息,提醒后续命令使用 workingDirectory 而非 cd。
最后一个是列出目录工具(listDirectoryTool):
JavaScript
const listDirectoryTool = tool(
async ({directoryPath}) => {
try {
const files = await fs.readdir(directoryPath);
console.log(`[工具调用] list_directory("${directoryPath}") 成功列出 ${files.length} 个文件`);
return `目录内容:\n ${files.map(f => `- ${f}`).join('\n')}`;
} catch (error) {
console.log(`工具调用 list_directory("${directoryPath}") 失败:${error.message}`);
return `列出目录失败:${error.message}`;
}
},
{
name: 'list_directory',
description: "列出指定目录的所有文件和文件夹",
schema: z.object({
directoryPath: z.string().describe('要列出的目录路径'),
})
}
);
简单高效,使用 fs.readdir 返回文件列表,以 Markdown 格式输出,便于阅读。
这些工具导出后,可在代理中使用。
构建 AI 代理
现在,我们将这些工具与 LLM 绑定。使用 @langchain/openai 的 ChatOpenAI,配置模型、API 密钥和 baseURL(从 .env 加载)。
JavaScript
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
configuration: {
baseURL: process.env.OPENAI_API_BASE_URL,
}
});
const tools = [
readFileTool,
writeFileTool,
executeCommandTool,
listDirectoryTool,
];
const modelWithTools = model.bindTools(tools);
代理函数 runAgentWithTools 接受查询,初始化消息数组,包括系统提示:
JavaScript
const messages = [
new SystemMessage(`
你是一个项目管理助手,使用工具完成任务。
当前工作目录:${process.cwd()}
工具:
1. read_file: 读取文件
2. write_file: 写入文件
3. execute_command: 执行命令(支持workingDirectory参数)
4. list_directory: 列出目录
重要规则 - execute_command:
- workingDirectory 参数会自动切换到指定目录
- 当使用workingDirectory参数时,不要在command中使用cd命令
- 错误示例: { command: "cd react-todo-app && pnpm install", workingDirectory: "react-todo-app" }
这是错误的!因为 workingDirectory 已经在 react-todo-app 目录了,再 cd react-todo-app 会找不到目录
- 正确示例: { command: "pnpm install", workingDirectory: "react-todo-app" }
这样就对了!workingDirectory 已经切换到 react-todo-app,直接执行命令即可
回复要简洁,只说做了什么
`),
new HumanMessage(query),
];
系统提示定义角色、工具和规则,确保代理高效。循环迭代(最多 30 次):
- 调用 modelWithTools.invoke(messages),获取响应。
- 如果无 tool_calls,任务完成,返回内容。
- 否则,执行每个 tool_call,添加 ToolMessage 到 messages。
使用 chalk 彩色日志增强可读性。
示例:自动化创建 React TodoList 应用
让我们看一个实际查询(case1):
JavaScript
const case1 = `
创建一个功能丰富的 React TodoList 应用:
1. 创建项目:echo -e "n\nn" | pnpm create vite react-todo-app --template react-ts
2. 修改 src/App.tsx,实现完整功能的 TodoList:
- 添加、删除、编辑、标记完成
- 分类筛选(全部/进行中/已完成)
- 统计信息显示
- localStorage 数据持久化
3. 添加复杂样式:
- 渐变背景(蓝到紫)
- 卡片阴影、圆角
- 悬停效果
4. 添加动画:
- 添加/删除时的过渡动画
- 使用 CSS transitions
5. 列出目录确认
注意:使用 pnpm,功能要完整,样式要美观,要有动画效果
之后在 react-todo-app 项目中:
1. 使用 pnpm install 安装依赖
2. 使用 pnpm run dev 启动服务器
`;
代理接收这个查询,启动循环。LLM 先规划:使用 execute_command 执行项目创建命令(echo -e "n\nn" | pnpm create vite react-todo-app --template react-ts)。成功后,切换 workingDirectory 到 react-todo-app。
然后,读取 src/App.tsx(read_file),修改内容(write_file),实现 TodoList 逻辑。代理会生成 React 代码,包括 useState、管理 todos、localStorage 持久化、筛选过滤器、统计(如完成数)、CSS 样式(background: linear-gradient(to right, blue, purple); box-shadow, border-radius, :hover 效果)和过渡动画(transition: opacity 0.3s;)。
例如,代理可能生成的 App.tsx 片段(简化):
tsx
import React, { useState, useEffect } from 'react';
import './App.css';
interface Todo {
id: number;
text: string;
completed: boolean;
}
function App() {
const [todos, setTodos] = useState<Todo[]>([]);
const [input, setInput] = useState('');
const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all');
// ... 添加、删除、编辑、标记完成逻辑
useEffect(() => {
const stored = localStorage.getItem('todos');
if (stored) setTodos(JSON.parse(stored));
}, []);
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
// 筛选和统计
const filteredTodos = todos.filter(/* 根据 filter */);
const completedCount = todos.filter(t => t.completed).length;
return (
<div className="app">
{/* UI 组件,带动画 class */}
</div>
);
}
添加 App.css 以渐变背景、阴影等。
代理继续:list_directory 确认文件,使用 execute_command 安装依赖(pnpm install, workingDirectory: "react-todo-app"),然后 pnpm run dev 启动。
整个过程代理自动迭代,处理错误(如目录不存在),直到完成。输出如 "项目创建成功,代码修改完成,服务器启动"。
结语
通过 LangChain 构建 AI 代理,我们能自动化前端开发任务,如创建 React TodoList,提升效率。