總結
在本文中,您將學習如何使用 Langraph、CopilotKit 和 Tavily 建立結合人機互動功能的代理原生研究畫布應用程式。
在開始之前,我們將介紹以下內容:
什麼是 AI 代理?
使用 LangGraph Studio 建置和視覺化 LangGraph AI 代理
使用 CopilotKit 為 LangGraph AI 代理程式建立 UI
這是我們將要建立的應用程式的預覽。
簡單來說,AI 代理是自主的軟體程序,可以使用人工智慧執行任務、做出決策並與環境互動。
在這種情況下,它們是可以在執行過程中進行研究、處理資訊和與人類互動以確保可靠性和可信度的系統。
您可以在 CopilotKit 文件中閱讀有關 AI 代理的更多資訊。
CopilotKit是一個用於建立使用者互動代理和副駕駛的開源全端框架。它使您的代理能夠控制您的應用程式、傳達其正在做的事情並產生完全自訂的 UI。
https://go.copilotkit.ai/copilotkit 查看 CopilotKit 的 GitHub ⭐️
為了完全理解本教程,您需要對 React 或 Next.js 有基本的了解。
我們還將利用以下內容:
Python-一種使用 LangGraph 建構 AI 代理的流行程式語言;確保它已安裝在您的電腦上。
LangGraph-用於建立和部署 AI 代理程式的框架。它還有助於定義代理要執行的控制流程和操作。
OpenAI API 金鑰-使我們能夠使用 GPT 模型執行各種任務;對於本教程,請確保您可以存取 GPT-4 模型。
Tavily AI——一種搜尋引擎,使AI代理能夠在應用程式內進行研究並存取即時知識。
CopilotKit - 一個開源副駕駛框架,用於建立自訂 AI 聊天機器人、應用程式內 AI 代理程式和文字區域。
Docker—一個用於在容器中開發、運送和執行應用程式的平台。
在本節中,您將學習如何使用 Docker 建置和啟動 LangGraph 代理,並使用 LangGraph Studio 視覺化其工作流程。
讓我們開始吧。
首先,從複製代理原生研究畫布應用程式儲存庫開始,其中包含基於 Python 的 Langraph 代理程式的程式碼:
git clone https://github.com/CopilotKit/open-research-ANA.git
此儲存庫包含兩個資料夾:代理程式和前端。若要啟動代理,請導航至代理目錄。
cd agent
然後使用pip安裝所有代理依賴項。
pip install -r requirements.txt
接下來,在代理目錄中建立一個.env
檔。然後將OpenAI 、 Tavily和LangSmith API 金鑰新增到環境變數中。
OPENAI_API_KEY=your_key
TAVILY_API_KEY=your_key
LANGSMITH_API_KEY=your_key
如果你開啟agent/graph.py
文件,它定義了一個執行研究工作流程的 MasterAgent 類別。
它使用有向圖(StateGraph)來管理 LangGraph AI 代理節點、工具執行和人工回饋之間的狀態和轉換。
此工作流程旨在透過收集資料、提出大綱和編寫章節來協助產生研究報告 - 同時允許透過使用 CopilotKit 的前端整合進行人工回饋。
要啟動 Langraph AI 代理,請開啟 Docker 應用程式並執行以下命令來啟動代理。
langgraph up
一旦 LangGraph API 伺服器啟動,使用提供的 LangGraph Studio 連結導航到 LangGraph Studio。請記下輸出中的 API URL(例如, http://localhost:8123 )。我們將使用它透過 CopilotKit Cloud 將代理連接到前端。
此後,LangGraph 代理程式將在 LangGraph 工作室上打開,您可以對其進行視覺化,如下所示。
若要測試 LangGraph 代理,請在messages
狀態變數中新增一則訊息,然後按一下「提交」按鈕。
然後,代理將按照定義的工作流程透過連接的節點處理輸入,並在線程中回應您的訊息,如下所示。
在我們繼續之前,讓我們先討論一下代理副駕駛中的一個關鍵概念,稱為「人機在環」 (HITL)。 HITL 允許代理商在執行過程中請求人工輸入或批准,以使 AI 系統可靠且值得信賴。
您可以在 CopilotKit Docs 上閱讀更多關於 Human-in-the-Loop 的資訊。
在這種情況下,您可以透過點擊其中一個節點並標記Interrupt After
複選框將 HITL 新增至代理,如下所示。
然後將另一則訊息(例如「關於人工智慧模型的研究」)新增至messages
狀態變數中,然後按一下「提交」按鈕。代理將開始研究 AI 模型,一旦完成,它會要求您查看各個部分並提供您的反饋或您想要做的任何具體更改,如下所示。
向messages
狀態變數新增「是」訊息,然後按一下提交按鈕。代理商將處理該訊息並為您提供有關 AI 模型報告的概要提案。然後它會詢問您是否要批准大綱或是否要進行任何更改,如下所示。
回覆“我想批准該大綱”訊息並點擊提交按鈕。然後,代理將編制一份按不同部分分組的人工智慧模型報告並完成研究過程,如下所示。
現在我們已經了解如何使用 LangGraph Studio 視覺化和測試 LangGraph AI 代理,讓我們看看如何新增前端 UI 來與其互動。
在本節中,您將了解如何使用 CopilotKit 雲端將您的 LangGraph AI 代理程式連接到 CopilotKit 前端 UI。
讓我們開始吧。
若要建立至 LangGraph AI 代理程式的隧道,請使用下列命令,以便 Copilot Cloud 可以連接到它。記住我告訴你的啟動代理時要注意的 API URL;使用提供的連接埠號碼。就我而言,連接埠號碼是 8123。
npx copilotkit@latest dev --port 8123
選擇一個專案,隧道應該處於活動狀態並連接到 Copilot Cloud,如下所示。
然後導航到前端資料夾。
cd frontend
之後,使用 pnpm 安裝前端相依性。
pnpm install
接下來,在前端目錄中建立一個.env
檔。然後將OpenAI 、 Copilot Cloud和LangSmith API 金鑰加入到環境變數中。
OPENAI_API_KEY=your_openai_key
LANGSMITH_API_KEY=your_langsmith_key
NEXT_PUBLIC_COPILOT_CLOUD_API_KEY=your_copilot_cloud_key
然後使用以下命令啟動應用程式。
pnpm run dev
導航到http://localhost:3000/ ,你應該會看到 LangGraph AI 代理前端已啟動並正在執行
現在讓我們看看如何使用 CopilotKit 為 LangGraph AI 代理程式建立 UI。
要設定 CopilotKit 提供程序, <CopilotKit>
元件必須包裝應用程式中支援 Copilot 的部分。對於大多數用例來說,將 CopilotKit 提供者包裝在整個應用程式中是合適的,例如在layout.tsx
中,如下面的frontend/src/app/layout.tsx
檔案中所示
import { CopilotKit } from "@copilotkit/react-core";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className="h-full">
{/* CopilotKit component for integrating an AI agent */}
<body className={`${lato.variable} ${noto.className} antialiased h-full`}>
<CopilotKit
{/* Pass the Copilot Cloud API key from environment variables */}
publicApiKey={process.env.NEXT_PUBLIC_COPILOT_CLOUD_API_KEY}
{/* Disable the development console (set to true for debugging) */}
showDevConsole={false}
{/* Specify the LangGraph agent name */}
agent="agent"
>
<TooltipProvider>
<ResearchProvider>
{children}
</ResearchProvider>
</TooltipProvider>
</CopilotKit>
</body>
</html>
);
}
要設定您的 Copilot UI,請先在根元件中匯入預設樣式(通常是layout.tsx
)。
import "@copilotkit/react-ui/styles.css";
Copilot UI 附帶許多內建的 UI 模式;從CopilotPopup 、 CopilotSidebar 、 CopilotChat到Headless UI中選擇您喜歡的哪一個。
在本例中,我們將使用src/components/chat.tsx
檔案中定義的 CopilotChat。
// Indicate that this component runs on the client side (Next.js directive)
'use client'
// Import the CopilotChat component from the CopilotKit React UI library
import { CopilotChat } from "@copilotkit/react-ui";
// Import constant values for chat configuration from a local library file
import { INITIAL_MESSAGE, MAIN_CHAT_INSTRUCTIONS, MAIN_CHAT_TITLE } from "@/lib/consts";
// @ts-expect-error -- ignore: Suppresses TypeScript error for missing/incorrect type definition
import { CopilotChatProps } from "@copilotkit/react-ui/dist/components/chat/Chat";
// Define the Chat component, accepting props typed as CopilotChatProps
export default function Chat(props: CopilotChatProps) {
return (
// Render the CopilotChat component with custom configuration
<CopilotChat
instructions={MAIN_CHAT_INSTRUCTIONS} // Pass predefined instructions for the chat behavior
labels={{
title: MAIN_CHAT_TITLE, // Set the chat window title from constants
initial: INITIAL_MESSAGE, // Set the initial message displayed in the chat
}}
className="h-full w-full font-noto" // Apply custom CSS classes for full height/width and Noto font
{...props} // Spread additional props passed to the component (e.g., overrides or customizations)
/>
);
}
然後在src/app/page.tsx
檔案中匯入並使用聊天元件。然後聊天內容將顯示在前端 UI 上,如下所示。
CoAgents 維持共享狀態,無縫連接您的 UI 與代理程式的執行。此共享狀態系統允許您:
顯示代理程式的當前進度和中間結果
透過 UI 互動更新代理程式的狀態
對整個應用程式的狀態變化做出即時反應
您可以在CopilotKit 文件中了解有關 CoAgents 共享狀態的更多資訊。
要在 UI 和 LangGraph AI 代理之間建立共用狀態,首先,您需要定義代理程式狀態並將其傳送到前端,如agent/graph.py
檔案所示。
# Async method to handle tool execution and update research state
async def tool_node(self, state: ResearchState, config: RunnableConfig):
# Customize config to disable message emission to frontend during tool execution
config = copilotkit_customize_config(config, emit_messages=False)
msgs = [] # List to store tool messages
tool_state = {} # Dictionary to store updated state after tool execution
# Process each tool call from the last message (assumed to be an AIMessage)
for tool_call in state["messages"][-1].tool_calls:
tool = self.tools_by_name[tool_call["name"]] # Lookup tool by name
# Simplify messages structure temporarily for tool access
state['messages'] = {'HumanMessage' if type(message) == HumanMessage else 'AIMessage': message.content for message in state['messages']}
tool_call["args"]["state"] = state # Inject state into tool arguments
# Run the tool asynchronously and get updated state and message
new_state, tool_msg = await tool.ainvoke(tool_call["args"])
tool_call["args"]["state"] = None # Clear state from args after execution
# Append tool result as a ToolMessage
msgs.append(ToolMessage(content=tool_msg, name=tool_call["name"], tool_call_id=tool_call["id"]))
# Build updated tool state with research data
tool_state = {
"title": new_state.get("title", ""),
"outline": new_state.get("outline", {}),
"sections": new_state.get("sections", []),
"sources": new_state.get("sources", {}),
"proposal": new_state.get("proposal", {}),
"logs": new_state.get("logs", []),
"tool": new_state.get("tool", {}),
"messages": msgs
}
# Emit updated state to frontend
await copilotkit_emit_state(config, tool_state)
return tool_state # Return the updated state
然後使用CopilotKit useCoAgent 鉤子在src/components/research-context.tsx
檔案中與前端 UI 共用 LangGraph AI 代理狀態。 useCoAgent 鉤子可讓您在應用程式和代理程式之間雙向共用狀態。
// Indicate that this component runs on the client side (Next.js directive)
'use client'
// Import React utilities for context, state, and effects
import { createContext, useContext, useState, ReactNode, useEffect } from 'react';
// Import the ResearchState type from a shared library
import type { ResearchState } from '@/lib/types';
// Import CopilotKit's hook for managing agent state
import { useCoAgent } from "@copilotkit/react-core";
// Import a custom hook for interacting with local storage
import useLocalStorage from "@/lib/hooks/useLocalStorage";
// Define the shape of the context value
interface ResearchContextType {
state: ResearchState; // The current research state
setResearchState: (newState: ResearchState | ((prevState: ResearchState) => ResearchState)) => void; // Function to update research state
sourcesModalOpen: boolean; // Boolean to toggle the sources modal
setSourcesModalOpen: (open: boolean) => void; // Function to set the modal state
runAgent: () => void; // Function to trigger the agent execution
}
// Create a context for sharing research state, initially undefined
const ResearchContext = createContext<ResearchContextType | undefined>(undefined);
// Define the ResearchProvider component to wrap children with context
export function ResearchProvider({ children }: { children: ReactNode }) {
// State for controlling the visibility of a sources modal
const [sourcesModalOpen, setSourcesModalOpen] = useState<boolean>(false);
// Use CopilotKit's useCoAgent hook to manage agent state and execution
const { state: coAgentState, setState: setCoAgentsState, run } = useCoAgent<ResearchState>({
name: 'agent', // Name of the agent (matches backend configuration)
initialState: {}, // Initial empty state for the agent
});
// Use custom hook to manage research state in local storage, initially null
// @ts-expect-error -- force null: Suppresses TypeScript error for null initial value
const [localStorageState, setLocalStorageState] = useLocalStorage<ResearchState>('research', null);
// Effect to synchronize agent state and local storage
useEffect(() => {
// Check if agent state or local storage is empty
const coAgentsStateEmpty = Object.keys(coAgentState).length < 1;
const localStorageStateEmpty = localStorageState == null || Object.keys(localStorageState).length < 1;
// If local storage has data but agent state is empty, initialize agent state
if (!localStorageStateEmpty && coAgentsStateEmpty) {
setCoAgentsState(localStorageState);
return;
}
// If agent state has data but local storage is empty, save to local storage
if (!coAgentsStateEmpty && localStorageStateEmpty) {
setLocalStorageState(coAgentState);
return;
}
// If both exist but differ, update local storage with agent state
if (!localStorageStateEmpty && !coAgentsStateEmpty && JSON.stringify(localStorageState) !== JSON.stringify(coAgentState)) {
setLocalStorageState(coAgentState);
return;
}
}, [coAgentState, localStorageState, setCoAgentsState, setLocalStorageState]); // Dependencies for the effect
// Provide context value to children
return (
<ResearchContext.Provider value={{
state: coAgentState, // Current research state from CopilotKit
setResearchState: setCoAgentsState as ResearchContextType['setResearchState'], // Setter for research state (cast for type compatibility)
setSourcesModalOpen, // Function to toggle sources modal
sourcesModalOpen, // Current modal state
runAgent: run // Function to run the agent
}}>
{children} // Render child components within the provider
</ResearchContext.Provider>
);
}
// Custom hook to access the research context
export function useResearch() {
// Get the context value
const context = useContext(ResearchContext);
// Throw an error if used outside of ResearchProvider
if (context === undefined) {
throw new Error('useResearch must be used within a ResearchProvider');
}
return context; // Return the context value
}
接下來,在聊天 UI 中呈現代理程式的狀態。這對於以更符合上下文的方式告知用戶代理的狀態很有用。為此,您可以使用src/app/page.tsx
檔案中的useCoAgentStateRender掛鉤。
// ...
import {useCoAgentStateRender} from "@copilotkit/react-core";
// ...
export default function HomePage() {
// ...
useCoAgentStateRender<ResearchState>(
{
name: "agent",
render: ({ state }) => {
if (state.logs?.length > 0) {
return <Progress logs={state.logs} />;
}
return null;
},
},
[researchState]
);
// ...
}
然後導航至http://localhost:3000/ ,將「研究 AI 模型」加入聊天中,然後按下「Enter」。您應該會看到聊天 UI 中呈現的 LangGraph AI 代理程式狀態,如下所示。
為了允許 LangGraph 代理在聊天 UI 中執行期間請求人工輸入或批准,請在src/app/page.tsx
檔案中使用名為review_proposal
的 CopiloKit useCopilotKitAction鉤子。
// ...
import { useCopilotAction } from "@copilotkit/react-core";
// ...
export default function HomePage() {
// ...
// Define a custom action using the useCopilotAction hook from CopilotKit
useCopilotAction({
name: "review_proposal", // Unique name for the action
description:
"Prompt the user to review structure proposal. Right after proposal generation", // Description of the action's purpose
available: "remote", // Indicates the action is available remotely (e.g., triggered by a backend or agent)
parameters: [], // No parameters required for this action
// @ts-expect-error -- null element is legit: Suppresses TypeScript error for returning null
renderAndWaitForResponse: (
{ respond, status } // Function to render UI and wait for user response
) =>
status !== "complete" ? ( // Check if the action is still in progress
<ProposalViewer // Render a custom ProposalViewer component
onSubmit={(
approved,
proposal // Callback when the user submits their review
) =>
respond?.({
// Send the response back to CopilotKit
...proposal, // Spread the proposal object (assumed to contain structure details)
approved, // Add the approval status (true/false)
})
}
/>
) : null, // Return null when the action is complete (hides the UI)
});
// ...
}
然後導航到http://localhost:3000/ 。一旦 LangGraph 代理完成對 AI 模型的研究,它會要求您批准該提案,如下所示。
選擇您想要的部分,新增一些備註,然後按一下Approve Proposal
按鈕。 LangGraph AI代理將開始撰寫AI模型的研究報告。
若要串流研究報告內容,請使用src/app/page.tsx
檔案中src/lib/hooks/useStreamingContent.ts
檔案中定義的 useStreamingContent 鉤子。
import { useStreamingContent } from "@/lib/hooks/useStreamingContent";
export default function HomePage() {
// ...
const streamingSection = useStreamingContent(researchState);
// ...
return (
// ...
{/* Document Viewer */}
<DocumentsView
sections={sections ?? []}
streamingSection={streamingSection}
selectedSection={sections?.find((s) => s.id === selectedSectionId)}
onSelectSection={setSelectedSectionId}
/>
// ...
)
}
您應該會在右側看到串流的研究內容,如下所示。
我們在本教程中涵蓋了很多內容。我希望您學會如何使用 CopilotKit 為您的應用程式建立代理副駕駛的 UI,也學會如何即時執行狀態變更並實現人機互動概念。
點擊此處查看 GitHub 上的完整原始碼
在Twitter上關注 CopilotKit 並打招呼,如果您想建立一些很酷的東西,請加入Discord社群。