iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
AI 自動化

AaaS from Scratch: 從一次性定義,到規模化分析系列 第 10 篇

[Day 10] Tool、Skill 與 Domain Knowledge

  • 分享至 

  • xImage
  •  

最近的熱門的話題是 Jev model,它的設計非常適合 automation。因為 automation 的工作其實很單純,主要是 routing:走 A / B / C / D、做 classification,或判斷下一步該做什麼。

現在的 frontier model,例如 GPT-5.6 Sol、Luna 或 Claude Opus,雖然能力很強,但對這類 automation task 來說,很多時候能力其實超過需求,反而增加 latency、cost,以及整個 pipeline 的複雜度。

如果這個系列後面還有時間,會嘗試把 Jev 和整個 Pipeline 整合,看看對整體效益變化的影響是什麼。

回到今天的主題,我們已經對 Deep Agents 有基本理解,今天來把這些東西組裝起來,看一下這個 ReAct — Reasoning and Acting system 實際上是怎麼運作的。

整篇文章我們都會用同一個問題來測試:

Which video category gets the most views?

看起來很簡單,但其實 dataset 裡面藏了一個坑。

建立 Agent

最基本的 Deep Agent,只需要 model 和 system prompt:

from deepagents import create_deep_agent

agent = create_deep_agent(
    model=cfg.agent.model,
    system_prompt=SYSTEM_PROMPT,
)

Deep Agents 本身就已經提供了一個 ReAct loop:

Reason
  ↓
Act
  ↓
Observe
  ↓
Repeat

不過這時候 Agent 還沒有辦法存取我們的資料。

加入 Database Tool

我們可以把用一個 tool 讓 Agent 可以讀到資料庫:

@tool
async def query_database(sql: str) -> str:
    """Run one read-only PostgreSQL query and return rows as JSON."""
    ...

接著把它放進 Agent:

agent = create_deep_agent(
    model=cfg.agent.model,
    tools=[query_database],
    system_prompt=SYSTEM_PROMPT,
)

這樣 Agent 就真的可以查詢 dataset 了。

這裡有一個我覺得很實用的設計:database error 不要直接讓整個 Agent run crash,而是把 error 當成 tool result 回傳。

例如:

ERROR: column "view" does not exist

Agent 可以看到這個錯誤,修改 SQL,然後再試一次。

這正是 ReAct 裡面 Observe 這一步的用途。

Tool Description 很重要

對 Agent 來說,tool 不只是一個 function。

Model 在決定要不要呼叫 tool 時,也會看到:

  • tool name
  • description
  • input schema

例如:

@tool
async def query_database(sql: str) -> str:
    """Run one read-only PostgreSQL query and return rows as JSON.

    Aggregate in SQL instead of fetching unnecessary raw rows.
    """

這個 docstring 其實也會成為 Agent context 的一部分。

所以工具的設計會直接影響 Agent 的什麼時候呼喚他跟放進什麼內容當 input。

問題裡的坑

回到剛才的問題:

Which video category gets the most views?

這個 dataset 有一個很重要的特性:

一筆 row 代表的是某支 video 在某一天進入 trending。

所以同一支 video 可能會出現很多次。

如果很直覺地寫:

SELECT
    category_name,
    SUM(views)
FROM videos
GROUP BY category_name;

那同一支 video 的 views 就會被重複計算。 我們可以看到程式可以正常執行, SQL 也沒問題,只是結果是不對的。

這個 dataset 裡面有:

40,949 rows
6,351 distinct videos

所以這其實不是 SQL syntax 的問題,而是 對 dataset 的 domain knowledge。

加入 Skills

透過 skills 我們可以把一些細節告訴 Agent , 但不可能把所有相關知識全部塞進同一個 skill,這只會浪費很多錢以及效能下降,所以通常我會把它拆成兩層:

skills/
├── query_database/
│   └── SKILL.md
└── youtube-trending/
    └── SKILL.md

query_database 是 database tool 的 general skill,裡面可以索引其他細節的 skill。

youtube-trending 則放這個特定 dataset 的 domain knowledge。

核心概念是:

Tool
→ Agent 能做什麼

General skill
→ 這個 capability 應該怎麼使用

Domain skill
→ 這份資料實際代表什麼

把 Database Skill 當成 Index

query_database 這個 skill 也可以同時作為一個簡單的 routing index。

例如:

---
name: query_database
description: Guidance for querying the database safely and correctly.
---

# Querying the database

Before writing SQL:

- inspect the relevant schema
- avoid assuming what one row represents
- avoid unnecessary `SELECT *`
- aggregate in SQL when possible
- check whether there is datasource-specific guidance for the data you are using

## Datasource-specific guidance

### videos

For analysis involving the `videos` table, read:

`/skills/youtube-trending/SKILL.md`

It contains:
- row semantics
- counting rules
- aggregation guidance

這樣預期的 flow 就會變成:

query_database
      ↓
read general database skill
      ↓
找到 relevant domain skill
      ↓
read youtube-trending skill
      ↓
write SQL

這樣 query_database skill 本身可以保持 general,同時也能引導 Agent 找到正確的 domain knowledge。

Domain Skill

接著 youtube-trending/SKILL.md 就只需要專注在這個 dataset 本身:

---
name: youtube-trending
description: Domain rules for the YouTube trending dataset.
---

One row is one video on one trending day.

For per-video analysis:

- collapse to one row per video first
- use MAX(views)
- do not use COUNT(*) as the number of videos
- prefer median when comparing typical video performance

這樣責任就很清楚。

Database skill 處理的是:

how to query

Dataset skill 處理的是:

how to interpret the data

Progressive Disclosure

Agent 不需要一開始就把所有 skill 的完整內容都塞進 context。

而是可以先看到:

Skill name + description
        ↓
Agent 判斷是否 relevant
        ↓
read_file(SKILL.md)
        ↓
完整 skill 進入 context

這樣即使未來 skill 數量變多,也不需要一開始就把所有 instruction 都放進 prompt。

而 routing index 也提供了一條路徑,讓 Agent 可以從 general capability,一路找到更 specific 的 domain knowledge。

再跑一次同樣的問題

現在我們可以用同一個問題,比較不同階段的 Agent:

Bare Agent
    ↓
+ query_database
    ↓
+ database skill
    ↓
+ youtube-trending skill

每個階段都可以觀察:

  • Agent 呼叫了什麼 tool
  • 產生了什麼 SQL
  • 有沒有找到 relevant skill
  • 最後的答案有沒有改善

這部分的細節會在之後的 Agent performance evaluation 談到。

Takeaways

這次的實驗可以整理成很簡單的分工:

Tool
→ capability

General skill
→ usage guidance + routing

Domain skill
→ dataset-specific knowledge

這樣的結構之後要加入更多 table,或不同 datasource,也比較容易擴充。

但這也帶出另一個問題:

我們要怎麼確保 Agent 會在正確的時間讀到正確的 skill?

把 skill 放在那裡,不代表 Agent 一定會去讀。

而如果 skill 裡面放的是重要的 domain knowledge,跳過它之後,Agent 很可能產生一個完全合法、但意義錯誤的 tool call。

如果某個 skill 在使用 tool 之前是必讀的,那我們需要的就不只是 prompt。

下一篇會來嘗試處理這個問題。


上一篇
[Day 9] 介紹 Deep Agents (2)
下一篇
[Day 11] Context 管理
系列文
AaaS from Scratch: 從一次性定義,到規模化分析 共 11 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言