Ứng dụng Agent AI đang dần trở thành một phần không thể thiếu trong thế giới công nghệ, mở ra vô số khả năng ứng dụng thực tế. Tuy nhiên, nhiều nhà phát triển vẫn cảm thấy phức tạp khi bắt tay vào xây dựng chúng. Bài viết này sẽ hướng dẫn bạn cách xây dựng hai agent AI thực tế với kiến trúc fullstack hiện đại, sử dụng những công cụ mạnh mẽ như Google Gemini, CopilotKit và LangGraph.
Chúng ta sẽ cùng xây dựng:
- Post Generator: Một agent có khả năng soạn thảo nội dung chất lượng cao cho LinkedIn và X (trước đây là Twitter), dựa trên kết quả tìm kiếm web trực tiếp.
- Stack Analyzer: Một agent có thể kiểm tra các kho lưu trữ GitHub công khai và tạo ra các báo cáo cấu trúc chi tiết về công nghệ được sử dụng.
Bạn sẽ khám phá kiến trúc tổng thể, các khái niệm cốt lõi, cách viết prompts hiệu quả và những mẹo thực tế trong quá trình triển khai. Hãy cùng bắt đầu xây dựng!
Để tham khảo mã nguồn đầy đủ, bạn có thể xem qua CopilotKit GitHub ⭐️.
Mục lục
1. Agent AI Fullstack Là Gì và Tại Sao Cần Đến Nó?
Agent AI là những hệ thống tự động có khả năng nhận thức, lập luận, hành động và học hỏi để đạt được mục tiêu cụ thể. Trong bối cảnh hiện đại, chúng thường được hỗ trợ bởi các mô hình ngôn ngữ lớn (LLM) và có thể tương tác với thế giới bên ngoài thông qua các công cụ.
Việc xây dựng một agent AI “fullstack” đòi hỏi sự kết hợp hài hòa giữa giao diện người dùng (frontend), dịch vụ backend xử lý logic AI và các mô hình ngôn ngữ. Mặc dù tiềm năng của agent AI là rất lớn, nhưng quá trình phát triển thường phức tạp do cần tích hợp nhiều công nghệ khác nhau, quản lý trạng thái, và xử lý luồng công việc phức tạp.
Giải pháp mà chúng ta sẽ khám phá trong bài viết này mang lại một cách tiếp cận hợp lý để giải quyết những thách thức này, cho phép các nhà phát triển tạo ra các ứng dụng agent AI mạnh mẽ và có khả năng mở rộng.
2. Giới Thiệu Các Agent Thực Tế Sẽ Xây Dựng
Để minh họa, chúng ta sẽ xây dựng hai agent cụ thể, mỗi agent giải quyết một vấn đề khác nhau, nhưng đều tận dụng kiến trúc fullstack đã đề ra:
Agent Tạo Bài Viết (Post Generator Agent)
Agent này được thiết kế để tạo ra các bài đăng chất lượng cao cho các nền tảng mạng xã hội như LinkedIn và X. Điểm đặc biệt là nó có khả năng sử dụng kết quả tìm kiếm Google trực tiếp để đảm bảo nội dung luôn cập nhật và chính xác.
Dưới đây là chuỗi cuộc gọi được đơn giản hóa khi người dùng yêu cầu tạo bài viết:
[Người dùng nhập yêu cầu]
↓
Giao diện người dùng Next.js (CopilotChat)
↓ (POST /api/copilotkit → GraphQL)
Next.js API route (copilotkit)
↓ (chuyển tiếp)
FastAPI backend (/copilotkit)
↓ (LangGraph workflow)
Các node của Post Generator graph
↓ (gọi → Google Gemini + tìm kiếm web)
Phản hồi streaming & log công cụ
↓
Giao diện người dùng hiển thị cuộc trò chuyện + log công cụ + các thẻ bài viết cuối cùng
Agent Phân Tích Stack (Stack Analyzer Agent)
Agent thứ hai sẽ chuyên về phân tích các kho lưu trữ GitHub công khai. Nó sẽ kiểm tra metadata, file README, các manifest code và từ đó suy ra stack công nghệ (frontend, backend, cơ sở dữ liệu, hạ tầng) của dự án, sau đó tạo ra một báo cáo có cấu trúc rõ ràng.
Dưới đây là chuỗi cuộc gọi được đơn giản hóa khi người dùng yêu cầu phân tích stack công nghệ:
[Người dùng dán URL GitHub]
↓
Giao diện người dùng Next.js (/stack‑analyzer)
↓
/api/copilotkit → FastAPI
↓
Các node của Stack Analysis graph (thu thập ngữ cảnh → phân tích → kết thúc)
↓
Log công cụ streaming & các thẻ phân tích có cấu trúc
Đây chính là những gì chúng ta sẽ xây dựng và khám phá chi tiết!
3. Kiến Trúc và Bộ Công Nghệ (Tech Stack & Architecture)
Để xây dựng hai agent này, chúng ta sẽ sử dụng bộ công nghệ (tech stack) sau:
- Next.js 15: Framework frontend mạnh mẽ với TypeScript, tạo giao diện người dùng tương tác và định tuyến API hiệu quả.
- CopilotKit SDK: Bộ công cụ thiết yếu để nhúng các agent vào giao diện người dùng. Bao gồm các gói
@copilotkit/react-core,@copilotkit/runtime, và@copilotkit/react-ui. - FastAPI & Uvicorn: Framework backend Python hiệu suất cao cho việc xây dựng API, nơi các agent của chúng ta sẽ được lưu trữ và phục vụ.
- LangGraph (StateGraphs): Một thư viện từ LangChain để xây dựng các luồng công việc (workflow) của agent có trạng thái, cho phép định nghĩa các chuỗi hành động phức tạp.
- Google Gemini qua
google-genai(SDK chính thức): Mô hình ngôn ngữ lớn (LLM) của Google, được sử dụng cho khả năng lập luận và tạo văn bản của các agent. - LangChain’s Google adapter: Cầu nối để tích hợp Gemini vào các luồng công việc của LangChain/LangGraph một cách liền mạch.
- Pydantic: Thư viện Python để xác thực dữ liệu và quản lý cài đặt, đảm bảo đầu ra JSON của các công cụ có cấu trúc chặt chẽ.
Kiến Trúc Tổng Quan
Dự án của chúng ta sẽ tuân theo kiến trúc microservice, nơi frontend (Next.js) giao tiếp với backend (FastAPI) thông qua một API proxy. Backend sẽ là nơi LangGraph chạy các luồng công việc của agent, sử dụng Gemini và các công cụ khác như Google Search.
* Frontend (Next.js): Đảm nhiệm hiển thị giao diện người dùng, thu thập yêu cầu từ người dùng và gửi chúng đến backend.
* Next.js API Route: Hoạt động như một proxy trung gian, chuyển tiếp yêu cầu từ trình duyệt đến dịch vụ backend FastAPI, giúp tránh các vấn đề CORS và quản lý xác thực.
* FastAPI Agent Service: Đây là trái tim của backend, nơi các agent LangGraph được định nghĩa và chạy. Nó nhận yêu cầu từ proxy và tương tác với LLM và các công cụ.
* LangGraph StateGraph: Định nghĩa luồng công việc có trạng thái cho từng agent, quyết định các bước hành động, gọi LLM và công cụ khi cần.
* Google Gemini + Tools: LLM cung cấp khả năng lập luận và tạo văn bản. Các công cụ như Google Search mở rộng khả năng của agent để tương tác với thế giới thực.
Cấu Trúc Dự Án
Cấu trúc thư mục của dự án sẽ được tổ chức rõ ràng như sau:
.
├── assets/
├── frontend/ ← Next.js 15 App (UI + API routes)
│ ├── app/
│ │ ├── layout.tsx ← Bọc ứng dụng với `<CopilotKit>`
│ │ ├── post-generator/ ← Các route UI cho Post Generator
│ │ ├── stack-analyzer/ ← Các route UI cho Stack Analyzer
│ │ └── api/ ← Các route API của Next.js được sử dụng bởi UI
│ │ ...
│ ├── contexts/LayoutContext.tsx
│ ├── wrapper.tsx ← Wrapper cho CopilotKit provider
│ ├── components/ ← Các component UI dùng chung
│ │ ...
├── agent/ ← FastAPI + LangGraph “agents” (Python)
│ ├── main.py ← Đăng ký các agent và expose chúng qua FastAPI
│ ├── posts_generator_agent.py ← Luồng công việc cho agent tạo nội dung
│ ├── stack_agent.py ← Luồng công việc cho agent phân tích repo
│ ├── prompts.py ← Các template prompt dùng chung
│ ├── agent.py ← Các lớp agent cốt lõi và hàm hỗ trợ
│ ...
└── README.md ← Tổng quan dự án và hướng dẫn thiết lập
Bạn có thể tham khảo mã nguồn đầy đủ trên GitHub repository và xem demo trực tiếp tại copilot-kit-deepmind.vercel.app.
Thêm Khóa API Cần Thiết
Để các agent hoạt động, bạn cần có khóa API của Google Gemini. Hãy tạo một file `.env` trong cả thư mục `agent` và `frontend`, sau đó thêm khóa API của bạn vào:
GOOGLE_API_KEY=<<your-gemini-key-here>>
Bạn có thể lấy khóa API Gemini từ AI Studio của Google.
4. Phát Triển Giao Diện Người Dùng (Frontend Development – Next.js)
Phần frontend của ứng dụng sẽ được xây dựng bằng Next.js 15, cung cấp giao diện tương tác cho người dùng để tương tác với các agent.
Cấu Trúc Frontend
Để dễ theo dõi, đây là cấu trúc chính của thư mục `frontend`:
frontend/
├── app/
│ ├── page.tsx ← Redirect trang chủ
│ ├── post‑generator/page.tsx← UI Post Generator
│ ├── stack‑analyzer/page.tsx← UI Stack Analyzer
│ ├── api/
│ │ ├── copilotkit/route.ts← Endpoint router của CopilotKit
│ │ └── chat/route.ts ← Demo nghiên cứu OpenAI
│ ├── contexts/LayoutContext.tsx
│ ├── wrapper.tsx ← Wrapper cho CopilotKit provider
│ └── prompts/prompts.ts ← Các template prompt UI
├── components/… ← Các component UI dùng chung (tool‑logs, cards, posts…)
└── layout.tsx, globals.css, etc.
Nếu bạn bắt đầu từ một dự án mới, hãy tạo một ứng dụng Next.js với TypeScript:
npx create-next-app@latest frontend
Sau đó, cài đặt các dependency cần thiết. Trong kho lưu trữ đã clone, bạn chỉ cần chạy `pnpm i` trong thư mục `frontend`.
Bước 1: Thiết Lập CopilotKit Provider và Layout
Đầu tiên, chúng ta cần cài đặt các gói CopilotKit cần thiết:
pnpm install copilotkit @copilotkit/react-core @copilotkit/react-ui @copilotkit/runtime @copilotkit/runtime-client-gql
Giải thích các gói:
- `copilotkit`: SDK cấp thấp hơn, cung cấp các tiện ích backend Python để kết nối các biểu đồ trạng thái, phát ra cập nhật trạng thái và tương tác với Gemini.
- `@copilotkit/react-core`: Cung cấp ngữ cảnh và logic cốt lõi để kết nối ứng dụng React của bạn với backend CopilotKit.
- `@copilotkit/react-ui`: Cung cấp các component UI sẵn có như `
` để nhanh chóng xây dựng giao diện trò chuyện hoặc trợ lý AI. - `@copilotkit/runtime`: Thư viện runtime phía máy chủ, cho phép bạn khai báo agent, kết nối chúng với các luồng công việc LangGraph và hiển thị chúng qua một endpoint API.
- `@copilotkit/runtime-client-gql`: Một client cho GraphQL transport, được sử dụng nội bộ bởi Next.js API route để proxy giữa trình duyệt và backend của bạn.
Component `
// frontend/app/layout.tsx
import "./globals.css"
import { LayoutProvider } from "./contexts/LayoutContext"
import Wrapper from "./wrapper"
export default function RootLayout({ children }) {
return (
<html lang="en">
<LayoutProvider>
<Wrapper>
<body>{children}</body>
</Wrapper>
</LayoutProvider>
</html>
)
}
`LayoutProvider` (`frontend\app\contexts\LayoutContext.tsx`) thiết lập một React context để quản lý trạng thái layout và chọn agent đang hoạt động dựa trên route hiện tại (`/post-generator` hoặc các route khác) bằng cách sử dụng `usePathname()`.
// frontend/app/contexts/LayoutContext.tsx
"use client"
import { usePathname } from "next/navigation"
import React, { createContext, useContext, useState } from "react"
interface LayoutState { agent: string; /* ... other layout states */ }
interface LayoutContextType {
layoutState: LayoutState
updateLayout: (updates: Partial<LayoutState>) => void
}
const LayoutContext = createContext<LayoutContextType | undefined>(undefined)
const defaultLayoutState = { agent: "post_generation_agent" }
export function LayoutProvider({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
const [layoutState, setLayoutState] = useState({
...defaultLayoutState,
agent: (pathname === "/post-generator"
? "post_generation_agent"
: "stack_analysis_agent"),
})
const updateLayout = (updates: Partial<LayoutState>) =>
setLayoutState((prev) => ({ ...prev, ...updates }))
return (
<LayoutContext.Provider value={{ layoutState, updateLayout }}>
{children}
</LayoutContext.Provider>
)
}
export function useLayout() {
const context = useContext(LayoutContext);
if (context === undefined) {
throw new Error('useLayout must be used within a LayoutProvider');
}
return context;
}
Wrapper cho CopilotKit client (`frontend\app\wrapper.tsx`) sẽ bọc mọi trang để các component UI biết agent nào cần gọi.
// frontend/app/wrapper.tsx
"use client"
import { CopilotKit } from "@copilotkit/react-core";
import { useLayout } from "./contexts/LayoutContext";
export default function Wrapper({ children }: { children: React.ReactNode }) {
const { layoutState } = useLayout()
return (
<CopilotKit runtimeUrl="/api/copilotkit" agent={layoutState.agent}>
{children}
</CopilotKit>
)
}
Bước 2: Next.js API Routes: Proxy đến FastAPI
Endpoint của CopilotKit Runtime tại Next.js API route `app/api/copilotkit/route.ts` sẽ đóng vai trò proxy, chuyển tiếp tất cả các cuộc gọi từ agent/graph đến backend FastAPI.
Việc sử dụng proxy có nhiều lợi ích:
- Tránh các vấn đề CORS (Cross-Origin Resource Sharing).
- Cho phép Next.js xử lý xác thực, định tuyến môi trường và đóng gói.
- Đảm bảo một định dạng GraphQL/REST thống nhất cho UI React.
// frontend/app/api/copilotkit/route.ts
import { CopilotRuntime, copilotRuntimeNextJSAppRouterEndpoint, GoogleGenerativeAIAdapter } from "@copilotkit/runtime";
import { NextRequest } from "next/server";
// Bạn có thể sử dụng bất kỳ service adapter nào ở đây để hỗ trợ multi-agent.
const serviceAdapter = new GoogleGenerativeAIAdapter();
const runtime = new CopilotRuntime({
remoteEndpoints: [{ url: process.env.NEXT_PUBLIC_LANGGRAPH_URL || "http://localhost:8000/copilotkit" }],
});
export const POST = async (req: NextRequest) => {
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime,
serviceAdapter,
endpoint: "/api/copilotkit",
});
return handleRequest(req);
};
Bước 3: Tự Động Chuyển Hướng đến Post Generator
Để đơn giản hóa trải nghiệm người dùng, chúng ta sẽ thiết lập để mọi yêu cầu đến route gốc (`/`) sẽ tự động chuyển hướng đến route `/post-generator` tại `frontend\app\page.tsx`.
// frontend/app/page.tsx
"use client"
import "@copilotkit/react-ui/styles.css";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useLayout } from "./contexts/LayoutContext";
export default function GoogleDeepMindChatUI() {
const router = useRouter();
const { updateLayout } = useLayout();
useEffect(() => {
updateLayout({ agent: "post_generation_agent" });
router.push("/post-generator");
}, [router]);
return (
<></>
)
}
Bước 4: UI cho Agent Tạo Bài Viết (Post Generator Agent UI)
Chúng ta sẽ xây dựng giao diện cho Post Generator tại `frontend/app/post-generator/page.tsx`, sử dụng `
// frontend/app/post-generator/page.tsx (đoạn mã rút gọn)
import { CopilotChat, useCopilotChatSuggestions } from "@copilotkit/react-ui"
import { initialPrompt, suggestionPrompt } from "../prompts/prompts"
import { useCopilotAction, useCoAgentStateRender } from "@copilotkit/react-core"
import { XPostCompact, LinkedInPostCompact } from "@/components/ui/posts"
import { ToolLogs } from "@/components/ui/tool-logs"
// Thiết lập gợi ý cho CopilotChat
useCopilotChatSuggestions({
available: "enabled",
instructions: suggestionPrompt,
})
// Định nghĩa action tùy chỉnh để render bài viết
useCopilotAction({
name: "generate_post",
description: "Render a LinkedIn and X post",
parameters: {
tweet: { type: "object", properties: { title: { type: "string" }, content: { type: "string" } }, required: ["title", "content"] },
linkedIn: { type: "object", properties: { title: { type: "string" }, content: { type: "string" } }, required: ["title", "content"] }
},
render: ({ args }) => (
<>
{args.tweet?.content && (
<XPostCompact title={args.tweet.title} content={args.tweet.content} />
)}
{args.linkedIn?.content && (
<LinkedInPostCompact title={args.linkedIn.title} content={args.linkedIn.content} />
)}
</>
)
})
// Hiển thị log công cụ trực tiếp
useCoAgentStateRender({
name: "post_generation_agent",
render: (state) => (
<ToolLogs logs={state?.state?.tool_logs || []} />
)
})
export default function PostGeneratorPage() {
return (
<div className="flex flex-col h-full">
{/* Sidebar & header (đã lược bỏ) */}
<div className="flex-1 overflow-auto">
{/* Chat canvas */}
<CopilotChat
className="h-full p-2"
labels={{ initial: initialPrompt }}
/>
{/* Các preview bài viết (được render sau khi tạo) */}
<div className="flex gap-6 mt-6">
{/* Các component XPostCompact và LinkedInPostCompact sẽ được render bởi useCopilotAction */}
</div>
</div>
</div>
)
}
Các prompts hệ thống và gợi ý được lấy từ `app/prompts/prompts.ts`:
// frontend/app/prompts/prompts.ts
export const initialPrompt = "Xin chào! Tôi là một AI agent được vận hành bởi Langgraph và Gemini, có khả năng tìm kiếm web và tạo bài đăng trên LinkedIn và X (Twitter).\n\n Nhấp vào các gợi ý để bắt đầu."
export const suggestionPrompt = "Tạo các gợi ý xoay quanh việc tạo/sản xuất bài đăng trên LinkedIn và X (Twitter) về bất kỳ chủ đề cụ thể nào."
Bước 5: UI cho Agent Phân Tích Stack (Stack Analyzer Agent UI)
Trang phân tích stack (`frontend/app/stack-analyzer/page.tsx`) sẽ kết nối với `stack_analysis_agent` và hiển thị một tập hợp các thẻ phân tích. Giao diện này tương tự như Post Generator, bao gồm `CopilotChat` và việc sử dụng `useCoAgentStateRender` để hiển thị log công cụ.
// frontend/app/stack-analyzer/page.tsx (đoạn mã rút gọn)
"use client"
import { CopilotChat, useCopilotChatSuggestions } from "@copilotkit/react-ui"
import { initialPrompt1, suggestionPrompt1 } from "../prompts/prompts"
import { StackAnalysisCards } from "@/components/ui/stack-analysis-cards"
import { ToolLogs } from "@/components/ui/tool-logs"
import { useCoAgentStateRender, useCopilotAction } from "@copilotkit/react-core"
import { useLayout } from "../contexts/LayoutContext"
import { useEffect, useState } from "react"
export default function StackAnalyzerPage() {
const { layoutState, updateLayout } = useLayout();
const [analysisState, setAnalysisState] = useState<{ show_cards: boolean; analysis: any }>({
show_cards: false,
analysis: null,
});
useEffect(() => {
updateLayout({ agent: "stack_analysis_agent" });
}, [updateLayout]);
useCoAgentStateRender({
name: "stack_analysis_agent",
render: (state) => {
// Cập nhật trạng thái hiển thị thẻ phân tích từ backend
if (state?.state?.show_cards !== undefined) {
setAnalysisState({
show_cards: state.state.show_cards,
analysis: state.state.analysis,
});
}
return <ToolLogs logs={state?.state?.tool_logs || []} />;
},
});
useCopilotChatSuggestions({
available: "enabled",
instructions: suggestionPrompt1,
})
return (
<div className="flex flex-col h-full">
{/* Sidebar (đã lược bỏ) */}
<div className="flex-1 overflow-auto">
<CopilotChat
className="h-full p-2"
labels={{ initial: initialPrompt1 }}
/>
{analysisState.show_cards && analysisState.analysis && (
<StackAnalysisCards analysis={JSON.parse(analysisState.analysis)} />
)}
</div>
</div>
)
}
Các prompts hệ thống và gợi ý từ `app/prompts/prompts.ts`:
// frontend/app/prompts/prompts.ts
export const initialPrompt1 = 'Xin chào! Tôi là một AI agent được vận hành bởi Langgraph và Gemini, có khả năng phân tích các Kho lưu trữ GitHub công khai.\n\n Nhấp vào các gợi ý để bắt đầu.'
export const suggestionPrompt1 = `Tạo các gợi ý xoay quanh việc phân tích các Kho lưu trữ GitHub công khai. Chỉ cung cấp gợi ý từ các URL công khai sau:
[
"https://github.com/freeCodeCamp/freeCodeCamp",
"https://github.com/EbookFoundation/free-programming-books",
"https://github.com/jwasham/coding-interview-university",
"https://github.com/kamranahmedse/developer-roadmap",
"https://github.com/public-apis/public-apis",
"https://github.com/donnemartin/system-design-primer",
"https://github.com/facebook/react",
"https://github.com/tensorflow/tensorflow",
"https://github.com/trekhleb/javascript-algorithms",
"https://github.com/twbs/bootstrap",
"https://github.com/vinta/awesome-python",
"https://github.com/ohmyzsh/ohmyzsh",
"https://github.com/tldr-pages/tldr",
"https://github.com/ytdl-org/youtube-dl",
"https://github.com/taigaio/taiga-back"
]`
5. Xây Dựng Dịch Vụ Agent Backend (FastAPI + CopilotKit SDK)
Phần backend của ứng dụng được đặt trong thư mục `/agent` và là một máy chủ FastAPI, có nhiệm vụ lưu trữ và expose hai agent dựa trên LangGraph.
Chuẩn Bị Môi Trường Backend
Backend sử dụng Poetry để quản lý các dependency, thay vì `requirements.txt` truyền thống.
1. Cài đặt Poetry:
pip install poetry
2. Khởi tạo dự án Poetry: Di chuyển vào thư mục `agent` và khởi tạo dự án:
cd agent
poetry init # Hoặc `poetry init --no-interaction` để bỏ qua các câu hỏi
Thao tác này sẽ tạo ra file `pyproject.toml` và `poetry.lock` mới.
3. Chọn phiên bản Python: Hầu hết các thư viện AI (LangChain, LangGraph, Google SDKs) hiện tại hỗ trợ tốt nhất đến Python 3.12. Đảm bảo Poetry sử dụng phiên bản tương thích:
poetry env use python3.12
4. Cài đặt các dependency:
poetry add fastapi uvicorn copilotkit langgraph langchain langchain-google-genai google-genai pydantic python-dotenv
Giải thích các gói:
- `fastapi`: Framework web để phục vụ các endpoint của agent (`/copilotkit`).
- `uvicorn`: Máy chủ ASGI được sử dụng để chạy FastAPI trong môi trường phát triển hoặc production.
- `copilotkit`: Python SDK của CopilotKit, tích hợp các luồng công việc LangGraph với khả năng streaming trạng thái của CopilotKit.
- `langgraph`: Framework máy trạng thái để định nghĩa các agent dưới dạng biểu đồ các node (chat, analyze, end).
- `langchain`: Cung cấp các trừu tượng cốt lõi (`RunnableConfig`, message types, v.v.) được sử dụng bên trong các node.
- `langchain-google-genai`: Wrapper của LangChain cho các mô hình Google Gemini (ví dụ: `ChatGoogleGenerativeAI`).
- `google-genai`: SDK client chính thức của Google cho Gemini, được sử dụng cho các cuộc gọi cấp thấp hơn (ví dụ: `genai.Client`).
- `pydantic`: Để xác thực schema (`StructuredStackAnalysis`) nhằm đảm bảo đầu ra JSON của các công cụ là nghiêm ngặt.
- `python-dotenv`: Tải file `.env` để quản lý các khóa API (như `GOOGLE_API_KEY`).
Sau đó, chạy lệnh sau để tạo file `poetry.lock` với các phiên bản chính xác:
poetry install
Thiết Lập Máy Chủ FastAPI và SDK
Tất cả các agent sẽ hoạt động phía sau một máy chủ FastAPI duy nhất (`agent/main.py`), được mount tại `/copilotkit`.
# agent/main.py
import os
import uvicorn
from fastapi import FastAPI
from dotenv import load_dotenv
from copilotkit.integrations.fastapi import add_fastapi_endpoint
from copilotkit import CopilotKitSDK, LangGraphAgent
from posts_generator_agent import post_generation_graph
from stack_agent import stack_analysis_graph
# Tải các biến môi trường từ .env
load_dotenv()
app = FastAPI()
# Khởi tạo CopilotKit SDK và đăng ký các agent
sdk = CopilotKitSDK(
agents=[
LangGraphAgent(
name="post_generation_agent",
description="Một agent có thể giúp tạo bài đăng LinkedIn và X.",
graph=post_generation_graph,
),
LangGraphAgent(
name="stack_analysis_agent",
description="Phân tích URL kho lưu trữ GitHub để suy luận mục đích và stack công nghệ (frontend, backend, DB, hạ tầng).",
graph=stack_analysis_graph,
),
]
)
# Thêm endpoint FastAPI cho CopilotKit SDK
add_fastapi_endpoint(app, sdk, "/copilotkit")
# Một endpoint đơn giản để xác nhận máy chủ đang hoạt động
@app.get("/healthz")
def health():
return {"status": "ok"}
def main():
"""Chạy máy chủ uvicorn."""
port = int(os.getenv("PORT", "8000"))
uvicorn.run(
"main:app",
host="0.0.0.0",
port=port,
reload=True,
)
if __name__ == "__main__":
main()
Đoạn mã trên thực hiện các bước sau:
- Khởi động một máy chủ FastAPI.
- Đăng ký hai agent LangGraph (`post_generation_agent`, `stack_analysis_agent`) vào CopilotKit.
- Expose chúng tại `/copilotkit` để frontend có thể giao tiếp.
- Chạy bằng Uvicorn.
6. Quy Trình Agent (LangGraph StateGraphs)
Cả hai agent đều được thể hiện dưới dạng các máy trạng thái (state machines) của LangGraph, được kết nối với nhau bằng một vài node bất đồng bộ (async nodes).
Mỗi file agent (dù là `posts_generator_agent.py` hay `stack_agent.py`) đều tuân theo cùng một cấu trúc LangGraph cơ bản:
- Định nghĩa một `StateGraph`.
- Thêm các node (mỗi node là một hàm bất đồng bộ).
- Kết nối các cạnh (`START → … → END`).
- Compile với `MemorySaver()`.
Điểm khác biệt chính là chức năng thực tế của mỗi node.
Luồng Công Việc của Agent Tạo Bài Viết (Post Generator Graph)
Luồng công việc của “Post Generator” được định nghĩa trong `posts_generator_agent.py`. Nó kết nối ba node (`chat_node`, `fe_actions_node`, `end_node`) thành một StateGraph đã được biên dịch.
Luồng công việc cơ bản:
- `chat_node`: Gọi Google Gemini qua `genai.Client`, tùy chọn gọi công cụ tìm kiếm web, stream các log công cụ trung gian trở lại UI.
- `fe_actions_node`: Xử lý hậu kỳ kết quả trò chuyện để tạo ra các bài đăng LinkedIn/X cuối cùng.
- `end_node`: Kết thúc luồng công việc.
Ví dụ về `chat_node` và `fe_actions_node`:
# agent/posts_generator_agent.py (đoạn mã rút gọn)
# ... các import khác ...
from copilotkit import CopilotKitState
from copilotkit.langgraph import copilotkit_emit_state
from copilotkit.langchain import copilotkit_customize_config
from prompts import system_prompt, system_prompt_3
class AgentState(CopilotKitState):
tool_logs: List[Dict[str, Any]]
response: Dict[str, Any]
async def chat_node(state: AgentState, config: RunnableConfig):
# ... logic khởi tạo và log ...
state["tool_logs"].append({"id": str(uuid.uuid4()), "message": "Analyzing the user's query", "status": "processing"})
await copilotkit_emit_state(config, state)
# ... xử lý tin nhắn công cụ nếu có ...
grounding_tool = types.Tool(google_search=types.GoogleSearch())
model_config = types.GenerateContentConfig(tools=[grounding_tool])
config = copilotkit_customize_config(config, emit_messages=True, emit_tool_calls=True)
response = model.models.generate_content(
model="gemini-2.5-pro",
contents=[
types.Content(role="user", parts=[types.Part(text=system_prompt)]),
# ... các tin nhắn khác ...
],
config=model_config,
)
state["tool_logs"][-1]["status"] = "completed"
await copilotkit_emit_state(config, state)
state["response"] = response.text
# ... xử lý log tìm kiếm web ...
return Command(goto="fe_actions_node", update=state)
async def fe_actions_node(state: AgentState, config: RunnableConfig):
# ... logic kiểm tra tin nhắn công cụ ...
state["tool_logs"].append({"id": str(uuid.uuid4()), "message": "Generating post", "status": "processing"})
await copilotkit_emit_state(config, state)
model = ChatGoogleGenerativeAI(
model="gemini-2.5-pro",
temperature=1.0,
max_retries=2,
google_api_key=os.getenv("GOOGLE_API_KEY"),
)
response = await model.bind_tools([*state["copilotkit"]["actions"]]).ainvoke(
[system_prompt_3.replace("{context}", state["response"]), *state["messages"]],
config,
)
state["tool_logs"] = []
await copilotkit_emit_state(config, state)
return Command(goto="end_node", update={"messages": response})
# ... định nghĩa workflow và compile ...
Luồng Công Việc của Agent Phân Tích Stack (Stack Analysis Graph)
Tương tự, luồng công việc của “Stack Analyzer” được định nghĩa trong `stack_agent.py`. Nó cũng kết nối ba node (`gather_context_node`, `analyze_with_gemini_node`, `end_node`) thành một StateGraph đã được biên dịch.
Mỗi node có chức năng cụ thể:
- `gather_context_node`: Node này phân tích URL GitHub từ tin nhắn của người dùng, lấy metadata qua GitHub API (thông tin repo, ngôn ngữ, README, file gốc, manifest) và lưu trữ vào `state[“context”]` để phân tích tiếp theo.
# agent/stack_agent.py (đoạn mã rút gọn) async def gather_context_node(state: StackAgentState, config: RunnableConfig): last_user_content = state["messages"][-1].content if state["messages"] else "" parsed = _parse_github_url(last_user_content) if not parsed: # ... xử lý lỗi hoặc chuyển hướng ... owner, repo = parsed repo_info = _fetch_repo_info(owner, repo) languages = _fetch_languages(owner, repo) readme = _fetch_readme(owner, repo) root_items = _list_root(owner, repo) manifests = _fetch_manifest_contents(owner, repo, repo_info.get("default_branch"), root_items) context = {"owner": owner, "repo": repo, "repo_info": repo_info, "languages": languages, "readme": readme, "root_files": _summarize_root_files(root_items), "manifests": manifests} return Command(goto="analyze", update={"context": context, "tool_logs": state["tool_logs"]}) - `analyze_with_gemini_node`: Xây dựng một prompt đầu ra có cấu trúc từ ngữ cảnh repo và yêu cầu Gemini (`gemini-2.5-pro`) phân tích. Gemini được yêu cầu gọi công cụ `return_stack_analysis`, công cụ này sẽ đảm bảo một schema JSON nghiêm ngặt.
# agent/stack_agent.py (đoạn mã rút gọn) async def analyze_with_gemini_node(state: StackAgentState, config: RunnableConfig): prompt = _build_analysis_prompt(state["context"]) messages = [ SystemMessage(content="Bạn là một kiến trúc sư phần mềm cấp cao..."), HumanMessage(content=prompt), ] model = ChatGoogleGenerativeAI(model="gemini-2.5-pro", temperature=0.4, google_api_key=os.getenv("GOOGLE_API_KEY")) bound = model.bind_tools([return_stack_analysis_tool]) tool_msg = await bound.ainvoke(messages, config) # Trích xuất payload có cấu trúc (chi tiết stack) for call in getattr(tool_msg, "tool_calls", []): if call.get("name") == "return_stack_analysis": args = call.get("args", {}) state["analysis"] = json.dumps(args) state["show_cards"] = True break # Chỉ xử lý cuộc gọi công cụ đầu tiên phù hợp return Command(goto="end", update=state) - `end_node`: Node cuối cùng này xóa các log công cụ và phát ra phân tích hoàn chỉnh trở lại UI.
# agent/stack_agent.py (đoạn mã rút gọn) async def end_node(state: StackAgentState, config: RunnableConfig): state["tool_logs"] = [] await copilotkit_emit_state(config or RunnableConfig(recursion_limit=25), state) return Command(goto=END, update={ "messages": state["messages"], "show_cards": state["show_cards"], "analysis": state["analysis"] })
7. Prompts và Công Cụ (Prompts & Tooling)
Trước khi kết nối các biểu đồ và node, các agent phụ thuộc rất nhiều vào các `prompts và công cụ`. Prompts định nghĩa cách mô hình nên hoạt động (chẳng hạn như “luôn sử dụng tìm kiếm Google” hoặc “tạo bài đăng theo phong cách LinkedIn”), trong khi các công cụ cung cấp các cách có cấu trúc để thu thập đầu ra.
Prompts Hệ Thống cho Tạo Bài Viết
Tất cả các template “system & user prompt” cho Post Generator đều nằm trong `agent/prompts.py`. Các template này đóng vai trò là nhân cách của agent.
Giữ prompts trong một file riêng giúp dễ dàng điều chỉnh chúng độc lập với logic của luồng công việc.
# agent/prompts.py
system_prompt = """Bạn có quyền truy cập vào một công cụ google_search...
Bạn PHẢI LUÔN LUÔN sử dụng công cụ google_search cho MỌI truy vấn..."""
system_prompt_2 = """
Bạn là một nghệ sĩ tuyệt vời. Bạn cần tạo một hình ảnh...
"""
system_prompt_3 = """
Bạn là một trợ lý tuyệt vời. Bạn quen thuộc với các thuật toán của LinkedIn và X (Twitter)...
Luôn sử dụng công cụ generate_post để tạo bài đăng.
{context}
"""
Cách sử dụng:
- `system_prompt` được inject vào `chat_node`, buộc Gemini phải dựa trên các câu trả lời bằng công cụ `google_search`.
- `system_prompt_3` được sử dụng trong `fe_actions_node` để hướng dẫn Gemini cách tạo bài đăng LinkedIn/X.
Xây Dựng Prompt Phân Tích Stack
Trong Stack Analyzer, chúng ta sử dụng một hàm trợ giúp để inject ngữ cảnh kho lưu trữ GitHub vào một prompt “phân tích stack” duy nhất. Hàm này nằm trong `agent/stack_agent.py`.
Không giống như các prompts tĩnh, hàm trợ giúp này được gắn chặt với logic phân tích stack (schema, phân tích ngữ cảnh), vì vậy nó nằm trong cùng file agent.
# agent/stack_agent.py (đoạn mã rút gọn)
def _build_analysis_prompt(context: Dict[str, Any]) -> str:
return (
"Bạn là một kiến trúc sư phần mềm cấp cao. Phân tích kho lưu trữ GitHub sau ở cấp độ cao.\n"
"Mục tiêu: Cung cấp tổng quan ngắn gọn, có cấu trúc về những gì dự án thực hiện và stack công nghệ.\n\n"
f"Metadata kho lưu trữ:\n{json.dumps(context['repo_info'], indent=2)}\n\n"
f"Ngôn ngữ:\n{json.dumps(context['languages'], indent=2)}\n\n"
"Các mục gốc:\n" + json.dumps(context['root_files'], indent=2) + "\n\n"
"Nội dung README (đã cắt ngắn):\n" + context["readme"][:8000] + "\n\n"
"Suy luận stack với các framework và thư viện cụ thể khi có thể..."
)
Cách sử dụng:
- `_build_analysis_prompt` được truyền vào Gemini trong `analyze_with_gemini_node`, cung cấp một cái nhìn tổng hợp về metadata repo, ngôn ngữ, manifest và README.
Công Cụ Đầu Ra Có Cấu Trúc cho Phân Tích Stack
Trong `stack_agent.py`, chúng ta khai báo một công cụ kiểu OpenAI để đảm bảo đầu ra JSON.
# agent/stack_agent.py (đoạn mã rút gọn)
from langchain_core.tools import tool
from pydantic import BaseModel, Field # cần định nghĩa StructuredStackAnalysis
class StructuredStackAnalysis(BaseModel):
project_purpose: str = Field(..., description="Mục đích chính của dự án.")
frontend_stack: List[str] = Field(..., description="Các framework/ngôn ngữ/thư viện frontend.")
backend_stack: List[str] = Field(..., description="Các framework/ngôn ngữ/thư viện/kiến trúc backend.")
database_details: List[str] = Field(..., description="Chi tiết về cơ sở dữ liệu được sử dụng.")
infrastructure_hosting: List[str] = Field(..., description="Hạ tầng và dịch vụ hosting.")
ci_cd_setup: List[str] = Field(..., description="Cài đặt CI/CD.")
key_root_files: List[str] = Field(..., description="Các file gốc quan trọng.")
how_to_run_instructions: str = Field(..., description="Hướng dẫn cách chạy dự án.")
risks_notes: str = Field(..., description="Các rủi ro hoặc ghi chú quan trọng.")
@tool("return_stack_analysis", args_schema=StructuredStackAnalysis)
def return_stack_analysis_tool(**kwargs) -> Dict[str, Any]:
"""Trả về phân tích stack cuối cùng trong cấu trúc JSON nghiêm ngặt."""
validated = StructuredStackAnalysis(**kwargs)
return validated.model_dump(exclude_none=True)
Cách sử dụng:
- `return_stack_analysis_tool` được gắn với Gemini trong `analyze_with_gemini_node`, buộc nó phải xuất ra JSON thay vì văn bản tự do.
Schema đảm bảo mọi phân tích repo đều có cùng định dạng, điều mà UI có thể hiển thị một cách đáng tin cậy.
8. Tổng Quan Luồng Dữ Liệu Toàn Diện (Complete End-to-End Data Flow)
Sau khi tích hợp tất cả các thành phần, đây là cách luồng dữ liệu end-to-end hoạt động:
- Giao diện Người dùng (Browser UI – CopilotChat): Người dùng tương tác với giao diện Next.js, nhập các truy vấn hoặc dán URL GitHub.
- Gửi Yêu cầu GraphQL/HTTP: UI gửi yêu cầu POST đến `/api/copilotkit` (GraphQL) đến Next.js API Route.
- Next.js API Route (copilotRuntimeNextJSAppRouterEndpoint): Route này hoạt động như một proxy, nhận yêu cầu từ trình duyệt và chuyển tiếp nó đến dịch vụ backend FastAPI.
- FastAPI + CopilotKitSDK (agent/main.py): Máy chủ FastAPI nhận yêu cầu, xác định agent mục tiêu (ví dụ: `post_generation_agent` hoặc `stack_analysis_agent`) và chuyển yêu cầu đến `LangGraphAgent` tương ứng.
- LangGraph StateGraph: LangGraph bắt đầu thực thi luồng công việc của agent. Các node (ví dụ: `chat_node`, `gather_context_node`) được kích hoạt theo trình tự. Trong quá trình này, agent stream các cập nhật trạng thái trung gian về frontend thông qua `copilotkit_emit_state()`.
- Google Gemini (LLM) + Công cụ (Google Search): Các node trong LangGraph gọi Google Gemini (thông qua `ChatGoogleGenerativeAI`) để lập luận hoặc tạo văn bản. Gemini có thể sử dụng các công cụ như Google Search để thu thập thông tin hoặc các công cụ nội bộ để trả về dữ liệu có cấu trúc.
- Phản hồi Streaming & Log Công cụ: Gemini gửi các phản hồi streaming và `tool_calls` trở lại LangGraph. LangGraph, thông qua CopilotKitSDK, stream các tin nhắn và log công cụ này trở lại Next.js API Route.
- Giao diện Người dùng Hiển thị Kết quả: Cuối cùng, giao diện người dùng trình duyệt nhận các tin nhắn streaming, log công cụ, và các kết quả cuối cùng (như thẻ bài viết hoặc thẻ phân tích stack), và hiển thị chúng cho người dùng theo thời gian thực.
Luồng dữ liệu này đảm bảo trải nghiệm người dùng mượt mà, cung cấp thông tin phản hồi liên tục về quá trình làm việc của agent.
9. Triển Khai và Trải Nghiệm Thực Tế (Deployment and Live Demo)
Sau khi hoàn thành tất cả các phần của mã, đã đến lúc chạy ứng dụng cục bộ. Đảm bảo bạn đã thêm thông tin xác thực Google Gemini vào file `.env` ở cả thư mục `agent` và `frontend`.
Khởi động Backend (FastAPI agent)
Chạy các lệnh sau trong thư mục `agent`:
cd agent
poetry install
# Đảm bảo GOOGLE_API_KEY đã được thiết lập trong agent/.env
poetry run python main.py
Máy chủ backend sẽ chạy, thường là trên `http://localhost:8000`.
Khởi động Frontend
Chạy các lệnh sau để khởi động máy chủ frontend cục bộ trong thư mục `frontend`:
cd frontend
pnpm install # Nếu bạn đã clone repo
pnpm run dev
Sau đó, truy cập `http://localhost:3000` (hoặc cổng mà pnpm thông báo) trong trình duyệt của bạn để xem giao diện người dùng. Theo cấu hình, bạn sẽ được chuyển hướng tự động đến `/post-generator`.
Kết Quả của Agent Tạo Bài Viết (Output of Post Generator Agent)
Agent Post Generator sẽ tạo ra các bài đăng chất lượng cao cho LinkedIn và X, dựa trên nghiên cứu sâu rộng. Nó sẽ hiển thị “tool-logs” trung gian, cho phép bạn thấy từng bước nghiên cứu, tìm kiếm và tạo nội dung theo thời gian thực. Bạn cũng sẽ tìm thấy các prompts khởi đầu được tạo sẵn để bắt đầu chỉ với một cú nhấp chuột.
Kết Quả của Agent Phân Tích Stack (Output of Stack Analyzer Agent)
Agent Stack Analyzer sẽ phân tích một kho lưu trữ GitHub công khai (metadata, README, manifest code) và suy ra stack công nghệ của nó. Nó sử dụng một mô hình dữ liệu Pydantic (`StructuredStackAnalysis`) để đảm bảo đầu ra JSON được định nghĩa nghiêm ngặt, bao gồm:
- Mục đích dự án
- Stack frontend (framework/ngôn ngữ/thư viện)
- Stack backend (framework/ngôn ngữ/thư viện/kiến trúc)
- Chi tiết cơ sở dữ liệu
- Hạ tầng/hosting
- Thiết lập CI/CD
- Các file gốc chính
- Hướng dẫn cách chạy
- Các rủi ro/ghi chú
Tương tự như Post Generator, nó stream từng bước (phân tích URL → lấy metadata → phân tích → tóm tắt) trở lại UI, mang lại cái nhìn toàn diện về quá trình phân tích.
Kết Luận
Việc xây dựng các ứng dụng agent AI fullstack không còn là một nhiệm vụ phức tạp không thể vượt qua. Bằng cách tận dụng sức mạnh của Google Gemini, CopilotKit và LangGraph, bạn có thể tạo ra các agent thông minh, có khả năng tương tác với thế giới thực và giải quyết các vấn đề phức tạp một cách hiệu quả. Các mẫu thiết kế được sử dụng trong hướng dẫn này—như biểu đồ trạng thái có trạng thái, liên kết công cụ và đầu ra có cấu trúc—sẽ giúp bạn tiết kiệm đáng kể thời gian phát triển.
Hy vọng bạn đã tìm thấy những kiến thức giá trị trong hướng dẫn thực hành này. Nếu bạn đã từng xây dựng thứ gì đó tương tự, hãy chia sẻ trong phần bình luận nhé!
Chúc bạn một ngày tuyệt vời! Hẹn gặp lại lần sau.
Bạn có thể xem thêm các sản phẩm của tôi tại anmolbaranwal.com. Cảm ơn bạn đã đọc! 🥰
Theo dõi CopilotKit trên Twitter để cập nhật tin tức và tham gia cộng đồng Discord nếu bạn muốn xây dựng những điều thú vị!



