iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
AI Engineering

30天拆Agent:從Repo看設計系列 第 14 篇

Day 14|commerce-agents 怎麼做 Memory?從結構化記憶到 Claude Agent SDK

  • 分享至 

  • xImage
  •  

上一篇看完 commerce-agents 的 Runtime 與 MCP,這篇來看它另一個很值得拆的設計:Memory。

假設乘客第一次跟客服說:

我長途航班通常坐走道,而且不要紅眼班機。

幾個月後又來問:

幫我找下週去東京的班機。

Agent 如果要記得前面的偏好,最簡單的方法是把以前的聊天紀錄全部找回來。

但 commerce-agents 不是這樣做。

它會把真正值得留下的資訊整理成:

{
  "key": "seat_preference",
  "value": "Prefers aisle seats",
  "category": "preference"
}

也就是一筆 MemoryFact。

這篇只回答三個問題:

  1. 一句對話怎麼變成一筆 Memory?
  2. 這套 Memory 怎麼和 Claude Agent SDK 一起跑?
  3. 如果改成航空客服,需要改哪些地方?

1. 一句對話怎麼變成一筆 Memory?

核心程式主要在:

commerce-common/
└── commerce_common/
    ├── memory.py
    └── types.py

一筆 Memory 的資料結構很簡單:

class MemoryFact(BaseModel):
    key: str
    value: str
    category: MemoryCategory   # category 只有preference | constraint | context
    updated_at: datetime | None
    source_session_id: str | None

例如航空客服可能存成:

seat_preference
→ Prefers aisle seats
→ preference

overnight_flight
→ Avoids overnight flights
→ constraint

travel_party
→ Usually travels with spouse
→ context

它不是把這段原始對話整段保存。

我上次坐靠窗,
後來覺得進出很麻煩,
以後長途班機還是幫我找走道。

哪一句話值得被留下?

這是這套 Memory 最重要的地方。

commerce-agents 不是看到 User 說什麼就全部存,而是先用 Memory Extraction Prompt 判斷。

共用規則放在:

commerce-common/commerce_common/memory.py

MEMORY_EXTRACTION_TEMPLATE

它要求一筆長期 Memory 大致符合三個條件:

使用者自己明確說過
        +
過一段時間仍可能成立
        +
下次服務時仍可能有用

所以:

「我通常坐走道。」
→ 存

「我不搭紅眼班機。」
→ 存

「我通常和先生一起旅行。」
→ 可以存

但:

「這次改成明天晚上九點。」
→ 不存
   只是這次改票條件

「CI100 現在還有三個位置。」
→ 不存
   這是查詢結果

「他看起來應該比較喜歡靠窗。」
→ 不存
   這是 Model 推論

Shopping Agent 再把這個通用規則具體化。

shopping-agent/core/shopping_agent/memory.py

SHOPPING_MEMORY_EXTRACTION_PROMPT

例如它允許記:

seat / room preference
size
budget
travel companion
preferred maker

但不記:

商品搜尋結果
商店政策
本次操作內容
Model 自己推論的資訊

Prompt 判斷完,還不會直接寫入

完整流程是:

Conversation
     ↓
memory_model
     ↓
Extraction Prompt
     ↓
Candidate Fact 
     ↓
validate_fact()
     ↓
MemoryWriteFilter
     ↓
duplicate check
     ↓
MemoryStore

它會先讀目前已經保存的 Fact,再讓 Model 判斷這次有沒有新東西。

例如目前已有:

seat_preference = Prefers aisle seats

Model 又產生:

seat_preference = Likes aisle seats

程式會把它視為同一件事,不再多存一筆。

如果原本是:

seat_preference = Prefers window seats

User 這次明確說:

以後還是幫我選走道。

新的 Fact 就沿用:

seat_preference

更新成:

Prefers aisle seats

每次 extraction 預設最多產生三筆新 Fact:

max_new_facts = 3

避免一輪聊天被拆成大量 Memory。


Fact 最後存在哪裡?

Repo 沒有指定一定要用哪個 Database。

它只定義:

class MemoryStore(Protocol):

Storage 要提供:

get_facts(subject_id)

upsert_facts(subject_id, facts)

search_facts(subject_id, query)

delete_fact(subject_id, key)

clear(subject_id)

purge_generation(subject_id)

Repo 內建兩個簡單版本。

測試用:

InMemoryMemoryStore

資料只存在 process 裡。

Demo 則可以用:

JsonFileMemoryStore

Retail demo 實際設定:

memory_store=JsonFileMemoryStore(
    DATA_DIR / ".memory-store.json"
)

如果正式服務使用 PostgreSQL,可以自己做:

class PostgresMemoryStore:
    async def get_facts(...):
        ...

    async def upsert_facts(...):
        ...

    async def search_facts(...):
        ...

底層可以只是:

memory_fact

subject_id
key
value
category
updated_at
source_session_id

下一次對話,Memory 怎麼讀回來?

它也沒有把某個 User 的全部 Fact 一次送給 Model。

先取一小部分:

tier_one()

規則是:

所有 constraint
+
最近更新的 Fact

數量由memory_tier_one_cap控制。

例如乘客有二十筆 Memory,這次可能直接帶:

Avoids overnight flights
Prefers aisle seats
Usually travels with spouse

在 Shopping Agent 的 SDK runtime 中,這些資料是在 get_preferences() 時一起加入:

facts = await self._memory.tier_one(
    self._session.user_id
)

payload["saved_memory"] = [
    memory_fact_payload(f)
    for f in facts
]

其他較舊的資訊則等真的需要時才:

recall_memories

例如:

我爸上次坐什麼位置比較方便?

Agent 才去查相關 Memory。

因此它的讀取策略其實很簡單:

常用的重要 Fact
→ 每次帶

其他 Fact
→ 需要時才查

Skill 在這裡負責什麼?

Repo 還有:

shopping-agent/
└── skills/
    └── memory-personalization/
        └── SKILL.md

負責告訴 Agent:什麼時候應該使用 Memory?

例如:

這次 User 已經明確指定需求
→ 這次的需求優先

Memory 已經在 profile 中
→ 不用再 recall

舊 Memory 只有真的會影響選擇
→ 才查

所以兩者責任很清楚:

Extraction Prompt
→ 決定「記什麼」

Memory Skill
→ 決定「什麼時候用」

2. 這套 Memory 怎麼和 Claude Agent SDK 一起跑?

commerce-agents 的 Shopping Agent 可以直接跑在 Claude Agent SDK。

建立方式例如:

options = ClaudeAgentOptions(
    system_prompt=...,
    mcp_servers=...,
    allowed_tools=...,
    tools=["Skill"],
    skills=...,
    model=config.model,
)

Claude Agent SDK 負責:

Agent loop
Model call
Skill
Tool call
Hook

但 Customer Memory 還是使用 Commerce 自己的:

Claude Agent SDK
       │
       ▼
ShoppingToolset
       │
       ▼
ShoppingToolExecutor
       │
       ├── save_memory
       └── recall_memories
                │
                ▼
          MemoryRuntime
                │
                ▼
           MemoryStore

也就是:

Claude Agent SDK
→ 負責 Agent 執行

commerce-agents
→ 負責 User Memory

為什麼不直接使用 Claude SDK 的 Memory Tool?

Claude API 也有 Memory Tool,但抽象方式不一樣。

Claude Memory Tool 比較像:

/memories/
├── preferences.md
├── project.md
└── notes.md

Claude 可以操作這些 Memory file。

而 commerce-agents 使用的是:

subject_id
key
value
category

所以差異可以簡化成:

Claude Memory Tool commerce-agents
File-like Memory Structured Fact
Claude 組織內容 Schema 固定
通用 Agent Memory Customer / Merchant Memory
Application 提供 storage Application 一樣提供 storage

對 Commerce application 來說,後者比較容易控制:

這筆 Memory 是誰的?
這個欄位能不能存?
多久過期?
是不是 constraint?

所以 Repo 沒有直接用一份自由格式的 Memory file 保存 Customer preference。


Agent SDK 還有一個實作細節

Agent SDK runtime 已經可以使用:

save_memory
recall_memories

但 README 特別標示:

Memory extraction
→ not applied here

也就是說:

User:
我通常不搭紅眼班機。

Agent 沒有主動 save_memory

SDK runtime 預設不會在 Turn 結束後,自動再問一次:

這輪有沒有值得留下的 Fact?

如果產品希望有這種 Auto Learn,Host 要在 Turn 完成後另外執行:

await toolset.memory.extract(
    anthropic_client,
    subject_id=user_id,
    session_id=session_id,
    transcript=transcript,
)

完整流程才會變成:

Claude Agent SDK
      ↓
Agent reply
      ↓
Host
      ↓
MemoryRuntime.extract()
      ↓
MemoryStore

這是使用 Agent SDK 版本時需要額外注意的地方。


如果把 Shopping Agent 改成航空客服,也不用重新設計整套 Memory。

主要只換:

MemoryStore
→ 正式 DB

Extraction Prompt + Skill
→ 航空業的記憶規則

Config / Filter
→ 保存量、期限與敏感資料

這也是這個 Repo 的 Memory 最值得參考的地方:

它不是讓 Agent 自由寫一份「記憶筆記」,而是先把對話整理成固定格式的 Fact,再由 Runtime 控制這筆 Fact 能不能被保存與使用。

References

  • shopping-agent/skills/memory-personalization/SKILL.md — Memory 使用規則
  • shopping-agent/runtime-agent-sdk/README.md — Claude Agent SDK runtime 與 Memory 的整合方式
  • Anthropic — Memory Tool

上一篇
Day 13|Tool 怎麼接進 Agent?從 `commerce-common` 到三種 Runtime
下一篇
Day 15|Codex vs Claude Commerce Agents:Config、Memory、Tool 與 Sandbox 快速比較
系列文
30天拆Agent:從Repo看設計 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言