人工智慧驅動的應用程式正在不斷發展,不僅僅是執行任務的自主代理。一種涉及人機互動的新方法允許用戶提供回饋、審查結果並決定人工智慧的後續步驟。這些執行時代理程式稱為 CoAgent。
在本教程中,您將學習如何使用LangGraph 、 CopilotKit和Tavily建立 Perplexity 克隆。
是時候開始建造了!
Agentic copilots 是 CopilotKit 將 LangGraph 代理程式引入您的應用程式的方式。
CoAgents是 CopilotKit 建立代理體驗的方法!
簡而言之,它將透過執行多個搜尋查詢來處理使用者請求,並將搜尋狀態和結果即時傳回客戶端。
{% cta https://git.new/devtoarticle1 %} 查看 CopilotKit ⭐️ {% endcta %}
要完全理解本教程,您需要對 React 或 Next.js 有基本的了解。
我們還將利用以下內容:
Python - 一種流行的程式語言,用於使用 LangGraph 建立 AI 代理程式;確保它已安裝在您的電腦上。
LangGraph - 用於建立和部署人工智慧代理的框架。它還有助於定義代理要執行的控制流程和操作。
OpenAI API 金鑰- 使我們能夠使用 GPT 模型執行各種任務;對於本教程,請確保您有權存取 GPT-4 模型。
Tavily AI - 一個搜尋引擎,使人工智慧代理能夠在應用程式中進行研究並存取即時知識。
CopilotKit - 一個開源副駕駛框架,用於建立自訂 AI 聊天機器人、應用程式內 AI 代理程式和文字區域。
Shad Cn UI - 提供應用程式內可重複使用 UI 元件的集合。
在本部分中,您將學習如何使用 LangGraph 和 CopilotKit 建立 AI 代理程式。
首先,複製CopilotKit CoAgents 入門儲存庫。 ui
目錄包含 Next.js 應用程式的前端, agent
目錄包含應用程式的 CoAgent。
在agent
目錄中,使用Poetry安裝專案相依性。
cd agent
poetry install
在代理資料夾中建立一個.env
文件,並將OpenAI和Tavily AI API 金鑰複製到該文件中:
OPENAI_API_KEY=
TAVILY_API_KEY=
將以下程式碼片段複製到agent.py
檔案中:
"""
This is the main entry point for the AI.
It defines the workflow graph and the entry point for the agent.
"""
# pylint: disable=line-too-long, unused-import
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from ai_researcher.state import AgentState
from ai_researcher.steps import steps_node
from ai_researcher.search import search_node
from ai_researcher.summarize import summarize_node
from ai_researcher.extract import extract_node
def route(state):
"""Route to research nodes."""
if not state.get("steps", None):
return END
current_step = next((step for step in state["steps"] if step["status"] == "pending"), None)
if not current_step:
return "summarize_node"
if current_step["type"] == "search":
return "search_node"
raise ValueError(f"Unknown step type: {current_step['type']}")
# Define a new graph
workflow = StateGraph(AgentState)
workflow.add_node("steps_node", steps_node)
workflow.add_node("search_node", search_node)
workflow.add_node("summarize_node", summarize_node)
workflow.add_node("extract_node", extract_node)
# Chatbot
workflow.set_entry_point("steps_node")
workflow.add_conditional_edges(
"steps_node",
route,
["summarize_node", "search_node", END]
)
workflow.add_edge("search_node", "extract_node")
workflow.add_conditional_edges(
"extract_node",
route,
["summarize_node", "search_node"]
)
workflow.add_edge("summarize_node", END)
memory = MemorySaver()
graph = workflow.compile(checkpointer=memory)
上面的程式碼片段定義了 LangGraph 代理程式工作流程。它從steps_node
開始,搜尋結果,總結結果,提取關鍵點。
接下來建立一個demo.py
文件,其中包含以下程式碼片段:
"""Demo"""
import os
from dotenv import load_dotenv
load_dotenv()
from fastapi import FastAPI
import uvicorn
from copilotkit.integrations.fastapi import add_fastapi_endpoint
from copilotkit import CopilotKitSDK, LangGraphAgent
from ai_researcher.agent import graph
app = FastAPI()
sdk = CopilotKitSDK(
agents=[
LangGraphAgent(
name="ai_researcher",
description="Search agent.",
graph=graph,
)
],
)
add_fastapi_endpoint(app, sdk, "/copilotkit")
# add new route for health check
@app.get("/health")
def health():
"""Health check."""
return {"status": "ok"}
def main():
"""Run the uvicorn server."""
port = int(os.getenv("PORT", "8000"))
uvicorn.run("ai_researcher.demo:app", host="0.0.0.0", port=port, reload=True)
上面的程式碼會建立一個託管 LangGraph 代理並將其連接到 CopilotKit SDK 的 FastAPI 端點。
您可以從GitHub 儲存庫複製用於建立 CoAgent 的其餘程式碼。在以下部分中,您將了解如何為 Perplexity 克隆建立使用者介面並使用 CopilotKit 處理搜尋請求。
在本節中,我將引導您完成建立應用程式使用者介面的過程。
首先,透過執行以下程式碼片段來建立一個 Next.js Typescript 專案:
# 👉🏻 Navigate into the ui folder
npx create-next-app ./
透過執行以下程式碼片段將ShadCn UI庫安裝到新建立的專案中:
npx shadcn@latest init
接下來,在 Next.js 專案的根目錄中建立components
資料夾,然後將ui
資料夾從此 GitHub 儲存庫複製到該資料夾中。 Shadcn 可讓您透過命令列安裝各種元件,輕鬆地將它們新增至您的應用程式。
除了 Shadcn 元件之外,您還需要建立一些代表應用程式介面不同部分的元件。在components
資料夾中執行以下程式碼片段,將這些元件加入 Next.js 專案中:
touch ResearchWrapper.tsx ResultsView.tsx HomeView.tsx
touch AnswerMarkdown.tsx Progress.tsx SkeletonLoader.tsx
將下面的程式碼片段複製到app/page.tsx
檔案中:
"use client";
import { ResearchWrapper } from "@/components/ResearchWrapper";
import { ModelSelectorProvider, useModelSelectorContext } from "@/lib/model-selector-provider";
import { ResearchProvider } from "@/lib/research-provider";
import { CopilotKit } from "@copilotkit/react-core";
import "@copilotkit/react-ui/styles.css";
export default function ModelSelectorWrapper() {
return (
<CopilotKit runtimeUrl={useLgc ? "/api/copilotkit-lgc" : "/api/copilotkit"} agent="ai_researcher">
<ResearchProvider>
<ResearchWrapper />
</ResearchProvider>
</CopilotKit>
);
}
在上面的程式碼片段中, ResearchProvider
是一個自訂的 React 上下文提供程序,它共享使用者的搜尋查詢和結果,使應用程式中的所有元件都可以存取它們。 ResearchWrapper
元件包含核心應用程式元素並管理 UI。
在 Next.js 專案的根目錄下建立一個包含research-provider.tsx
檔案的lib
資料夾,並將下列程式碼複製到該檔案:
import { createContext, useContext, useState, ReactNode, useEffect } from "react";
type ResearchContextType = {
researchQuery: string;
setResearchQuery: (query: string) => void;
researchInput: string;
setResearchInput: (input: string) => void;
isLoading: boolean;
setIsLoading: (loading: boolean) => void;
researchResult: ResearchResult | null;
setResearchResult: (result: ResearchResult) => void;
};
type ResearchResult = {
answer: string;
sources: string[];
}
const ResearchContext = createContext<ResearchContextType | undefined>(undefined);
export const ResearchProvider = ({ children }: { children: ReactNode }) => {
const [researchQuery, setResearchQuery] = useState<string>("");
const [researchInput, setResearchInput] = useState<string>("");
const [researchResult, setResearchResult] = useState<ResearchResult | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
if (!researchQuery) {
setResearchResult(null);
setResearchInput("");
}
}, [researchQuery, researchResult]);
return (
<ResearchContext.Provider
value={{
researchQuery,
setResearchQuery,
researchInput,
setResearchInput,
isLoading,
setIsLoading,
researchResult,
setResearchResult,
}}
>
{children}
</ResearchContext.Provider>
);
};
export const useResearchContext = () => {
const context = useContext(ResearchContext);
if (context === undefined) {
throw new Error("useResearchContext must be used within a ResearchProvider");
}
return context;
};
狀態被聲明並保存到ResearchContext
以確保它們在應用程式內的多個元件中得到正確的管理。
建立一個ResearchWrapper
元件,如下所示:
import { HomeView } from "./HomeView";
import { ResultsView } from "./ResultsView";
import { AnimatePresence } from "framer-motion";
import { useResearchContext } from "@/lib/research-provider";
export function ResearchWrapper() {
const { researchQuery, setResearchInput } = useResearchContext();
return (
<>
<div className="flex flex-col items-center justify-center relative z-10">
<div className="flex-1">
{researchQuery ? (
<AnimatePresence
key="results"
onExitComplete={() => {
setResearchInput("");
}}
mode="wait"
>
<ResultsView key="results" />
</AnimatePresence>
) : (
<AnimatePresence key="home" mode="wait">
<HomeView key="home" />
</AnimatePresence>
)}
</div>
<footer className="text-xs p-2">
<a
href="https://copilotkit.ai"
target="_blank"
rel="noopener noreferrer"
className="text-slate-600 font-medium hover:underline"
>
Powered by CopilotKit 🪁
</a>
</footer>
</div>
</>
);
}
ResearchWrapper
元件將HomeView
元件呈現為預設視圖,並在提供搜尋查詢時顯示ResultView
。 useResearchContext
掛鉤使我們能夠存取researchQuery
狀態並相應地更新視圖。
最後,建立HomeView
元件來渲染應用程式主頁介面。
"use client";
import { useEffect, useState } from "react";
import { Textarea } from "./ui/textarea";
import { cn } from "@/lib/utils";
import { Button } from "./ui/button";
import { CornerDownLeftIcon } from "lucide-react";
import { useResearchContext } from "@/lib/research-provider";
import { motion } from "framer-motion";
import { useCoAgent } from "@copilotkit/react-core";
import { TextMessage, MessageRole } from "@copilotkit/runtime-client-gql";
import type { AgentState } from "../lib/types";
import { useModelSelectorContext } from "@/lib/model-selector-provider";
const MAX_INPUT_LENGTH = 250;
export function HomeView() {
const { setResearchQuery, researchInput, setResearchInput } =
useResearchContext();
const { model } = useModelSelectorContext();
const [isInputFocused, setIsInputFocused] = useState(false);
const {
run: runResearchAgent,
} = useCoAgent<AgentState>({
name: "ai_researcher",
initialState: {
model,
},
});
const handleResearch = (query: string) => {
setResearchQuery(query);
runResearchAgent(() => {
return new TextMessage({
role: MessageRole.User,
content: query,
});
});
};
const suggestions = [
{ label: "Electric cars sold in 2024 vs 2023", icon: "🚙" },
{ label: "Top 10 richest people in the world", icon: "💰" },
{ label: "Population of the World", icon: "🌍 " },
{ label: "Weather in Seattle VS New York", icon: "⛅️" },
];
return (
<motion.div
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.4 }}
className="h-screen w-full flex flex-col gap-y-2 justify-center items-center p-4 lg:p-0"
>
<h1 className="text-4xl font-extralight mb-6">
What would you like to know?
</h1>
<div
className={cn(
"w-full bg-slate-100/50 border shadow-sm rounded-md transition-all",
{
"ring-1 ring-slate-300": isInputFocused,
}
)}
>
<Textarea
placeholder="Ask anything..."
className="bg-transparent p-4 resize-none focus-visible:ring-0 focus-visible:ring-offset-0 border-0 w-full"
onFocus={() => setIsInputFocused(true)}
onBlur={() => setIsInputFocused(false)}
value={researchInput}
onChange={(e) => setResearchInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleResearch(researchInput);
}
}}
maxLength={MAX_INPUT_LENGTH}
/>
<div className="text-xs p-4 flex items-center justify-between">
<div
className={cn("transition-all duration-300 mt-4 text-slate-500", {
"opacity-0": !researchInput,
"opacity-100": researchInput,
})}
>
{researchInput.length} / {MAX_INPUT_LENGTH}
</div>
<Button
size="sm"
className={cn("rounded-full transition-all duration-300", {
"opacity-0 pointer-events-none": !researchInput,
"opacity-100": researchInput,
})}
onClick={() => handleResearch(researchInput)}
>
Research
<CornerDownLeftIcon className="w-4 h-4 ml-2" />
</Button>
</div>
</div>
<div className="grid grid-cols-2 w-full gap-2 text-sm">
{suggestions.map((suggestion) => (
<div
key={suggestion.label}
onClick={() => handleResearch(suggestion.label)}
className="p-2 bg-slate-100/50 rounded-md border col-span-2 lg:col-span-1 flex cursor-pointer items-center space-x-2 hover:bg-slate-100 transition-all duration-300"
>
<span className="text-base">{suggestion.icon}</span>
<span className="flex-1">{suggestion.label}</span>
</div>
))}
</div>
</motion.div>
);
}
在本部分中,您將了解如何將 CopilotKit CoAgent 連接到 Next.js 應用程式,以使用戶能夠在應用程式中執行搜尋操作。
安裝以下 CopilotKit 軟體包和OpenAI Node.js SDK 。 CopilotKit 套件允許協同代理與 React 狀態值互動並在應用程式內做出決策。
npm install @copilotkit/react-core @copilotkit/react-ui @copilotkit/runtime @copilotkit/runtime-client-gql openai
在 Next.js app
資料夾中建立一個api
資料夾。在api
資料夾內,建立一個包含route.ts
檔案的copilotkit
目錄。這將建立一個 API 端點 ( /api/copilotkit
),將前端應用程式連接到 CopilotKit CoAgent。
cd app
mkdir api && cd api
mkdir copilotkit && cd copilotkit
touch route.ts
將下面的程式碼片段複製到api/copilotkit/route.ts
檔案中:
import { NextRequest } from "next/server";
import {
CopilotRuntime,
OpenAIAdapter,
copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";
import OpenAI from "openai";
//👇🏻 initializes OpenAI as the adapter
const openai = new OpenAI();
const serviceAdapter = new OpenAIAdapter({ openai } as any);
//👇🏻 connects the CopilotKit runtime to the CoAgent
const runtime = new CopilotRuntime({
remoteEndpoints: [
{
url: process.env.REMOTE_ACTION_URL || "http://localhost:8000/copilotkit",
},
],
});
export const POST = async (req: NextRequest) => {
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime,
serviceAdapter,
endpoint: "/api/copilotkit",
});
return handleRequest(req);
};
上面的程式碼片段在/api/copilotkit
API 端點設定 CopilotKit 執行時,允許 CopilotKit 透過 AI 協同代理處理使用者請求。
最後,透過使用CopilotKit 元件包裝整個應用程式來更新app/page.tsx
,該元件為所有應用程式元件提供 copilot 上下文。
"use client";
import { ModelSelector } from "@/components/ModelSelector";
import { ResearchWrapper } from "@/components/ResearchWrapper";
import { ModelSelectorProvider, useModelSelectorContext } from "@/lib/model-selector-provider";
import { ResearchProvider } from "@/lib/research-provider";
import { CopilotKit } from "@copilotkit/react-core";
import "@copilotkit/react-ui/styles.css";
export default function ModelSelectorWrapper() {
return (
<main className="flex flex-col items-center justify-between">
<ModelSelectorProvider>
<Home/>
<ModelSelector />
</ModelSelectorProvider>
</main>
);
}
function Home() {
const { useLgc } = useModelSelectorContext();
return (
<CopilotKit runtimeUrl={useLgc ? "/api/copilotkit-lgc" : "/api/copilotkit"} agent="ai_researcher">
<ResearchProvider>
<ResearchWrapper />
</ResearchProvider>
</CopilotKit>
);
}
CopilotKit 元件包裝了整個應用程式並接受兩個 props - runtimeUrl
和agent
。 runtimeUrl
是託管 AI 代理程式的後端 API 路由,而agent
是執行操作的代理程式的名稱。
為了使 CopilotKit 能夠存取和處理使用者輸入,它提供了useCoAgent
掛鉤,該掛鉤允許從應用程式內的任何位置存取代理程式的狀態。
例如,下面的程式碼片段示範如何使用useCoAgent
掛鉤。 state
變數允許存取代理程式的目前狀態, setState
用於修改狀態, run
函數使用代理執行指令。 start
和stop
函數啟動和停止代理的執行。
const { state, setState, run, start, stop } = useCoAgent({
name: "search_agent",
});
更新HomeView
元件以在提供搜尋查詢時執行代理程式。
//👇🏻 import useCoAgent hook from CopilotKit
import { useCoAgent } from "@copilotkit/react-core";
const { run: runResearchAgent } = useCoAgent({
name: "search_agent",
});
const handleResearch = (query: string) => {
setResearchQuery(query);
runResearchAgent(query); //👉🏻 starts the agent execution
};
接下來,您可以透過存取useCoAgent
掛鉤中的狀態變數,將搜尋結果串流傳輸到ResultsView
。將下面的程式碼片段複製到ResultsView
元件中。
"use client";
import { useResearchContext } from "@/lib/research-provider";
import { motion } from "framer-motion";
import { BookOpenIcon, LoaderCircleIcon, SparkleIcon } from "lucide-react";
import { SkeletonLoader } from "./SkeletonLoader";
import { useCoAgent } from "@copilotkit/react-core";
import { Progress } from "./Progress";
import { AnswerMarkdown } from "./AnswerMarkdown";
export function ResultsView() {
const { researchQuery } = useResearchContext();
//👇🏻 agent state
const { state: agentState } = useCoAgent({
name: "search_agent",
});
console.log("AGENT_STATE", agentState);
//👇🏻 keeps track of the current agent processing state
const steps =
agentState?.steps?.map((step: any) => {
return {
description: step.description || "",
status: step.status || "pending",
updates: step.updates || [],
};
}) || [];
const isLoading = !agentState?.answer?.markdown;
return (
<motion.div
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -50 }}
transition={{ duration: 0.5, ease: "easeOut" }}
>
<div className='max-w-[1000px] p-8 lg:p-4 flex flex-col gap-y-8 mt-4 lg:mt-6 text-sm lg:text-base'>
<div className='space-y-4'>
<h1 className='text-3xl lg:text-4xl font-extralight'>
{researchQuery}
</h1>
</div>
<Progress steps={steps} />
<div className='grid grid-cols-12 gap-8'>
<div className='col-span-12 lg:col-span-8 flex flex-col'>
<h2 className='flex items-center gap-x-2'>
{isLoading ? (
<LoaderCircleIcon className='animate-spin w-4 h-4 text-slate-500' />
) : (
<SparkleIcon className='w-4 h-4 text-slate-500' />
)}
Answer
</h2>
<div className='text-slate-500 font-light'>
{isLoading ? (
<SkeletonLoader />
) : (
<AnswerMarkdown markdown={agentState?.answer?.markdown} /> //👈🏼 displays search results
)}
</div>
</div>
{agentState?.answer?.references?.length && (
<div className='flex col-span-12 lg:col-span-4 flex-col gap-y-4 w-[200px]'>
<h2 className='flex items-center gap-x-2'>
<BookOpenIcon className='w-4 h-4 text-slate-500' />
References
</h2>
<ul className='text-slate-900 font-light text-sm flex flex-col gap-y-2'>
{agentState?.answer?.references?.map(
(ref: any, idx: number) => (
<li key={idx}>
<a
href={ref.url}
target='_blank'
rel='noopener noreferrer'
>
{idx + 1}. {ref.title}
</a>
</li>
)
)}
</ul>
</div>
)}
</div>
</div>
</motion.div>
);
}
上面的程式碼片段從代理的狀態檢索搜尋結果,並使用useCoAgent
掛鉤將它們串流傳輸到前端。搜尋結果以 Markdown 格式傳回並傳遞到AnswerMarkdown
元件,該元件在頁面上呈現內容。
最後,將下面的程式碼片段複製到AnswerMarkdown
元件中。這將使用React Markdown 庫將 Markdown 內容呈現為格式化文字。
import Markdown from "react-markdown";
export function AnswerMarkdown({ markdown }: { markdown: string }) {
return (
<div className='markdown-wrapper'>
<Markdown>{markdown}</Markdown>
</div>
);
}
恭喜!您已完成本教學的專案。您也可以在這裡觀看影片錄製:
當 LLM 智慧與人類智慧一起工作時,它是最有效的,而CopilotKit CoAgents允許您在短短幾分鐘內將 AI 代理、副駕駛和各種類型的助手整合到您的軟體應用程式中。
如果您需要建立 AI 產品或將 AI 代理整合到您的應用程式中,您應該考慮 CopilotKit。
本教學的源程式碼可在 GitHub 上取得:
https://github.com/CopilotKit/CopilotKit/tree/main/examples/coagents-ai-researcher
感謝您的閱讀!
原文出處:https://dev.to/copilotkit/build-a-clone-of-perplexity-with-langgraph-copilotkit-tavily-nextjs-23j2