从”人工智障”到”AI全能助手”:CopilotKit让我见识了什么叫真正的人机协作
一行代码让现有产品秒变智能,这个刚爆火的AI开发框架终于被我们扒透了
一、为什么这个项目值得你花时间?
1.1 传统AI集成的痛点
想象一下这个场景:你在做一个SaaS产品,产品经理跑过来跟你说:”咱们能不能加个AI助手?让用户问问题的时候能自动回答。”
作为开发者,你的内心可能是崩溃的。因为这意味着你可能要:
# 传统的AI集成方式 —— 每一个都是坑
# 坑1: 需要自己处理流式响应
def chat_with_ai(message):
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[{"role": "user", "content": message}]
)
return response.choices[0].message.content
# 问题:这是同步的,用户要等好几秒才能看到结果
# 问题:没有流式输出,用户体验很差
# 坑2: 需要自己实现上下文管理
# 你得手动维护一个messages数组,处理token限制
# 处理历史消息的截断
# 处理不同角色的消息格式
# 坑3: 需要自己构建UI组件
# 打字机效果?消息气泡?输入框?加载状态?
# 这些看似简单的东西,做起来全是细节
# 坑4: 需要自己处理多轮对话
# 记忆管理?对话状态持久化?
# 如果用户刷新页面怎么办?
# 坑5: 需要自己实现agent能力
# 工具调用?插件系统?函数执行?
# 这部分代码量巨大,而且很容易出bug
1.2 CopilotKit带来了什么?
CopilotKit(https://github.com/CopilotKit/CopilotKit)是一个专门为应用内AI助手设计的开源框架。它的核心理念是:让开发者专注于业务逻辑,而不是AI基础设施。
# 使用CopilotKit后的代码 —— 简洁到不敢相信
# 只需要几行代码,就能创建一个完整的AI助手
from copilotkit import CopilotKitSDK
sdk = CopilotKitSDK(
model="gpt-4",
system_prompt="你是一个专业的客服助手"
)
# 然后在你的应用里直接使用
# <CopilotKit id="customer-service" />
# 就是这么简单!
1.3 CopilotKit的核心优势
| 特性 | 传统方式 | CopilotKit |
|---|---|---|
| 接入速度 | 3-5天 | 10分钟 |
| 代码量 | 1000+行 | 50行 |
| 流式输出 | 需自己实现 | 内置支持 |
| 上下文管理 | 手动处理 | 自动管理 |
| 工具调用 | 从零开发 | 装饰器即可 |
| UI组件 | 从零开发 | 丰富组件库 |
二、环境搭建:手把手从零开始
2.1 前置要求
在开始之前,请确保你的开发环境满足以下要求:
# 检查Node.js版本(需要18.0或更高)
node --version
# v20.10.0
# 检查npm或yarn
npm --version
# 10.2.0
# 如果使用Python后端(可选)
python --version
# Python 3.10+
2.2 创建你的第一个CopilotKit项目
方式一:使用官方CLI(推荐新手)
# 全局安装CopilotKit CLI
npm install -g @copilotkit/cli
# 创建一个新项目
copilotkit create my-first-ai-app
# 进入项目目录
cd my-first-ai-app
# 安装依赖
npm install
# 启动开发服务器
npm run dev
方式二:手动集成到现有项目
# 如果你已经有了一个React/Next.js项目,可以直接添加CopilotKit
# 安装核心包
npm install @copilotkit/react-core @copilotkit/react-ui
# 如果需要后端支持
npm install @copilotkit/langchain
# 如果使用Next.js
npm install @copilotkit/nextjs
2.3 项目结构一览
创建一个标准项目后,你会看到这样的目录结构:
my-first-ai-app/
├── src/
│ ├── app/
│ │ ├── layout.tsx # 根布局
│ │ ├── page.tsx # 首页
│ │ └── api/
│ │ └── copilotkit/
│ │ └── route.ts # CopilotKit API路由
│ ├── components/
│ │ ├── copilot/
│ │ │ ├── ChatWindow.tsx # 聊天窗口组件
│ │ │ └── MessageBubble.tsx # 消息气泡组件
│ │ └── business/
│ │ └── Dashboard.tsx # 你的业务组件
│ ├── lib/
│ │ ├── copilotkit.ts # CopilotKit配置
│ │ └── tools.ts # 自定义工具
│ └── styles/
│ └── globals.css
├── public/
├── package.json
├── next.config.js
├── tailwind.config.js
└── tsconfig.json
2.4 环境变量配置
在项目根目录创建.env.local文件:
# OpenAI配置
OPENAI_API_KEY=sk-your-api-key-here
# 如果你想使用其他模型
# ANTHROPIC_API_KEY=sk-ant-your-api-key
# CopilotKit配置(可选)
COPILOT_CLOUD_API_KEY=cpk-your-cloud-key
# 应用配置
NEXT_PUBLIC_APP_URL=http://localhost:3000
三、核心功能详解
3.1 CopilotKit的三大核心模块
CopilotKit主要由三个模块组成:
┌─────────────────────────────────────────────────────────┐
│ CopilotKit 架构 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ React │ │ Runtime │ │ Agents │ │
│ │ UI │ │ Engine │ │ Framework │ │
│ ├─────────────┤ ├─────────────┤ ├─────────────────┤ │
│ │ • 聊天窗口 │ │ • 上下文管理 │ │ • 工具调用 │ │
│ │ • 消息气泡 │ │ • 流式处理 │ │ • 任务分解 │ │
│ │ • 输入框 │ │ • 状态管理 │ │ • 多步骤执行 │ │
│ │ • 侧边栏 │ │ • 记忆存储 │ │ • 循环控制 │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Provider Integration │ │
│ │ OpenAI │ Anthropic │ Azure │ 自定义 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
3.2 模块一:React UI 组件库
CopilotKit提供了一套完整的UI组件,让你可以快速构建专业的AI聊天界面。
基础聊天窗口
// src/components/copilot/BasicChat.tsx
"use client";
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
export function BasicChat() {
return (
// CopilotKit 是所有AI功能的容器
// 它需要包裹所有使用AI功能的组件
<CopilotKit
// API端点,指向你的后端
url="/api/copilotkit"
// 可选:默认的系统提示词
defaultSystemMessage="你是一个友好、专业的AI助手。"
>
{/* CopilotPopup 是可弹出的聊天窗口 */}
<CopilotPopup
// 是否默认打开
defaultOpen={false}
// 自定义触发按钮的标签
triggerLabel="💬 咨询AI助手"
// 自定义样式(可选)
className="my-custom-chat"
/>
</CopilotKit>
);
}
内联AI助手(嵌入在页面中)
// src/components/copilot/InlineAssistant.tsx
"use client";
import { CopilotKit, useCopilotAction } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";
export function InlineAssistant() {
return (
<CopilotKit url="/api/copilotkit">
<div className="flex">
{/* 侧边栏模式 */}
<CopilotSidebar
// 侧边栏位置
side="right"
// 侧边栏宽度
width="350px"
>
{/* 这里放你的主应用内容 */}
<YourMainApp />
</CopilotSidebar>
</div>
</CopilotKit>
);
}
消息气泡组件
// src/components/copilot/MessageBubble.tsx
"use client";
import { useCopilotChat } from "@copilotkit/react-core";
import { cn } from "@/lib/utils";
export function MessageList() {
// useCopilotChat 是一个hook,用于获取聊天相关的状态和方法
const { messages, isLoading } = useCopilotChat();
return (
<div className="flex flex-col gap-4 p-4">
{messages.map((message, index) => (
<div
key={index}
className={cn(
"flex",
message.role === "user" ? "justify-end" : "justify-start"
)}
>
<div
className={cn(
"max-w-[80%] rounded-2xl px-4 py-2",
message.role === "user"
? "bg-blue-500 text-white"
: "bg-gray-100 text-gray-900"
)}
>
{/* 支持Markdown渲染 */}
<div className="prose prose-sm">
{message.content}
</div>
{/* 显示时间戳 */}
<div className="text-xs opacity-70 mt-1">
{new Date(message.createdAt).toLocaleTimeString()}
</div>
</div>
</div>
))}
{/* 加载指示器 */}
{isLoading && (
<div className="flex justify-start">
<div className="bg-gray-100 rounded-2xl px-4 py-2">
<div className="flex gap-1">
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-75" />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-150" />
</div>
</div>
</div>
)}
</div>
);
}
3.3 模块二:Runtime Engine(运行时引擎)
Runtime Engine是CopilotKit的大脑,负责处理所有AI相关的逻辑。
上下文管理
// src/components/copilot/ContextAwareAssistant.tsx
"use client";
import {
CopilotKit,
useCopilotContext
} from "@copilotkit/react-core";
export function ContextAwareAssistant() {
// useCopilotContext 提供对上下文的访问
const context = useCopilotContext();
// 获取当前选中的文本
const selectedText = context.selectedText;
// 获取当前页面的URL
const currentUrl = context.url;
// 获取应用自定义的上下文数据
const customContext = context.get("user_preferences");
const documentContent = context.get("document_content");
return (
<CopilotKit url="/api/copilotkit">
<div className="p-4">
<h2>当前上下文信息</h2>
<ul>
<li>选中文本: {selectedText || "无"}</li>
<li>当前页面: {currentUrl}</li>
<li>用户偏好: {JSON.stringify(customContext)}</li>
<li>文档内容: {documentContent?.substring(0, 100)}...</li>
</ul>
</div>
</CopilotKit>
);
}
状态持久化
// src/components/copilot/PersistentChat.tsx
"use client";
import {
CopilotKit,
useCopilotChat
} from "@copilotkit/react-core";
import { useEffect } from "react";
import { useLocalStorage } from "@/hooks/useLocalStorage";
export function PersistentChat() {
// 自定义的本地存储hook
const [savedMessages, setSavedMessages] = useLocalStorage<
Array<{role: string; content: string}>
>("chat-history", []);
const { messages, appendMessage, clearMessages } = useCopilotChat({
// 启用消息持久化
enablePersistence: true,
// 持久化存储位置
storageKey: "chat-history",
// 加载历史消息
initialMessages: savedMessages,
});
// 当消息变化时,自动保存
useEffect(() => {
setSavedMessages(messages);
}, [messages, setSavedMessages]);
return (
<div className="flex flex-col h-[500px]">
<div className="flex-1 overflow-y-auto">
{messages.map((msg, i) => (
<MessageItem key={i} message={msg} />
))}
</div>
<div className="flex gap-2 p-4 border-t">
<button
onClick={() => clearMessages()}
className="px-4 py-2 text-sm text-red-600 hover:bg-red-50 rounded"
>
清空对话
</button>
</div>
</div>
);
}
3.4 模块三:Agents Framework(智能体框架)
这是CopilotKit最强大的部分 —— 它让你可以创建能够执行复杂任务的AI智能体。
基础工具定义
// src/lib/tools.ts
"use client";
import {
Tool,
tool
} from "@copilotkit/langchain";
// 定义一个简单的工具:获取天气
export const getWeatherTool = tool({
// 工具名称,AI会看到这个名称
name: "get_weather",
// 工具描述,这非常重要!
// AI会根据这个描述决定是否调用这个工具
description: "获取指定城市的天气信息。当用户询问某个城市的天气时使用。",
// 参数模式定义(使用Zod schema)
parameters: {
type: "object",
properties: {
city: {
type: "string",
description: "城市名称,例如:北京、上海、东京",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "温度单位,默认是摄氏度",
default: "celsius",
},
},
required: ["city"],
},
// 工具的实际执行逻辑
execute: async ({ city, unit }) => {
// 这里是你的实际业务逻辑
const response = await fetch(
`https://api.weather.example.com?city=${city}&unit=${unit}`
);
const data = await response.json();
return `当前${city}的天气是${data.condition},温度为${data.temperature}°${
unit === "celsius" ? "C" : "F"
}。`;
},
});
复杂工具:数据库查询
// src/lib/database-tools.ts
"use client";
import { tool } from "@copilotkit/langchain";
import { z } from "zod";
// 数据库查询工具
export const queryDatabaseTool = tool({
name: "query_database",
description: `在数据库中执行SQL查询。这个工具只能用于:
- 查询用户请求的数据
- 不能执行UPDATE、DELETE等危险操作
- 每次查询最多返回100条记录
当用户想要查看、分析数据报告时使用。`,
parameters: z.object({
sql: z.string().describe("要执行的SQL查询语句"),
params: z.record(z.any()).optional().describe("查询参数"),
}),
execute: async ({ sql, params }) => {
// 安全性检查
const dangerousKeywords = ["DROP", "DELETE", "UPDATE", "INSERT", "TRUNCATE"];
const upperSql = sql.toUpperCase();
for (const keyword of dangerousKeywords) {
if (upperSql.includes(keyword)) {
return `错误:不允许执行包含 "${keyword}" 的操作。`;
}
}
try {
// 执行查询
const results = await db.query(sql, params);
// 格式化返回结果
return {
rowCount: results.rows.length,
columns: results.fields.map(f => f.name),
rows: results.rows,
};
} catch (error) {
return `查询出错:${error.message}`;
}
},
});
文件系统操作工具
// src/lib/file-tools.ts
"use client";
import { tool } from "@copilotkit/langchain";
import { z } from "zod";
import { writeFile, readFile, glob } from "fs/promises";
import path from "path";
export const fileSystemTools = {
// 读取文件
readFileTool: tool({
name: "read_file",
description: "读取指定路径的文件内容。用于查看代码、配置文件或文档。",
parameters: z.object({
filePath: z.string().describe("文件的完整路径"),
}),
execute: async ({ filePath }) => {
try {
const absolutePath = path.resolve(filePath);
const content = await readFile(absolutePath, "utf-8");
return content;
} catch (error) {
return `读取文件失败:${error.message}`;
}
},
}),
// 写入文件
writeFileTool: tool({
name: "write_file",
description: "创建或更新文件内容。谨慎使用,仅用于用户明确要求的文件修改。",
parameters: z.object({
filePath: z.string().describe("文件的完整路径"),
content: z.string().describe("要写入的内容"),
createDirectories: z.boolean().optional().describe("是否自动创建目录"),
}),
execute: async ({ filePath, content, createDirectories = false }) => {
try {
const absolutePath = path.resolve(filePath);
if (createDirectories) {
const dir = path.dirname(absolutePath);
await mkdir(dir, { recursive: true });
}
await writeFile(absolutePath, content, "utf-8");
return `文件已成功写入:${absolutePath}`;
} catch (error) {
return `写入文件失败:${error.message}`;
}
},
}),
// 搜索文件
searchFilesTool: tool({
name: "search_files",
description: "根据模式搜索文件。例如:搜索所有.tsx文件、查找包含特定内容的文件等。",
parameters: z.object({
pattern: z.string().describe("glob模式,例如:*.tsx、src/**/*.js"),
basePath: z.string().optional().describe("搜索的起始路径"),
}),
execute: async ({ pattern, basePath = "." }) => {
try {
const files = await glob(pattern, { cwd: basePath });
return {
count: files.length,
files: files.slice(0, 50), // 限制返回数量
};
} catch (error) {
return `搜索失败:${error.message}`;
}
},
}),
};
四、实战教程:从需求到实现
4.1 场景一:构建一个智能客服助手
需求分析
- 用户可以随时点击按钮打开客服窗口
- 客服能够回答常见问题
- 支持转人工(当AI无法解决问题时)
- 聊天记录需要保存
Step 1: 创建API路由
// src/app/api/copilotkit/route.ts
import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/runtime";
import { NextRequest } from "next/server";
// 你的知识库数据(实际项目中可以从数据库获取)
const knowledgeBase = [
{
question: "如何重置密码?",
answer: "请访问 /forgot-password 页面,输入您的注册邮箱,我们会发送重置链接。",
},
{
question: "如何联系人工客服?",
answer: "您可以通过以下方式联系我们:\n- 电话:400-123-4567\n- 邮箱:support@example.com\n- 工作时间:周一至周五 9:00-18:00",
},
{
question: "如何开通会员?",
answer: "请访问 /pricing 页面选择适合您的会员套餐,完成支付后即可开通。",
},
];
const copilotKit = new CopilotRuntime({
// 公开可访问的工具列表
tools: [
{
name: "searchKnowledgeBase",
description: "在知识库中搜索相关内容。当用户询问产品使用、常见问题时使用。",
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "用户的查询内容",
},
},
required: ["query"],
},
handler: async ({ query }) => {
// 简单的关键词匹配(实际项目可以用更复杂的算法)
const results = knowledgeBase.filter(
(item) =>
item.question.includes(query) || item.answer.includes(query)
);
if (results.length === 0) {
return "抱歉,我在知识库中没有找到相关内容。建议您联系人工客服获取帮助。";
}
return results.map((r) => `${r.question}\n${r.answer}`).join("\n\n");
},
},
{
name: "transferToHuman",
description: "将对话转接给人工客服。当用户明确要求人工服务、AI无法回答或用户表示不满时使用。",
parameters: {
type: "object",
properties: {
reason: {
type: "string",
description: "转接原因",
},
},
},
handler: async ({ reason }) => {
// 这里可以调用你的工单系统
console.log("转接人工:", reason);
return "好的,我将为您转接人工客服。请稍候...";
},
},
],
});
export async function POST(req: NextRequest) {
const { headers } = req;
const response = await copilotKit.generateText({
messages: req.messages,
systemPrompt: `你是公司的智能客服助手。你的职责是:
1. 友好、专业地回答用户的问题
2. 优先使用知识库回答问题
3. 如果知识库无法回答,可以基于你的知识给出一般性建议
4. 如果问题无法解决,主动建议转人工
5. 永远保持耐心和礼貌
6. 不要编造不存在的产品功能或政策`,
});
return response;
}
Step 2: 创建客服组件
// src/components/customer-service/CustomerServiceChat.tsx
"use client";
import { CopilotKit, useCopilotChat } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import { useState, useRef, useEffect } from "react";
import "@copilotkit/react-ui/styles.css";
export function CustomerServiceChat() {
return (
<CopilotKit url="/api/copilotkit">
<CopilotPopup
defaultOpen={false}
triggerLabel="在线咨询"
// 自定义弹窗样式
className="customer-service-popup"
// 弹窗位置
position="bottom-right"
/>
</CopilotKit>
);
}
// 更高级的客服组件 - 带有满意度评价
export function AdvancedCustomerService() {
const {
messages,
isLoading,
sendMessage,
setMessages
} = useCopilotChat();
const [inputValue, setInputValue] = useState("");
const [showRating, setShowRating] = useState(false);
const [rating, setRating] = useState(0);
const messagesEndRef = useRef<HTMLDivElement>(null);
// 自动滚动到底部
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
// 当对话结束时显示评价
useEffect(() => {
if (!isLoading && messages.length > 0) {
const lastMessage = messages[messages.length - 1];
if (lastMessage.role === "assistant") {
setShowRating(true);
}
}
}, [isLoading, messages]);
const handleSend = async () => {
if (!inputValue.trim() || isLoading) return;
const userMessage = inputValue;
setInputValue("");
await sendMessage(userMessage);
};
const handleRating = async (score: number) => {
setRating(score);
// 发送评价到服务器
console.log("用户评价:", score, "对话历史:", messages);
setTimeout(() => {
setShowRating(false);
setRating(0);
}, 2000);
};
return (
<div className="flex flex-col h-[600px] w-[400px] bg-white rounded-lg shadow-xl overflow-hidden">
{/* 头部 */}
<div className="bg-blue-600 text-white p-4 flex items-center gap-3">
<div className="w-10 h-10 bg-white rounded-full flex items-center justify-center text-blue-600 font-bold">
AI
</div>
<div>
<h3 className="font-semibold">智能客服助手</h3>
<p className="text-xs text-blue-100">在线 · 随时为您服务</p>
</div>
</div>
{/* 消息区域 */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, index) => (
<div
key={index}
className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}
>
<div
className={`max-w-[80%] rounded-2xl px-4 py-3 ${
msg.role === "user"
? "bg-blue-500 text-white rounded-br-md"
: "bg-gray-100 text-gray-800 rounded-bl-md"
}`}
>
<div className="text-sm whitespace-pre-wrap">
{msg.content}
</div>
<div
className={`text-xs mt-1 ${
msg.role === "user" ? "text-blue-100" : "text-gray-400"
}`}
>
{new Date(msg.createdAt || Date.now()).toLocaleTimeString()}
</div>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-gray-100 rounded-2xl rounded-bl-md px-4 py-3">
<div className="flex gap-1">
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-75" />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-150" />
</div>
</div>
</div>
)}
{/* 评价组件 */}
{showRating && !isLoading && rating === 0 && (
<div className="bg-gray-50 rounded-lg p-4 text-center">
<p className="text-sm text-gray-600 mb-2">本次服务满意吗?</p>
<div className="flex justify-center gap-2">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
onClick={() => handleRating(star)}
className="text-2xl hover:scale-110 transition-transform"
>
⭐
</button>
))}
</div>
</div>
)}
{rating > 0 && (
<div className="bg-green-50 rounded-lg p-4 text-center">
<p className="text-green-600 font-medium">感谢您的评价!</p>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* 输入区域 */}
<div className="border-t p-4">
<div className="flex gap-2">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && handleSend()}
placeholder="请输入您的问题..."
className="flex-1 px-4 py-2 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleSend}
disabled={!inputValue.trim() || isLoading}
className="px-6 py-2 bg-blue-500 text-white rounded-full hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
发送
</button>
</div>
</div>
</div>
);
}
4.2 场景二:构建AI数据分析助手
需求分析
- 用户可以上传数据文件
- 用自然语言询问数据问题
- AI自动生成SQL查询
- 可视化展示结果
Step 1: 创建数据分析工具
// src/lib/analytics-tools.ts
"use client";
import { tool } from "@copilotkit/langchain";
import { z } from "zod";
import { analyzeCSV, generateChart } from "@/lib/data-processing";
// 数据分析工具
export const analyzeDataTool = tool({
name: "analyze_data",
description: `对上传的数据进行分析。这个工具可以:
- 计算各种统计指标(平均值、中位数、总和等)
- 按维度分组统计
- 找出数据中的规律和异常
- 生成数据洞察
当用户询问数据分析、统计、趋势等问题时使用。`,
parameters: z.object({
action: z.enum([
"summary", // 总体统计
"group_by", // 分组统计
"trend", // 趋势分析
"outlier", // 异常检测
"correlation", // 相关性分析
]).describe("要执行的分析动作"),
columns: z.array(z.string()).optional().describe("要分析的列"),
groupBy: z.string().optional().describe("分组字段"),
options: z.record(z.any()).optional().describe("其他选项"),
}),
execute: async ({ action, columns, groupBy, options }) => {
// 获取当前加载的数据
const data = getCurrentData();
if (!data) {
return "请先上传数据文件。";
}
switch (action) {
case "summary":
return calculateSummary(data, columns);
case "group_by":
return calculateGroupBy(data, groupBy, columns);
case "trend":
return analyzeTrend(data, columns, options);
case "outlier":
return detectOutliers(data, columns);
case "correlation":
return calculateCorrelation(data, columns);
default:
return "不支持的分析类型";
}
},
});
// 数据可视化工具
export const visualizeDataTool = tool({
name: "visualize_data",
description: `创建数据可视化图表。支持多种图表类型:
- 折线图:展示趋势变化
- 柱状图:比较不同类别
- 饼图:展示占比
- 散点图:展示相关性
当用户需要图表来理解数据时使用。`,
parameters: z.object({
chartType: z.enum(["line", "bar", "pie", "scatter", "histogram"]),
xAxis: z.string().describe("X轴字段"),
yAxis: z.string().or(z.array(z.string())).describe("Y轴字段"),
title: z.string().optional().describe("图表标题"),
options: z.record(z.any()).optional().describe("其他配置"),
}),
execute: async ({ chartType, xAxis, yAxis, title, options }) => {
const data = getCurrentData();
if (!data) {
return "请先上传数据文件。";
}
const chartConfig = {
type: chartType,
xAxis,
yAxis,
title: title || "数据可视化",
data,
...options,
};
const chartHtml = generateChart(chartConfig);
return {
type: "chart",
html: chartHtml,
message: `已生成${getChartTypeName(chartType)}:${title || "数据可视化"}`,
};
},
});
Step 2: 创建数据助手组件
// src/components/analytics/DataAssistant.tsx
"use client";
import { CopilotKit, useCopilotChat, useCopilotAction } from "@copilotkit/react-core";
import { useState, useRef } from "react";
import { Upload, FileSpreadsheet, MessageSquare, BarChart3 } from "lucide-react";
import "@copilotkit/react-ui/styles.css";
export function DataAssistant() {
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
const [dataPreview, setDataPreview] = useState<any[]>([]);
const [chartHtml, setChartHtml] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const {
messages,
isLoading,
sendMessage,
appendMessage
} = useCopilotChat();
// 处理文件上传
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
setUploadedFile(file);
// 解析CSV文件
const text = await file.text();
const data = parseCSV(text);
setDataPreview(data.slice(0, 10)); // 只显示前10行预览
// 将数据注入到AI上下文中
await appendMessage({
role: "system",
content: `用户上传了数据文件:${file.name}
数据包含 ${data.length} 行。
列名:${Object.keys(data[0] || {}).join(", ")}
数据预览:
${JSON.stringify(data.slice(0, 5), null, 2)}`,
});
};
// 处理自然语言查询
const handleQuery = async (query: string) => {
await sendMessage(query);
};
// 监听AI返回的图表
useCopilotAction({
name: "visualize_data",
description: "创建数据可视化图表",
handler: async (result) => {
if (result.type === "chart" && result.html) {
setChartHtml(result.html);
}
return result.message;
},
});
return (
<CopilotKit url="/api/copilotkit">
<div className="flex h-screen">
{/* 左侧:数据面板 */}
<div className="w-1/3 border-r bg-gray-50 p-6 overflow-y-auto">
<h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
<FileSpreadsheet className="w-5 h-5" />
数据面板
</h2>
{/* 文件上传 */}
<div
className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-blue-500 cursor-pointer transition-colors"
onClick={() => fileInputRef.current?.click()}
>
<Upload className="w-12 h-12 mx-auto text-gray-400 mb-2" />
<p className="text-gray-600">
{uploadedFile ? uploadedFile.name : "点击上传数据文件"}
</p>
<p className="text-sm text-gray-400 mt-1">
支持 CSV、Excel 格式
</p>
<input
ref={fileInputRef}
type="file"
accept=".csv,.xlsx,.xls"
onChange={handleFileUpload}
className="hidden"
/>
</div>
{/* 数据预览 */}
{dataPreview.length > 0 && (
<div className="mt-6">
<h3 className="font-medium mb-2">数据预览(前10行)</h3>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr>
{Object.keys(dataPreview[0]).map((key) => (
<th key={key} className="px-2 py-1 bg-gray-200 text-left">
{key}
</th>
))}
</tr>
</thead>
<tbody>
{dataPreview.map((row, i) => (
<tr key={i} className="border-b">
{Object.values(row).map((val, j) => (
<td key={j} className="px-2 py-1">
{String(val)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* 图表展示 */}
{chartHtml && (
<div className="mt-6">
<h3 className="font-medium mb-2 flex items-center gap-2">
<BarChart3 className="w-4 h-4" />
可视化结果
</h3>
<div
className="bg-white rounded-lg p-4"
dangerouslySetInnerHTML={{ __html: chartHtml }}
/>
</div>
)}
</div>
{/* 右侧:聊天界面 */}
<div className="flex-1 flex flex-col bg-white">
<div className="flex-1 overflow-y-auto p-6 space-y-4">
{messages.length === 0 && (
<div className="text-center text-gray-400 mt-20">
<MessageSquare className="w-16 h-16 mx-auto mb-4 opacity-50" />
<p>上传数据后,用自然语言询问数据问题</p>
<p className="text-sm mt-2">
例如:"分析销售数据的趋势"
</p>
</div>
)}
{messages.map((msg, index) => (
<div
key={index}
className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}
>
<div
className={`max-w-[70%] rounded-2xl px-4 py-3 ${
msg.role === "user"
? "bg-blue-500 text-white"
: "bg-gray-100 text-gray-800"
}`}
>
<div className="prose prose-sm max-w-none">
{msg.content}
</div>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-gray-100 rounded-2xl px-4 py-3">
<div className="flex gap-1">
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-75" />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-150" />
</div>
</div>
</div>
)}
</div>
{/* 输入框 */}
<div className="border-t p-4">
<div className="flex gap-2 max-w-3xl mx-auto">
<input
type="text"
placeholder="用自然语言询问数据..."
className="flex-1 px-4 py-3 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
onKeyPress={(e) => {
if (e.key === "Enter" && !isLoading) {
handleQuery(e.currentTarget.value);
e.currentTarget.value = "";
}
}}
/>
</div>
</div>
</div>
</div>
</CopilotKit>
);
}
// CSV解析辅助函数
function parseCSV(text: string): any[] {
const lines = text.split("\n");
const headers = lines[0].split(",").map(h => h.trim());
return lines.slice(1).filter(line => line.trim()).map(line => {
const values = line.split(",").map(v => v.trim());
const row: any = {};
headers.forEach((header, i) => {
row[header] = values[i] || "";
});
return row;
});
}
4.3 场景三:构建AI代码审查助手
需求分析
- 自动分析代码问题
- 提供修复建议
- 生成代码改进报告
- 支持多种编程语言
Step 1: 创建代码审查工具
// src/lib/code-review-tools.ts
"use client";
import { tool } from "@copilotkit/langchain";
import { z } from "zod";
import { readFile, glob } from "fs/promises";
import path from "path";
// 代码审查工具
export const codeReviewTool = tool({
name: "review_code",
description: `对代码进行全面审查。检查内容包括:
- 代码质量问题(复杂度、重复代码)
- 安全漏洞(SQL注入、XSS、敏感信息泄露)
- 性能问题(内存泄漏、无效循环)
- 代码风格和最佳实践
- 测试覆盖率
当用户要求审查代码或指出代码问题时使用。`,
parameters: z.object({
filePath: z.string().describe("要审查的文件路径"),
reviewTypes: z.array(
z.enum(["security", "performance", "style", "best-practice"])
).optional().describe("要检查的类型"),
}),
execute: async ({ filePath, reviewTypes = ["security", "performance", "style"] }) => {
try {
const absolutePath = path.resolve(filePath);
const content = await readFile(absolutePath, "utf-8");
const extension = path.extname(filePath);
const results: string[] = [];
// 安全检查
if (reviewTypes.includes("security")) {
const securityIssues = checkSecurity(content, extension);
results.push(...securityIssues);
}
// 性能检查
if (reviewTypes.includes("performance")) {
const performanceIssues = checkPerformance(content, extension);
results.push(...performanceIssues);
}
// 代码风格
if (reviewTypes.includes("style")) {
const styleIssues = checkStyle(content, extension);
results.push(...styleIssues);
}
if (results.length === 0) {
return `✅ 代码审查通过!\n\n文件:${filePath}\n未发现问题。`;
}
return `🔍 代码审查报告\n\n文件:${filePath}\n\n发现问题 ${results.length} 个:\n\n${results.join("\n\n")}`;
} catch (error) {
return `无法读取文件:${error.message}`;
}
},
});
// 安全检查规则
function checkSecurity(content: string, extension: string): string[] {
const issues: string[] = [];
// 模板:SQL注入检测
if (content.includes("SELECT") && content.includes("+") && extension === ".js") {
issues.push(`⚠️ 安全风险:可能存在SQL注入\n行号:${findLineNumber(content, "SELECT")}\n建议:使用参数化查询`);
}
// 模板:硬编码密码
if (content.match(/password\s*=\s*["'][^"']{1,50}["']/i)) {
issues.push(`🔴 安全漏洞:发现硬编码密码\n建议:将敏感信息移至环境变量`);
}
// 模板:eval使用
if (content.includes("eval(")) {
issues.push(`⚠️ 安全风险:使用eval可能引发安全问题\n建议:考虑使用更安全的替代方案`);
}
return issues;
}
// 性能检查规则
function checkPerformance(content: string, extension: string): string[] {
const issues: string[] = [];
// 嵌套循环检测
const nestedLoops = detectNestedLoops(content);
if (nestedLoops > 2) {
issues.push(`⚠️ 性能问题:发现${nestedLoops}层嵌套循环\n建议:考虑优化算法复杂度`);
}
// 大数组操作
if (content.includes(".map(") && content.includes(".map(").length > 5) {
issues.push(`💡 性能提示:连续使用多个map操作\n建议:考虑合并或使用流式处理`);
}
return issues;
}
// 代码风格检查
function checkStyle(content: string, extension: string): string[] {
const issues: string[] = [];
// 过长函数检测
const functionLengths = detectFunctionLengths(content);
for (const { name, length } of functionLengths) {
if (length > 100) {
issues.push(`📝 代码风格:函数 ${name} 过长 (${length}行)\n建议:考虑拆分为更小的函数`);
}
}
// 缺少注释
const linesWithoutComments = countUncommentedLines(content);
if (linesWithoutComments > 100) {
issues.push(`📝 代码风格:${linesWithoutComments}行代码缺少注释\n建议:添加必要的代码注释`);
}
return issues;
}
// 辅助函数
function findLineNumber(content: string, keyword: string): number {
const lines = content.split("\n");
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes(keyword)) {
return i + 1;
}
}
return 0;
}
function detectNestedLoops(content: string): number {
let maxDepth = 0;
let currentDepth = 0;
const lines = content.split("\n");
for (const line of lines) {
const forMatches = (line.match(/\bfor\s*\(/g) || []).length;
const whileMatches = (line.match(/\bwhile\s*\(/g) || []).length;
const openBraces = (line.match(/\{/g) || []).length;
const closeBraces = (line.match(/\}/g) || []).length;
currentDepth += forMatches + whileMatches;
maxDepth = Math.max(maxDepth, currentDepth);
currentDepth -= closeBraces;
}
return maxDepth;
}
function detectFunctionLengths(content: string): Array<{ name: string; length: number }> {
const results: Array<{ name: string; length: number }> = [];
const lines = content.split("\n");
let inFunction = false;
let functionName = "";
let functionStart = 0;
let braceCount = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// 检测函数开始
const funcMatch = line.match(/(?:function|def|const|let|var)\s+(\w+)\s*[=\(]/);
if (funcMatch && !inFunction) {
inFunction = true;
functionName = funcMatch[1];
functionStart = i;
braceCount = 0;
}
// 统计大括号或缩进
if (inFunction) {
braceCount += (line.match(/\{/g) || []).length;
braceCount -= (line.match(/\}/g) || []).length;
if (braceCount === 0 && line.includes("}")) {
results.push({
name: functionName,
length: i - functionStart + 1,
});
inFunction = false;
}
}
}
return results;
}
function countUncommentedLines(content: string): number {
const lines = content.split("\n");
let count = 0;
for (const line of lines) {
const trimmed = line.trim();
if (
trimmed.length > 0 &&
!trimmed.startsWith("//") &&
!trimmed.startsWith("#") &&
!trimmed.startsWith("/*") &&
!trimmed.startsWith("*")
) {
count++;
}
}
return count;
}
Step 2: 创建代码审查界面
// src/components/code-review/CodeReviewPanel.tsx
"use client";
import { CopilotKit, useCopilotChat, useCopilotAction } from "@copilotkit/react-core";
import { useState } from "react";
import { FileCode, AlertTriangle, CheckCircle, Lightbulb } from "lucide-react";
import "@copilotkit/react-ui/styles.css";
interface ReviewResult {
filePath: string;
issues: Array<{
type: "error" | "warning" | "info";
message: string;
line?: number;
suggestion?: string;
}>;
summary: {
total: number;
errors: number;
warnings: number;
suggestions: number;
};
}
export function CodeReviewPanel() {
const [currentFile, setCurrentFile] = useState<string>("");
const [reviewResults, setReviewResults] = useState<ReviewResult[]>([]);
const [selectedResult, setSelectedResult] = useState<ReviewResult | null>(null);
const { messages, isLoading, sendMessage } = useCopilotChat();
// 监听AI的代码审查结果
useCopilotAction({
name: "review_code",
description: "审查代码文件",
handler: async (result) => {
// 解析审查结果
const parsed = parseReviewResult(result, currentFile);
setReviewResults((prev) => [...prev, parsed]);
setSelectedResult(parsed);
return result;
},
});
const handleReviewRequest = async () => {
if (!currentFile) return;
await sendMessage(`请审查这个文件:${currentFile}`);
};
return (
<CopilotKit url="/api/copilotkit">
<div className="flex h-screen">
{/* 左侧:文件列表 */}
<div className="w-64 border-r bg-gray-50 p-4">
<h2 className="font-semibold mb-4 flex items-center gap-2">
<FileCode className="w-5 h-5" />
文件列表
</h2>
<input
type="text"
placeholder="输入文件路径..."
value={currentFile}
onChange={(e) => setCurrentFile(e.target.value)}
className="w-full px-3 py-2 border rounded mb-4 text-sm"
/>
<button
onClick={handleReviewRequest}
disabled={!currentFile || isLoading}
className="w-full px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50 mb-4"
>
{isLoading ? "审查中..." : "开始审查"}
</button>
{/* 审查历史 */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-500">审查历史</h3>
{reviewResults.map((result, index) => (
<button
key={index}
onClick={() => setSelectedResult(result)}
className={`w-full text-left p-2 rounded text-sm ${
selectedResult === result
? "bg-blue-100 border border-blue-300"
: "hover:bg-gray-100"
}`}
>
<div className="truncate">{path.basename(result.filePath)}</div>
<div className="flex gap-2 mt-1">
<span className="text-red-500">{result.summary.errors}</span>
<span className="text-yellow-500">{result.summary.warnings}</span>
<span className="text-blue-500">{result.summary.suggestions}</span>
</div>
</button>
))}
</div>
</div>
{/* 中间:代码展示 */}
<div className="flex-1 p-6 overflow-auto">
{selectedResult ? (
<div>
<h2 className="text-xl font-semibold mb-4">
审查报告:{selectedResult.filePath}
</h2>
{/* 统计摘要 */}
<div className="grid grid-cols-4 gap-4 mb-6">
<div className="bg-gray-100 rounded-lg p-4 text-center">
<div className="text-2xl font-bold">{selectedResult.summary.total}</div>
<div className="text-sm text-gray-600">总问题数</div>
</div>
<div className="bg-red-50 rounded-lg p-4 text-center">
<div className="text-2xl font-bold text-red-600">
{selectedResult.summary.errors}
</div>
<div className="text-sm text-red-600">错误</div>
</div>
<div className="bg-yellow-50 rounded-lg p-4 text-center">
<div className="text-2xl font-bold text-yellow-600">
{selectedResult.summary.warnings}
</div>
<div className="text-sm text-yellow-600">警告</div>
</div>
<div className="bg-blue-50 rounded-lg p-4 text-center">
<div className="text-2xl font-bold text-blue-600">
{selectedResult.summary.suggestions}
</div>
<div className="text-sm text-blue-600">建议</div>
</div>
</div>
{/* 问题列表 */}
<div className="space-y-3">
{selectedResult.issues.map((issue, index) => (
<div
key={index}
className={`border rounded-lg p-4 ${
issue.type === "error"
? "border-red-200 bg-red-50"
: issue.type === "warning"
? "border-yellow-200 bg-yellow-50"
: "border-blue-200 bg-blue-50"
}`}
>
<div className="flex items-start gap-3">
{issue.type === "error" ? (
<AlertTriangle className="w-5 h-5 text-red-500 flex-shrink-0" />
) : issue.type === "warning" ? (
<AlertTriangle className="w-5 h-5 text-yellow-500 flex-shrink-0" />
) : (
<Lightbulb className="w-5 h-5 text-blue-500 flex-shrink-0" />
)}
<div className="flex-1">
<p className="font-medium">{issue.message}</p>
{issue.line && (
<p className="text-sm text-gray-500 mt-1">
行号:{issue.line}
</p>
)}
{issue.suggestion && (
<p className="text-sm text-green-600 mt-2">
💡 建议:{issue.suggestion}
</p>
)}
</div>
</div>
</div>
))}
</div>
{selectedResult.summary.total === 0 && (
<div className="text-center py-12 text-gray-400">
<CheckCircle className="w-16 h-16 mx-auto mb-4 text-green-500" />
<p className="text-lg">代码审查通过!</p>
<p>未发现问题</p>
</div>
)}
</div>
) : (
<div className="flex items-center justify-center h-full text-gray-400">
<div className="text-center">
<FileCode className="w-16 h-16 mx-auto mb-4 opacity-50" />
<p>选择一个文件开始审查</p>
</div>
</div>
)}
</div>
{/* 右侧:AI对话 */}
<div className="w-80 border-l bg-gray-50 flex flex-col">
<div className="p-4 border-b bg-white">
<h3 className="font-semibold">AI 助手</h3>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-3">
{messages.map((msg, index) => (
<div
key={index}
className={`text-sm ${
msg.role === "user" ? "text-right" : "text-left"
}`}
>
<div
className={`inline-block px-3 py-2 rounded-lg ${
msg.role === "user"
? "bg-blue-500 text-white"
: "bg-white border"
}`}
>
{msg.content}
</div>
</div>
))}
</div>
</div>
</div>
</CopilotKit>
);
}
// 解析审查结果
function parseReviewResult(text: string, filePath: string): ReviewResult {
const issues: ReviewResult["issues"] = [];
// 简单的文本解析(实际项目应该用更复杂的解析逻辑)
const lines = text.split("\n");
for (const line of lines) {
if (line.includes("🔴")) {
issues.push({
type: "error",
message: line.replace("🔴", "").trim(),
});
} else if (line.includes("⚠️")) {
issues.push({
type: "warning",
message: line.replace("⚠️", "").trim(),
});
} else if (line.includes("📝") || line.includes("💡")) {
issues.push({
type: "info",
message: line.replace("📝", "").replace("💡", "").trim(),
});
}
}
return {
filePath,
issues,
summary: {
total: issues.length,
errors: issues.filter(i => i.type === "error").length,
warnings: issues.filter(i => i.type === "warning").length,
suggestions: issues.filter(i => i.type === "info").length,
},
};
}
// path.basename polyfill
const path = {
basename: (p: string) => p.split(/[\\/]/).pop() || p,
};
五、高级使用技巧
5.1 自定义AI模型配置
CopilotKit支持多种AI模型,你可以根据需要选择:
// src/lib/copilotkit-config.ts
import { CopilotRuntime, OpenAIAdapter, AnthropicAdapter } from "@copilotkit/runtime";
// OpenAI配置
const openaiAdapter = new OpenAIAdapter({
model: "gpt-4-turbo",
temperature: 0.7,
maxTokens: 2000,
topP: 0.9,
});
// Anthropic配置
const anthropicAdapter = new AnthropicAdapter({
model: "claude-3-sonnet-20240229",
maxTokens: 2000,
});
// 本地模型配置(使用Ollama等)
const localAdapter = new OpenAIAdapter({
model: "llama2",
apiKey: "ollama", // 特殊标记
baseUrl: "http://localhost:11434/v1",
});
// 根据环境选择适配器
export function getAdapter() {
const modelProvider = process.env.MODEL_PROVIDER;
switch (modelProvider) {
case "anthropic":
return anthropicAdapter;
case "local":
return localAdapter;
case "openai":
default:
return openaiAdapter;
}
}
5.2 多语言支持
// src/lib/i18n.ts
export const translations = {
zh: {
welcome: "你好!有什么可以帮助你的?",
placeholder: "输入消息...",
send: "发送",
thinking: "思考中...",
error: "出错了,请重试",
},
en: {
welcome: "Hello! How can I help you?",
placeholder: "Type a message...",
send: "Send",
thinking: "Thinking...",
error: "Something went wrong. Please try again.",
},
ja: {
welcome: "こんにちは!何かお手伝いできることはありますか?",
placeholder: "メッセージを入力...",
send: "送信",
thinking: "考え中...",
error: "エラーが発生しました。もう一度お試しください。",
},
};
// 使用翻译
export function useTranslation(lang: keyof typeof translations = "zh") {
return translations[lang];
}
5.3 性能优化
// src/components/copilot/OptimizedChat.tsx
"use client";
import {
CopilotKit,
useCopilotChat,
useLazyCopilotChat
} from "@copilotkit/react-core";
import { useMemo, useCallback } from "react";
// 大量消息时使用虚拟列表
import { FixedSizeList } from "react-window";
export function OptimizedChat() {
// useLazyCopilotChat 是懒加载版本
// 适用于不需要立即初始化的情况
const chat = useLazyCopilotChat({
// 限制保存的消息数量
maxMessages: 100,
// 消息截断配置
truncateMessages: {
maxTokens: 8000,
strategy: "newest", // 保留最新的消息
},
});
// 使用 useMemo 缓存消息组件
const MessageComponents = useMemo(() => {
return chat.messages.map((msg, i) => (
<MessageItem key={msg.id || i} message={msg} />
));
}, [chat.messages]);
// 使用 useCallback 缓存回调
const handleSend = useCallback(async (content: string) => {
await chat.sendMessage(content);
}, [chat]);
return (
<div>
{/* 使用固定高度列表提高渲染性能 */}
<FixedSizeList
height={500}
itemCount={chat.messages.length}
itemSize={80}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<MessageItem message={chat.messages[index]} />
</div>
)}
</FixedSizeList>
</div>
);
}
5.4 事件追踪与监控
// src/lib/analytics.ts
"use client";
import { useCopilotChat } from "@copilotkit/react-core";
// 事件追踪钩子
export function useChatAnalytics() {
const chat = useCopilotChat();
// 追踪消息发送
const trackMessageSent = (content: string) => {
// 发送到你的分析服务
analytics.track("chat_message_sent", {
content,
contentLength: content.length,
timestamp: Date.now(),
});
};
// 追踪响应时间
const trackResponseTime = (startTime: number, endTime: number) => {
const duration = endTime - startTime;
analytics.track("chat_response_time", {
duration,
durationBucket: getDurationBucket(duration),
});
};
// 追踪工具调用
const trackToolUsage = (toolName: string, success: boolean) => {
analytics.track("chat_tool_used", {
toolName,
success,
});
};
return {
trackMessageSent,
trackResponseTime,
trackToolUsage,
};
}
function getDurationBucket(duration: number): string {
if (duration < 1000) return "fast";
if (duration < 5000) return "normal";
if (duration < 10000) return "slow";
return "very_slow";
}
六、最佳实践与注意事项
6.1 安全最佳实践
// src/lib/security.ts
"use client";
import { tool } from "@copilotkit/langchain";
import { z } from "zod";
// 安全的文件操作工具(带权限检查)
export const secureFileTool = tool({
name: "secure_read_file",
description: "读取文件(仅限授权目录)",
parameters: z.object({
filePath: z.string(),
}),
execute: async ({ filePath }) => {
// 1. 验证文件路径
const allowedPaths = ["/app/data", "/app/config"];
const isAllowed = allowedPaths.some(p => filePath.startsWith(p));
if (!isAllowed) {
throw new Error("权限不足:无法访问此文件");
}
// 2. 验证文件类型
const allowedExtensions = [".json", ".txt", ".csv"];
const ext = path.extname(filePath);
if (!allowedExtensions.includes(ext)) {
throw new Error("不支持的文件类型");
}
// 3. 记录访问日志
console.log(`[安全日志] 用户读取文件: ${filePath}`);
// 4. 执行实际操作
return await readFile(filePath);
},
});
// 输入验证和清理
export function sanitizeInput(input: string): string {
return input
.replace(/[<>]/g, "") // 移除HTML标签
.trim()
.slice(0, 10000); // 限制长度
}
6.2 错误处理
// src/components/copilot/ErrorHandledChat.tsx
"use client";
import {
CopilotKit,
useCopilotChat,
CopilotKitError
} from "@copilotkit/react-core";
import { useState } from "react";
export function ErrorHandledChat() {
const [error, setError] = useState<string | null>(null);
const [isRetrying, setIsRetrying] = useState(false);
const chat = useCopilotChat({
onError: (error) => {
if (error instanceof CopilotKitError) {
// 处理API错误
switch (error.code) {
case "RATE_LIMIT":
setError("请求过于频繁,请稍后再试");
break;
case "INVALID_API_KEY":
setError("API配置错误,请检查设置");
break;
case "MODEL_UNAVAILABLE":
setError("当前模型不可用,已自动切换到备用方案");
break;
default:
setError("发生未知错误");
}
} else {
setError("网络连接失败");
}
},
});
const handleRetry = async () => {
setIsRetrying(true);
setError(null);
try {
// 重试最后一次消息
const lastUserMessage = chat.messages
.filter(m => m.role === "user")
.pop();
if (lastUserMessage) {
await chat.sendMessage(lastUserMessage.content);
}
} finally {
setIsRetrying(false);
}
};
return (
<div>
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
<p className="text-red-700">{error}</p>
<button
onClick={handleRetry}
disabled={isRetrying}
className="mt-2 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 disabled:opacity-50"
>
{isRetrying ? "重试中..." : "重试"}
</button>
</div>
)}
{/* 聊天内容 */}
<ChatMessages messages={chat.messages} />
</div>
);
}
6.3 测试策略
// src/__tests__/copilot-integration.test.tsx
"use client";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { CopilotKit } from "@copilotkit/react-core";
import { ChatComponent } from "@/components/ChatComponent";
// Mock API响应
const mockApiResponse = {
choices: [
{
message: {
content: "这是一个测试响应",
},
},
],
};
describe("CopilotKit 集成测试", () => {
it("应该正确渲染聊天界面", async () => {
render(
<CopilotKit url="/api/copilotkit">
<ChatComponent />
</CopilotKit>
);
// 检查主要元素
expect(screen.getByPlaceholderText("输入消息...")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "发送" })).toBeInTheDocument();
});
it("应该能够发送消息并接收响应", async () => {
// Mock fetch
global.fetch = jest.fn().mockResolvedValue({
json: () => Promise.resolve(mockApiResponse),
}) as jest.Mock;
render(
<CopilotKit url="/api/copilotkit">
<ChatComponent />
</CopilotKit>
);
const input = screen.getByPlaceholderText("输入消息...");
const sendButton = screen.getByRole("button", { name: "发送" });
// 输入并发送消息
await userEvent.type(input, "你好");
await userEvent.click(sendButton);
// 等待响应
await waitFor(() => {
expect(screen.getByText("这是一个测试响应")).toBeInTheDocument();
});
});
it("应该显示加载状态", async () => {
// Mock一个延迟响应
global.fetch = jest.fn().mockImplementation(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
json: () => Promise.resolve(mockApiResponse),
});
}, 1000);
});
}) as jest.Mock;
render(
<CopilotKit url="/api/copilotkit">
<ChatComponent />
</CopilotKit>
);
const sendButton = screen.getByRole("button", { name: "发送" });
await userEvent.click(sendButton);
// 检查加载指示器
expect(screen.getByText("发送中...")).toBeInTheDocument();
});
});
七、总结与资源推荐
7.1 CopilotKit的核心价值
通过本文的详细讲解,我们可以看到CopilotKit为AI应用开发带来了革命性的变化:
开发效率提升
- 从数天的开发时间缩短到几小时
- 代码量从数千行减少到几百行
- 无需深入了解AI底层原理
用户体验优化
- 内置流式输出,响应即时可见
- 丰富的UI组件,开箱即用
- 支持多种交互模式
功能强大
- 完善的Agent框架
- 灵活的工具系统
- 支持多种AI模型
7.2 学习路径建议
# 学习路线图
第一阶段:入门(1-2天)
├── 安装和配置环境
├── 创建第一个简单的AI助手
└── 了解基本概念和组件
第二阶段:进阶(3-5天)
├── 学习工具定义和使用
├── 掌握上下文管理
└── 实现一个完整的客服系统
第三阶段:精通(1-2周)
├── 自定义AI模型配置
├── 性能优化技巧
├── 安全最佳实践
└── 项目实战经验积累
7.3 相关资源推荐
官方资源
- GitHub仓库:https://github.com/CopilotKit/CopilotKit
- 官方文档:https://docs.copilotkit.ai
- 示例代码:https://github.com/CopilotKit/CopilotKit/tree/main/examples
相关AI项目推荐
| 项目名称 | 简介 | 适用场景 |
|---|---|---|
| LangChain | 构建AI应用的通用框架 | 复杂的AI应用 |
| AutoGPT | 自主AI代理 | 实验性项目 |
| Semantic Kernel | 微软的AI编排框架 | 企业应用 |
| Guidance | 微软的结构化输出框架 | 需要精确控制输出 |
社区资源
- Discord社区:加入与其他开发者交流
- GitHub Discussions:提问和分享经验
- Twitter:关注最新动态
7.4 展望未来
CopilotKit作为一个快速发展的开源项目,未来可能在以下方向持续进化:
# 未来可能的功能方向
1. 更多预置工具
├── 图像处理工具
├── 数据库操作工具
└── API集成工具
2. 更智能的Agent
├── 多Agent协作
├── 自主学习能力
└── 更强的推理能力
3. 更好的开发体验
├── 可视化调试工具
├── 更强大的调试能力
└── 实时预览功能
4. 企业级特性
├── 多租户支持
├── 更细粒度的权限控制
└── 合规性支持
写在最后
CopilotKit的出现标志着AI应用开发进入了一个新的阶段。它让开发者能够专注于创造价值,而不是被技术细节所困扰。无论你是想快速为现有产品添加AI能力,还是想从头构建一个AI原生应用,CopilotKit都值得一试。
希望这篇教程能够帮助你快速上手CopilotKit。如果有任何问题,欢迎在评论区留言交流!
祝编码愉快! 🚀
评论区