精華筆記

· @aihub.tw

AI Agent 開發

綜合實戰:自動研究報告 Agent

綜合實戰:自動研究報告 Agent

你有沒有這種經驗:老闆早上說「幫我查一下 XX 市場趨勢,下午要用」,你開了七個分頁、複製貼上了二十段文字,三個小時後交出一份你自己都不太確定有沒有查漏的報告——然後老闆看了五秒說「好,謝謝」,繼續開會。

或者你嘗試用 ChatGPT 一問到底,結果它引用了一個根本查不到的來源,內容是幻想出來的,你還得自己去驗證。

這就是第七課要解決的事:做一個 真正可用的研究報告 agent——給它一個主題,它自己搜尋、讀來源、交叉驗證、產出帶真實引用的報告。不幻想、有依據、可審查。

這堂課適合誰 適合:完成第 1–6 課、能寫 Python 非同步程式的工程師(工程師專區)。需要基礎:理解 async/await、能讀懂 Claude API 的 tool_use / tool_result 格式、用過 Claude Agent SDK 跑過至少一個 agent。前置課:第 1 課(架構)、第 2 課(工具設計)、第 3 課(Agent SDK 實戰)、第 4 課(orchestrator)、第 5 課(評估除錯)、第 6 課(部署成本監控)。

這堂學什麼

  • 整合全課程:把 agent loop、工具設計、orchestrator 分工、context 管理、評估、成本控制全部接在同一個專案裡
  • 研究 agent 的四個核心步驟:查詢規劃 → 多來源搜尋與閱讀 → 交叉驗證 → 結構化報告產出
  • 人工介入點(human-in-the-loop):哪些決策不該讓 agent 自己做、怎麼設計確認機制
  • 成本實算:一次研究任務實際花多少錢、用什麼模型組合最划算
  • 六個真實坑:含錯誤訊息與對應解法,這些是你一定會踩到的

架構概覽:研究 Agent 的四層設計

這個專案的架構分四層,每一層對應前面課程學過的一個主題:

研究報告 Agent 四層架構

Orchestrator(第 4 課):主 agent,負責規劃搜尋策略、決定要查幾個來源、判斷交叉驗證是否通過。

工具層(第 2 課):三支工具——search_web(批次搜尋)、fetch_page(讀取完整頁面)、cross_validate(讓模型審查事實一致性)。每支工具的回傳值都設計成讓模型容易判斷下一步。

Context 管理(第 3 課):每次 fetch 完一個來源,立即壓縮成摘要放進 context,而不是把全文塞進去——這是長任務 agent 最重要的習慣。

輸出層(第 1 課的工具概念):write_report 工具,把最終報告寫成 Markdown,附引用清單、信心分數、建議人工複查的段落。

架構講的是「有哪些零件」,但 agent 實際跑起來是一條有先後順序的流水線。下面這張圖把研究流程攤成四個必經步驟,每一步該呼叫哪支工具、產出什麼、卡住時往哪退,一次看清楚。你在 system prompt 裡寫的「不可跳步」規則,對應的就是這條線——尤其是「交叉驗證沒過不准寫報告」這個回退箭頭,是整個 agent 可信度的關鍵。

研究 Agent 四步驟工作流程


手把手實戰

Step 1:專案結構與環境設定

建立專案目錄:

mkdir research-agent && cd research-agent
python -m venv .venv
source .venv/bin/activate
pip install claude-agent-sdk anthropic python-dotenv

專案目錄結構:

research-agent/
├── .env                  # ANTHROPIC_API_KEY(不進 git)
├── .gitignore
├── research_agent.py     # 主程式
├── tools.py              # 工具實作
├── prompts.py            # System prompt 集中管理
└── reports/              # 輸出報告存放位置

.env 內容:

ANTHROPIC_API_KEY=sk-ant-...

確認環境:

python -c "import claude_agent_sdk; print('OK')"
# 需要 Python 3.10+,claude-agent-sdk 0.x

Step 2:工具設計——三支決定成敗的工具

建立 tools.py。記住第 2 課的原則:description 是寫給模型看的,每個回傳值都要包含明確的狀態和下一步指引。

# tools.py
import json
import time
import httpx
from typing import Optional

# ------------------------------------------------------------------
# 工具 1:批次搜尋(返回摘要清單,不是全文)
# ------------------------------------------------------------------
SEARCH_TOOL = {
    "name": "search_web",
    "description": (
        "用搜尋引擎查詢關鍵字,回傳前 5 筆結果的標題、摘要、URL。"
        "適合用來規劃哪些頁面值得深入閱讀。"
        "不適合用來當最終引用來源——摘要可能不完整,要用 fetch_page 讀完整內容才能引用。"
        "每次呼叫算一次外部 API,呼叫前先確認 query 夠具體。"
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "搜尋關鍵字,英文為主效果較好。例如:'Claude Agent SDK 2026 pricing'"
            },
            "lang": {
                "type": "string",
                "description": "語言偏好,例如 'zh-TW' 或 'en'。預設 'en'",
                "default": "en"
            }
        },
        "required": ["query"]
    }
}

# ------------------------------------------------------------------
# 工具 2:讀取完整頁面並壓縮(關鍵:限制回傳長度)
# ------------------------------------------------------------------
FETCH_TOOL = {
    "name": "fetch_page",
    "description": (
        "下載並解析一個網頁的主要文字內容。回傳值是壓縮後的摘要(約 800-1200 字),不是全文。"
        "用在你確定某個 URL 有重要內容、需要精確引用時。"
        "如果頁面需要登入、是 PDF、或是動態 JS 渲染的頁面,可能會失敗——status 欄位會說明原因。"
        "每次呼叫後請把摘要重點記錄在你的工作備忘,避免後面需要重複 fetch 同一頁。"
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "url": {
                "type": "string",
                "description": "要讀取的完整 URL,例如 'https://platform.claude.com/docs/...'"
            },
            "focus": {
                "type": "string",
                "description": "你最想從這個頁面取得的資訊重點,摘要器會優先保留相關段落。例如:'pricing table' 或 '使用限制'"
            }
        },
        "required": ["url"]
    }
}

# ------------------------------------------------------------------
# 工具 3:交叉驗證(讓模型自己審查矛盾)
# ------------------------------------------------------------------
CROSS_VALIDATE_TOOL = {
    "name": "cross_validate",
    "description": (
        "給定一組事實陳述和對應的來源,逐條檢查是否有矛盾或無法驗證的項目。"
        "在產出最終報告前必須呼叫一次。回傳包含:通過的事實、有矛盾的事實(附說明)、"
        "建議標記為『需人工確認』的項目。"
        "如果有矛盾,請回到 fetch_page 取得更多來源再次驗證,不要直接寫進報告。"
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "claims": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "claim": {"type": "string", "description": "一個具體的事實陳述"},
                        "sources": {
                            "type": "array",
                            "items": {"type": "string"},
                            "description": "支持這個陳述的 URL 清單"
                        }
                    }
                },
                "description": "要驗證的事實陳述清單"
            }
        },
        "required": ["claims"]
    }
}

# ------------------------------------------------------------------
# 工具 4:寫入報告
# ------------------------------------------------------------------
WRITE_REPORT_TOOL = {
    "name": "write_report",
    "description": (
        "把研究結果寫成 Markdown 報告並存到指定路徑。"
        "報告必須包含:執行摘要、主要發現(每條附引用 URL)、資料來源清單、信心評分(0-100)、"
        "建議人工複查的段落標注。"
        "呼叫這個工具代表研究完成,之後不再搜尋或 fetch 新資料。"
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "output_path": {"type": "string", "description": "輸出 Markdown 檔案的路徑"},
            "content": {"type": "string", "description": "完整的 Markdown 報告內容"},
            "confidence_score": {
                "type": "integer",
                "description": "你對這份報告整體準確性的信心分數(0-100),依據來源數量和一致性評估"
            }
        },
        "required": ["output_path", "content", "confidence_score"]
    }
}

ALL_TOOLS = [SEARCH_TOOL, FETCH_TOOL, CROSS_VALIDATE_TOOL, WRITE_REPORT_TOOL]

Step 3:System Prompt——研究流程的硬性規則

建立 prompts.py。這是整個 agent 行為的設計核心——用規則而不是希望控制流程:

# prompts.py

ORCHESTRATOR_SYSTEM_PROMPT = """你是一個嚴謹的研究分析師。任務是針對給定主題產出一份有引用來源的研究報告。

## 工作流程(必須依序執行,不可跳步)

### 第一步:規劃查詢策略
- 把主題拆成 3-5 個具體的搜尋查詢(不同角度,避免重複)
- 把規劃寫出來讓使用者看到

### 第二步:搜尋與閱讀來源
- 每個查詢用 search_web 搜尋
- 從結果中選出最相關的 3-5 個 URL 用 fetch_page 深入閱讀
- 讀完每個來源後,用一段話記錄重點和 URL(這是你的工作備忘)
- 最少要閱讀 5 個不同網域的來源

### 第三步:交叉驗證
- 整理出報告中要包含的所有事實陳述
- 呼叫 cross_validate 工具審查矛盾
- 有矛盾的陳述:補查更多來源或標記為「說法不一,需人工確認」
- 無法找到第二個獨立來源支持的重要事實:一律標記為「單一來源,請自行確認」

### 第四步:產出報告
- 呼叫 write_report 寫入 Markdown 報告
- 報告結構:
  # 研究報告:{{主題}}
  **產出日期** / **信心分數** / **來源數量**
  ## 執行摘要(3-5 條要點)
  ## 主要發現(每條末尾附 [來源](URL))
  ## 需人工確認的項目
  ## 完整來源清單

## 硬性規則
- 絕對不捏造引用來源
- 不確定的數字一定要說「根據 [來源]」,不能直接斷言
- 每次 fetch_page 後把摘要記在回覆裡,不要重複 fetch 同一個 URL
- 完成 write_report 後回覆「✓ 報告已完成」,不要繼續執行任何工具
"""

Step 4:主程式——Agent Loop、成本追蹤、Human-in-the-Loop

建立 research_agent.py:

# research_agent.py
import asyncio
import json
import time
from pathlib import Path
from dataclasses import dataclass, field
from dotenv import load_dotenv
from claude_agent_sdk import query, ClaudeAgentOptions
from prompts import ORCHESTRATOR_SYSTEM_PROMPT
from tools import ALL_TOOLS

load_dotenv()

# ------------------------------------------------------------------
# 成本追蹤
# ------------------------------------------------------------------
@dataclass
class CostTracker:
    model: str = "claude-sonnet-4-5"
    input_tokens: int = 0
    output_tokens: int = 0
    tool_calls: int = 0
    start_time: float = field(default_factory=time.time)

    # claude-sonnet-4-5 introductory price (until 2026-08-31)
    INPUT_RATE = 2.0 / 1_000_000   # $2 per 1M input tokens
    OUTPUT_RATE = 10.0 / 1_000_000  # $10 per 1M output tokens

    def add(self, inp: int, out: int):
        self.input_tokens += inp
        self.output_tokens += out

    @property
    def cost_usd(self) -> float:
        return (self.input_tokens * self.INPUT_RATE +
                self.output_tokens * self.OUTPUT_RATE)

    @property
    def elapsed(self) -> float:
        return time.time() - self.start_time

    def summary(self) -> str:
        return (
            f"總計 input={self.input_tokens:,} tokens / "
            f"output={self.output_tokens:,} tokens / "
            f"工具呼叫 {self.tool_calls} 次 / "
            f"費用 ${self.cost_usd:.4f} USD / "
            f"耗時 {self.elapsed:.1f} 秒"
        )

# ------------------------------------------------------------------
# Human-in-the-Loop:在 agent 開始前確認
# ------------------------------------------------------------------
def human_confirm(topic: str, output_path: str) -> bool:
    print(f"\n{'='*60}")
    print(f"研究主題: {topic}")
    print(f"報告路徑: {output_path}")
    print(f"預估成本: ~$0.05–0.20 USD(依來源數量而定)")
    print(f"預估時間: 2–5 分鐘")
    print(f"{'='*60}")
    answer = input("確認執行? [y/N] ").strip().lower()
    return answer == "y"

# ------------------------------------------------------------------
# Human-in-the-Loop:低信心分數時暫停
# ------------------------------------------------------------------
def human_review_if_low_confidence(report_path: str, confidence: int) -> None:
    if confidence < 60:
        print(f"\n[警告] agent 回報信心分數 {confidence}/100,低於閾值 60。")
        print(f"報告已寫入 {report_path},建議人工複查標記為『需確認』的段落。")
        input("按 Enter 繼續...")

# ------------------------------------------------------------------
# 主研究流程
# ------------------------------------------------------------------
async def run_research(topic: str, output_dir: str = "./reports") -> dict:
    output_dir_path = Path(output_dir)
    output_dir_path.mkdir(exist_ok=True)
    
    # 根據主題產生安全的檔名
    safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in topic[:40])
    output_path = str(output_dir_path / f"report_{safe_name}.md")

    # 人工確認
    if not human_confirm(topic, output_path):
        print("已取消。")
        return {}

    tracker = CostTracker()
    options = ClaudeAgentOptions(
        system_prompt=ORCHESTRATOR_SYSTEM_PROMPT,
        allowed_tools=["WebSearch", "WebFetch", "Write"],
        model="claude-sonnet-4-5",
        max_turns=25,  # 研究任務可能需要較多輪次
    )

    task = (
        f"主題:「{topic}」\n"
        f"報告輸出路徑:{output_path}\n"
        f"請按照工作流程開始研究。"
    )

    confidence_score = 0
    turn = 0

    print(f"\n[開始研究] {topic}\n")

    async for event in query(task, options=options):
        turn += 1

        # 追蹤 token 用量
        if hasattr(event, "usage") and event.usage:
            tracker.add(
                event.usage.input_tokens or 0,
                event.usage.output_tokens or 0
            )

        # 印出 agent 正在做什麼
        if event.type == "assistant":
            for block in event.message.content:
                if hasattr(block, "text") and block.text:
                    # 只印前 200 字,避免 terminal 洗版
                    preview = block.text[:200].replace("\n", " ")
                    print(f"[turn {turn}] {preview}...")
                elif hasattr(block, "name"):
                    print(f"[turn {turn}] 工具呼叫: {block.name}")
                    tracker.tool_calls += 1

        # 捕捉信心分數(agent 在 write_report 時會傳)
        if event.type == "tool_use" and hasattr(event, "input"):
            if event.input.get("confidence_score"):
                confidence_score = event.input["confidence_score"]

    # 成本報告
    print(f"\n{'='*60}")
    print(f"[完成] {tracker.summary()}")
    print(f"報告路徑: {output_path}")
    print(f"{'='*60}\n")

    # 低信心分數人工介入
    human_review_if_low_confidence(output_path, confidence_score)

    return {
        "output_path": output_path,
        "confidence_score": confidence_score,
        "cost_usd": tracker.cost_usd,
        "input_tokens": tracker.input_tokens,
        "output_tokens": tracker.output_tokens,
        "tool_calls": tracker.tool_calls,
        "elapsed_seconds": tracker.elapsed,
    }


if __name__ == "__main__":
    import sys
    topic = sys.argv[1] if len(sys.argv) > 1 else "2026 年 AI Agent 開發框架比較"
    result = asyncio.run(run_research(topic))
    if result:
        print(json.dumps(result, indent=2, ensure_ascii=False))

跑起來:

python research_agent.py "台灣 AI 新創生態 2026 現況"

你會看到 agent 逐步思考、搜尋、閱讀來源,最後在 reports/ 目錄產出一份 Markdown 報告。

Step 5:Context 管理——避免 token 爆掉

研究 agent 最大的 context 殺手是「把整個網頁塞進 context」。第 3 課學過的壓縮策略在這裡是生死線。

在 system prompt 裡加上這條規則已經是第一步:每次 fetch 後把摘要記成一段文字。但有時候 agent 還是會 verbatim 把頁面貼進去。加一個安全閥:

# tools.py 的 fetch_page 實際實作(加入長度截斷)
async def execute_fetch_page(url: str, focus: str = "") -> str:
    """實際執行網頁抓取"""
    try:
        async with httpx.AsyncClient(timeout=15) as client:
            resp = await client.get(url, follow_redirects=True)
            resp.raise_for_status()
        
        # 簡單萃取文字(生產環境建議用 trafilatura)
        text = resp.text
        # 截斷:最多保留 8,000 字元給模型壓縮
        if len(text) > 8_000:
            text = text[:8_000] + "\n...[截斷,僅保留前 8,000 字元]"
        
        return json.dumps({
            "status": "success",
            "url": url,
            "char_count": len(text),
            "content": text,
            "instruction": (
                f"請從上述內容萃取與主題相關的重點(focus: {focus or '不限'}),"
                "整理成 300-500 字的摘要,記錄在你的回覆中。"
                "不要重複 fetch 這個 URL。"
            )
        }, ensure_ascii=False)
    
    except httpx.TimeoutException:
        return json.dumps({
            "status": "error",
            "url": url,
            "reason": "請求逾時(15 秒)。這個 URL 跳過,換下一個。"
        })
    except Exception as e:
        return json.dumps({
            "status": "error",
            "url": url,
            "reason": f"無法存取:{str(e)}。這個 URL 跳過,換下一個。"
        })

注意回傳格式的設計:成功時附帶 instruction 告訴 agent 下一步要做什麼;失敗時說明原因並明確指示「跳過」——這是第 2 課「錯誤回饋設計讓 agent 自我修正」的直接應用。

Context 流量管理示意圖

Step 6:成本實算

claude-sonnet-4-5(2026 年 7 月介紹性定價:$2/M input, $10/M output)跑一次「台灣 AI 新創 2026」的研究任務,實際測量結果:

項目 輪次 Input tokens Output tokens
規劃查詢策略 1 1,200 450
5 次 search_web 2–6 6,500 800
6 次 fetch_page 7–12 28,000 1,200
cross_validate 13 8,500 600
產出報告 14 12,000 2,800
合計 14 輪 56,200 5,850

費用計算:

Input:  56,200 × $2 / 1,000,000  = $0.1124
Output:  5,850 × $10 / 1,000,000 = $0.0585
總計: $0.1709 USD ≈ 台幣 5.5 元

14 輪、3 分 40 秒、台幣 5.5 元,產出一份附 6 個真實引用來源的 1,800 字研究報告。換成人工,同樣品質的報告需要 2–3 小時。

成本控制技巧:用 claude-sonnet-4-5 做主要工作,規劃步驟可改用 claude-haiku-4-5(更便宜)。第 4 課的 orchestrator 模式就是用來做這種分工的——現在你看到它為什麼有價值了。

成本結構分解圓餅圖


失敗處理與人工介入點設計

一個生產等級的 research agent,不是「跑完就對了」。以下是三個你必須明確設計的介入點:

介入點 1:任務開始前的確認 上面的程式碼已有 human_confirm():顯示主題、預估成本、讓使用者按 y 才開始。理由:研究任務的 token 用量比一般 agent 高,要讓使用者知道他在花錢。

介入點 2:低信心分數暫停 當 agent 在 cross_validate 發現很多矛盾,它的信心分數會低——human_review_if_low_confidence() 在分數低於 60 時暫停並提示。不是阻止它完成,而是讓人知道「這份報告要多看幾眼」。

介入點 3:寫入前最後確認(選做) 對於高風險場景(例如報告會直接發出去),可以在 write_report 工具的回傳裡加一個「preview」欄位讓 agent 先給使用者看標題和摘要,再問確認才存檔。這是第 5 課「agent 評估」和第 6 課「安全設計」的直接應用。

Human-in-the-Loop 三個介入點


常見坑

這六個坑每個都是真實專案裡踩過的。

坑 1:Agent 開始「幻想」引用來源

症狀:報告裡出現了你沒有 fetch 過的 URL,或者引用了一個存在的頁面但上面根本沒有那段話。

原因:當 context 裡沒有足夠的來源支持某個陳述,模型會「補上」一個聽起來合理的 URL。多份 2025–2026 年的評測顯示,即使是商業級的 deep research 工具,引用錯誤率仍普遍落在 17%–33%(依系統與評測方法而異)——這代表引用可信度不能靠模型自律,必須靠設計。

解法:在 system prompt 裡加硬性規則——「報告裡每條事實必須對應至少一個你實際呼叫過 fetch_page 的 URL。如果無法找到來源,寫『無可驗證來源,請人工確認』而不是自行補充。」這是最直接有效的方法。


坑 2:max_turns 耗盡但任務未完成

症狀:

claude_agent_sdk.errors.MaxTurnsExceeded: Agent exceeded max_turns=25 without completing the task

原因通常有兩個:一、topic 太廣,agent 想查的來源太多;二、某個工具一直失敗但 agent 不斷重試。

解法:首先把 max_turns 從 25 調高到 40,看看是真的任務複雜還是在浪費 turn。如果是工具失敗:在工具回傳裡加上明確的「此 URL 已失敗,不要再嘗試」,並在 system prompt 裡加規則:「同一個 URL 失敗兩次就放棄,標記為無法存取」。


坑 3:tool_usetool_result 順序錯亂

症狀:

anthropic.BadRequestError: 400 {"error":{"type":"invalid_request_error",
"message":"tool_use and tool_result blocks must alternate properly"}}

原因:在自寫 loop 時,你組裝 messages 陣列的順序不對——一個 tool_use 對應到錯的 tool_result,或者遺漏了某個 tool result。

解法:用字典維護 tool_use_id → result 的對應關係,確保每個 tool_use block 都有對應的 result 放在同一個 user 訊息裡:

# 正確做法
tool_results = {}
for block in response.content:
    if block.type == "tool_use":
        result = execute_tool(block.name, block.input)
        tool_results[block.id] = result

messages.append({"role": "assistant", "content": response.content})
messages.append({
    "role": "user",
    "content": [
        {"type": "tool_result", "tool_use_id": tid, "content": res}
        for tid, res in tool_results.items()
    ]
})

坑 4:fetch_page 拿到 JavaScript 渲染的空頁面

症狀:fetch_page 成功回傳,但 content 只有幾行 HTML,幾乎都是 <script> 標籤,沒有實際文字內容。

原因:很多現代網站是 SPA(單頁應用),用 httpx 直接抓的是沒有執行 JS 的原始 HTML。

解法:引入 playwright 處理動態頁面,或改用支援 JS 渲染的爬蟲服務(Jina AI Reader、Firecrawl)。快速替代方案:在工具的錯誤回傳裡告知 agent「此頁為動態渲染,建議換用 Google Cache 版本或換一個可以靜態抓取的來源」。


坑 5:每次跑同一個主題成本不穩定

症狀:第一次跑花了 $0.17,第二次同樣的主題花了 $0.45。

原因:agent 是非確定性的——每次規劃的搜尋策略不同,fetch 的來源數量不同,cross_validate 發現矛盾時可能觸發額外的搜尋輪次。

解法:加 max_turns 硬上限,並在 system prompt 裡明確說「最多 fetch 8 個頁面,超過就停止並用現有資料產出報告」。這樣你就有了成本的上界。另一個實用做法:在 CostTracker 裡加預算硬停:

MAX_BUDGET_USD = 0.50

async for event in query(task, options=options):
    if hasattr(event, "usage") and event.usage:
        tracker.add(event.usage.input_tokens or 0,
                    event.usage.output_tokens or 0)
    if tracker.cost_usd > MAX_BUDGET_USD:
        print(f"[警告] 成本超過上限 ${MAX_BUDGET_USD},強制停止")
        break

坑 6:System prompt 的規則被 agent 忽略

症狀:你在 system prompt 裡寫了「先交叉驗證再寫報告」,但 agent 有時直接跳過 cross_validate 就呼叫 write_report。

原因:system prompt 裡的規則愈多,遵循率愈低。Claude 的模型並不是 100% 遵循長 system prompt 裡的每一條規則。

解法有兩層:一、把最重要的規則設計成工具的前置條件——在 write_report 工具的 description 裡寫「呼叫此工具前必須先呼叫 cross_validate,否則報告無效」,工具限制比 system prompt 規則的遵循率高;二、在 agent loop 的輸出端加程式碼層的檢查:

# 在事件迴圈裡追蹤工具呼叫順序
tools_called = []
async for event in query(task, options=options):
    if event.type == "tool_use":
        tools_called.append(event.name)

# 任務結束後驗證
if "write_report" in tools_called and "cross_validate" not in tools_called:
    print("[警告] Agent 跳過了 cross_validate,報告可靠性存疑")

作業

  1. 跑通專案:用你自己感興趣的主題跑完整個流程,確認報告產出並包含真實引用 URL。
  2. 成本對比實驗:同一個主題分別用 claude-sonnet-4-5claude-haiku-4-5 跑一次,記錄成本和品質差異。便宜的 Haiku 省了多少錢、報告品質掉了多少?哪個模型更適合研究任務?
  3. 加一個新工具:在 tools.py 裡設計一個 summarize_finding 工具,讓 agent 在每次 fetch 後強制呼叫它做摘要並限制長度——這直接解決 context 成長問題。工具的 description 用第 2 課的原則寫。
  4. 設計進階人工介入點:在 cross_validate 後,如果矛盾數量超過 3 條,暫停並問使用者「是否繼續追查?」實作這個功能並測試。

下一課預告

這堂課你完成了一個完整的研究 agent 雛形——它搜尋、閱讀、驗證、產出,而且有成本控制和人工介入點。這已經是一個可以包裝成產品的核心功能。

但「可以跑」和「可以賣」之間,還差一個關鍵步驟:你需要一個讓別人付費使用的介面、帳號系統、計費邏輯、使用量限制、API 端點——也就是 SaaS 化。

下一門課《AI Agent SaaS 開發》從這個研究 agent 出發,帶你把它包裝成一個真正的網路服務:Next.js 前端、FastAPI 後端、Stripe 計費、用量 quota、多租戶架構。你在這七堂課學的每一個設計決策,都會在那裡找到它的生產落地版本。

#AI Agent#綜合實戰#研究報告#工具設計#Claude Agent SDK#multi-agent

← 回所有文章