Web Search
Agent gọi tool web_search khi cần tra cứu thông tin thời gian thực. Server crawl và summarize kết quả, trả về cả nội dung lẫn danh sách nguồn.
Flow
1. Agent gọi tool web_search với query + prompt
2. Server tìm kiếm via Google Serper API
3. Server crawl 5 URL đầu tiên để lấy nội dung
4. AI summarize nội dung + trích xuất sources
5. Trả về summary text + artifact.sources
Realtime Stream
Bước 1 — Tool_use Block
Agent gọi web_search, server stream tool_use block với input đầy đủ trong content_block_start:
{
"type": "content_block_start",
"index": 2,
"content_block": {
"type": "tool_use",
"id": "toolu_01",
"name": "web_search",
"tool_content_message": "Tìm kiếm web",
"input": {
"query": "cổ phiếu HPG giá hôm nay",
"prompt": "Tìm giá và thông tin giao dịch mới nhất của HPG"
}
}
}
{
"type": "content_block_stop",
"index": 2
}
Bước 2 — Tool_result Block
Sau khi server hoàn thành crawl và summarize, emit tool_result block kèm artifact:
{
"type": "content_block_start",
"index": 3,
"content_block": {
"type": "tool_result",
"tool_use_id": "toolu_01",
"name": "web_search",
"status": "success",
"content": "HPG đang giao dịch ở 25,500 VND (+2.0%)...\n\nSources:\n[Simplize](https://simplize.vn/...)",
"artifact": {
"query": "cổ phiếu HPG giá hôm nay",
"sources": [
{
"url": "https://simplize.vn/co-phieu/HPG",
"title": "Cổ phiếu HPG — Tập đoàn Hòa Phát",
"snippet": "HPG: 25,500 VND (+2.0%) - Khối lượng: 12.3M",
"domain": "simplize.vn",
"favicon": "https://www.google.com/s2/favicons?domain=simplize.vn&sz=16"
},
{
"url": "https://cafef.vn/...",
"title": "HPG tăng mạnh trong phiên sáng",
"snippet": "...",
"domain": "cafef.vn",
"favicon": null
}
]
}
}
}
{
"type": "content_block_stop",
"index": 3
}
artifactkhông stream từng field — được trả về đầy đủ trong mộtcontent_block_start.
Cập Nhật Side Panel
Frontend lắng nghe tool_result event của web_search để cập nhật WebSearchSourcesPanel:
function handleSearchSourceSSEEvent(data, setSearchSources) {
if (data.type === 'content_block_start' && data.content_block?.type === 'tool_result') {
if (data.content_block.name === 'web_search' && data.content_block.artifact) {
const group = data.content_block.artifact;
if (group.query && group.sources) {
setSearchSources(prev => [...(prev || []), group]);
}
}
}
}
Mỗi lần web_search được gọi, một nhóm mới được append vào danh sách sources — không replace.
Artifact Structure
interface WebSearchArtifact {
query: string;
sources: WebSearchSource[];
}
interface WebSearchSource {
url: string;
title: string;
snippet: string;
domain: string;
favicon: string | null;
}
Favicon fallback: Nếu favicon null → tự tạo từ domain:
const favicon = source.favicon
?? `https://www.google.com/s2/favicons?domain=${source.domain}&sz=16`;
History (F5 Reload)
Sources Side Panel
Panel restore từ workspace.sources trong history response:
{
"workspace": {
"sources": [
{
"query": "cổ phiếu HPG giá hôm nay",
"sources": [
{
"url": "https://simplize.vn/co-phieu/HPG",
"title": "Cổ phiếu HPG — Tập đoàn Hòa Phát",
"snippet": "HPG: 25,500 VND (+2.0%)",
"domain": "simplize.vn",
"favicon": "https://www.google.com/s2/favicons?domain=simplize.vn&sz=16"
}
]
}
]
}
}
const restoredSources = historyData?.workspace?.sources || null;
setSearchSources(restoredSources);
Blocks Trong AI Message
Trong history, web_search xuất hiện như cặp tool_use trong msg.content và tool_result trong message kế tiếp (role: "tool"):
[
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_01",
"name": "web_search",
"input": {
"query": "cổ phiếu HPG giá hôm nay",
"prompt": "..."
}
}
]
},
{
"role": "tool",
"tool_call_id": "toolu_01",
"name": "web_search",
"content": "HPG đang giao dịch ở 25,500 VND...",
"status": "success",
"artifact": {
"query": "cổ phiếu HPG giá hôm nay",
"sources": [
{
"url": "...",
"title": "...",
"domain": "simplize.vn",
"favicon": "..."
}
]
}
}
]
parseV2History() merge tool messages vào AI message rồi build blocks:
// parseV2History(): nhìn về phía trước để lấy tool messages
let j = i + 1;
while (j < messages.length && messages[j].role === 'tool') {
blocks.push({
type: 'tool_result',
tool_use_id: toolMsg.tool_call_id,
name: toolMsg.name,
content: toolMsg.content,
status: toolMsg.status || 'success',
artifact: toolMsg.artifact, // sources nằm đây
});
j++;
}
// Sau mergeToolBlocks(), kết quả AI message:
{
id: "session-history-1",
type: "ai_answer",
isStreaming: false,
blocks: [
{
type: "tool_use",
id: "toolu_01",
name: "web_search",
input: { query: "cổ phiếu HPG giá hôm nay" },
toolResult: {
type: "tool_result",
status: "success",
content: "HPG đang giao dịch...",
artifact: {
query: "cổ phiếu HPG giá hôm nay",
sources: [{ url: "...", title: "...", domain: "simplize.vn", favicon: "..." }]
}
}
}
]
}
renderBlock() detect block.name === 'web_search' → render WebSearchBlock với block.toolResult.artifact.sources.
Frontend Display
WebSearchBlock (Inline)
🔍 Tìm kiếm web [3 results] ▶
↓ click để expand
┌────────────────────────────────────────────┐
│ 🌐 Cổ phiếu HPG — Tập đoàn Hòa Phát │
│ simplize.vn ↗ │
│ 🌐 HPG tăng mạnh trong phiên sáng │
│ cafef.vn ↗ │
│ 🌐 Hòa Phát Q1/2024 lợi nhuận tăng 30% │
│ vnexpress.net ↗ │
└────────────────────────────────────────────┘
- Click source → mở trong tab mới (
target="_blank") - Show query text + số lượng result dạng badge
WebSearchSourcesPanel (Sidebar)
- Hiển thị tất cả search groups của session, group theo
query - Click source → mở tab mới
- Slide panel drawer (320–600px, resizable) cho expanded view
- Persist qua các message trong cùng session
Ví Dụ: Xử Lý Sources
function WebSearchBlock({ block }) {
const [expanded, setExpanded] = useState(false);
const sources = block.toolResult?.artifact?.sources || [];
return (
<div>
<button onClick={() => setExpanded(!expanded)}>
🔍 Tìm kiếm web [{sources.length} results] {expanded ? '▼' : '▶'}
</button>
{expanded && (
<ul>
{sources.map((s, i) => (
<li key={i}>
<img
src={s.favicon ?? `https://www.google.com/s2/favicons?domain=${s.domain}&sz=16`}
width={16} height={16}
alt=""
/>
<a href={s.url} target="_blank" rel="noopener noreferrer">
{s.title}
</a>
<span>{s.domain}</span>
</li>
))}
</ul>
)}
</div>
);
}