iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
AI Engineering

地端 AI 建築學系列 第 24 篇

24 LangChain DeepAgent (3) 核心模組(下)

  • 分享至 

  • xImage
  •  

上一篇我們整理了 DeepAgent 五塊拼圖裡的前兩塊:

  1. Tools — 讓 Agent 能對外採取行動的手
  2. Backends — 決定 Agent 工作空間長什麼樣子、資料存在哪裡的地基

有了行動力與安全的工作空間,這篇接著補完剩下三塊:

  1. Skills — 把「怎麼做」封裝成可重複使用的知識包
  2. Memory — 讓 Agent 能跨對話記得事情的長期記憶
  3. Summarization Middleware — 避免 context window 爆掉的壓縮機制

一樣全部搭配 Ollama 地端模型 ornith-1.5:9b 示範,把整套地端 DeepAgent 架構補齊。


Skills:賦予 Agent 操作技能的能力

Tools 是「動作」,Skills 則是「知識 + 流程」。一個 Skill 是一個資料夾,裡面放一份 SKILL.md(含 YAML frontmatter 的操作說明),可以再搭配 scripts/、references/、assets/ 等輔助資源。

漸進式揭露(Progressive Disclosure)

Skills 最關鍵的設計理念,是避免把所有指令一次塞進 system prompt 造成 context 膨脹。它分三個層級載入:

層級 載入內容 時機
1. Metadata SKILL.md frontmatter 的 name、description Agent 啟動時,所有已配置 skill 都會載入
2. Instructions SKILL.md 完整內文 該 skill 被觸發時才讀取
3. Resources scripts/、references/、assets/ 底下的檔案 Agent 依指令需要時才讀取

也就是說,Agent 啟動時只看得到每個 Skill 的「名字+一句話描述」,直到任務內容命中某個描述,才會用 read_file 把完整的 SKILL.md 讀進來。

建立一個 Skill

目錄結構範例:

skills/
└── langgraph-docs/
    ├── SKILL.md
    ├── scripts/
    │   └── fetch_docs.py
    └── references/
        └── api-patterns.md

SKILL.md 範例:

---
name: langgraph-docs
description: 
  當使用者詢問 LangGraph 相關問題時使用本技能,
  透過抓取官方文件來提供準確且最新的回答。
---

# langgraph-docs

## 使用方式

1. 用 fetch_url 工具讀取 https://docs.langchain.com/llms.txt
2. 依問題挑選 2-4 篇最相關的文件連結
3. 抓取內容後綜合回答,優先給直接答案,避免整段照抄

掛載到 Agent:

from deepagents import create_deep_agent
from deepagents.backends.filesystem import FilesystemBackend

backend = FilesystemBackend(root_dir="./my-project")

agent = create_deep_agent(
    model="ollama:ornith-1.5:9b",
    backend=backend,
    skills=["./my-project/skills/"],
)

Skill 撰寫技巧

  • description 要具體:這是 Agent 挑選 skill 時唯一看得到的資訊,寫清楚「做什麼」與「何時用」,並帶入使用者可能提到的關鍵字。

  • SKILL.md 本文控制在 500 行以內,frontmatter 建議在 5,000 tokens 以下,細節內容拆到 references/。

  • 技能數量寧少勿多:描述重疊的 skill 越多,Agent 選錯或猶豫的機率越高,必要時合併成一個涵蓋多個子任務的 skill。

Skills、Memory、Tools 的分工

Skills Memory Tools
目的 依需求載入的能力 啟動時就載入的持久上下文 程式化可呼叫的動作
載入時機 Agent 判斷相關時才讀 Agent 啟動時載入 每一輪都可用
格式 具名目錄下的 SKILL.md AGENTS.md 檔案 綁定在 Agent 上的函式
適用時機 指令具任務特化性、內容可能很長 內容永遠相關(專案慣例、偏好設定) 需要程式化動作,或無法透過檔案系統存取

小結:如果你發現自己一直在跟 Agent 重複交代同一套步驟,那就是把它蒸餾成 Skill 的訊號。地端場景下,把公司內部 SOP、故障排查流程封裝成 Skill,讓小模型也能穩定重現複雜流程。


Memory:建立 Agent 的記憶能力

DeepAgent 把長期記憶做成「檔案系統」的延伸:Agent 讀寫記憶就像讀寫檔案,實際存在哪裡則交給 Backend 決定。這一節專注在跨對話持久的長期記憶;單一對話內的短期記憶(訊息歷史、暫存檔案)則由 Agent state 自動管理。

Agent-scoped Memory:讓 Agent 有自己的人格

把 namespace 設成 (assistant_id,),代表所有使用者、所有對話共用同一份記憶檔案——Agent 因此能累積出一致的「人設」與跨使用者學到的通用知識。

from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend

agent = create_deep_agent(
    model="ollama:ornith-1.5:9b",
    memory=["/memories/AGENTS.md"],
    skills=["/skills/"],
    backend=CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(namespace=lambda rt: (rt.server_info.assistant_id,)),
            "/skills/": StoreBackend(namespace=lambda rt: (rt.server_info.assistant_id,)),
        },
    ),
)

User-scoped Memory:每位使用者的獨立記憶

把 namespace 換成 (rt.server_info.user.identity,),每位使用者就各自擁有一份記憶檔,彼此的偏好與歷史不會外洩:

backend=CompositeBackend(
    default=StateBackend(),
    routes={
        "/memories/": StoreBackend(namespace=lambda rt: (rt.server_info.user.identity,)),
    },
)

Episodic Memory:搜尋過去的對話

除了「事實型」記憶(存在 AGENTS.md 裡的偏好、規則),DeepAgent 也能利用 checkpointer 保存的完整對話歷史做「情節型」記憶 — 讓 Agent 回想起「上次是怎麼解決這個問題的」,而不只是「學到了什麼結論」:

from langgraph_sdk import get_client
from langchain.tools import tool, ToolRuntime

client = get_client(url="<DEPLOYMENT_URL>")

@tool
async def search_past_conversations(query: str, runtime: ToolRuntime) -> str:
    """搜尋過去與這位使用者相關的對話"""
    user_id = runtime.server_info.user.identity
    threads = await client.threads.search(metadata={"user_id": user_id}, limit=5)
    results = [await client.threads.get_history(thread_id=t["thread_id"]) for t in threads]
    return str(results)

讀寫策略:Hot Path vs. 背景整併

方式 優點 缺點
Hot path(對話當下寫入) 記憶立即可用,對使用者透明 增加延遲,Agent 要邊做事邊記錄
背景整併(sleep-time compute) 不拖慢當下對話,可跨多輪對話彙整 記憶要等下一次對話才生效,需要第二個 Agent

多數場景 hot path 就夠用;若使用量大、想降低延遲或提升記憶品質,可以另外部署一個「整併 Agent」,用 cron job 定期讀取近期對話、萃取重點、合併回記憶檔。

唯讀 vs. 可寫記憶

組織層級的政策(合規規則、共用知識庫)建議設為唯讀,只由應用程式碼寫入,避免透過共享狀態被 prompt injection:

permissions=[
    FilesystemPermission(operations=["write"], paths=["/policies/**"], mode="deny"),
]

小結:地端部署常見的節省成本手法,是把「使用者個人偏好」設成 user-scoped、可寫;把「公司規範、SOP」設成 organization-scoped、唯讀。兩者混搭在同一個 CompositeBackend 裡,各自路由到不同的儲存空間。


Summarization Middleware:壓縮當前 Session 的上下文

地端模型(尤其是 9B 這個量級)的 context window 通常比雲端旗艦模型小得多。當對話輪數一多、工具呼叫結果一長,很容易撞到上限。SummarizationMiddleware 就是為了解決這個問題:當接近 token 上限時,自動把較舊的訊息壓縮成摘要,同時保留最近的訊息原文。

基本用法

from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware

agent = create_agent(
    model="ollama:ornith-1.5:9b",
    tools=[your_weather_tool, your_calculator_tool],
    middleware=[
        SummarizationMiddleware(
            model="ollama:ornith-1.5:9b",
            trigger=("tokens", 4000),
            keep=("messages", 20),
        ),
    ],
)

觸發條件(trigger)與保留條件(keep)

trigger 決定「什麼時候開始壓縮」,keep 決定「壓縮後留下多少」,兩者都支援三種度量單位:

  • fraction:模型 context 視窗的比例(0–1)
  • tokens:絕對 token 數
  • messages:訊息則數

trigger 還支援組合邏輯:

# OR:tokens >= 3000 或 messages >= 6,符合其一即觸發
SummarizationMiddleware(
    model="ollama:ornith-1.5:9b",
    trigger=[("tokens", 3000), ("messages", 6)],
    keep=("messages", 20),
)

# AND:tokens >= 4000 且 messages >= 10 同時成立才觸發
SummarizationMiddleware(
    model="ollama:ornith-1.5:9b",
    trigger={"tokens": 4000, "messages": 10},
    keep=("messages", 20),
)

針對地端小模型的實務建議

因為 ornith-1.5:9b 這類地端模型的可用 context 通常較小,實務上有幾個要注意的地方:

  1. fraction 條件依賴模型 profile 資料。若 Ollama 模型沒有對應的 profile,建議改用 tokens 或手動指定 custom_profile,避免比例計算失準。
  2. trigger 的閾值要抓得比模型上限更保守,因為摘要本身也要消耗一次模型呼叫,若壓縮太晚觸發,可能連摘要這次呼叫都會超出上限。
  3. 可以用另一個更輕量的 Ollama 模型專門做摘要(例如 ornith-1.5:9b 之外再跑一個更小的模型),把「摘要」與「主任務」的算力分開,減少地端 GPU/CPU 負擔。

與 Context Editing 的分工

除了 Summarization,官方也提供 ContextEditingMiddleware(搭配 ClearToolUsesEdit),策略是直接清掉較舊的工具呼叫結果,而不是生成摘要:

from langchain.agents.middleware import ContextEditingMiddleware, ClearToolUsesEdit

middleware=[
    ContextEditingMiddleware(
        edits=[ClearToolUsesEdit(trigger=2000, keep=3)],
    ),
]

兩者可以同時使用:ContextEditingMiddleware 負責清掉不再需要的工具原始輸出(例如超長的搜尋結果),SummarizationMiddleware 負責把對話本身濃縮成摘要——分進合擊,讓地端小模型也能撐住長對話。

小結:地端模型的 context window 是稀缺資源,Summarization 與 Context Editing 是延長 Agent「有效對話長度」最直接的手段,建議一開始就把這兩層中介層納入預設架構,而不是等到爆 context 才補上。


總結:把五塊拼圖組起來

這兩篇涵蓋了 Tools、Backends、Skills、Memory、Summarization,也對應到打造一個生產級地端 Agent 的五個問題:

  • Agent 能做什麼?→ Tools
  • Agent 的資料放哪、誰能碰?→ Backends
  • Agent 怎麼知道複雜任務的做法?→ Skills
  • Agent 怎麼跨對話記得事情?→ Memory
  • Agent 怎麼撐過又長又貴的對話?→ Summarization

上一篇
23 LangChain DeepAgent (2) 核心模組(上)
下一篇
25 案例四:Hermes Agent (1)簡介與安裝
系列文
地端 AI 建築學 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言