关键点
- AI Agent 定义:AI Agent 是一种能够自主感知环境、决策并执行任务的智能系统,超越了传统聊天机器人的对话功能。
- 与聊天机器人的区别:AI Agent 不仅能回答问题,还能主动调用外部 API、执行多步任务,具备更高的自主性和复杂性。
- 技术实现:通过 OpenAI Function Call 或 LangChain 框架,开发者可以构建支持多轮对话和多工具调用的 AI Agent。
- 前端设计:用户界面需直观展示 Agent 的状态(如思考中、执行中),并支持多轮对话的交互。
- 适用场景:AI Agent 适用于自动化任务、客服、数据处理等场景,2025 年将更广泛应用于企业级应用。
- 注意事项:实现 AI Agent 需要权衡性能、复杂性和用户体验,需谨慎管理 API 调用和状态同步。
什么是 AI Agent?
AI Agent(智能代理)是一种能够自主执行任务的软件系统,它可以感知环境、分析数据、做出决策并采取行动。与传统聊天机器人相比,AI Agent 不仅限于回答用户的问题,还能主动调用外部工具(如 API)完成复杂任务。例如,一个 AI Agent 可以根据用户请求查询天气、翻译文本或处理报销申请,而无需用户逐一指导每一步操作。
在 2025 年的技术趋势中,AI Agent 正成为企业自动化和智能化转型的核心工具。它们通过结合大型语言模型(LLM)和外部工具,实现了从简单对话到复杂任务自动化的跨越。
OpenAI Function Call 与 LangChain
OpenAI Function Call 是一种允许 AI 模型调用开发者定义的外部函数的机制。例如,模型可以根据用户输入决定调用天气 API 或翻译服务。LangChain 则是一个开源框架,专为构建基于 LLM 的应用设计,支持多工具集成、记忆管理和复杂任务链。
OpenAI Function Call 的优势:
- 简单易用,直接集成在 OpenAI API 中。
- 适合快速开发和与 OpenAI 模型的深度集成。
LangChain 的优势:
- 支持多种 LLM(如 OpenAI、Mistral),更灵活。
- 提供高级功能,如工具选择、记忆管理和任务链调度。
由于 LangChain 的灵活性和对多工具的支持,本文将以 LangChain 为主要实现框架,同时简要介绍 OpenAI Function Call 的用法。
项目目标
我们将构建一个前端 AI Agent 网页,支持以下功能:
- 多轮对话:用户可以与 Agent 进行连续对话,Agent 保持上下文。
- 多工具调用:Agent 可以调用天气、翻译和报销助手工具。
- 状态反馈:界面实时显示 Agent 的状态(如“思考中”或“执行中”)。
- 用户体验:通过直观的 UI 和动画效果提升交互体验。
动手做一个能主动执行任务的 AI Agent 网页
引言
在人工智能的快速发展中,AI Agent(智能代理)作为一种能够自主执行任务的系统,正成为 Web 开发的前沿技术。不同于传统的聊天机器人,AI Agent 不仅能理解和回应用户的查询,还能主动调用外部 API、执行多步操作,甚至根据用户需求动态调整行为路径。这种能力使得 AI Agent 在自动化工作流、智能客服、个性化推荐等领域具有巨大潜力。
本文将带您一步步构建一个基于 React 的前端 AI Agent 系统,利用 LangChain 框架和 OpenAI API,实现一个支持多轮对话和多工具调用的智能代理。我们将从需求分析开始,逐步完成技术选型、功能实现、性能优化和部署上线,并在最后提供一个练习,帮助您巩固所学内容。本教程融入 2025 年的技术趋势,特别强调模块化设计、用户体验优化和 AI 驱动的开发模式。
通过本项目,您将体验到:
- 需求分析:将业务需求转化为技术实现。
- 技术栈选择:选择适合 AI Agent 的工具和框架。
- 多工具集成:实现天气、翻译和报销助手功能。
- 前端设计:构建直观的 UI,展示 Agent 状态和多轮对话。
- 性能优化:通过缓存和错误处理提升体验。
- 部署上线:将应用部署到 Vercel。
本文面向有经验的开发者,假设您熟悉 React、JavaScript 和基本的 API 调用知识。准备好了吗?让我们开始吧!
需求分析
在动手编码之前,我们需要明确项目的功能需求。一个清晰的需求清单不仅能指导开发过程,还能帮助我们理解每个功能的意义。以下是 AI Agent 网页的核心需求:
- 多轮对话
- 用户可以与 Agent 进行连续对话,Agent 需保持上下文。
- 支持文本输入和输出,界面直观显示对话历史。
- 多工具调用
- Agent 支持调用天气 API、翻译 API 和报销助手。
- 根据用户输入,Agent 自动选择合适的工具并执行任务。
- 状态反馈
- 界面实时显示 Agent 的状态(如“思考中”、“执行中”)。
- 提供友好的错误提示和任务进度反馈。
- 用户体验
- 使用动画效果(如消息渐入)提升交互体验。
- 支持响应式设计,适配桌面和移动端。
- 数据持久化
- 对话历史保存在本地存储,刷新页面后依然可用。
- 部署
- 应用部署到 Vercel,支持全球访问。
需求背后的意义
这些需求覆盖了 AI Agent 应用的核心场景,同时为学习 React 和 AI 技术提供了丰富的实践机会:
- 多轮对话:考验状态管理和上下文处理能力。
- 多工具调用:展示 LangChain 的工具选择和任务链调度。
- 状态反馈:提升用户体验,体现前端设计的复杂性。
- 用户体验:通过动画和响应式设计优化交互。
- 数据持久化:引入本地存储的概念。
- 部署:展示生产级应用的部署流程。
这些需求还为性能优化(如缓存和错误处理)提供了实际场景,确保应用在高负载下依然流畅。
技术栈选择
在实现功能之前,我们需要选择合适的技术栈。以下是本项目使用的工具和技术,以及选择它们的理由:
- React
核心前端框架,用于构建用户界面。React 的组件化和声明式编程提升开发效率。 - Vite
构建工具,提供快速的开发服务器和高效的打包能力,符合 2025 年高性能开发趋势。 - LangChain
开源框架,用于构建基于大型语言模型(LLM)的应用,支持多工具集成和任务链调度。 - OpenAI API
提供强大的语言模型支持,结合 Function Call 实现工具调用。 - Framer Motion
用于实现动画效果,提升用户体验。 - React Query
管理数据请求和缓存,简化与后端交互。 - Tailwind CSS
提供快速、灵活的样式解决方案,支持响应式设计。 - Vercel
用于部署应用,提供高可用性和全球 CDN 支持。
技术栈优势
- React:生态丰富,社区活跃,适合快速开发。
- Vite:启动速度快,热更新体验优越。
- LangChain:支持多工具集成,灵活性高。
- OpenAI API:提供强大的语言处理能力。
- Framer Motion:实现流畅的动画效果。
- React Query:自动管理数据同步。
- Tailwind CSS:简化样式开发。
- Vercel:与 React 生态深度集成。
这些工具组合不仅易于上手,还能帮助您掌握 React 开发的最佳实践。
项目实现
现在进入核心部分——代码实现。我们将从项目搭建开始,逐步完成组件设计、LangChain 集成、工具调用、状态反馈、动画效果和部署。
1. 项目搭建
使用 Vite 创建一个 React 项目:
npm create vite@latest ai-agent -- --template react
cd ai-agent
npm install
npm run dev
安装必要的依赖:
npm install @langchain/openai @tanstack/react-query framer-motion tailwindcss postcss autoprefixer
初始化 Tailwind CSS:
npx tailwindcss init -p
编辑 tailwind.config.js
:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
在 src/index.css
中引入 Tailwind:
@tailwind base;
@tailwind components;
@tailwind utilities;
2. 组件拆分
组件化是 React 的核心思想。我们将应用拆分为以下组件:
- App:根组件,负责整体布局。
- ChatWindow:显示对话历史和状态。
- Message:单个消息组件,支持动画。
- InputBox:用户输入框。
- StatusIndicator:显示 Agent 状态。
文件结构
src/
├── components/
│ ├── ChatWindow.tsx
│ ├── Message.tsx
│ ├── InputBox.tsx
│ └── StatusIndicator.tsx
├── hooks/
│ └── useAgent.ts
├── App.tsx
├── main.tsx
└── index.css
3. LangChain 集成
我们将使用 LangChain 构建 AI Agent,支持多工具调用。
配置后端
创建一个简单的 Node.js 后端来处理 Agent 逻辑:
mkdir backend
cd backend
npm init -y
npm install @langchain/openai dotenv express
backend/index.js
:
require('dotenv').config();
const express = require('express');
const { ChatOpenAI } = require('@langchain/openai');
const { initializeAgentExecutorWithOptions } = require('langchain/agents');
const app = express();
app.use(express.json());
const tools = [
{
name: 'get_weather',
description: 'Get the weather for a given location',
func: async (location) => `The weather in ${location} is sunny.`,
},
{
name: 'translate',
description: 'Translate text to a target language',
func: async (text, targetLang) => `Translated text: ${text} (to ${targetLang})`,
},
{
name: 'process_expense',
description: 'Process an expense report',
func: async (amount, category) => `Expense of ${amount} in ${category} processed.`,
},
].map(tool => ({
name: tool.name,
description: tool.description,
schema: {
type: 'object',
properties: {
[tool.name === 'get_weather' ? 'location' : tool.name === 'translate' ? 'text' : 'amount']: { type: 'string' },
...(tool.name === 'translate' ? { targetLang: { type: 'string' } } : {}),
...(tool.name === 'process_expense' ? { category: { type: 'string' } } : {}),
},
required: [tool.name === 'get_weather' ? 'location' : tool.name === 'translate' ? 'text' : 'amount'],
},
func: tool.func,
}));
const model = new ChatOpenAI({ temperature: 0, openAIApiKey: process.env.OPENAI_API_KEY });
app.post('/api/agent', async (req, res) => {
const { input, history } = req.body;
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: 'openai-functions',
verbose: true,
});
const result = await executor.invoke({ input, history });
res.json({ output: result.output });
});
app.listen(3001, () => console.log('Server running on port 3001'));
创建 .env
文件:
OPENAI_API_KEY=your_openai_api_key
运行后端:
node index.js
4. 前端实现
App 组件
src/App.tsx
:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import ChatWindow from './components/ChatWindow';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<div className="min-h-screen bg-gray-100 flex flex-col items-center p-4">
<h1 className="text-3xl font-bold mb-4">AI Agent 网页</h1>
<ChatWindow />
</div>
</QueryClientProvider>
);
}
export default App;
ChatWindow 组件
src/components/ChatWindow.tsx
:
import { useState, useEffect } from 'react';
import { useQuery, useMutation } from '@tanstack/react-query';
import InputBox from './InputBox';
import Message from './Message';
import StatusIndicator from './StatusIndicator';
interface MessageType {
role: 'user' | 'assistant';
content: string;
}
function ChatWindow() {
const [messages, setMessages] = useState<MessageType[]>([]);
const [status, setStatus] = useState<'idle' | 'thinking' | 'executing'>('idle');
const { mutate, isPending } = useMutation({
mutationFn: async (input: string) => {
setStatus('thinking');
const response = await fetch('[invalid url, do not cite]
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input, history: messages }),
});
setStatus('executing');
const data = await response.json();
setStatus('idle');
return data;
},
onSuccess: (data) => {
setMessages([...messages, { role: 'assistant', content: data.output }]);
},
});
const handleSend = (input: string) => {
setMessages([...messages, { role: 'user', content: input }]);
mutate(input);
};
return (
<div className="w-full max-w-2xl bg-white rounded-lg shadow-lg flex flex-col h-[80vh]">
<StatusIndicator status={status} />
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, index) => (
<Message key={index} role={msg.role} content={msg.content} />
))}
</div>
<InputBox onSend={handleSend} disabled={isPending} />
</div>
);
}
export default ChatWindow;
Message 组件
src/components/Message.tsx
:
import { motion } from 'framer-motion';
interface MessageProps {
role: 'user' | 'assistant';
content: string;
}
function Message({ role, content }: MessageProps) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className={`p-3 rounded-lg max-w-xs ${role === 'user' ? 'bg-blue-500 text-white ml-auto' : 'bg-gray-200'}`}
>
<p>{content}</p>
</motion.div>
);
}
export default Message;
InputBox 组件
src/components/InputBox.tsx
:
import { useState } from 'react';
interface InputBoxProps {
onSend: (input: string) => void;
disabled: boolean;
}
function InputBox({ onSend, disabled }: InputBoxProps) {
const [input, setInput] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || disabled) return;
onSend(input);
setInput('');
};
return (
<div className="p-4 border-t bg-white flex items-center space-x-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSubmit(e)}
className="flex-1 p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="输入您的请求..."
disabled={disabled}
/>
<button
onClick={handleSubmit}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400"
disabled={disabled}
>
发送
</button>
</div>
);
}
export default InputBox;
StatusIndicator 组件
src/components/StatusIndicator.tsx
:
interface StatusIndicatorProps {
status: 'idle' | 'thinking' | 'executing';
}
function StatusIndicator({ status }: StatusIndicatorProps) {
const statusText = {
idle: '待机',
thinking: '思考中...',
executing: '执行中...',
};
return (
<div className="p-2 bg-gray-100 text-center text-sm text-gray-600">
{statusText[status]}
</div>
);
}
export default StatusIndicator;
5. 优化
消息缓存
使用 React Query 缓存对话历史:
const { data: history, isLoading } = useQuery({
queryKey: ['chatHistory'],
queryFn: () => JSON.parse(localStorage.getItem('chatHistory') || '[]'),
onSuccess: (data) => setMessages(data),
});
useEffect(() => {
localStorage.setItem('chatHistory', JSON.stringify(messages));
}, [messages]);
错误处理
在 ChatWindow
中添加错误处理:
const { mutate, isPending, error } = useMutation({
mutationFn: async (input: string) => {
setStatus('thinking');
const response = await fetch('[invalid url, do not cite]
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input, history: messages }),
});
if (!response.ok) throw new Error('网络错误');
setStatus('executing');
const data = await response.json();
setStatus('idle');
return data;
},
onSuccess: (data) => {
setMessages([...messages, { role: 'assistant', content: data.output }]);
},
onError: () => {
setMessages([...messages, { role: 'assistant', content: '发生错误,请重试' }]);
setStatus('idle');
},
});
6. 部署
构建项目
npm run build
部署到 Vercel
- 注册 Vercel:访问 Vercel 官网并创建账号。
- 新建项目:选择“New Project”。
- 导入仓库:将项目推送至 GitHub 并导入。
- 配置构建:
- 构建命令:
npm run build
- 输出目录:
dist
- 构建命令:
- 部署:点击“Deploy”。
后端需单独部署到 Vercel 或其他平台。
练习:添加语音输入功能
为巩固所学,设计一个练习:为应用添加语音输入功能。
需求
- 用户可以通过语音输入与 Agent 交互。
- 支持语音转文字,显示在输入框中。
- 提供语音输入按钮。
实现步骤
- 添加语音识别
使用 Web Speech API 实现语音输入。 - 更新 InputBox
添加语音输入按钮。 - 处理语音输入
将语音转文字结果传递给 Agent。
示例代码
src/components/InputBox.tsx
(更新):
import { useState } from 'react';
interface InputBoxProps {
onSend: (input: string) => void;
disabled: boolean;
}
function InputBox({ onSend, disabled }: InputBoxProps) {
const [input, setInput] = useState('');
const [isRecording, setIsRecording] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || disabled) return;
onSend(input);
setInput('');
};
const startRecording = () => {
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
recognition.lang = 'zh-CN';
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
setInput(transcript);
setIsRecording(false);
};
recognition.start();
setIsRecording(true);
};
return (
<div className="p-4 border-t bg-white flex items-center space-x-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSubmit(e)}
className="flex-1 p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="输入您的请求..."
disabled={disabled}
/>
<button
onClick={startRecording}
className={`px-4 py-2 rounded-lg ${isRecording ? 'bg-red-500' : 'bg-gray-200'} text-white`}
disabled={disabled}
>
{isRecording ? '录音中' : '语音'}
</button>
<button
onClick={handleSubmit}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400"
disabled={disabled}
>
发送
</button>
</div>
);
}
export default InputBox;
练习目标
通过此练习,您将学会使用 Web Speech API 实现语音输入,提升应用的交互性。
注意事项
- 性能优化:谨慎管理 API 调用频率,避免超限。
- 错误处理:确保 Agent 在网络错误或工具失败时提供友好反馈。
- 用户体验:通过动画和状态反馈提升交互性。
- 安全:保护 OpenAI API 密钥,避免泄露。
- 学习建议:参考 LangChain 文档 和 OpenAI API 文档。
结语
通过这个 AI Agent 网页项目,您完整体验了一个 React 项目从需求分析到部署的全流程,掌握了 LangChain 集成、多工具调用、状态反馈、动画效果和 Vercel 部署等技能。这些知识将成为您开发复杂 AI 应用的坚实基础。
AI Agent 技术在 2025 年将进一步推动自动化和智能化发展。希望您继续探索 AI 驱动的开发模式,打造创新应用。欢迎在社区分享成果,一起成长!