flare-agent Bài 7: Áp dụng vào edu-ai-platform — End-to-end
Kết hợp tất cả packages của flare-agent để xây dựng edu-ai-platform hoàn chỉnh — vocabulary learning agent với quiz workflow, RAG memory và SSE streaming trên Cloudflare.
Tập này đang được chuẩn bị, quay lại sau nhé.
flare-agent Bài 7: Áp dụng vào edu-ai-platform
Series: Build Your Own AI Agent Framework trên Cloudflare Bài: 7 / 7 — End-to-end
Tổng kết những gì đã build
| Package | Vai trò |
|---|---|
@flare-agent/types |
Interfaces — hợp đồng giữa các layer |
@flare-agent/providers |
LLM Router — Groq, WorkersAI, Ollama |
@flare-agent/memory |
KV + D1 + Vectorize adapters |
@flare-agent/core |
Agent Loop + Tool Registry |
@flare-agent/workflow |
Graph-based Workflow Engine |
Bài này kết nối tất cả vào edu-ai-platform thực tế.
Kiến trúc edu-ai-platform
React/Vite (Cloudflare Pages)
↓ POST /api/chat
↓ GET /api/quiz/start
Hono Worker
├── /api/chat → VocabularyAgent (Agent Loop)
├── /api/quiz/* → QuizWorkflow (Workflow Engine)
└── /api/recall → RAG query
Durable Object (session state)
└── AgentSession
Storage
├── KV → conversation history
├── D1 → vocabulary, progress, users
└── Vectorize → semantic word search
Project Structure
edu-ai-platform/
├── apps/
│ ├── web/ # React + Vite
│ │ └── src/
│ │ ├── hooks/
│ │ │ └── useChat.ts # SSE consumer
│ │ └── components/
│ │ └── ChatBox.tsx
│ └── worker/ # Hono Worker
│ └── src/
│ ├── index.ts # routes
│ ├── agents/
│ │ └── vocabulary.ts
│ ├── workflows/
│ │ └── quiz.ts
│ └── tools/
│ ├── vocabulary.ts
│ └── progress.ts
└── packages/ # flare-agent packages
├── types/
├── providers/
├── memory/
├── core/
└── workflow/
Tools
// apps/worker/src/tools/vocabulary.ts
import { tool } from '@flare-agent/core';
export const getVocabulary = tool({
schema: {
name: 'get_vocabulary',
description: 'Lấy danh sách từ vựng theo chủ đề và level',
parameters: {
type: 'object',
properties: {
topic: { type: 'string', description: 'Chủ đề (business, travel, ...)' },
level: {
type: 'string',
enum: ['beginner', 'intermediate', 'advanced'],
},
limit: { type: 'number', default: 5 },
},
required: ['topic'],
},
},
execute: async ({ topic, level, limit }, ctx) => {
const db = ctx.env.DB as D1Database;
const { results } = await db
.prepare(
`SELECT word, definition, example
FROM vocabulary
WHERE topic = ? AND level = ?
ORDER BY RANDOM() LIMIT ?`
)
.bind(topic, level ?? 'beginner', limit ?? 5)
.all();
return results;
},
});
export const saveProgress = tool({
schema: {
name: 'save_progress',
description: 'Lưu tiến độ học của user',
parameters: {
type: 'object',
properties: {
wordId: { type: 'string' },
status: { type: 'string', enum: ['learned', 'reviewing', 'skipped'] },
score: { type: 'number' },
},
required: ['wordId', 'status'],
},
},
execute: async ({ wordId, status, score }, ctx) => {
const db = ctx.env.DB as D1Database;
await db
.prepare(
`INSERT OR REPLACE INTO progress (user_id, word_id, status, score, updated_at)
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`
)
.bind(ctx.userId, wordId, status, score ?? 0)
.run();
return { success: true };
},
});
Vocabulary Agent
// apps/worker/src/agents/vocabulary.ts
import { Agent } from '@flare-agent/core';
import { getVocabulary, saveProgress } from '../tools/vocabulary';
export const vocabularyAgent = new Agent({
name: 'vocabulary-tutor',
model: { provider: 'groq', model: 'llama-3.3-70b-versatile' },
memory: 'd1',
maxIterations: 8,
systemPrompt: (ctx) =>
`Bạn là trợ lý học từ vựng tiếng Anh thân thiện.
Hỗ trợ người dùng ${ctx.userId ?? 'ẩn danh'} học từ vựng hiệu quả.
Luôn giải thích bằng tiếng Việt, ví dụ bằng tiếng Anh.
Khuyến khích và động viên khi user trả lời đúng.`,
})
.use(getVocabulary, saveProgress);
Hono Routes
// apps/worker/src/index.ts
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { createSSEResponse } from '@flare-agent/core';
import { vocabularyAgent } from './agents/vocabulary';
import { QuizWorkflow } from './workflows/quiz';
import { MemoryManager, KVMemoryAdapter, D1MemoryAdapter, VectorizeAdapter } from '@flare-agent/memory';
const app = new Hono<{ Bindings: Env }>();
app.use('*', cors());
// Chat endpoint — stream response
app.post('/api/chat/stream', async (c) => {
const { input, sessionId, userId } = await c.req.json();
const ctx = {
sessionId,
userId,
env: c.env as any,
};
const provider = /* resolve từ config */;
const stream = provider.stream(
[{ role: 'user', content: input }]
);
return createSSEResponse(stream);
});
// Chat endpoint — full response
app.post('/api/chat', async (c) => {
const { input, sessionId, userId } = await c.req.json();
const result = await vocabularyAgent.run(input, {
sessionId,
userId,
env: c.env as any,
});
return c.json(result);
});
// Quiz workflow
app.post('/api/quiz/answer', async (c) => {
const { answer, sessionId, userId } = await c.req.json();
const workflow = new QuizWorkflow(c.env);
const result = await workflow.run(answer, {
sessionId,
userId,
env: c.env as any,
});
return c.json(result);
});
// RAG — tìm từ theo nghĩa
app.get('/api/recall', async (c) => {
const query = c.req.query('q') ?? '';
const memory = new MemoryManager({
vector: new VectorizeAdapter(c.env.VECTORIZE, c.env.AI),
});
const results = await memory.recall(query, 5);
return c.json({ results });
});
export const AgentSession = vocabularyAgent.toDurableObject();
export default app;
React Hook — SSE Consumer
// apps/web/src/hooks/useChat.ts
import { useState, useCallback } from 'react';
export function useChat() {
const [messages, setMessages] = useState<Array<{
role: 'user' | 'assistant';
content: string;
}>>([]);
const [streaming, setStreaming] = useState('');
const [loading, setLoading] = useState(false);
const send = useCallback(async (input: string, sessionId: string) => {
setLoading(true);
setMessages((prev) => [...prev, { role: 'user', content: input }]);
const res = await fetch('/api/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input, sessionId }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let fullText = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value).split('\n');
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const data = line.slice(6);
if (data === '[DONE]') break;
try {
const { text } = JSON.parse(data);
fullText += text;
setStreaming(fullText);
} catch { /* skip */ }
}
}
setMessages((prev) => [...prev, { role: 'assistant', content: fullText }]);
setStreaming('');
setLoading(false);
}, []);
return { messages, streaming, loading, send };
}
wrangler.toml
name = "edu-ai-worker"
main = "src/index.ts"
compatibility_date = "2025-01-01"
[[durable_objects.bindings]]
name = "AGENT_SESSION"
class_name = "AgentSession"
[[migrations]]
tag = "v1"
new_classes = ["AgentSession"]
[[kv_namespaces]]
binding = "KV"
id = "YOUR_KV_ID"
[[d1_databases]]
binding = "DB"
database_name = "edu-ai"
database_id = "YOUR_D1_ID"
[[vectorize]]
binding = "VECTORIZE"
index_name = "edu-vocabulary"
[ai]
binding = "AI"
[vars]
OLLAMA_BASE_URL = "https://your-oracle-vm/ollama"
[secrets]
GROQ_API_KEY = ""
Kết quả
Sau 7 bài, bạn có:
- Framework ~800 lines TypeScript — hiểu 100% internals
- Zero lock-in — swap provider, memory, runtime bằng 1 dòng
- edu-ai-platform chạy native trên Cloudflare
- Tái sử dụng được cho bất kỳ project nào khác
Bước tiếp theo
- Extract framework thành npm packages khi cần dùng cho project thứ 2
- Thêm Observability / Tracing (OpenTelemetry)
- Thêm Multi-agent support
- Viết tests cho từng package
Series hoàn tất. Nếu bài viết hữu ích, hãy star repo và chia sẻ với cộng đồng developer Việt Nam! 🇻🇳