- 8 min read

【Agent-Native Web】我給網站寫了一份說明書,然後 AI Agent 真的讀懂了

把個人網站的 Agent Skill Index 放上去之後,一個真實的問題浮現:Agent 真的會去讀那份說明書嗎?這篇用 Google ADK Python 寫了一個驗證專案,從靜態 Skill 定義到 Agent 真實呼叫,跑完完整的閉環。

AI AI Agent ADK Agent-Native Web

上一篇文章把個人網站從 33 分改造到 Agent-Native 滿分,其中最有趣的一環是 Agent Skills Index —— 一份放在 /.well-known/agent-skills/index.json 的技能目錄,教 AI Agent 如何與這個網站互動。

延伸閱讀:【Agent-Native Web】SEO 時代結束了,你的網站 Agent Ready 了嗎?

但規格好看,不代表能用。一份 JSON 文件放在伺服器上,到底有沒有辦法讓 Agent 動態讀取、動態學習、然後真的去呼叫?

這篇用 Google ADK Python 寫了一個驗證專案,親手跑完整個流程 —— 從讀取 Skill Index,到真實的對話互動。


這個網站有哪三個 Agent Skill

在說明驗證怎麼跑之前,先說清楚「說明書」裡寫了什麼。我在這個網站放了三個 Agent Skill,這三個 Skill 各司其職,覆蓋了一個內容型個人網站最常見的 Agent 互動情境:

1. markdown-negotiation:驗證 Markdown 支援

SKILL.md 教 Agent 如何確認一個網站是否支援 Accept: text/markdown Content Negotiation:

  • 對目標 URL 發送帶有 Accept: text/markdown Header 的 GET 請求
  • 確認回應的 Content-Type 是否為 text/markdown
  • 可選:呼叫 isitagentready.com 的掃描 API 取得完整評分

這個 Skill 設計上是一個「工具 Skill」—— 它的受眾不只是要讀懂這個網站的 Agent,而是任何需要驗證 Agent-Native Web 合規程度的 Agent。

2. content-retrieval:搜尋與讀取文章

SKILL.md 定義了兩個操作:

  • 取得最新文章列表:GET /rss.xml,取回所有文章的標題、摘要、標籤、URL。因為沒有伺服器端搜尋 API,Skill 明確告訴 Agent「自己在 RSS 的 XML 中用關鍵字過濾」
  • 讀取特定文章全文:GET /blog/{slug}/,取回完整文章頁面。搭配 Markdown Content Negotiation,Agent 會拿到乾淨的 Markdown,而非 HTML

這個 Skill 讓 Agent 不需要搜尋引擎就能回答「最近寫了什麼」、「有沒有關於 Kubernetes 的文章」這類問題。

3. author-identity:查詢作者背景

SKILL.md 提供作者的結構化資料:

  • 專業背景與技術棧(SRE、Kubernetes、AI Ops 等)
  • 歷年演講記錄(GET /talks/)
  • 聯絡方式(Email、GitHub、LinkedIn)

當使用者問「這個部落格的作者是誰、專長是什麼、怎麼聯絡他」,Agent 不需要爬 About 頁面 —— Skill 裡已經有結構化的答案。


問題意識:Agent Skill 不能只是靜態文件

傳統的 API 文件是寫給人看的 —— 工程師讀完 OpenAPI spec,手動接入 SDK,測試,部署。這個流程最短也要幾小時。

Agent Skill 的設計目標截然不同:讓 Agent 在執行期自動發現技能、理解技能、然後呼叫技能。沒有人類作為中間人。

isitagentready.com 的評分框架只告訴你「你有沒有發布技能索引」,但沒有告訴你「這個技能索引有沒有辦法被真實的 Agent 框架使用?」,這是我想驗證的問題。


工具選擇:Google ADK

ADK Python 是什麼

Google ADK(Agent Development Kit) 是 Google 開源的 Python Agent 框架,設計目標是讓開發者能用最少的程式碼建構、測試、部署 AI Agent。

幾個核心概念:

  • Agent:具備目標的執行單元,背後由 LLM(如 Gemini)驅動,能自主決策要呼叫哪些工具
  • Tool(工具):Agent 可以呼叫的函數。ADK 提供 FunctionTool,只需傳入一個 Python 函數,框架自動處理 JSON schema 生成與 LLM 的工具呼叫協議
  • Runner:負責管理對話狀態、串接 LLM 與 Tool 的執行循環
  • adk web:內建的瀏覽器 UI,啟動後可以直接在網頁介面與 Agent 對話,適合快速驗證與展示

ADK 特別適合這個驗證場景的原因:

  1. ADK 是目前對 Agent Tool 概念支援最完整的 Python 框架之一,FunctionTool 的抽象層正好對得上 Web Skill 的 HTTP 呼叫模型 —— 每個 Skill 對應一個 Tool
  2. 它有 adk web 介面,可以直接在瀏覽器中與 Agent 互動,方便測試
  3. 框架的設計鼓勵動態組裝 Tool 列表,與「在啟動期讀取 Skill Index 再轉換成 Tool」的需求完全吻合

專案架構:python-web-skill-adapter-adk

原始碼開源於 https://github.com/charles-hsiao/python-web-skill-adapter-adk。

步驟如下:

  1. 讀取設定中的目標 Domain
  2. 抓取 /.well-known/agent-skills/index.json
  3. 解析每個 Skill 定義(名稱、描述、端點、參數)
  4. 把每個 Skill 轉換成一個 ADK Tool
  5. 啟動一個帶著這些 Tool 的 ADK Agent

只要換一個 WEB_SKILL_DOMAIN,Agent 啟動時就自動載入那個網站的所有 Skill。

目錄結構

python-web-skill-adapter-adk/
├── docs/
├── web_skill_adapter/
│   ├── __init__.py
│   ├── agent.py          # ADK Agent 初始化,組裝 Tool 列表並啟動 root_agent
│   ├── cli.py            # 本地 CLI 介面
│   ├── config.py         # 環境變數設定(WEB_SKILL_DOMAIN、MODEL 等)
│   ├── discovery.py      # 抓取並解析 /.well-known/agent-skills/index.json
│   ├── dynamic_tools.py  # 將 SkillSpec 轉換為 ADK BaseTool
│   └── models.py         # 資料模型(SkillSpec、SkillCatalog)
├── .env.example
├── pyproject.toml
└── README.md

Get Started

export WEB_SKILL_DOMAIN=www.charles-hsiao.com
export GOOGLE_API_KEY=your-api-key

uv run adk web

uv run adk web 會在本地啟動一個 Web UI,預設監聽 localhost:8000,可以直接在瀏覽器中對 Agent 發問。


Demo 流程:從一個問題到真實的內容

以下是三個實際執行的 Demo,分別驗證三個不同的 Skill。

Demo 1:驗證 Markdown Content Negotiation

對話從「你能幫我做什麼?」開始。Agent 啟動後自動讀取 Skill Index,直接回答自己具備哪些能力(因為能力清單就寫在 /.well-known/agent-skills/index.json 裡)。

確認能力後,用戶接著發起驗證。

用戶問:「Validate markdown content negotiation」

Agent 詢問 URL,用戶提供 www.charles-hsiao.com,Agent 呼叫 markdown-negotiation Skill 的驗證工具,對目標 URL 發送帶有 Accept: text/markdown Header 的請求,確認伺服器是否正確回應。

web_skill_markdown_negotiation_valida → HTTP GET (Accept: text/markdown) → pass

結果:

The website https://www.charles-hsiao.com successfully supports Markdown content
negotiation. The check shows a "pass" status for markdownNegotiation, indicating
that the site supports Markdown for Agents.

對話截圖:用戶先問 Agent 能做什麼(能力清單來自 Skill Index),接著發起驗證,Agent 呼叫 markdown-negotiation Skill 並回報 pass

Demo 2:搜尋部落格文章

用戶問:「Search for blog posts discussing agentic payment.」

Agent 沒有伺服器端搜尋函數可用,但它知道可以呼叫 content-retrieval Skill 中的 get_latest 工具取得完整文章列表,再自行篩選。

web_skill_content_retrieval_get_latest → RSS Feed

返回結果後,Agent 從標題和描述中識別出三篇相關文章:

篇名連結
【Agentic Payment 系列 #1】HTTP 402 …/blog/202604-agentic-payment-stack-part1/
【Agentic Payment 系列 #2】信任代理…/blog/202604-agentic-payment-stack-part2/
【Agentic Payment 系列 #3】兆美元賽道…/blog/202604-agentic-payment-stack-part3/

Demo 2:Agent 呼叫 content-retrieval Skill 取得文章列表,自行篩選出三篇 Agentic Payment 相關文章

Agent 自主完成了一個原本要靠搜尋引擎才能做到的任務,完全沒有呼叫任何搜尋 API。

Demo 3:讀取特定文章

用戶問:「Summary the content of post series #1」

Agent 呼叫 content-retrieval Skill 的 read_a_specific_post 工具,傳入文章 URL,取回完整的 Markdown 內容,然後生成摘要。

web_skill_content_retrieval_read_a_sp → 文章全文(Markdown)→ 摘要生成

Demo 3:Agent 讀取 Agentic Payment 系列第一篇,取回完整 Markdown 全文並生成摘要

Tool 回傳的 Function Response 裡是完整的文章 Markdown 原文,而非 HTML:

Function Response 截圖:status: success,skill: content-retrieval / Read a Specific Post,回傳內容為完整 Markdown 文章全文

關鍵是「取回的是 Markdown」,不是 HTML。這正是上一篇修復 Markdown Content Negotiation 的價值所在 —— Agent 拿到的是乾淨的語義內容,不會夾雜著 CSS class 名稱和 <div> 標籤的 HTML 等等對 Agent 無意義的網站裝飾。


架構解析:ADK 如何把 Skill 變成 Tool

這是整個專案最核心的設計,值得拆開來看。

flowchart TD
    subgraph startup["ADK Agent(啟動期)"]
        A[WEB_SKILL_DOMAIN] --> B[SkillLoader]
        B --> C["GET /.well-known/agent-skills/index.json"]
        C --> D["解析 Skill 定義(名稱、描述、端點)"]
        D --> E["SkillTool A\n(content-retrieval)"]
        D --> F["SkillTool B\n(markdown-negotiation)"]
        E --> G["ADK FunctionTool[]"]
        F --> G
        G --> H["LlmAgent(gemini-2.5-flash)"]
    end

    subgraph runtime["用戶對話(執行期)"]
        I[用戶提問] --> J["LLM 決策:應呼叫哪個 Tool?"]
        J --> K["SkillTool.call()"]
        K --> L["HTTP 請求至遠端 Skill 端點"]
        L --> M["解析 Skill 回應"]
        M --> N["LLM 生成最終回答"]
        N --> O[返回用戶]
    end

    H -->|執行期| I

DiscoveredSkillTool 的核心是繼承 ADK BaseTool:讀取 SkillSpec 中的 method、url、parameter_locations,動態建構 HTTP 請求,然後把結果原文回傳給 LLM。

這個設計的優點是 Agent 不需要預先知道網站提供哪些技能。


延伸方向

這個驗證專案還有幾個值得繼續的方向:

1. 多 Domain 聚合:讓 Agent 同時載入多個網站的 Skill,成為一個跨站點的通用 Agent。這在設計上只需要讓 SkillLoader 接受 Domain 列表,但 Tool 命名空間需要謹慎處理衝突。

2. Skill 版本管理:Skill Index 裡每個 Skill 都帶有 digest(SHA-256 內容指紋),格式上已具備偵測變動的基礎。目前的實作在啟動時抓取一次即固定,若網站更新了 SKILL.md,Agent 需要重啟才能感知。應可利用 digest 在執行期做 cache invalidation。

3. Skill 評分:對 Agent 呼叫的 Skill 加入成功率、延遲、回應品質的追蹤,讓 Agent 在多個同類 Skill 中學會優先選擇更可靠的來源。


結語

這個專案想探討的核心只有兩個:Skill 怎麼被發現、HTTP 端點怎麼封裝成 Tool。

更值得注意的是這個驗證過程揭示的整體設計哲學:Agent-Native Web 的基礎設施不是為人類設計的,它假設消費者是 Agent。Skill Index 是機器讀的,Markdown Content Negotiation 是機器用的,API Catalog 是機器找的。人類開發者的工作,是把這些基礎設施搭好,然後讓 AI Agent 自己摸清楚怎麼用。

這和二十年前 Web 2.0 的 RSS 普及有點像:標準本身不複雜,但當足夠多的網站都支援,它就成為了整個生態系的管道。只不過這次,讀取這些結構化內容的不是 RSS 閱讀器,是自主運行的 AI Agent。

話雖如此,這個網站還是有提供復古的 RSS。如果你是比較懷舊 (Aka. 有年紀) 的讀者可以參考看看

原始碼附上,歡迎取用:github.com/charles-hsiao/python-web-skill-adapter-adk