Agent research: đừng chỉ nối tool với model

Agent research: đừng chỉ nối tool với model

Một playbook mổ xẻ agent tìm kiếm web local: đáng tin không nằm ở model mới, mà ở orchestration, guardrail và log đủ rõ.

“Em đã nối web search với Qwen rồi, sao kết quả vẫn như karaoke lệch nhịp?” — một bạn dev hỏi mình vậy sau khi demo agent research nội bộ. Terminal chạy đẹp, query ra kết quả, model tóm tắt có vẻ trơn. Nhưng hỏi ngược lại nguồn nào đáng tin, trang nào bị bỏ qua, vì sao kết luận A chứ không phải B, thì cả team im như dàn nhạc mất nhạc trưởng.

Agent research cá nhân chạy local với Ollama, Qwen và Python là một demo rất đáng làm. Nó cho bạn cảm giác kiểm soát: query không bay qua hosted chat, không tính phí theo lượt gọi model, có thể chạy trên máy mình. Nhưng nếu bạn đang build cho production, câu hỏi không phải là “có chạy được không?”. Câu hỏi đúng hơn là: khi nó sai, bạn biết sai ở lớp nào không?

Sơ đồ minh họa cho bài Agent research: đừng chỉ nối tool với model

Sơ đồ tóm tắt ý chính của bài viết.

Mục tiêu: biến demo agent thành hệ thống có nhịp

Một agent tìm kiếm web thường có flow nhìn rất gọn:

  1. Nhận chủ đề từ người dùng.
  2. Gọi web search để lấy kết quả mới.
  3. Fetch vài trang liên quan.
  4. Đưa nội dung vào local LLM để tóm tắt.
  5. Trả về digest ngắn gọn.

Ở mức tutorial, flow này ổn. Ở mức team builder, nó thiếu vài lớp sống còn: tiêu chí chọn nguồn, giới hạn context, log quyết định, kiểm tra trích dẫn, và cách xử lý khi tool trả về rác.

Orchestration là cách điều phối nhiều bước, nhiều tool hoặc nhiều agent để hoàn thành việc. Với agent research, orchestration không chỉ là gọi hàm theo thứ tự. Nó giống người chỉ huy dàn nhạc: không tạo ra âm thanh thay từng nhạc công, nhưng quyết định ai vào trước, ai im lại, và đoạn nào phải đánh lại vì lệch nhịp.

Sau bài này, mình muốn bạn đổi một cách nghĩ: đừng đánh giá agent research bằng câu trả lời cuối cùng; hãy đánh giá bằng khả năng truy vết từng lớp tạo ra câu trả lời đó.

Checklist trước khi cho agent đọc web thật

Trước khi viết thêm prompt, hãy kiểm tra 6 câu này:

Ví dụ cụ thể: giả sử team bạn 5 người đang làm agent theo dõi tin tuyển dụng AI ở Việt Nam. Nếu agent chỉ trả về “có nhiều vị trí AI Engineer mới” mà không lưu query, URL, ngày fetch, lý do loại bỏ job cũ, thì báo cáo đó khó dùng. Nó có thể đúng hôm nay, sai ngày mai, và không ai biết sai vì đâu.

Mổ theo lớp: 5 tầng nên giữ, 2 tầng nên hoài nghi

Thay vì nhìn agent như một khối đen, mình hay tách thành 5 tầng vận hành.

1. Tầng intent: đừng để prompt gánh hết việc

User nhập: latest AI agent news.

Nghe đơn giản, nhưng intent có thể là:

Nếu không phân loại intent, agent sẽ tóm tắt kiểu chung chung. Với builder, nên có bước classify_task trước khi search:

Task type: daily_digest | technical_compare | vendor_watch | implementation_research
Output contract: bullets | decision memo | table | citations
Freshness requirement: today | this week | no constraint

Đây không phải thêm ceremony cho vui. Đây là cách bạn giữ nhịp phách cho cả flow.

2. Tầng retrieval: search không phải bằng chứng

Web search chỉ đưa ứng viên nguồn, chưa phải sự thật. Tool calling — khả năng để model gọi công cụ/API thay vì chỉ trả lời chữ — giúp agent lấy dữ liệu mới, nhưng không tự động làm dữ liệu đó đáng tin.

Một retrieval tốt nên log:

Nếu nguồn có nội dung trùng lặp, trang SEO mỏng, hoặc bài cũ được cập nhật tiêu đề mới, agent phải có cơ chế hạ điểm. Không cần phức tạp ngay từ đầu; chỉ cần rule rõ là đã hơn rất nhiều demo “search rồi nhét hết vào model”.

3. Tầng đọc trang: HTML là bản nhạc đầy tạp âm

Fetch page xong chưa chắc dùng được. Nội dung có menu, quảng cáo, related posts, cookie banner, comment, footer. Nếu bạn đưa toàn bộ vào model, hallucination — bịa nhưng nói tự tin — có thêm đất diễn.

Một bước làm sạch tối thiểu nên có:

def clean_page(raw_html):
    text = extract_main_content(raw_html)
    text = remove_boilerplate(text)
    text = normalize_whitespace(text)
    return text[:MAX_CHARS_PER_SOURCE]

Đừng copy snippet này như code production; hãy xem nó như contract. Bạn cần một hàm nhận HTML bẩn và trả về phần nội dung chính, có giới hạn độ dài.

4. Tầng synthesis: tóm tắt phải biết mức tự tin

Local LLM như Qwen chạy qua Ollama rất hợp cho privacy và chi phí. Nhưng model local nhỏ hơn có thể yếu hơn ở reasoning dài hoặc tổng hợp nhiều nguồn nhiễu. Vì vậy prompt synthesis nên bắt model tách ba phần:

Mẫu output hữu ích hơn:

Summary:
- ...

Evidence:
- Claim A: Source 1, Source 3
- Claim B: Source 2

Uncertain:
- ...

Recommended follow-up queries:
- ...

Dịch sang tiếng người: đừng bắt ca sĩ hát luôn cả phần bè, trống, violin rồi tự chấm điểm. Hãy chia vai để biết đoạn nào bị phô.

5. Tầng trace: không có log là không có production

Trace không cần đẹp. Nhưng phải trả lời được:

Ở đây, các bài về agentic harness và trajectory parsing rất đáng chú ý vì cùng nhấn vào một điểm: muốn cải thiện agent, bạn phải nhìn được hành trình hành động, không chỉ patch cuối hoặc câu trả lời cuối. Với research agent cũng vậy. Một digest hay nhưng không có trace chỉ là may mắn được đóng gói đẹp.

Làm trong một buổi: bản local có guardrail tối thiểu

Nếu bạn muốn dựng phiên bản thực dụng trong một buổi chiều, đừng bắt đầu bằng UI. Làm CLI trước.

Bước 1: định nghĩa output contract

{
  "topic": "string",
  "summary": ["string"],
  "claims": [
    {"claim": "string", "sources": ["url"], "confidence": "high|medium|low"}
  ],
  "uncertain": ["string"],
  "follow_up_queries": ["string"]
}

Bước 2: tách module theo lớp

agent/
  intent.py
  search.py
  fetch.py
  clean.py
  synthesize.py
  trace.py

Đừng gom hết vào một file main.py dài lê thê. Demo thì nhanh, debug thì đau.

Bước 3: lưu trace dạng JSONL

Mỗi event một dòng:

{"step":"search","query":"...","results_count":5}
{"step":"fetch","url":"...","status":"ok"}
{"step":"synthesize","model":"qwen-local","sources_used":3}

Bước 4: thêm refusal rule

Trong system prompt, thêm rule rõ:

If fewer than two relevant sources support a major claim, mark it as uncertain. Do not present it as fact.

Bước 5: test bằng 3 chủ đề khó chịu

Chọn một topic mới, một topic gây tranh cãi, và một topic có nhiều trang SEO. Nếu agent vẫn biết nói “chưa đủ bằng chứng”, bạn đang đi đúng hướng.

Pitfall: local không tự động đồng nghĩa với an toàn

Chạy local giúp giữ query trên máy và tránh phí theo lượt gọi hosted LLM. Điểm này rất đáng giá, nhất là với research cá nhân hoặc dữ liệu nhạy cảm. Nhưng local không giải quyết các lỗi sau:

Điều dễ bị thổi quá mức là “agent tự tìm web rồi tóm tắt” nghe như đã thành nhân viên nghiên cứu. Thực tế, nó mới là một pipeline có model ở giữa. Muốn thành hệ thống đáng tin, bạn phải thiết kế quyền hạn, bằng chứng, và điểm dừng.

Nếu là mình, mình sẽ chốt thế này

Với team Việt Nam đang build agent research nội bộ, mình sẽ chọn hướng này:

Còn nếu bạn đang phân vân giữa framework nhiều tầng và script Python đơn giản: bắt đầu bằng script, nhưng thiết kế ranh giới module như thể ngày mai phải thay nhạc trưởng. Khi production gọi tên, thứ cứu bạn không phải đoạn solo của model, mà là cả dàn nhạc biết vào đúng nhịp.

---
Bụi Wire — nghiện đọc release notes lúc 2 giờ sáng

Nguồn tham khảo