Đến giờ ta có các mảnh rời: tra cứu (RAG), trích rủi ro, soạn thảo. Bài này ghép chúng thành một agent điều phối biết chọn công cụ phù hợp cho từng yêu cầu, giữ ngữ cảnh (memory), và luôn dừng lại ở điểm human review.
Kiến trúc agent
+----------------------+
User -> | Orchestrator (LLM) | quyết định gọi tool nào
+----------+-----------+
|
+---------------+----------------+-------------------+
v v v v
[search_docs] [extract_risks] [draft_clause] [check_playbook]
| | | |
+---------------+----------------+-------------------+
v
[Human Review Gate] -> mọi output ra ngoài
Định nghĩa tools (tool schema)
Agent chọn tool dựa trên mô tả. Viết mô tả rõ ràng để nó không gọi nhầm:
[
{
"name": "search_docs",
"description": "Tra cứu điều khoản/quy định liên quan trong kho tài liệu. Dùng khi cần tìm thông tin trước khi trả lời.",
"parameters": {"query": "string", "top_k": "int"}
},
{
"name": "extract_risks",
"description": "Trích các điều khoản rủi ro từ một hợp đồng ra JSON có cấu trúc.",
"parameters": {"contract_id": "string"}
},
{
"name": "draft_clause",
"description": "Soạn NHÁP điều khoản từ clause library. Luôn kèm dấu cần xác nhận.",
"parameters": {"clause_type": "string", "context": "string"}
}
]
Vòng lặp agent (ReAct rút gọn)
def run_agent(user_request):
messages = [system_prompt, user_request]
for step in range(MAX_STEPS): # chặn vòng lặp vô hạn
resp = llm(messages, tools=TOOLS)
if resp.tool_call:
result = call_tool(resp.tool_call) # thực thi tool
messages.append(result)
else:
break # agent đã có câu trả lời
return require_human_review(resp) # KHÔNG trả thẳng cho user
Hai chốt an toàn: MAX_STEPS chống vòng lặp tốn tiền, và require_human_review đảm bảo output legal luôn qua người.
Memory: giữ ngữ cảnh đúng cách
- Short-term (trong phiên): lịch sử hội thoại, hợp đồng đang xử lý.
- Long-term (qua phiên): playbook, clause library, các quyết định đã duyệt trước đó — lưu ở vector store/DB, KHÔNG nhồi hết vào prompt.
- KHÔNG lưu dữ liệu nhạy cảm vào memory dùng chung nếu chưa ẩn danh.
Ví dụ luồng thực tế
Người dùng: "Rà giúp hợp đồng HĐ-2024-07 và soạn đề xuất sửa điều khoản phạt."
- Agent gọi
extract_risks(HĐ-2024-07)-> tìm thấy Điều 15 phạt 8%/ngày. - Gọi
check_playbook-> "phạt > 0.05%/ngày = rủi ro cao". - Gọi
search_docs("mẫu điều khoản phạt vi phạm")-> lấy mẫu chuẩn. - Gọi
draft_clause(penalty, context)-> nháp phạt 0.05%/ngày, có [CẦN XÁC NHẬN]. - Đưa toàn bộ vào Human Review Gate -> luật sư duyệt.
Xử lý lỗi giữa chừng
Agent thực tế phải chịu được lỗi: tool trả về rỗng, JSON hỏng, hoặc vector store timeout. Nguyên tắc fail-safe cho legal là khi nghi ngờ thì dừng và báo người, tuyệt đối không đoán bừa để "cho có kết quả".
def call_tool(tc):
try:
return TOOLS[tc.name](**tc.args)
except Exception as e:
# Không nuốt lỗi âm thầm; báo để người xử lý
return {"status": "tool_error", "tool": tc.name, "detail": str(e)}
Khi orchestrator thấy tool_error, nó nên dừng và chuyển sang human review kèm thông báo rõ, thay vì bịa câu trả lời từ dữ liệu thiếu.
Khung tư duy: PACER
Plan: agent lập kế hoạch tool nào trước.
Act: gọi tool.
Check: đối chiếu playbook.
Explain: giải thích lý do đề xuất.
Review: con người duyệt trước khi ra ngoài.
Checklist agent hoàn chỉnh
- [ ] Mỗi tool có mô tả rõ để agent chọn đúng?
- [ ] Có MAX_STEPS chống vòng lặp vô hạn?
- [ ] Human review gate đứng trước MỌI output ra ngoài?
- [ ] Long-term memory không chứa dữ liệu nhạy cảm chưa ẩn danh?
- [ ] Có log lại chuỗi tool đã gọi để audit?
Sai lầm thường gặp
- Mô tả tool mơ hồ: agent gọi nhầm tool, ra kết quả sai loại.
- Không giới hạn số bước: agent lặp gọi tool, đốt token và có thể treo.
- Nhồi toàn bộ playbook vào prompt: vượt context, tốn tiền, giảm chất lượng — hãy tra cứu khi cần.
- Bỏ human gate vì "để tự động cho nhanh": đúng lỗi chết người trong legal.
- Lưu hợp đồng thật vào memory dùng chung: rò rỉ chéo giữa các phiên/khách hàng.