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 --> F

Yê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__.py
      • orchestrator.py — LangGraph agent orchestrator
      • router.py — Dinh tuyen request thong minh
      • llm.py — LLM client (Ollama)
      • memory.py — Bo nho hoi thoai
      • server.py — FastAPI endpoint
    • rag/
      • __init__.py
      • indexer.py — Pipeline danh index tai lieu
      • retriever.py — Tim kiem tuong tu
      • embeddings.py — Thiet lap embedding model
    • mcp_server/
      • __init__.py
      • server.py — MCP server voi tools
      • tools/
        • task_manager.py — CRUD operations cho tasks
        • file_ops.py — Doc/ghi files
        • web_search.py — Tim kiem web
    • frontend/
      • app.py — Streamlit UI
      • components/
    • observability/
      • tracing.py — Tich hop Langfuse
      • metrics.py — Custom metrics
    • tests/
      • test_agent.py
      • test_rag.py
      • test_mcp.py
    • docs/ — Kho kien thuc cua ban
    • docker-compose.yml
    • Dockerfile
    • pyproject.toml
    • README.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 --> End

Bướ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íLangGraphCrewAI
Kiểm soátFull graph controlAgent delegation abstraction
DebugTrace từng bướcChỉ log cấp cao
Production ReadyĐã kiểm chứngĐang phát triển, ít proven hơn
Tốt nhất choSingle agent phức tạpPhố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.

Xuất nội dung

Bình luận