前面幾天看 Codex 時,比較常從 Agent Runtime 往內拆:Thread 怎麼保存、Turn 怎麼跑、Tool 怎麼執行、Sandbox 怎麼限制,以及 Memory 怎麼留下來。
接下來想換一個角度。
如果今天真的要開始做一個 Agent Product,例如:
用 Claude 做一個可以查商品、操作購物車,甚至讓內部人員調整價格與庫存的 Agent。
程式到底應該怎麼拆?
Anthropic 的 commerce-agents 很適合拿來看這件事。
Repo 裡主要示範兩種 Agent:
Customer
v
Shopping Agent
Merchant Staff
v
Merchant Agent
Shopping Agent 面向消費者,處理商品搜尋、購物車、訂單與政策。
Merchant Agent 面向內部人員,處理業績、商品、庫存、價格與 Campaign。
這個 Repo 真正值得看的是:
當 Agent 開始操作企業系統,一個 Agent Application 到底應該拆成哪些部分?
這篇先把 Repo 與 Agent Core 拆開。
下一篇再深入 Tool、Executor、Gate 與 Backend 為什麼要刻意分開。
架構可以先縮成四層:
Web / LINE / Slack
v
Host / API
v
Runtime
v
Role Core
v
Backend
v
Business System
對應到 Repo,大致是:
commerce-agents/
|
+-- commerce-common/
|
+-- shopping-agent/
| +-- core/
| +-- skills/
| +-- runtime-messages-api/
| +-- runtime-agent-sdk/
| +-- managed-agents/
|
+-- merchant-agent/
| +-- core/
| +-- skills/
| +-- runtime-messages-api/
| +-- runtime-agent-sdk/
| +-- managed-agents/
|
+-- examples/
先記住三個核心概念就好:
| 層級 | 負責什麼 |
|---|---|
commerce-common |
Shopping、Merchant 都會使用的共用 Agent 機制 |
| Role Core | 定義這個 Agent 的 Domain 與能力 |
| Runtime | 決定 Agent Loop 怎麼執行 |
最外面的 Host / UI,才決定使用者從 Web、LINE 還是 Slack 進來。
換句話說:
Role Core 決定「這是什麼 Agent」,Runtime 決定「它怎麼跑」。
Shopping Core 裡可以看到:
backend.py
config.py
prompt.py
types.py
tools/
executor.py
gates.py
grounding.py
serialization.py
fencing.py
memory.py
Merchant Core 也幾乎有同樣的結構。
第一次看到,很容易產生一個疑問:
它們是不是其實是同一個 Agent,只是換不同 Skill?
不是。
比較接近下面這種關係:
| 模組 | Shopping | Merchant |
|---|---|---|
types.py |
Product、Cart | Listing、Change |
backend.py |
Storefront Backend | Merchant Backend |
tools/ |
Search、Cart | Metrics、Pricing |
gates.py |
購物車安全限制 | Change、Approval |
prompt.py |
Customer Role | Operator Role |
檔案負責的責任相似,但裡面的 Domain Logic 不同。
例如 Shopping 的 types.py 可能定義:
class Product(BaseModel):
product_id: str
title: str
price: float
in_stock: bool = True
Merchant 則可能是:
class Listing(BaseModel):
listing_id: str
title: str
status: str
price: float
stock: int
兩邊都叫 types.py,但描述的是不同的 Business World。
也就是 相同的組織方式,不同的 Agent 邏輯。
看到十幾個 .py,很容易覺得:
做一個 Agent 要寫這麼多 Module?
但把它們重新分組後,其實只是在回答四個問題。
| 問題 | 主要模組 |
|---|---|
| Agent 認得什麼? | types.py、backend.py |
| Claude 可以做什麼? | tools/、prompt.py、skills/ |
| Action 怎麼安全執行? | executor.py、gates.py、grounding.py |
| 哪些機制可以重用或調整? | config.py、serialization.py、fencing.py、memory.py |
接下來逐組看就會簡單很多。
types.py + backend.pytypes.py 回答的是:
這個 Agent 的世界裡有哪些 Business Object?
Shopping 可能有:
Product
Cart
Order
Policy
Merchant 則可能有:
Listing
MetricSeries
InventoryAlert
Campaign
StagedChange
有了這些資料結構後,下一個問題是:
這個 Agent 可以從公司系統取得什麼?
這就是 backend.py 的工作。
例如 Shopping:
class StorefrontBackend(ABC):
async def search_products(...):
...
async def get_cart(...):
...
注意,這裡沒有真的去查 PostgreSQL。
因為它不是 Backend Implementation,而是 Backend Contract。
它只是在說:
Shopping Agent 要正常工作,你的系統至少要提供這些能力。
至於後面實際接的是 Shopify、SAP、PostgreSQL 或 REST API,Agent Core 不需要知道。
tools/ + Prompt + SkillBackend 裡存在:
search_products(...)
不代表 Claude 自動知道這個 Function。
還需要 Tool Contract 告訴 Model:
{
"name": "search_products",
"description": "Search the catalog...",
"input_schema": {
...
}
}
所以 Backend 和 Tool 可以用一句話區分:
| 回答的問題 | |
|---|---|
| Backend | 系統真的可以做什麼? |
| Tool | Claude 知道自己可以做什麼? |
而 prompt.py、Skill 則補上行為規則。
例如:
因此不是所有規則都塞進 System Prompt。
不同規則會放在不同位置。
Claude 決定呼叫:
search_products
之後,真正接手的是 executor.py。
例如:
def handlers(self):
return {
"search_products": self._search_products,
"get_cart": self._get_cart,
"add_to_cart": self._add_to_cart,
}
Executor 的工作可以簡化成:
Model Tool Call
v
Executor
v
Backend
v
Business System
但它不只是 Function Mapping。
中間還可能處理:
其中 gates.py 特別值得注意。
例如加入購物車前,可以真的用 Code 驗證:
這和只在 Prompt 裡寫:
請不要亂填
product_id。
完全不同。
前者是 Runtime 真的會阻止 Action 執行。
兩者很容易混在一起。
可以直接記:
Gate:現在能不能做?
Grounding:回答以前是不是一定要先查?
例如使用者問:
我的訂單什麼時候到?
這不一定是危險 Action,所以 Gate 未必有事。
但 Runtime 不能只靠之前聊天內容回答,而應該先取得最新的:
get_order_status
這就是 Grounding。
所以:
Gate
-> 保護 Action
Grounding
-> 保護回答依據
剩下的幾個檔案通常比較薄。
serialization.py它負責決定:
Backend 回來的資料,哪些真的需要給 Model?
例如 Backend Object 可能還包含:
database_row_id
internal_user_key
fraud_score
這些資訊不一定需要暴露給 Claude。
因此 Serialization 會整理成適合 Model 使用的 Payload。
memory.pyRole-specific 的 memory.py 比較像是在定義:
這個 Role 值得留下什麼?
例如 Shopping 可能保留:
Merchant 可能保留:
而真正的 Memory Store、Retention 等共用機制,主要放在 commerce-common。
config.pyConfig 也很容易被誤解。
它不是:
用 Config 寫出一個新的 Agent。
比較像:
調整一個已經存在的 Role。
例如:
ShoppingAgentConfig(
enable_cart=False,
enable_orders=False,
)
可以把既有 Capability 關掉。
適合 Config 的通常是:
但如果想增加:
quote_flight_change
Config 就幫不上忙。
因為這已經不是「調整 Shopping Agent」,而是 新增 Domain Capability。
add_to_cart 一次把整個 Core 串起來前面看了很多模組名稱。
現在直接用一個 Action 串一次。
假設使用者說:
把剛剛第二個商品加進購物車。
整條流程大致是:
User
v
Prompt / Skill
v
Tool Contract
v
Executor
v
Gate
v
Backend
v
Business System
v
Serialization
v
Model / UI
Claude 先知道自己有:
add_to_cart(product_id, quantity)
接著 Executor 接到 Tool Call:
async def _add_to_cart(...):
return await gated_add_to_cart(...)
Gate 先確認:
全部通過後,才真正執行:
await backend.add_to_cart(...)
Backend 再操作真正的 Commerce System。
這也解釋了 Anthropic 為什麼沒有把所有邏輯都塞進:
def add_to_cart():
validate()
update_db()
return result
因為這幾層負責的事情並不一樣。
回到一開始最實際的問題:
如果我要自己做航空改票 Agent,是不是所有
.py都要重寫?
答案是:不用。
新的 Domain 可以先不要想檔案,而是先定義 Agent 要負責哪些能力。
例如航空改票:
查 Reservation
搜尋替代航班
查 Ticket Rule
取得改票 Estimate
Stage Change
Apply Change
確定這些事情之後,才會自然產生需要的 Module:
| 問題 | 對應模組 |
|---|---|
| 需要哪些資料? | types.py |
| 公司提供哪些 API? | backend.py |
| Claude 可以呼叫什麼? | tools/ |
| Tool 怎麼執行? | executor.py |
| 哪些 Action 一定要擋? | gates.py |
| 哪些問題一定要先重新查資料? | grounding.py |
例如 Domain Model 可能變成:
class Reservation(BaseModel):
reservation_id: str
traveler_name: str
class FlightOption(BaseModel):
flight_id: str
departure: datetime
arrival: datetime
class ChangeEstimate(BaseModel):
estimate_id: str
price_difference: float
change_fee: float
Backend Contract 則可能是:
class FlightChangeBackend(ABC):
async def get_reservation(...):
...
async def search_flights(...):
...
async def estimate_change(...):
...
async def apply_change(...):
...
真正 Implementation 再去接:
而 Gate 也會換成航空業自己的規則:
因此從 Shopping 換成航空改票 Agent,並不是:
換一個 Prompt 或 Skill。
而是需要重新定義 Domain Logic。
但底下很多 Infrastructure 不用重新發明,例如:
這些仍然可以沿用 commerce-common 的設計。
所以比較精確的說法是:
新的 Role Agent 要重新定義 Domain Logic,但不需要重新發明整套 Agent Runtime。
最後還有一個 Repo 層級的重要設計。
同一套 Role Core 可以搭配三種 Runtime:
Messages API
Claude Agent SDK
Managed Agents
但這裡不要理解成:
同一個
ShoppingAgentPython Object 原封不動插到三個地方。
比較準確的是:
同一套 Role Core,有不同的 Runtime Adapter。
共同保留的是:
Role Core
+-- Config
+-- Prompt / Skills
+-- Tools
+-- Executor
+-- Gates
+-- Backend Contract
下面再接不同 Runtime:
Role Core
+-----------------------+
| Config |
| Prompt / Skills |
| Tools |
| Executor |
| Gates |
| Backend Contract |
+-----------------------+
| | |
v v v
Messages Agent Managed
API SDK Agents
差別主要在:
Agent Loop 由誰負責。
而 Runtime 再往外,才是 Host Application。
例如換成 LINE:
LINE
v
Webhook / FastAPI
v
Runtime
v
Role Core
v
Backend
因此這兩組概念不要混在一起:
| 回答的問題 | |
|---|---|
| Messages API / Agent SDK / Managed Agents | Agent 怎麼執行? |
| Web / LINE / Slack | 使用者從哪裡進來? |
Host Application 才是兩者之間的接點。