Skip to content

Repository files navigation

JS Club Logo

🤖 AI Hub - Nền Tảng Quản Lý AI Agent JS Club

Screenshot

Dự án học tập - Xây dựng AI Agent Platform đầy đủ tính năng trên Cloudflare Workers với Next.js

AI Hub là một nền tảng quản lý và triển khai AI Agent, cho phép tạo, cấu hình và sử dụng các chatbot AI với khả năng RAG (Retrieval-Augmented Generation) và Web Search. Dự án này sử dụng hoàn toàn các dịch vụ của Cloudflare để có chi phí thấp và hiệu suất cao.


📚 Mục Lục


🏗 Kiến Trúc Tổng Quan

flowchart TB
subgraph Client
Browser["Client (Browser)"]
end
subgraph CF["Cloudflare Workers"]
subgraph NextJS["Next.js"]
ReactUI["React UI<br/>(App Router)"]
APIRoutes["API Routes<br/>(/api/*)"]
Hono["Hono Backend<br/>(REST API)"]
end
end
subgraph AISDK["AI SDK"]
SDK["Unified Interface<br/>(streamText, generateText)"]
Tools["Tool Calling<br/>(semanticSearch, webSearch)"]
end
subgraph Providers["LLM Providers"]
OpenAI["OpenAI<br/>(@ai-sdk/openai)"]
Google["Google AI<br/>(@ai-sdk/google)"]
Others["Other Providers<br/>(Anthropic, etc.)"]
end
subgraph Services["Cloudflare Services"]
D1[("D1 Database<br/>(SQLite)")]
Vectorize[("Vectorize<br/>(Vector DB)")]
WorkersAI["Workers AI<br/>(Embeddings)"]
KV[("KV Storage<br/>(Cache)")]
end
Browser --> CF
Hono --> SDK
SDK --> Tools
SDK --> OpenAI
SDK --> Google
SDK --> Others
Tools --> Vectorize
Tools --> WorkersAI
NextJS --> D1
NextJS --> KV
Loading

Flow Chính của Ứng Dụng (Agentic AI)

sequenceDiagram
participant User
participant API as Chat API
participant LLM as LLM Provider
participant RAG as semanticSearchTool
participant Web as webSearchTool
User->>API: Gửi message
API->>API: Load Agent Config (KV/D1)
API->>LLM: Stream request với tools
loop Tool Calling Loop (max 5 steps)
LLM->>LLM: Quyết định sử dụng tool
alt semanticSearchTool
LLM->>RAG: Query semantic search
RAG->>RAG: Generate embedding (Workers AI)
RAG->>RAG: Search Vectorize
RAG-->>LLM: Kết quả tìm kiếm
else webSearchTool
LLM->>Web: Query web search
Web->>Web: Ollama Web Search API
Web-->>LLM: Kết quả từ internet
end
end
LLM-->>API: Final response
API-->>User: Stream response
Loading
  1. User gửi message → Chat API nhận request
  2. Load Agent Config → Lấy từ KV cache hoặc D1 database
  3. Agentic Tool Calling: LLM tự động quyết định sử dụng tools khi cần
    • semanticSearchTool: Tìm kiếm trong knowledge base (Vectorize)
    • webSearchTool: Tìm kiếm thông tin mới nhất từ internet
  4. LLM Response → Tổng hợp kết quả từ tools và trả lời
  5. Stream Response → Trả về client real-time

Note

Flow sử dụng AI SDK với toolChoice: "auto"stepCountIs(5) để giới hạn số lần gọi tool tối đa là 5.


🛠 Công Nghệ Sử Dụng

Frontend

Công nghệMục đích
Next.js 15React framework với App Router
React 19UI library
TailwindCSS 4Styling
Radix UIHeadless UI components
React QueryServer state management
nuqsURL state management
AI SDK (Vercel)Chat UI và AI integration

Backend

Công nghệMục đích
HonoLightweight web framework cho API
Drizzle ORMType-safe database ORM
ZodSchema validation
Better AuthAuthentication library

Cloudflare Services

ServiceMục đích
WorkersServerless compute runtime
D1SQLite database
VectorizeVector database cho RAG
Workers AIEmbedding generation
KVKey-value cache storage
Rate LimitingAPI rate limiting
ImagesImage optimization

☁️ Cloudflare Services Chi Tiết

1. D1 Database (SQLite)

D1 là database SQL serverless của Cloudflare, sử dụng SQLite engine. Phù hợp cho ứng dụng với read-heavy workload.

Cấu hình trong wrangler.jsonc:

"d1_databases": [{
"binding": "AIHUB_DB", // Tên binding trong code"database_name": "aihub-db", // Tên database"database_id": "xxx-xxx-xxx", // ID từ Cloudflare Dashboard"migrations_dir": "drizzle/migrations"
}]

Sử dụng trong code (src/lib/db.ts):

import{drizzle}from'drizzle-orm/d1';import{getCloudflareContext}from'@opennextjs/cloudflare';exportasyncfunctiongetDb(){const{ env }=awaitgetCloudflareContext({async: true});returndrizzle(env.AIHUB_DB,{ schema });}

Commands quản lý:

# Generate migration từ schema changes
npm run db:generate
# Apply migrations lên remote D1
npm run db:push
# Mở Drizzle Studio để xem data
npm run db:studio

2. Vectorize (Vector Database)

Vectorize lưu trữ vector embeddings để thực hiện semantic search (RAG).

Cấu hình trong wrangler.jsonc:

"vectorize": [{
"binding": "VECTORIZE",
"index_name": "aihub-vectorize"
}]

Tạo index (chạy 1 lần):

wrangler vectorize create aihub-vectorize \
--dimensions=768 \
--metric=cosine

Sử dụng trong code (src/lib/vectorize.ts):

// Upsert vector với metadataexportasyncfunctionupsertVector(datasourceId: number,datasourceGroupId: number,content: string){const{ vectorize }=awaitgetBindings();constembedding=awaitgenerateEmbedding(content);// Workers AIawaitvectorize.upsert([{id: `datasource:${datasourceId}`,values: embedding,metadata: { datasourceId, datasourceGroupId, content }}]);}// Search với metadata filterexportasyncfunctionsearchVectors(query: string,datasourceGroupIds: number[],topK: number=5){constqueryEmbedding=awaitgenerateEmbedding(query);returnvectorize.query(queryEmbedding,{
topK,returnMetadata: 'all',filter: {datasourceGroupId: {$in: datasourceGroupIds}// Filter by groups}});}

Lưu ý quan trọng:

  • Dimensions phải khớp với model embedding (768 cho bge-base-en-v1.5)
  • Hỗ trợ operators: $eq, $ne, $in, $nin, $lt, $lte, $gt, $gte

Metadata và Multi-Agent Isolation:

Trong project này, mỗi vector được lưu với metadata datasourceGroupId:

flowchart LR
subgraph Agent1["Agent A"]
G1["Datasource Group 1"]
G2["Datasource Group 2"]
end
subgraph Agent2["Agent B"]
G3["Datasource Group 3"]
end
subgraph Vectorize["Vectorize Index"]
V1["Vector 1<br/>metadata: groupId=1"]
V2["Vector 2<br/>metadata: groupId=2"]
V3["Vector 3<br/>metadata: groupId=3"]
end
G1 --> V1
G2 --> V2
G3 --> V3
Loading
  • Khi Agent A search, chỉ tìm trong groupId: [1, 2]
  • Khi Agent B search, chỉ tìm trong groupId: [3]
  • Đảm bảo mỗi Agent chỉ truy cập knowledge base được assign

3. Workers AI

Workers AI cung cấp inference cho các AI models, trong project này dùng để generate embeddings.

Cấu hình trong wrangler.jsonc:

"ai": {
"binding": "AI"
}

Sử dụng:

exportasyncfunctiongenerateEmbedding(text: string): Promise<number[]>{const{ env }=awaitgetCloudflareContext({async: true});constresponse=awaitenv.AI.run('@cf/baai/bge-base-en-v1.5',{text: [text]});returnresponse.data[0];// 768-dimensional vector}

Models có sẵn:

  • @cf/baai/bge-base-en-v1.5 - Text embeddings (768 dims)
  • @cf/meta/llama-3.1-8b-instruct - LLM inference
  • @cf/stabilityai/stable-diffusion-xl - Image generation

4. KV Storage (Cache)

KV (Key-Value) storage dùng để cache data, giảm load cho database.

Cấu hình trong wrangler.jsonc:

"kv_namespaces": [{
"binding": "AIHUB_KV",
"id": "xxx-xxx-xxx"
}]

Tạo KV namespace:

wrangler kv namespace create AIHUB_KV
# Copy ID vào wrangler.jsonc

Sử dụng (src/lib/kv.ts):

// Cache với TTLexportasyncfunctionputKV(key: string,value: string,options?: {expirationTtl?: number}){const{ env }=awaitgetCloudflareContext({async: true});awaitenv.AIHUB_KV.put(key,value,options);}// Get cached valueexportasyncfunctiongetKV<T>(key: string,type: "json"|"text"="text"){const{ env }=awaitgetCloudflareContext({async: true});returnenv.AIHUB_KV.get(key,type);}

Pattern Cache trong project:

// Agent caching với 15 phút TTLconstCACHE_PREFIX='agent:';constCACHE_TTL_SECONDS=15*60;exportasyncfunctiongetAgentById(id: number){constcacheKey=`${CACHE_PREFIX}${id}`;// Try cache firstconstcached=awaitgetKV(cacheKey,'json');if(cached)returncached;// Fetch from DBconstagent=awaitfetchAgentFromDb(id);// Cache resultawaitputKV(cacheKey,JSON.stringify(agent),{expirationTtl: CACHE_TTL_SECONDS});returnagent;}

5. Rate Limiting

Rate Limiting bảo vệ API khỏi abuse và DDoS.

Cấu hình trong wrangler.jsonc:

"ratelimits": [{
"name": "AIHUB_RATE_LIMITER",
"namespace_id": "1001",
"simple": {
"limit": 50, // 50 requests"period": 60// per 60 seconds
}
}]

Sử dụng trong router:

.post("/completions/:agentId",async(c)=>{const{ env }=awaitgetCloudflareContext({async: true});// Get client IPconstipAddress=c.req.header("cf-connecting-ip")||"";// Check rate limitconst{ success }=awaitenv.AIHUB_RATE_LIMITER.limit({key: ipAddress});if(!success){returnc.json({error: 'Rate limit exceeded'},429);}// Continue processing...})

6. OpenNext Integration

OpenNext cho phép deploy Next.js application lên Cloudflare Workers.

Cấu hình open-next.config.ts:

importtype{OpenNextConfig}from'@opennextjs/cloudflare';exportdefault{// Config options}satisfiesOpenNextConfig;

Self-reference binding cho caching:

"services": [{
"binding": "WORKER_SELF_REFERENCE",
"service": "aihub"// Phải match worker name
}]

📁 Cấu Trúc Thư Mục

aihub/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (auth)/ # Auth pages (login, signup)
│ │ ├── (dashboard)/ # Protected dashboard pages
│ │ │ ├── (admin)/ # Admin CRUD pages
│ │ │ └── playground/ # Chat playground
│ │ ├── api/ # API routes
│ │ ├── layout.tsx # Root layout
│ │ └── globals.css # Global styles
│ │
│ ├── backend/ # Hono API server
│ │ ├── index.ts # Router aggregation
│ │ └── middleware/ # Auth middleware
│ │
│ ├── features/ # Feature-based architecture
│ │ ├── agents/ # Agent management
│ │ │ ├── components/ # UI components
│ │ │ ├── hooks/ # React hooks
│ │ │ ├── server/ # API routes & services
│ │ │ ├── params.ts # URL params schema
│ │ │ └── query-options.ts # React Query options
│ │ ├── llms/ # LLM configuration
│ │ ├── datasources/ # Knowledge base content
│ │ ├── datasource-groups/ # Knowledge base groups
│ │ ├── chat/ # Chat functionality
│ │ │ └── server/
│ │ │ ├── routers.ts # Chat endpoints
│ │ │ ├── service.ts # AI response logic
│ │ │ └── tools.ts # AI SDK tools
│ │ ├── playground/ # Playground UI
│ │ └── auth/ # Auth components
│ │
│ ├── components/ # Shared UI components
│ │ └── ui/ # shadcn/ui components
│ │
│ ├── config/ # App configuration
│ │ └── constants.ts # AI providers & models
│ │
│ ├── hooks/ # Shared hooks
│ │
│ └── lib/ # Core utilities
│ ├── db.ts # Drizzle D1 setup
│ ├── kv.ts # KV helpers
│ ├── vectorize.ts # Vector operations
│ ├── schema.ts # Database schema
│ ├── logger.ts # Logging utility
│ ├── auth/ # Better Auth setup
│ └── api/ # API client (ky)
│
├── drizzle/ # Database migrations
│ └── migrations/
│
├── public/ # Static assets
├── wrangler.jsonc # Cloudflare config
├── drizzle.config.ts # Drizzle config
├── open-next.config.ts # OpenNext config
└── package.json

🗄 Database Schema

Entity Relationship Diagram

erDiagram
user {
int id PK
string name
string email
string image
timestamp createdAt
}
session {
int id PK
int userId FK
string token
timestamp expiresAt
string ipAddress
string userAgent
}
account {
int id PK
int userId FK
string providerId
string accessToken
string refreshToken
}
llms {
int id PK
string name
string provider
string model
string baseUrl
string apiKey
}
agents {
int id PK
int llmId FK
string name
string description
string systemPrompt
int temperature
int topK
int maxTokens
}
datasource_groups {
int id PK
string name
string description
}
agent_datasource_groups {
int agentId FK
int datasourceGroupId FK
}
datasources {
int id PK
int datasourceGroupId FK
string content
}
user ||--o{ session : "has"
user ||--o{ account : "has"
llms ||--o{ agents : "powers"
agents ||--o{ agent_datasource_groups : "uses"
datasource_groups ||--o{ agent_datasource_groups : "belongs to"
datasource_groups ||--o{ datasources : "contains"
Loading

Mô tả các bảng:

BảngMô tả
userNgười dùng hệ thống
sessionPhiên đăng nhập
accountOAuth accounts (Google, etc.)
verificationEmail verification tokens
llmsCấu hình LLM providers (OpenAI, Google, etc.)
agentsAI Agents với system prompt và settings
datasource_groupsNhóm knowledge base
datasourcesNội dung knowledge base (embedded vào Vectorize)
agent_datasource_groupsMany-to-many: Agent ↔ Datasource Groups
ollama_keysAPI keys cho Ollama web search

🔌 API Endpoints

Authentication

POST /api/auth/sign-up # Đăng ký (⚠️ chỉ có trên develop env)
POST /api/auth/sign-in # Đăng nhập
POST /api/auth/sign-out # Đăng xuất
GET /api/auth/session # Lấy session hiện tại

Note

Route /signup chỉ tồn tại trên môi trường develop, dùng để tạo tài khoản admin.

LLMs (Protected)

GET /api/llms # List LLMs với pagination
POST /api/llms # Tạo LLM mới
GET /api/llms/:id # Chi tiết LLM
PATCH /api/llms/:id # Cập nhật LLM
DELETE /api/llms/:id # Xóa LLM

Agents (Protected)

GET /api/agents # List agents với search
POST /api/agents # Tạo agent mới
GET /api/agents/:id # Chi tiết agent
PATCH /api/agents/:id # Cập nhật agent
DELETE /api/agents/:id # Xóa agent

Datasource Groups (Protected)

GET /api/datasource-groups # List groups
POST /api/datasource-groups # Tạo group mới
GET /api/datasource-groups/:id # Chi tiết group
PATCH /api/datasource-groups/:id # Cập nhật group
DELETE /api/datasource-groups/:id # Xóa group

Datasources (Protected)

GET /api/datasources/group/:groupId # List datasources theo group
POST /api/datasources # Tạo datasource (auto-embed)
GET /api/datasources/:id # Chi tiết datasource
PATCH /api/datasources/:id # Cập nhật datasource
DELETE /api/datasources/:id # Xóa datasource

Chat

# Protected - dùng trong Playground
POST /api/chat/playground/:agentId # Public với Rate Limiting - dùng cho integration
POST /api/chat/completions/:agentId 

Request body (UIMessage format):

{
"stream": true,
"messages": [
{
"id": "msg-1",
"role": "user",
"parts": [
{
"type": "text",
"text": "Xin chào!"
}
]
}
]
}

🚀 Cài Đặt và Chạy

Prerequisites

  • Node.js 18+
  • npm hoặc pnpm
  • Cloudflare account
  • Wrangler CLI (npm install -g wrangler)

Bước 1: Clone và cài đặt dependencies

git clone <repository-url>cd aihub
npm install

Bước 2: Login Cloudflare

wrangler login

Bước 3: Tạo Cloudflare Resources

# Tạo D1 Database
wrangler d1 create aihub-db
# Tạo KV Namespace
wrangler kv namespace create AIHUB_KV
# Tạo Vectorize Index
wrangler vectorize create aihub-vectorize \
--dimensions=768 \
--metric=cosine

Bước 4: Cập nhật wrangler.jsonc

Copy các ID từ output của bước 3 vào wrangler.jsonc:

{
"d1_databases": [{
"database_id": "<your-d1-id>"
}],
"kv_namespaces": [{
"id": "<your-kv-id>"
}]
}

Bước 5: Tạo file môi trường

# .dev.vars (local development)
NEXTJS_ENV=development

Bước 6: Chạy migrations

# Generate migrations từ schema
npm run db:generate
# Apply migrations
npm run db:push

Bước 7: Chạy development server

# Chạy Next.js dev server
npm run dev

Bước 8: Preview trên Cloudflare runtime

npm run preview

Bước 9: Deploy

npm run deploy

📈 Hướng Dẫn Scale Tính Năng Mới

Thêm Feature Module Mới

Giả sử bạn muốn thêm feature "Conversations" để lưu lịch sử chat:

1. Tạo cấu trúc thư mục

src/features/conversations/
├── components/
│ ├── conversation-list.tsx
│ └── conversation-dialog.tsx
├── hooks/
│ └── use-conversations.ts
├── server/
│ ├── routers.ts
│ └── service.ts
├── params.ts
├── query-options.ts
└── index.ts

2. Thêm database schema (src/lib/schema.ts)

exportconstconversations=sqliteTable("conversations",{id: int("id").primaryKey({autoIncrement: true}),agentId: int("agent_id").references(()=>agents.id),userId: text("user_id").references(()=>user.id),title: text("title"),createdAt: integer("created_at",{mode: "timestamp"}).$defaultFn(()=>newDate()),});exportconstmessages=sqliteTable("messages",{id: int("id").primaryKey({autoIncrement: true}),conversationId: int("conversation_id").references(()=>conversations.id),role: text("role").notNull(),// "user" | "assistant"content: text("content").notNull(),createdAt: integer("created_at",{mode: "timestamp"}).$defaultFn(()=>newDate()),});

3. Generate và apply migration

npm run db:migrate

4. Tạo service (src/features/conversations/server/service.ts)

import{getDb}from'@/lib/db';import{conversations,messages}from'@/lib/schema';exportasyncfunctioncreateConversation(agentId: number,userId: string){constdb=awaitgetDb();constresult=awaitdb.insert(conversations).values({ agentId, userId,title: "New Conversation"}).returning();returnresult[0];}exportasyncfunctionaddMessage(conversationId: number,role: string,content: string){constdb=awaitgetDb();returndb.insert(messages).values({ conversationId, role, content });}

5. Tạo router (src/features/conversations/server/routers.ts)

import{Hono}from'hono';import{protectedRoute}from'@/backend/middleware/auth';import{createConversation,addMessage}from'./service';exportconstconversationsRouter=newHono().post('/',protectedRoute,async(c)=>{// Implementation});

6. Đăng ký router (src/backend/index.ts)

import{conversationsRouter}from'@/features/conversations/server/routers';constapp=newHono().basePath('/api').route('/conversations',conversationsRouter)// ... other routes

Thêm Tool Mới cho AI Agent

Giả sử bạn muốn thêm "Calculator Tool":

1. Định nghĩa tool (src/features/chat/server/tools.ts)

import{tool}from'ai';import{z}from'zod';exportconstcalculatorInputSchema=z.object({expression: z.string().describe("Biểu thức toán học cần tính"),reasoning: z.string().describe("Lý do cần tính toán"),});exportconstcreateCalculatorTool=()=>tool({description: "Thực hiện phép tính toán học",inputSchema: calculatorInputSchema,execute: async({ expression, reasoning })=>{try{// Sử dụng safe eval hoặc math libraryconstresult=eval(expression);// ⚠️ Cần sanitize!return{success: true,
result,
expression,
reasoning,};}catch(error){return{success: false,error: `Calculation failed: ${error}`,};}},});

2. Thêm vào createAgentTools

exportfunctioncreateAgentTools(agent: AgentDetailResolved){return{semanticSearchTool: createSemanticSearchTool(agent),webSearchTool: createWebSearchTool(),calculatorTool: createCalculatorTool(),// Thêm mới};}

Thêm AI Provider Mới

Giả sử bạn muốn thêm Anthropic Claude:

1. Cài đặt SDK

npm install @ai-sdk/anthropic

2. Thêm vào constants (src/config/constants.ts)

exportconstAI_PROVIDERS=[{slug: "openai",name: "OpenAI",defaultBaseUrl: "..."},{slug: "google",name: "Google",defaultBaseUrl: "..."},{slug: "anthropic",name: "Anthropic",defaultBaseUrl: "https://api.anthropic.com/v1"},// Thêm]asconst;exportconstAI_MODELS=[// ... existing models{id: "claude-3-5-sonnet-20241022",name: "Claude 3.5 Sonnet",chef: "Anthropic",chefSlug: "anthropic",providers: ["anthropic"]},];

3. Cập nhật createAIProvider (src/features/chat/server/service.ts)

import{createAnthropic}from'@ai-sdk/anthropic';exportconstcreateAIProvider=({ llm }: AgentDetailResolved)=>{switch(llm.provider){case"openai":
returncreateOpenAI({baseURL: llm.baseUrl,apiKey: llm.apiKey});case"google":
returncreateGoogleGenerativeAI({baseURL: llm.baseUrl,apiKey: llm.apiKey});case"anthropic":
returncreateAnthropic({baseURL: llm.baseUrl,apiKey: llm.apiKey});default:
returncreateOpenAI({baseURL: llm.baseUrl,apiKey: llm.apiKey});}};

✅ Best Practices

1. Caching Strategy

// Luôn cache với TTL để tránh stale dataawaitputKV(key,value,{expirationTtl: 15*60});// Invalidate cache khi data thay đổiawaitinvalidateAgentCache(id);awaitinvalidateAgentCachesByLlmId(llmId);// Cascade invalidation

2. Error Handling

// Wrap database operations trong try-catchtry{constresult=awaitdb.insert(table).values(data).returning();returnresult[0];}catch(error){logger.error('Database error',{error: String(error)});thrownewError('Failed to create record');}

3. Logging

// Sử dụng structured loggingimport{agentsLoggeraslogger}from'@/lib/logger';logger.info('Action started',{ agentId, userId });logger.error('Action failed',{error: String(error)});

4. Type Safety

// Infer types từ Zod schemasconstcreateSchema=z.object({name: z.string()});typeCreateInput=z.infer<typeofcreateSchema>;// Sử dụng database typestypeAgentDetail=Awaited<ReturnType<typeoffetchAgentFromDb>>;

5. Rate Limiting cho Public APIs

// Luôn rate limit các endpoint publicconst{ success }=awaitenv.RATE_LIMITER.limit({key: ipAddress});if(!success)returnc.json({error: 'Rate limit exceeded'},429);

📖 Tài Liệu Tham Khảo


📝 License

MIT License - Feel free to use this project for learning and development.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages