Xây dựng agent nghiên cứu với Web Search + URL Extract (tool use của Claude, từ đầu đến cuối)

Hầu hết các hướng dẫn về 'agent nghiên cứu AI' chỉ dừng ở 'đây là một định nghĩa công cụ'. Bài này giao một agent hoạt động được: đưa vào một câu hỏi, nhận ra một câu trả lời có trích dẫn. Tìm kiếm, trích xuất, suy luận, trích dẫn — tất cả trong chưa tới 120 dòng Python.
Tóm tắt
- •Vòng lặp agent nghiên cứu nhỏ nhất mà vẫn hữu ích là: tìm kiếm → chọn liên kết → trích xuất nội dung → yêu cầu mô hình trả lời kèm trích dẫn nội dòng.
- •Hai công cụ — Web Search (15 credit) và URL Extract (2 credit mỗi URL) — bao phủ 95% các trường hợp cần câu trả lời có căn cứ.
- •Tool use của Claude lo phần điều phối; bạn chỉ việc chuyển các khối tool_use → lời gọi API → các khối tool_result cho tới khi mô hình dừng lại.
- •Chi phí từ đầu đến cuối: ~25 credit + ~$0.02 tiền token LLM cho mỗi câu hỏi ở độ sâu thông thường.
Chúng ta sẽ xây dựng gì
Một agent nghiên cứu nhận vào một đầu vào — một câu hỏi bằng ngôn ngữ tự nhiên — và trả về một câu trả lời có nguồn. Về mặt kiến trúc:
question → [Claude]
↓ tool_use(search)
[API Pick Web Search] → ranked URLs
↓ tool_use(extract)
[API Pick URL Extract] → cleaned bodies
↓ Claude reads, decides if more is needed
↓ end_turn
answer with inline [source: URL] citationsKhoảng 120 dòng Python. Hai công cụ — search và extract — và một vòng lặp agent xử lý bất cứ điều gì Claude quyết định làm. Chúng ta sẽ xây dựng nó từ đầu trong 4 bước.
1Lấy về các schema công cụ (không cần viết JSON bằng tay)
Cả hai endpoint đều công bố một route tool-schema trả về một định nghĩa công cụ của Claude đúng theo hình dạng mà messages.create mong đợi.
import requests
API_KEY = "pk_yourkey"
def fetch_tool(tool_path: str) -> dict:
"""Fetch a Claude tool definition from API Pick's tool-schema endpoint."""
schema = requests.get(f"https://www.apipick.com{tool_path}/tool-schema").json()
return schema["claude"]
WEB_SEARCH_TOOL = fetch_tool("/api/search/web")
URL_EXTRACT_TOOL = fetch_tool("/api/extract")
# Cache these once at module load. They don't change between requests.Hai điều quan trọng mà việc này mang lại cho bạn miễn phí: schema tham số (để Claude biết bạn chấp nhận query, country_code, start_date, v.v.) và một mô tả rõ ràng, thân thiện với mô hình về việc công cụ làm gì.
2Viết handler cho công cụ
Khi Claude trả về một khối tool_use, nhiệm vụ của bạn là gọi API thực và trả về một khối tool_result. Một hàm cho mỗi công cụ, một dispatcher định tuyến dựa trên cái tên mà Claude đã chọn.
def call_tool(tool_use_block) -> dict:
"""Execute the tool Claude asked for and return a tool_result block."""
name = tool_use_block.name
args = tool_use_block.input
if name == "web_search":
endpoint = "https://www.apipick.com/api/search/web"
elif name == "extract_urls":
endpoint = "https://www.apipick.com/api/extract"
else:
return {
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": f"Unknown tool: {name}",
"is_error": True,
}
resp = requests.post(
endpoint,
headers={"x-api-key": API_KEY},
json=args,
timeout=30,
)
if resp.status_code != 200:
return {
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": f"HTTP {resp.status_code}: {resp.text[:500]}",
"is_error": True,
}
return {
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": resp.text,
}Ba điều đáng lưu ý. tool_use_id là cách Claude liên kết kết quả với lời gọi trước đó của nó — bạn phải gửi nó trả lại nguyên vẹn. is_error: True bảo Claude phục hồi một cách uyển chuyển (thường bằng cách thử một truy vấn khác). Và chúng ta truyền văn bản phản hồi thô — Claude thoải mái với hình dạng JSON mà API Pick trả về, nên không cần biến đổi gì.
3Vòng lặp agent
Vòng lặp rất ngắn: gửi cuộc hội thoại, xem từng khối trong phản hồi, chạy mọi công cụ, nối vào cuộc hội thoại cả tool_use của Claude lẫn tool_result của bạn, lặp lại cho tới khi stop_reason là end_turn.
import anthropic
client = anthropic.Anthropic()
SYSTEM_PROMPT = """You are a research assistant. When the user asks a question:
1. Use web_search to find relevant sources. Prefer recent (set start_date) for time-sensitive queries.
2. Use extract_urls to read the body of the top 3-5 most relevant URLs from the search results.
3. Answer the question concisely. After every factual claim, include an inline citation in the form [source: URL].
4. If you don't have enough information, say so — don't fabricate.
Be concise. Aim for 3-5 sentence answers unless the user asks for depth."""
def research(question: str) -> str:
messages = [{"role": "user", "content": question}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
system=SYSTEM_PROMPT,
tools=[WEB_SEARCH_TOOL, URL_EXTRACT_TOOL],
messages=messages,
)
# Append the assistant's response (may contain tool_use blocks)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
# Pull out the text answer
return "\n".join(b.text for b in response.content if b.type == "text")
if response.stop_reason == "tool_use":
# Run every tool the model asked for in this turn
tool_results = [
call_tool(b)
for b in response.content if b.type == "tool_use"
]
messages.append({"role": "user", "content": tool_results})
continue
raise RuntimeError(f"Unexpected stop_reason: {response.stop_reason}")Để ý rằng mô hình có thể kích hoạt nhiều lời gọi công cụ trong cùng một lượt — chẳng hạn, nó có thể phát ra một lời gọi extract_urls duy nhất với năm URL để xử lý chúng theo lô. Chúng ta xử lý việc đó bằng cách thu thập tất cả các khối tool_use từ lượt của trợ lý và trả về tất cả các khối tool_result trong lượt người dùng kế tiếp.
4Chạy thử
if __name__ == "__main__":
answer = research("What were the major announcements at OpenAI DevDay this year?")
print(answer)Bạn sẽ thấy một thứ đại loại như:
OpenAI's DevDay focused on three themes: cheaper inference (a new gpt-mini
tier at roughly half the prior cost) [source: https://openai.com/devday-2026],
agent infrastructure (a managed agent runtime with persistent memory and
tool sandboxing) [source: https://techcrunch.com/...], and developer
tooling (a new Responses API replacing the legacy Assistants API)
[source: https://platform.openai.com/docs/...].Đó là một agent nghiên cứu. ~120 dòng, hai công cụ, đầu ra có nguồn.
Chi phí của việc này
Một lời gọi nghiên cứu điển hình:
- 1 lời gọi tìm kiếm — 15 credit ($0.015)
- 1 lời gọi trích xuất bao phủ 3–5 URL — 6–10 credit ($0.006–$0.010)
- ~3,000 token đầu vào + 800 token đầu ra của Claude — ~$0.02
Con số tròn: ~3 xu cho mỗi câu trả lời đã nghiên cứu. Với 1,000 lời gọi nghiên cứu mỗi ngày thì khoảng ~$30/ngày — gần bằng ngân sách cà phê buổi sáng của một nhà phân tích. Hãy dự trù ngân sách cho LLM, chứ không phải cho các API tìm kiếm.
Ba tinh chỉnh cho môi trường sản xuất
1. Cache theo câu hỏi
Nếu sản phẩm của bạn đặt những câu hỏi tương tự nhau, hãy băm (question, today's date) và cache câu trả lời cuối trong một giờ. Phần lớn hoạt động nghiên cứu do người dùng dẫn dắt có tỷ lệ trúng đuôi dài trên 30%.
2. Ràng buộc khi tính cập nhật quan trọng
Với các câu hỏi 'tuần này' / 'hôm nay', hãy đặt start_date trong lời gọi tìm kiếm bằng ngày hiện tại hoặc hôm qua. Nếu không có nó, tìm kiếm có thể trả về các bài viết từ năm ngoái. Phiên bản đơn giản nhất: một quy tắc trong system prompt như 'luôn bao gồm start_date khi câu hỏi chứa hôm nay, tuần này, gần đây hoặc mới nhất'.
3. Rào chắn chống ảo giác
Quy tắc có đòn bẩy cao nhất: 'Nếu bạn không có đủ nội dung đã trích xuất để trả lời, hãy trả về: Tôi không tìm được nguồn đáng tin cậy cho điều này. Đừng đoán mò.' Thêm điều này vào system prompt giúp giảm các câu trả lời bịa đặt đi một bậc độ lớn trong các thử nghiệm của chúng tôi.
Đưa nó đi tiếp tới đâu
Ba mở rộng tự nhiên:
- Chuyên biệt hóa theo ngành: đổi Web Search sang Academic Search cho một agent điểm lại tài liệu, SEC Filings Search cho thẩm định, hoặc Clinical Search cho nghiên cứu y khoa. Handler không thay đổi — chỉ system prompt thay đổi.
- Thêm đầu ra có cấu trúc: yêu cầu mô hình trả về JSON với các trường
answervàcitations[]để bạn có thể hiển thị chúng thành một thẻ UI. - Stream token: chuyển sang
client.messages.stream()để có đầu ra tăng dần — hữu ích khi câu trả lời dài. Vòng lặp tool-call vẫn như cũ.
Câu hỏi thường gặp
Tại sao lại dùng hai công cụ thay vì một endpoint 'answer' duy nhất?
Tách riêng tìm kiếm và trích xuất cho bạn quyền kiểm soát đọc bao nhiêu URL, khi nào dừng và trích dẫn những URL nào. Một endpoint 'answer' được lưu trữ duy nhất sẽ che giấu điều này — bạn không thể dễ dàng đổi chiến lược mà không phải tái thiết kế. Với hai công cụ, cùng một đoạn mã có thể trở thành agent tóm tắt tin buổi sáng, một trình duyệt tài liệu hoặc một bộ thu thập thông tin cạnh tranh chỉ bằng cách chỉnh prompt.
Cái này có hoạt động với OpenAI Assistants / Responses API không?
Có. Kiến trúc hoàn toàn giống nhau — điều duy nhất thay đổi là cách bạn phân tích các khối tool-call và gửi kết quả về. Hình dạng của handler trở thành submit_tool_outputs(...) thay vì một khối nội dung tool_result, nhưng vòng lặp agent và các định nghĩa công cụ vẫn là cùng một JSON.
Agent quyết định khi nào dừng như thế nào?
Claude trả về stop_reason: end_turn khi nó có đủ ngữ cảnh để trả lời, và tool_use khi nó muốn gọi một công cụ. Vòng lặp cứ tiếp tục cho tới end_turn. Trên thực tế, mô hình thường thực hiện 1–3 chu kỳ tìm kiếm/trích xuất trước khi trả lời.
Làm sao để chắc chắn rằng nó thực sự trích dẫn nguồn?
Hai đòn bẩy. Thứ nhất: trong system prompt, yêu cầu rõ ràng '[source: URL]' nội dòng sau mỗi khẳng định. Thứ hai: phản hồi của URL Extract bao gồm cả URL, nên khi mô hình có nó trong ngữ cảnh thì nó có xu hướng trích dẫn một cách tự nhiên. Nếu trích dẫn bị bỏ sót, hãy thêm một bước định dạng cuối cùng để từ chối câu trả lời và yêu cầu mô hình bổ sung trích dẫn.
Còn độ trễ thì sao?
Mỗi lượt đi-về của tìm kiếm mất ~1s; mỗi lô trích xuất mất 1–4s tùy số lượng URL và việc kết xuất JS. Một lời gọi 'nghiên cứu một câu hỏi' điển hình rơi vào khoảng 5–15s thời gian thực. Nếu cần nhanh hơn, hãy song song hóa bước trích xuất (extract vốn đã nhận một danh sách URL trong một lời gọi) và giới hạn mô hình chỉ một lượt dùng công cụ cho mỗi câu hỏi bằng các chỉ dẫn trong system prompt.
Các API dùng trong bài viết này
Sarah Choy là CEO của API Pick. Cô viết về việc xây dựng các API sẵn sàng cho production cho AI agent và quy trình LLM.