Trong Phần 1, chúng ta đã vẽ bản đồ kiến trúc. Giờ là lúc xây dựng.
Hướng dẫn này đưa bạn từ một thư mục trống đến hệ thống AI agent hoạt động đầy đủ — với orchestration, RAG, tool use qua MCP, observability, và Docker deployment. Mọi dòng code đều production-ready.
Chúng Ta Sẽ Xây Dựng Gì
Một trợ lý AI cá nhân có khả năng:
- Trả lời câu hỏi dựa trên tài liệu của bạn (RAG)
- Thực thi hành động qua tools (MCP)
- Ghi nhớ lịch sử hội thoại
- Định tuyến request thông minh
- Log mọi bước để debug
flowchart TD
A["User: Tinh trang Project Alpha the nao\nva tao task cho milestone tiep theo?"]
B["1. Phan loai: Router quyet dinh"]
C["2. RAG: Tim tai lieu lien quan"]
D["3. Tool: Tao task moi"]
E["4. Tao phan hoi tong hop"]
F["Agent: Project Alpha hoan thanh 73%\nDa tao task 47 cho milestone 3"]
A --> B
B --> C
B --> D
C --> E
D --> E
E --> FYêu Cầu Chuẩn Bị
# Yêu cầu hệ thống
Python 3.11+
Docker & Docker Compose
8GB+ RAM (16GB khuyến nghị cho LLM local)
GPU tùy chọn nhưng khuyến nghị cho Ollama
# Kiểm tra
python3 --version
docker --version
docker compose version
Bước 1: Cấu Trúc Dự Án
mkdir agentic-system && cd agentic-system
# Tạo cấu trúc project
mkdir -p {agent,rag,mcp_server,frontend,observability,tests,docs}
touch {agent,rag,mcp_server,frontend,observability,tests}/__init__.py
agentic-system/agent/__init__.pyorchestrator.py— LangGraph agent orchestratorrouter.py— Dinh tuyen request thong minhllm.py— LLM client (Ollama)memory.py— Bo nho hoi thoaiserver.py— FastAPI endpoint
rag/__init__.pyindexer.py— Pipeline danh index tai lieuretriever.py— Tim kiem tuong tuembeddings.py— Thiet lap embedding model
mcp_server/__init__.pyserver.py— MCP server voi toolstools/task_manager.py— CRUD operations cho tasksfile_ops.py— Doc/ghi filesweb_search.py— Tim kiem web
frontend/app.py— Streamlit UIcomponents/
observability/tracing.py— Tich hop Langfusemetrics.py— Custom metrics
tests/test_agent.pytest_rag.pytest_mcp.py
docs/— Kho kien thuc cua bandocker-compose.ymlDockerfilepyproject.tomlREADME.md
Bước 2: Dependencies
# pyproject.toml
[project]
name = "agentic-system"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"langgraph>=0.3.0",
"langchain-core>=0.3.0",
"langchain-community>=0.3.0",
"llama-index-core>=0.12.0",
"llama-index-vector-stores-chroma>=0.4.0",
"llama-index-embeddings-huggingface>=0.4.0",
"chromadb>=0.6.0",
"ollama>=0.4.0",
"mcp>=1.0.0",
"fastapi>=0.115.0",
"uvicorn>=0.34.0",
"langfuse>=2.50.0",
"pydantic>=2.10.0",
"httpx>=0.28.0",
]
# Cài đặt
pip install -e ".[dev]"
# Hoặc dùng uv (nhanh hơn)
uv pip install -e ".[dev]"
Bước 3: LLM Client — Ollama Wrapper
# agent/llm.py
from ollama import Client, AsyncClient
from typing import Optional
import logging
logger = logging.getLogger(__name__)
class LLMClient:
"""Client LLM thống nhất với model routing và fallback."""
def __init__(
self,
host: str = "http://localhost:11434",
default_model: str = "gemma4:e4b"
):
self.client = Client(host=host)
self.async_client = AsyncClient(host=host)
self.default_model = default_model
# Bản đồ định tuyến model
self.model_router = {
"code": "deepseek-coder-v3:6b",
"reason": default_model,
"chat": "mistral-small:4",
"default": default_model,
}
def classify_intent(self, message: str) -> str:
"""Dùng model nhỏ để phân loại loại request."""
try:
response = self.client.chat(
model="mistral-small:4",
messages=[{
"role": "system",
"content": (
"Phân loại message này thành đúng MỘT từ: "
"CODE, REASON, hoặc CHAT. Chỉ trả lời từ đó."
)
}, {
"role": "user",
"content": message
}],
options={"temperature": 0}
)
return response.message.content.strip().lower()
except Exception:
return "default"
def get_model_for_intent(self, intent: str) -> str:
"""Map intent đến model tốt nhất."""
return self.model_router.get(intent, self.default_model)
def chat(
self,
messages: list[dict],
model: Optional[str] = None,
temperature: float = 0.7,
stream: bool = False
):
"""Gửi chat request đến LLM."""
selected_model = model or self.default_model
logger.info(f"LLM call: model={selected_model}, msgs={len(messages)}")
return self.client.chat(
model=selected_model,
messages=messages,
options={"temperature": temperature},
stream=stream
)
def health_check(self) -> bool:
"""Kiểm tra Ollama đang chạy và model có sẵn."""
try:
models = self.client.list()
available = [m.model for m in models.models]
return self.default_model in available
except Exception:
return False
Bước 4: RAG Pipeline — Truy Xuất Tài Liệu
# rag/indexer.py
from llama_index.core import (
VectorStoreIndex,
SimpleDirectoryReader,
StorageContext,
Settings
)
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
import chromadb
import logging
logger = logging.getLogger(__name__)
class DocumentIndexer:
"""Đánh index tài liệu vào ChromaDB cho retrieval."""
def __init__(
self,
persist_dir: str = "./chroma_db",
collection_name: str = "knowledge"
):
self.embed_model = HuggingFaceEmbedding(
model_name="BAAI/bge-small-en-v1.5",
cache_folder="./model_cache"
)
Settings.embed_model = self.embed_model
# Khởi tạo ChromaDB
self.chroma_client = chromadb.PersistentClient(path=persist_dir)
self.collection = self.chroma_client.get_or_create_collection(
name=collection_name,
metadata={"hnsw:space": "cosine"}
)
self.vector_store = ChromaVectorStore(
chroma_collection=self.collection
)
def index_directory(self, doc_path: str) -> int:
"""Index tất cả tài liệu trong thư mục."""
logger.info(f"Đang index tài liệu từ {doc_path}")
documents = SimpleDirectoryReader(
input_dir=doc_path,
recursive=True,
filename_as_id=True,
required_exts=[
".md", ".txt", ".pdf", ".py",
".js", ".ts", ".json", ".yaml"
]
).load_data()
logger.info(f"Tìm thấy {len(documents)} tài liệu")
storage_context = StorageContext.from_defaults(
vector_store=self.vector_store
)
index = VectorStoreIndex.from_documents(
documents,
storage_context=storage_context,
show_progress=True
)
self.index = index
return len(documents)
def get_index(self) -> VectorStoreIndex:
"""Load index có sẵn từ storage."""
if not hasattr(self, 'index'):
self.index = VectorStoreIndex.from_vector_store(
self.vector_store,
embed_model=self.embed_model
)
return self.index
# rag/retriever.py
from llama_index.core import VectorStoreIndex
from rag.indexer import DocumentIndexer
from dataclasses import dataclass
@dataclass
class RetrievalResult:
text: str
score: float
source: str
metadata: dict
class DocumentRetriever:
"""Truy xuất tài liệu liên quan cho một query."""
def __init__(self, indexer: DocumentIndexer, top_k: int = 5):
self.indexer = indexer
self.top_k = top_k
self.index = indexer.get_index()
def retrieve(self, query: str, min_score: float = 0.3) -> list[RetrievalResult]:
"""Truy xuất tài liệu liên quan đến query."""
retriever = self.index.as_retriever(similarity_top_k=self.top_k)
nodes = retriever.retrieve(query)
results = []
for node in nodes:
if node.score and node.score < min_score:
continue
results.append(RetrievalResult(
text=node.text,
score=node.score or 0.0,
source=node.metadata.get("file_name", "unknown"),
metadata=node.metadata
))
return results
def retrieve_as_context(self, query: str) -> str:
"""Truy xuất và format thành chuỗi context cho LLM."""
results = self.retrieve(query)
if not results:
return "Không tìm thấy tài liệu liên quan."
parts = []
for i, r in enumerate(results, 1):
parts.append(
f"[Tài liệu {i}] (nguồn: {r.source}, "
f"relevance: {r.score:.2f})\n{r.text}"
)
return "\n\n---\n\n".join(parts)
Bước 5: MCP Server — Thực Thi Tool
# mcp_server/tools/task_manager.py
import json
import sqlite3
from datetime import datetime
from pathlib import Path
DB_PATH = Path("./data/tasks.db")
def init_db():
"""Khởi tạo database tasks."""
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(DB_PATH))
conn.execute("""
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
status TEXT DEFAULT 'todo',
priority TEXT DEFAULT 'medium',
project TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
conn.close()
def create_task(title: str, description: str = "",
priority: str = "medium", project: str = "") -> dict:
"""Tạo task mới."""
init_db()
conn = sqlite3.connect(str(DB_PATH))
cursor = conn.execute(
"INSERT INTO tasks (title, description, priority, project) "
"VALUES (?, ?, ?, ?)",
(title, description, priority, project)
)
task_id = cursor.lastrowid
conn.commit()
conn.close()
return {"id": task_id, "title": title, "status": "todo"}
def list_tasks(project: str = "", status: str = "") -> list[dict]:
"""Liệt kê tasks với bộ lọc tùy chọn."""
init_db()
conn = sqlite3.connect(str(DB_PATH))
conn.row_factory = sqlite3.Row
query = "SELECT * FROM tasks WHERE 1=1"
params = []
if project:
query += " AND project = ?"
params.append(project)
if status:
query += " AND status = ?"
params.append(status)
query += " ORDER BY created_at DESC"
rows = conn.execute(query, params).fetchall()
conn.close()
return [dict(row) for row in rows]
def update_task(task_id: int, status: str) -> dict:
"""Cập nhật trạng thái task."""
init_db()
conn = sqlite3.connect(str(DB_PATH))
conn.execute(
"UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?",
(status, datetime.now().isoformat(), task_id)
)
conn.commit()
conn.close()
return {"id": task_id, "status": status, "updated": True}
# mcp_server/server.py
from mcp.server import Server
from mcp.types import TextContent
from mcp_server.tools.task_manager import create_task, list_tasks, update_task
import json
server = Server("agentic-tools")
@server.tool()
async def tool_create_task(
title: str, description: str = "",
priority: str = "medium", project: str = ""
) -> list[TextContent]:
"""Tạo task mới trong task manager."""
result = create_task(title, description, priority, project)
return [TextContent(type="text", text=json.dumps(result, indent=2))]
@server.tool()
async def tool_list_tasks(
project: str = "", status: str = ""
) -> list[TextContent]:
"""Liệt kê tất cả tasks."""
tasks = list_tasks(project, status)
return [TextContent(type="text", text=json.dumps(tasks, indent=2, default=str))]
@server.tool()
async def tool_update_task(
task_id: int, status: str
) -> list[TextContent]:
"""Cập nhật trạng thái task."""
result = update_task(task_id, status)
return [TextContent(type="text", text=json.dumps(result, indent=2))]
Bước 6: Agent Orchestrator — LangGraph
Đây là bộ não. Triển khai hoàn chỉnh:
# agent/orchestrator.py
from langgraph.graph import StateGraph, END
from typing import TypedDict
import json
import logging
from agent.llm import LLMClient
from agent.memory import ConversationMemory
from rag.retriever import DocumentRetriever
from mcp_server.tools.task_manager import create_task, list_tasks, update_task
logger = logging.getLogger(__name__)
class AgentState(TypedDict):
messages: list[dict]
user_input: str
session_id: str
intent: str
rag_context: str
tool_results: list[dict]
final_response: str
def classify_node(state: AgentState) -> AgentState:
"""Phân loại intent của người dùng."""
llm = LLMClient()
response = llm.chat(
messages=[{
"role": "system",
"content": """Phân loại message này thành MỘT category:
- RAG: cần tìm tài liệu, kiến thức, thông tin dự án
- TOOL: cần tạo/cập nhật/liệt kê task hoặc thực hiện hành động
- BOTH: cần tìm thông tin VÀ thực hiện hành động
- DIRECT: câu hỏi đơn giản có thể trả lời trực tiếp
Chỉ trả lời tên category."""
}, {"role": "user", "content": state["user_input"]}],
model="mistral-small:4",
temperature=0
)
state["intent"] = response.message.content.strip().lower()
logger.info(f"Intent: {state['intent']}")
return state
def rag_node(state: AgentState) -> AgentState:
"""Truy xuất tài liệu liên quan."""
from rag.indexer import DocumentIndexer
indexer = DocumentIndexer()
retriever = DocumentRetriever(indexer)
state["rag_context"] = retriever.retrieve_as_context(state["user_input"])
return state
def tool_node(state: AgentState) -> AgentState:
"""Thực thi tools dựa trên request."""
llm = LLMClient()
decision = llm.chat(
messages=[{
"role": "system",
"content": """Bạn có các tools sau:
1. create_task(title, description, priority, project)
2. list_tasks(project, status)
3. update_task(task_id, status)
Quyết định tool nào cần gọi.
Trả lời JSON: {"tool": "tên_tool", "params": {...}}"""
}, {"role": "user", "content": state["user_input"]}],
temperature=0
)
try:
d = json.loads(decision.message.content)
tool_name = d.get("tool", "none")
params = d.get("params", {})
if tool_name == "create_task":
result = create_task(**params)
elif tool_name == "list_tasks":
result = list_tasks(**params)
elif tool_name == "update_task":
result = update_task(**params)
else:
result = {"message": "Không cần tool"}
state["tool_results"].append({"tool": tool_name, "result": result})
except Exception as e:
state["tool_results"].append({"tool": "error", "result": {"error": str(e)}})
return state
def respond_node(state: AgentState) -> AgentState:
"""Tạo phản hồi cuối cùng."""
llm = LLMClient()
system_parts = ["Bạn là trợ lý AI hữu ích. Trả lời ngắn gọn và hữu ích."]
if state.get("rag_context"):
system_parts.append(f"\nContext từ tài liệu:\n{state['rag_context']}")
if state.get("tool_results"):
system_parts.append(f"\nKết quả tool:\n{json.dumps(state['tool_results'], indent=2, default=str)}")
messages = [
{"role": "system", "content": "\n".join(system_parts)},
*state["messages"],
{"role": "user", "content": state["user_input"]}
]
response = llm.chat(messages=messages, temperature=0.7)
state["final_response"] = response.message.content
return state
def route_by_intent(state: AgentState) -> str:
intent = state["intent"]
if "rag" in intent and "both" not in intent:
return "rag"
elif "tool" in intent:
return "tool"
elif "both" in intent:
return "rag"
return "respond"
def after_rag(state: AgentState) -> str:
if "both" in state["intent"]:
return "tool"
return "respond"
def build_agent_graph() -> StateGraph:
workflow = StateGraph(AgentState)
workflow.add_node("classify", classify_node)
workflow.add_node("rag", rag_node)
workflow.add_node("tool", tool_node)
workflow.add_node("respond", respond_node)
workflow.set_entry_point("classify")
workflow.add_conditional_edges("classify", route_by_intent,
{"rag": "rag", "tool": "tool", "respond": "respond"})
workflow.add_conditional_edges("rag", after_rag,
{"tool": "tool", "respond": "respond"})
workflow.add_edge("tool", "respond")
workflow.add_edge("respond", END)
return workflow.compile()
class AgentOrchestrator:
"""Agent cấp cao quản lý sessions và memory."""
def __init__(self):
self.graph = build_agent_graph()
self.memory = ConversationMemory()
def run(self, user_input: str, session_id: str = "default") -> str:
history = self.memory.get_history(session_id)
result = self.graph.invoke({
"messages": history,
"user_input": user_input,
"session_id": session_id,
"intent": "",
"rag_context": "",
"tool_results": [],
"final_response": ""
})
self.memory.add(session_id, "user", user_input)
self.memory.add(session_id, "assistant", result["final_response"])
return result["final_response"]
Luồng xử lý:
flowchart TD
Start["BAT DAU"] --> Classify["Phan Loai Intent"]
Classify -->|intent=rag| RAG["RAG"]
Classify -->|intent=tool| Tool1["Tool"]
Classify -->|intent=direct| Respond1["Phan Hoi"]
RAG -->|"intent=both?"| Tool2["Tool"]
RAG -->|"intent=rag"| Respond2["Phan Hoi"]
Tool1 --> Respond3["Phan Hoi"]
Tool2 --> Respond4["Phan Hoi"]
Respond1 --> End["KET THUC"]
Respond2 --> End
Respond3 --> End
Respond4 --> EndBước 7: API Server
# agent/server.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from agent.orchestrator import AgentOrchestrator
app = FastAPI(title="Agentic AI System", version="0.1.0")
agent = AgentOrchestrator()
class ChatRequest(BaseModel):
message: str
session_id: str = "default"
class ChatResponse(BaseModel):
response: str
session_id: str
@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
try:
response = agent.run(
user_input=request.message,
session_id=request.session_id
)
return ChatResponse(response=response, session_id=request.session_id)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
async def health():
from agent.llm import LLMClient
llm = LLMClient()
return {"status": "healthy", "llm_available": llm.health_check()}
Bước 8: Docker Compose — Mọi Thứ Cùng Nhau
# docker-compose.yml
version: '3.8'
services:
agent:
build: .
ports:
- "8000:8000"
environment:
- OLLAMA_HOST=http://ollama:11434
- CHROMA_HOST=http://chromadb:8000
- LANGFUSE_HOST=http://langfuse:3000
volumes:
- ./docs:/app/docs
- ./data:/app/data
depends_on:
- ollama
- chromadb
ollama:
image: ollama/ollama:latest
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
chromadb:
image: chromadb/chroma:latest
ports:
- "8001:8000"
volumes:
- chroma_data:/chroma/chroma
frontend:
build:
context: ./frontend
ports:
- "3000:8501"
environment:
- API_URL=http://agent:8000
langfuse:
image: langfuse/langfuse:latest
ports:
- "3100:3000"
environment:
- DATABASE_URL=postgresql://langfuse:langfuse@langfuse-db:5432/langfuse
depends_on:
- langfuse-db
langfuse-db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=langfuse
- POSTGRES_PASSWORD=langfuse
- POSTGRES_DB=langfuse
volumes:
- langfuse_db:/var/lib/postgresql/data
volumes:
ollama_data:
chroma_data:
langfuse_db:
Bước 9: Khởi Chạy & Kiểm Tra
# 1. Khởi động mọi thứ
docker compose up -d
# 2. Đợi Ollama sẵn sàng, sau đó tải models
docker compose exec ollama ollama pull gemma4:e4b
docker compose exec ollama ollama pull mistral-small:4
# 3. Index tài liệu
docker compose exec agent python -c "
from rag.indexer import DocumentIndexer
indexer = DocumentIndexer()
count = indexer.index_directory('./docs')
print(f'Đã index {count} tài liệu')
"
# 4. Health check
curl http://localhost:8000/health
# 5. Test agent
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message": "Tạo task review API design", "session_id": "test"}'
# 6. Mở UI
open http://localhost:3000
# 7. Xem traces
open http://localhost:3100
Quyết Định Kiến Trúc
Tại Sao LangGraph Thay Vì CrewAI?
LANGGRAPH vs CREWAI — Ma Trận Quyết Định
| Tiêu chí | LangGraph | CrewAI |
|---|---|---|
| Kiểm soát | Full graph control | Agent delegation abstraction |
| Debug | Trace từng bước | Chỉ log cấp cao |
| Production Ready | Đã kiểm chứng | Đang phát triển, ít proven hơn |
| Tốt nhất cho | Single agent phức tạp | Phối hợp multi-agent team |
Quyết định: LangGraph cho core orchestrator. Thêm CrewAI khi cần phối hợp multi-agent.
Lộ Trình Scale
LO TRINH SCALE CUA BAN
- Phase 1 (Xong): He thong dev local — Moi thu chay tren may ban
- Phase 2: Them tools — GitHub MCP, Slack MCP, calendar, email
- Phase 3: Multi-agent — CrewAI cho doi agent chuyen biet
- Phase 4: Cung hoa production — Auth, rate limiting, error recovery
- Phase 5: Scale — Cloud LLM, managed vector DB, Kubernetes
Pattern kiến trúc giữ nguyên. Chỉ implementation thay đổi.
Các Use Case Thực Tế: Có Thể Xây Dựng Gì?
Khi hệ thống cơ bản đã chạy, đây là các pattern production-ready để mở rộng:
Use Case 1: Bot Review PR Tự Động
# Them vao mcp_server/tools/github_tools.py
@server.tool()
async def review_pull_request(repo: str, pr_number: int) -> list[TextContent]:
"""Review mot GitHub PR de tim van de."""
# Lay diff PR qua GitHub MCP
diff = await github_client.get_pr_diff(repo, pr_number)
# Index diff vao temp vector store
indexer = DocumentIndexer(persist_dir="/tmp/pr_review")
indexer.index_text(diff)
# Agent review voi code context
agent = AgentOrchestrator()
review = agent.run(
f"Review PR diff nay de tim: loi bao mat, chat luong code, "
f"van de kien truc. Cu the voi tham chieu dong.\n\n{diff[:8000]}"
)
await github_client.post_review(repo, pr_number, review)
return [TextContent(type="text", text=f"Review da dang len PR #{pr_number}")]
Use Case 2: Ghi Chú Cuộc Họp Thành Action Items
async def xu_ly_ghi_chu_hop(ghi_chu: str, du_an: str) -> dict:
agent = AgentOrchestrator()
# Trich xuat action items
ket_qua = agent.run(
f"Trich xuat tat ca action items tu ghi chu hop nay. "
f"Xac dinh: nguoi phu trach, deadline, do uu tien.\n\n{ghi_chu}",
session_id="xu_ly_hop"
)
# Tu dong tao tasks
tasks_tao = []
for item in parse_action_items(ket_qua):
task = create_task(
title=item["hanh_dong"],
description=f"Tu ghi chu hop. Nguoi phu trach: {item['nguoi_phu_trach']}",
priority=item["do_uu_tien"],
project=du_an
)
tasks_tao.append(task)
return {"action_items": len(tasks_tao), "tasks": tasks_tao}
Use Case 3: Bot Onboarding RAG
Index toàn bộ knowledge base công ty để nhân viên mới hỏi bất cứ điều gì:
# Index nhieu nguon
indexer = DocumentIndexer(collection_name="kien_thuc_cong_ty")
indexer.index_directory("./docs/engineering")
indexer.index_directory("./docs/product")
indexer.index_directory("./docs/quy_trinh")
# Agent tra loi cau hoi onboarding kem trich dan
agent = AgentOrchestrator()
tra_loi = agent.run(
"Lam the nao de yeu cau quyen truy cap database production?",
session_id="nhan_vien_moi_123"
)
# Tra ve: "Theo Security Runbook (security/access.md),
# ban can gui yeu cau qua [link]..."
Bước 10: Persistent Memory
Không có memory, mỗi cuộc hội thoại bắt đầu từ đầu. Thêm persistence:
# agent/memory.py
import json
from pathlib import Path
from datetime import datetime
class ConversationMemory:
"""Lich su hoi thoai luu tru lau dai."""
def __init__(self, db_path: str = "./data/memory.json"):
self.db_path = Path(db_path)
self.db_path.parent.mkdir(parents=True, exist_ok=True)
self._store: dict = {}
self._load()
def _load(self):
if self.db_path.exists():
self._store = json.loads(self.db_path.read_text())
def _save(self):
self.db_path.write_text(json.dumps(self._store, default=str))
def add(self, session_id: str, role: str, content: str):
if session_id not in self._store:
self._store[session_id] = []
self._store[session_id].append({
"role": role,
"content": content,
"timestamp": datetime.now().isoformat()
})
# Giu toi da 20 tin nhan moi session
self._store[session_id] = self._store[session_id][-20:]
self._save()
def get_history(self, session_id: str, limit: int = 10) -> list[dict]:
messages = self._store.get(session_id, [])[-limit:]
return [{"role": m["role"], "content": m["content"]} for m in messages]
def list_sessions(self) -> list[str]:
return list(self._store.keys())
Bước 11: Deploy Lên Production
Ba lựa chọn deployment từ đơn giản nhất đến scalable nhất:
Lựa Chọn A: Single VPS (Khuyến Nghị Cho <1K Users)
# Tren VPS $20/thang bat ky (4 CPU, 16GB RAM)
# Khong can GPU - dung Ollama CPU mode hoac fallback OpenAI API
# 1. Pull repo
git clone https://github.com/your-org/agentic-system
cd agentic-system
# 2. Cau hinh environment
cp .env.example .env
# Chinh sua: OLLAMA_HOST, API keys, v.v.
# 3. Khoi dong tat ca
docker compose up -d
# 4. Cai dat reverse proxy (Caddy)
# HTTPS tu dong, khong can cau hinh
caddy reverse-proxy --from ten-mien-cua-ban.com --to localhost:8000
Lựa Chọn B: Cloudflare Workers (Serverless, Global)
Chỉ cho API layer — giữ Ollama trên server GPU riêng:
npm install -g wrangler
wrangler deploy --name agentic-api
# Dat secrets
wrangler secret put OLLAMA_HOST
wrangler secret put LANGFUSE_KEY
Lựa Chọn C: HuggingFace Spaces (GPU Tier Mien Phi)
# app.py - Streamlit tren HuggingFace Spaces
# Bao gom T4 GPU mien phi cho Ollama inference
import streamlit as st
import subprocess
import threading
def khoi_dong_ollama():
subprocess.run(["ollama", "serve"])
subprocess.run(["ollama", "pull", "gemma4:e4b"])
threading.Thread(target=khoi_dong_ollama, daemon=True).start()
Các Pattern Kiến Trúc Nâng Cao
Pattern 1: Agent Mesh (Nhiều Agent Chuyên Biệt)
class AgentMesh:
def __init__(self):
self.agents = {
"code": AgentOrchestrator(system_prompt="Ban la senior software engineer..."),
"data": AgentOrchestrator(system_prompt="Ban la chuyen vien phan tich du lieu..."),
"general": AgentOrchestrator()
}
def xu_ly(self, query: str, session_id: str) -> str:
domain = phan_loai_domain(query) # "code", "data", "general"
return self.agents[domain].run(query, session_id)
Pattern 2: Human-in-the-Loop
def thuc_thi_co_phe_duyet(hanh_dong: dict) -> dict:
HANH_DONG_RUI_RO = {"xoa_database", "gui_email_hang_loat", "deploy_production"}
if hanh_dong["tool"] in HANH_DONG_RUI_RO:
phe_duyet = yeu_cau_phe_duyet_nguoi_dung(
hanh_dong=hanh_dong,
timeout_giay=300
)
if not phe_duyet.da_duoc_chap_nhan:
return {"trang_thai": "bi_tu_choi", "ly_do": phe_duyet.ly_do}
return thuc_thi_hanh_dong(hanh_dong)
Pattern 3: Định Tuyến Tiết Kiệm Chi Phí
MUC_CHI_PHI = {
"nhanh_re": "mistral-small:4", # Cau hoi don gian
"can_bang": "gemma4:e4b", # Phan lon task
"manh": "llama3.3:70b", # Suy luan phuc tap
"chuyen_biet": "deepseek-coder-v3:6b" # Task code
}
def dinh_tuyen_theo_chi_phi(query: str, ngan_sach: str = "can_bang") -> str:
do_phuc_tap = uoc_tinh_do_phuc_tap(query) # Diem 1-10
if do_phuc_tap <= 3 or ngan_sach == "re":
return MUC_CHI_PHI["nhanh_re"]
elif "code" in query.lower():
return MUC_CHI_PHI["chuyen_biet"]
elif do_phuc_tap > 6:
return MUC_CHI_PHI["manh"]
return MUC_CHI_PHI["can_bang"]
Mọi tool trong hướng dẫn này đều open-source. Mọi dòng code đều production-ready. Kiến trúc sẽ phục vụ bạn từ laptop đến cloud.