详细教程展示如何整合 Anthropic API、向量数据库和 CopilotKit 框架构建 RAG 系统。
在本教程中,我们将逐步引导你使用 Anthropic AI API、Pinecone API 和 CopilotKit 为产品知识库构建一个 AI 驱动的 RAG Copilot🪁。
以下是我们将涵盖的内容:
使用 Next.js 构建一个简单的产品知识库
向你的应用客户端添加 CopilotKit UI 组件以及 CopilotKit 的 API 端点
集成 Pinecone 向量数据库 API 来创建可搜索的索引
最终的结果是一个使用 CopilotKit 构建的完整可用的 RAG 知识库应用:
CopilotKit 是将生产级 AI 副驾驶集成到应用中的领先开源框架。它提供了功能丰富的 SDK,支持各种 AI 副驾驶用例,包括上下文感知、副驾驶操作和生成式 UI。
这意味着你可以专注于定义副驾驶的角色,而不用费力从头构建一个副驾驶或处理复杂的集成。
查看 CopilotKit 的 GitHub
在开始之前,你需要准备以下内容:
熟悉使用 React.js 构建 Web 应用。快速提示—你还需要具备一些 TypeScript 知识,因为我们将在这个项目中使用它
确保系统上安装了 Node.js >20。如果没有,你可以从 Node.js 官方网站下载并安装
准备好这些之后,我们将设置开发环境。
以下是我们将构建内容的快速预览:
首先,运行这些命令为项目创建一个新目录,并生成 Next.js 应用样板代码的源文件和文件夹:
mkdir product-knowledge-base && cd product-knowledge-base
npx create-next-app product-knowledge-base
按照设置提示进行操作。你可以按如下所示进行选择(根据你的项目需求进行调整)。
项目创建完成后,导航到项目目录,并通过运行开发服务器验证所有内容都能正常工作:
cd product-knowledge-base
npm run dev
此时,你的应用应该在 http://localhost:3000 本地运行。
接下来,让我们安装这个项目所需的依赖项。这些包括:
运行以下命令来安装它们:
yarn add @anthropic-ai/sdk @mantine/core @mantine/hooks @copilotkit/react-core @copilotkit/react-ui @copilotkit/runtime lucide-react axios @pinecone-database/pinecone
现在,让我们为项目设置文件结构。以下是我们将创建的主要文件和目录的概览:
src/app/ui/service/index.ts:处理对后端的 API 调用以获取虚拟帖子
src/app/ui/components/KnowledgeBase.tsx:知识库的主 UI 组件
src/app/lib/types/post.ts:帖子类型定义
src/app/lib/data/data.ts:知识库的虚拟帖子数据
src/app/api/copilotkit/route.ts:CopilotKit API 端点
src/app/api/posts/route.ts:虚拟帖子 API 端点
你的项目结构将如下所示:
product-knowledge-base/
├── src/
│ ├── app/
│ │ ├── ui/
│ │ │ ├── service/
│ │ │ │ └── index.ts
│ │ │ ├── components/
│ │ │ │ └── KnowledgeBase.tsx
│ │ ├── lib/
│ │ │ ├── types/
│ │ │ │ └── post.ts
│ │ │ ├── data/
│ │ │ │ └── data.ts
│ │ ├── api/
│ │ │ ├── copilotkit/
│ │ │ │ └── route.ts
│ │ │ ├── posts/
│ │ │ │ └── route.ts
通过此设置,你现在拥有了本教程的一个可用的开发环境。
首先,导入 CopilotKit 和 Mantine UI 提供程序,然后用它们包装整个应用,使其全局可用。以下是更新 layout.tsx 文件的方法:
import { MantineProvider } from "@mantine/core";
import "@mantine/core/styles.css";
import "@copilotkit/react-ui/styles.css";
import { CopilotKit } from "@copilotkit/react-core";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<CopilotKit runtimeUrl="/api/copilotkit">
<MantineProvider>{children}</MantineProvider>
</CopilotKit>
</body>
</html>
);
}
用这些提供程序包装应用时,记得将 runtimeUrl="<endpoint-url>" 属性传递给 CopilotKit 提供程序。
在本部分,我们将介绍构建知识库组件所需的代码。让我们首先定义 Post 接口。在你的 src/app/lib/types/post.ts 中添加以下代码:
export interface Post {
id: number;
title: string;
summary: string;
content: string;
category: string;
createdAt: string;
}
接下来,导航到 src/app/ui/service/index.ts 文件,并添加以下代码来处理从应用后端获取帖子的 API 请求:
import axios from 'axios';
import { Post } from '@/app/lib/types/post';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL
export const fetchPosts = async (): Promise<Post[]> => {
const response = await axios.get(`${API_BASE_URL}/api/posts`);
return response.data;
};
让我们在项目根目录中创建一个 .env 文件,并添加以下基础 URL。
NEXT_PUBLIC_API_BASE_URL='http://localhost:3000'
现在,让我们创建知识库 UI 组件。在 src/app/ui/components/KnowledgeBase.tsx 中,首先添加以下导入:
"use client"
import { useState, useEffect } from "react";
import {
Container,
Title,
Grid,
Card,
Text,
Badge,
Group,
Stack,
Box,
Modal,
List,
} from "@mantine/core";
import { BookOpen } from "lucide-react";
import { Post } from "@/app/lib/types/post";
import { fetchPosts } from "@/app/ui/service";
接下来,定义 KnowledgeBase 函数组件并初始化以下状态:
export function KnowledgeBase() {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
const [selectedPost, setSelectedPost] = useState<Post | null>(null);
if (loading) {
return <Text>Loading...</Text>;
}
return (
<Container size="md" py="xl" ml="xl">
<Stack gap="xl">
<Group justify="center" align="center">
<BookOpen size={32} />
<Title order={1}>CopilotKit Product Knowledge Base</Title>
</Group>
</Stack>
</Container>
);
}
现在,让我们定义一个函数来从 API 获取帖子列表:
useEffect(() => {
const loadPosts = async () => {
try {
const data = await fetchPosts();
setPosts(data);
} catch (error) {
console.error("Error loading posts:", error);
} finally {
setLoading(false);
}
};
loadPosts();
}, []);
要显示从应用后端获取的帖子,添加以下代码。我们将在网格布局中使用卡片渲染帖子列表:
{/* Display posts */}
<Grid>
{posts.map((post) => (
<Grid.Col key={post.id} span={{ base: 12, sm: 6, md: 4 }}>
<Card
shadow="sm"
padding="lg"
radius="md"
withBorder
onClick={() => setSelectedPost(post)}
style={{ cursor: "pointer" }}
>
<Stack gap="md">
<Title order={3}>{post.title}</Title>
<Badge color="blue" variant="light">
{post.category}
</Badge>
<Text size="sm" c="dimmed">
{post.summary}
</Text>
<Text size="xs" c="dimmed">
Posted on: {new Date(post.createdAt).toLocaleDateString()}
</Text>
</Stack>
</Card>
</Grid.Col>
))}
</Grid>
要显示单篇帖子的内容,在本例中,我们将通过在单击文章卡片时在模态组件中显示虚拟内容来保持简单。为此,添加以下代码:
{/* Modal for displaying selected post */}
{selectedPost && (
<Modal
opened={!!selectedPost}
onClose={() => setSelectedPost(null)}
title={selectedPost.title}
centered
size="xl"
>
<Stack gap="md">
<List>
{selectedPost.content
.split("")
.filter((item) => item.trim() !== "")
.map((item, index) => (
<List.Item key={index}>{item}</List.Item>
))}
</List>
</Stack>
</Modal>
)}
接下来,定义以下函数以在用户单击帖子卡片时更新选中帖子的状态。
const handlePostClick = (post: Post) => {
setSelectedPost(post);
};
最后,要在浏览器中呈现此组件,在你的 src/app/page.tsx 文件中使用以下代码导入它(确保删除所有样板 Next.js 代码):
import KnowledgeBase from "@/app/ui/components/KnowledgeBase";
export default function Home() {
return (
<div>
<KnowledgeBase />
</div>
);
}
下一步是在知识库界面中添加 CopilotKit UI 组件。CopilotKit 的 React SDK 提供了一系列设计简洁且易于自定义的 UI 组件,包括侧边栏、弹出窗口、文本区域以及无头 UI 组件。在本示例中,我们将使用 CopilotSidebar 组件渲染应用内聊天机器人界面。
要添加 CopilotKit 的 UI 侧边栏组件,请在 src/app/ui/components/KnowledgeBase.tsx 中添加以下导入:
import { CopilotSidebar } from "@copilotkit/react-ui";
导入后,在 JSX 的 return 语句中添加该组件:
<Group justify="center" style={{ width: "100%" }}>
<Box style={{ flex: 1, maxWidth: "350px" }}>
<CopilotSidebar
instructions="Help the user get the right knowledge base articles for their query"
labels={{
initial: "Welcome! Describe the query you need assistance with.",
}}
defaultOpen={true}
clickOutsideToClose={false}
/>
</Box>
</Group>
该组件接受多种属性,包括 instructions、labels、defaultOpen 和 clickOutsideToClose。其中很重要的一点是,instructions 属性允许你提供额外的上下文,帮助底层 Copilot AI LLM 更好地理解并响应用户查询。
React CopilotKit SDK 还提供了一组实用的 Hooks,让你能够为应用中的 AI Copilot 定义自定义操作。在本示例中,我们将使用 useCopilotAction Hook 定义预期操作,即根据用户查询检索知识库文章。
为此,首先在 KnowledgeBase 组件文件中导入 useCopilotAction Hook,如下所示:
import { useCopilotAction } from "@copilotkit/react-core";
导入后,你可以初始化该 Hook,并指定希望 Copilot 执行的操作。在本例中,我们将定义一个名为“FetchKnowledgebaseArticles”的操作,根据用户提供的查询检索相关文章。代码如下:
useCopilotAction({
name: "FetchKnowledgebaseArticles",
description: "Fetch relevant knowledge base articles based on a user query",
parameters: [
{
name: "query",
type: "string",
description: "User query for the knowledge base",
required: true,
},
],
render: "Getting relevant answers to your query...",
});
该操作配置包含几个重要元素。name 属性为操作提供唯一标识符,而 description 则说明操作的用途以及应在何时使用。
此外,parameters 数组定义了执行操作所需的输入,在本例中就是用户的查询。最后,render 属性允许你指定操作执行期间显示的内容。在本示例中,我们将显示一条简单的状态消息,让用户了解正在进行的处理过程。
为了完成整个应用的工作流,让我们构建后端:添加用于获取文章的端点,集成 CopilotKit 功能,并使用 Pinecone API 为知识库文章创建可搜索索引。
首先,前往这个包含模拟文章数据的 GitHub 仓库文件,将其内容复制并粘贴到本地的 src/app/lib/data/data.ts 文件中。
接下来,在 src/app/api/posts/route.ts 文件中添加以下代码,设置模拟文章 API 端点:
import { NextResponse } from 'next/server';
import { posts } from '@/app/lib/data/data';
export async function GET() {
return NextResponse.json(posts);
}
此时,启动开发服务器并打开应用的 localhost URL。你应该能看到文章列表以及 CopilotKit 侧边栏组件。
回到本指南的主要目标,也就是为产品知识库集成 AI Copilot。通常,大多数产品知识库都包含各种类型的内容——博客文章、常见问题、内部标准作业程序、API 文档等。这远远多于本示例中使用的三篇模拟文章。
通常,这些知识库会集成 Algolia Search API,让用户能够快速搜索资源。现在,对于 AI Copilot,我们希望超越简单的“搜索—展示”模式。本质上,我们希望利用 LLM 理解自然语言的能力,为知识库资源实现“对话式”搜索功能。
Copilot 不会只是返回静态的搜索结果,而是允许用户围绕这些资源进行“对话”、提出后续问题,并以更加用户友好的方式获得答案。可以说,这种方式更加直观。
要实现这一点,我们需要创建可供 LLM 搜索的索引——本质上,它们是 AI 可以查询的数据源,用于获取正确的信息。为此,我们将使用 Pinecone 的向量数据库 API,为模拟数据创建可搜索索引。
Pinecone 是一种向量数据库服务,旨在为应用提供快速、可扩展且准确的智能搜索能力。它可以高效地存储和检索数据的向量表示,因此非常适合语义搜索和推荐系统等任务。
这意味着,我们不再仅依赖为 Copilot 提供支持的 Anthropic AI LLM 根据已有的训练知识生成响应,而是可以对 LLM 进行定制并为其补充上下文,使 Copilot 能够基于应用数据处理用户查询并生成响应。理想情况下,这正是 Pinecone 发挥作用的地方——它允许你为自己的数据创建向量数据库,并且大型语言模型能够轻松搜索这些数据库。
本质上,在这个示例中,我们只是集成 Pinecone,为知识库数据创建索引。这样一来,Copilot 就可以先搜索数据,然后生成更相关、更符合上下文的响应,同时还能根据同一份知识库数据准确回答后续问题。
下面简要概述我们要做的事情:
为示例文章生成知识库内容嵌入。
为知识库数据建立索引并进行查询。
在 src/app/api/copilotkit/route.ts 文件中,首先添加以下导入:
import { Pinecone } from '@pinecone-database/pinecone';
import {posts} from "@/app/lib/data/data";
接下来,为 Pinecone 和 Anthropic API 密钥定义环境变量:
const ANTHROPIC_API_KEY = process.env.NEXT_PUBLIC_ANTHROPIC_API_KEY;
const PINECONE_API_KEY = process.env.NEXT_PUBLIC_PINECONE_API_KEY;
最好添加一项检查,以确保已经提供这些密钥。不用担心——稍后我们会介绍获取 Anthropic API 密钥的步骤。
if (!ANTHROPIC_API_KEY || !PINECONE_API_KEY) {
console.error('Missing required API keys. ');
process.exit(1);
}
现在,初始化 Pinecone SDK 并设置必要的配置:
const pinecone = new Pinecone({ apiKey: PINECONE_API_KEY });
const model = 'multilingual-e5-large';
const indexName = 'knowledge-base-data';
现在可以创建 Pinecone 索引了。索引本质上是一种结构化存储(存储数据的数值表示),允许你基于向量相似度高效地搜索和检索数据。
理想情况下,对于生产应用,你通常会通过 API 调用动态获取文章数据。但在本例中,我们将使用模拟数据来模拟这一过程。
为了给知识库数据创建向量数据库,我们需要为这些数据初始化一个 Pinecone 索引。
以下是实现该功能的函数:
// Function to create the Pinecone index
const initializePinecone = async () => {
const maxRetries = 3;
const retryDelay = 2000;
for (let i = 0; i < maxRetries; i++) {
try {
const indexList = await pinecone.listIndexes();
if (!indexList.indexes?.some(index => index.name === indexName)) {
await pinecone.createIndex({
name: indexName,
dimension: 1024,
metric: 'cosine',
spec: {
serverless: {
cloud: 'aws',
region: 'us-east-1',
},
},
});
await new Promise(resolve => setTimeout(resolve, 5000));
}
return pinecone.index(indexName);
} catch (error) {
if (i === maxRetries - 1) throw error;
console.warn(`Retrying Pinecone initialization... (${i + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
return null;
};
设置完成后,你就可以使用它来存储和检索知识库数据。
接下来,我们需要为知识库生成嵌入。这一步对于让应用能够使用向量嵌入高效存储和搜索海量数据至关重要。
嵌入知识库内容,就是将原始数据(通常是文本)转换为能够表示其语义的向量。随后,Pinecone 会将这些向量存储到索引中。
建立索引后,你的应用就可以执行相似度搜索等操作,根据查询向量与已存储内容向量之间的相似度,快速检索最相关的数据。
为此,请添加以下函数:
// Initialize Pinecone and prepare the index
(async () => {
try {
const index = await initializePinecone();
if (index) {
const embeddings = await pinecone.inference.embed(
model,
posts.map(d => d.content),
{ inputType: 'passage', truncate: 'END' }
);
const records = posts.map((d, i) => ({
id: d.id.toString(),
values: embeddings[i]?.values ?? [],
metadata: { text: d.content },
}));
await index.namespace('knowledge-base-data-namespace').upsert(
records.map(record => ({
...record,
values: record.values || [],
}))
);
}
} catch (error) {
console.error('Error initializing Pinecone:', error);
process.exit(1);
}
})();
这里有几点需要注意:
嵌入(Embeddings):嵌入是内容(例如文本或文章)的向量表示,用来捕获数据的语义。在本例中,嵌入由 Pinecone 的 multilingual-e5-large 模型生成,该模型会处理内容并将其转换为向量。请注意,你也可以使用其他模型,例如 OpenAI 提供的嵌入 API 来完成这项任务。
命名空间(Namespace):Pinecone 中的命名空间是索引的逻辑分区。它允许你组织索引中的数据,并在特定的数据分区内执行操作。在本例中,命名空间被设置为 knowledge-base-data-namespace,用于将知识库内容归为一组。
记录(Records):记录表示要插入 Pinecone 的数据。每条记录都由 id、values(嵌入向量)和 metadata(例如文章文本)组成。Pinecone 使用 values 执行相似度搜索,而 metadata 则为每条记录提供额外的上下文。
现在,为了让这套设置正常工作,你需要获取 Pinecone API 密钥。
如何获取 Pinecone API 密钥:
按照以下步骤获取 Pinecone AI API 密钥:
前往 Pinecone Developer Console。
选择 API Keys 选项卡,然后单击 Create API Key。你可以使用默认密钥,也可以创建一个新密钥。
指定密钥名称,然后单击 Create key。
完成这些步骤后,返回 .env 文件并粘贴该密钥:
NEXT_PUBLIC_PINECONE_API_KEY=your-api-key
集成 CopilotKit Node.js 端点
最后一步是添加 CopilotKit Node.js 端点,用于处理来自前端的请求。
在继续之前,你需要配置 Anthropic API,以便向其服务发起请求。具体步骤如下:
访问 Anthropic AI 文档,创建 Anthropic 账户并设置计费。
登录 Anthropic API 控制台后,生成 API 密钥。
获得 API 密钥后,将其添加到项目根目录下的 .env 文件中:
请务必设置计费和配额,以便你的应用能够发起 API 请求。
还记得我们之前在应用客户端中添加了 CopilotKit API URL,使其能够将请求转发到 CopilotKit 后端进行处理。为了实现这一点,我们需要定义 CopilotKit API 端点来管理和处理这些请求。首先,在 src/app/api/copilotkit/route.ts 文件中导入以下内容:
import { CopilotRuntime, AnthropicAdapter, copilotRuntimeNextJSAppRouterEndpoint } from "@copilotkit/runtime";
import Anthropic from "@anthropic-ai/sdk";
import { NextRequest } from 'next/server'
Copilot Runtime 是 CopilotKit 的后端引擎,允许应用与 LLM 交互。借助它,你可以为 Copilot 定义后端操作。你可以指定大量任务,包括执行内部数据库调用,以及管理不同的进程和工作流。不过,在这个具体示例中,我们将定义一个操作,根据用户查询从 Pinecone 索引中检索相关文章。
为此,请添加以下代码:
const runtime = new CopilotRuntime({
actions: () => [
{
name: 'FetchKnowledgebaseArticles',
description: 'Fetch relevant knowledge base articles based on a user query',
parameters: [
{
name: 'query',
type: 'string',
description: 'The User query for the knowledge base index search to perform',
required: true,
},
],
handler: async ({ query }: { query: string }) => {
try {
const queryEmbedding = await pinecone.inference.embed(
model,
[query],
{ inputType: 'query' }
);
const queryResponse = await pinecone
.index(indexName)
.namespace('knowledge-base-data-namespace')
.query({
topK: 3,
vector: queryEmbedding[0]?.values || [],
includeValues: false,
includeMetadata: true,
});
return { articles: queryResponse?.matches || [] };
} catch (error) {
console.error('Error fetching knowledge base articles:', error);
throw new Error('Failed to fetch knowledge base articles.');
}
}, },
],
});
下面我们来拆解这个操作处理程序。理想情况下,这个处理程序是整个集成的核心引擎。它接收从 Copilot 客户端传递过来的 query 参数。
通过 Pinecone 的查询操作,查询内容会被转换成向量表示,然后与存储在 Pinecone 中的索引向量进行比较,从而找出索引中相关性最高的三篇文章。返回结果包含向量值和元数据,其中保存着与查询实际匹配的数据。
由于大多数产品知识库都包含大量文章,其中一些文章可能涵盖相似的观点,而另一些则完全不同,因此返回多条与查询匹配的相关数据是很实用的。(你可以调整 topK 的值,以设置返回的数量。