Repository: https://github.com/strands-agents/extension-template Stars: 2 | Forks: 2 | Language: Python + TypeScript License: Apache-2.0 | Updated: 2026-06-01 Tags: Template, Extension


1. 專案概覽 (Project Overview)

1.1 這是什麼

extension-template 是 Strands Agents 官方提供的 Extension (擴充套件) 起始模板,讓開發者能以標準化的方式建立自訂元件,並發佈到 PyPI (Python) 或 npm (TypeScript)。它是一個 Monorepo (單一儲存庫) 結構,同時包含 Python 與 TypeScript 兩種語言的模板,覆蓋 Strands Agents 框架的五大擴充點 (Extension Point)。

1.2 為什麼重要

在 Strands Agents 的生態系中,核心 SDK (sdk-python / sdk-typescript) 提供了 Agent 的執行引擎,而 tools 倉庫提供了官方工具集。但當開發者需要:

  • 整合自家的 LLM API (如公司內部部署的模型)
  • 建立特殊用途的工具 (如內部系統 API 的 Agent Tool)
  • 客製化對話持久化策略 (如存到 Redis / DynamoDB)
  • 開發可組合的 Plugin (插件)

就需要一個 一致的起始結構extension-template 正是這個標準化的出發點,它確保社群套件遵循統一的命名慣例、專案結構、CI/CD 配置和發佈流程。

1.3 五大擴充點

擴充點用途Python 類別/裝飾器TypeScript 類別/工廠
Tool (工具)賦予 Agent 與外部系統互動的能力@tool decoratortool() factory
Model Provider (模型提供者)整合自訂 LLM APIModel base classModel base class
Plugin (插件)將 Hook 與 Tool 打包成可組合的套件Plugin base classPlugin interface
Session Manager (Session 管理器)跨重啟持久化對話SessionManager base classSnapshotStorage interface
Conversation Manager (對話管理器)控制 Context Window 與訊息歷史ConversationManager base classConversationManager base class

1.4 誰做的

由 AWS 的 Strands Agents 團隊開發與維護,隸屬 strands-agents GitHub Organization。這是該組織 12 個倉庫中唯一專注於「社群擴充套件開發體驗」的專案。


2. 核心架構 (Core Architecture)

2.1 Monorepo 結構


graph TD
    ROOT["extension-template (Monorepo Root)"]

    ROOT --> GH[".github/workflows/"]
    ROOT --> PY["python/"]
    ROOT --> TS["typescript/"]
    ROOT --> META["README.md / LICENSE / NOTICE"]

    GH --> CI_PY["ci-python.yml"]
    GH --> CI_TS["ci-typescript.yml"]
    GH --> PUB_PY["publish-python.yml"]
    GH --> PUB_TS["publish-typescript.yml"]

    PY --> PY_SRC["src/strands_template/"]
    PY --> PY_TEST["tests/"]
    PY --> PY_SETUP["setup_template.py"]
    PY --> PY_TOML["pyproject.toml"]

    PY_SRC --> PY_TOOL["tool.py"]
    PY_SRC --> PY_MODEL["model.py"]
    PY_SRC --> PY_PLUGIN["plugin.py"]
    PY_SRC --> PY_SESSION["session_manager.py"]
    PY_SRC --> PY_CONV["conversation_manager.py"]
    PY_SRC --> PY_INIT["__init__.py"]

    TS --> TS_SRC["src/"]
    TS --> TS_TEST["test/"]
    TS --> TS_SETUP["setup-template.ts"]
    TS --> TS_PKG["package.json"]

    TS_SRC --> TS_TOOL["tool.ts"]
    TS_SRC --> TS_MODEL["model.ts"]
    TS_SRC --> TS_PLUGIN["plugin.ts"]
    TS_SRC --> TS_SESSION["session-manager.ts"]
    TS_SRC --> TS_CONV["conversation-manager.ts"]
    TS_SRC --> TS_INDEX["index.ts"]

    style ROOT fill:#1a1a2e,color:#e0e0ff
    style PY fill:#306998,color:#fff
    style TS fill:#3178c6,color:#fff
    style GH fill:#24292e,color:#fff

2.2 Setup Script 互動流程

兩個語言版本各自有一個互動式 Setup Script (設定腳本),執行後會自動客製化整個模板。以下是 Setup Script 的完整執行流程:


flowchart TD
    A["開始: 執行 setup_template.py / setup-template.ts"] --> B["詢問 Package Name
(如: google, slack, redis)"] B --> C["自動產生命名變體
snake_case / PascalCase / kebab-case"] C --> D["選擇要保留的 Components
(Tool / Model / Plugin / Session / Conversation)"] D --> E["輸入 Author / Email / GitHub Username"] E --> F["確認所有設定"] F -->|確認| G["Phase 1: 全域文字替換
Template → 你的名稱"] F -->|取消| Z["結束"] G --> H["Phase 2: 刪除未選擇的 Component 檔案"] H --> I["Phase 3: 更新 __init__.py / index.ts
只匯出已選的元件"] I --> J["Phase 4: 移除 Monorepo 專用檔案
(CODE_OF_CONDUCT / CONTRIBUTING / NOTICE)"] J --> K{"是否移除另一語言
並提升到 Repo Root?"} K -->|是| L["刪除 sibling 目錄
+ 移除對應 CI Workflow"] L --> M["重寫 CI/Publish Workflow
移除 monorepo 前綴邏輯"] M --> N["Hoist: 將檔案搬到上層
成為標準單語言 Repo"] N --> O["刪除 setup script 自身"] K -->|否| O O --> P["完成: 可開始實作"] style A fill:#4CAF50,color:#fff style P fill:#4CAF50,color:#fff style Z fill:#f44336,color:#fff

2.3 命名慣例轉換

Setup Script 會根據你輸入的名稱,自動產生所有必要的命名格式:

輸入snake_casePascalCasekebab-case用途
googlegoogleGooglegoogle-
my toolmy_toolMyToolmy-tool-
CustomLLMcustom_llmCustomllmcustom-llm-

轉換後的結果用於:

項目格式範例 (輸入 slack)
PyPI 套件名strands-{kebab}strands-slack
Python Modulestrands_{snake}strands_slack
npm 套件名strands-{kebab}strands-slack
Model 類別{Pascal}ModelSlackModel
Plugin 類別{Pascal}PluginSlackPlugin
Tool 函式 (Python){snake}_toolslack_tool
Tool 函式 (TypeScript){camel}ToolslackTool

3. 安裝與設定 (Installation & Setup)

3.1 先決條件

Python 版本:

  • Python 3.10 以上
  • piphatch 套件管理器
  • Git

TypeScript 版本:

  • Node.js 20.0.0 以上
  • npm
  • Git

3.2 建立你的 Repo

步驟一:從模板建立

前往 https://github.com/strands-agents/extension-template,點選 “Use this template” 按鈕,建立屬於你的 Repository。

1# Clone 你新建的 repo
2git clone https://github.com/你的帳號/你的repo名稱
3cd 你的repo名稱

3.3 Python 版本設定

1# 進入 Python 子目錄
2cd python
3
4# 執行互動式 Setup Script
5python setup_template.py

Setup Script 會提示你:

 1Package name (e.g., 'google', 'aws', 'slack'): redis
 2  PyPI package: strands-redis
 3  Module:       strands_redis
 4  Classes:      RedisModel, RedisHooks, etc.
 5
 6Which components do you want to include?
 7  1. Tool - Add capabilities to agents using the @tool decorator
 8  2. Model Provider - Integrate custom LLM APIs
 9  3. Plugin - Extend agent behavior with hooks and tools in a composable package
10  4. Session Manager - Persist conversations across restarts
11  5. Conversation Manager - Control context window and message history
12
13Enter numbers separated by commas (e.g., 1,2): 1,4

完成後安裝開發相依套件:

1# 安裝為可編輯模式 (editable install)
2pip install -e ".[dev]"
3
4# 或使用 hatch
5hatch env create

3.4 TypeScript 版本設定

1# 進入 TypeScript 子目錄
2cd typescript
3
4# 執行互動式 Setup Script
5npx tsx setup-template.ts
6# 或
7npm run setup

完成後安裝相依套件:

1npm install

3.5 驗證安裝

Python:

1# 執行完整檢查(格式化 → Lint → 型別檢查 → 測試)
2hatch run prepare
3
4# 或個別執行
5hatch run test        # 測試
6hatch run lint        # Linter
7hatch run typecheck   # 型別檢查
8hatch run format      # 格式化

TypeScript:

1# 執行完整檢查
2npm run check
3
4# 或個別執行
5npm run test          # 測試
6npm run lint          # ESLint
7npm run type-check    # TypeScript 型別檢查
8npm run format:check  # Prettier 格式檢查

4. 使用方式與程式碼範例 (Usage & Code Examples)

4.1 範例一:建立自訂 Tool (Python)

Tool 是最常見的擴充類型。以下示範如何把模板中的 tool.py 實作成一個實際可用的天氣查詢工具。

模板原始碼:

 1"""Tool Implementation."""
 2
 3import logging
 4from typing import Any
 5
 6from strands import tool
 7
 8logger = logging.getLogger(__name__)
 9
10
11@tool
12def template_tool(param1: str) -> dict[str, Any]:
13    """Brief description of what your tool does.
14
15    Args:
16        param1: Description of parameter 1.
17
18    Returns:
19        Dict containing status and response content.
20    """
21    # TODO: Implement your tool logic
22    raise NotImplementedError

實作後的範例 (天氣查詢工具):

 1"""Weather lookup tool for Strands Agents."""
 2
 3import logging
 4from typing import Any
 5import httpx                        # 使用 httpx 做 HTTP 請求
 6
 7from strands import tool
 8
 9logger = logging.getLogger(__name__)
10
11# 1. @tool 裝飾器自動將此函式註冊為 Agent 可呼叫的工具
12#    Strands SDK 會解析 docstring 與 type hints 來產生 tool schema
13@tool
14def get_weather(city: str, units: str = "metric") -> dict[str, Any]:
15    """查詢指定城市的目前天氣狀況。
16
17    Args:
18        city: 要查詢天氣的城市名稱(如 "Taipei")。
19        units: 溫度單位,"metric" 為攝氏,"imperial" 為華氏。
20
21    Returns:
22        包含溫度、濕度和天氣描述的字典。
23    """
24    # 2. 實作實際的 API 呼叫邏輯
25    api_key = "YOUR_API_KEY"  # 實務上應從環境變數讀取
26    url = f"https://api.openweathermap.org/data/2.5/weather"
27    params = {"q": city, "appid": api_key, "units": units}
28
29    # 3. 發送請求並處理回應
30    response = httpx.get(url, params=params)
31    response.raise_for_status()
32    data = response.json()
33
34    # 4. 回傳結構化結果,Agent 會將此資訊整合到回覆中
35    return {
36        "status": "success",
37        "city": city,
38        "temperature": data["main"]["temp"],
39        "humidity": data["main"]["humidity"],
40        "description": data["weather"][0]["description"],
41    }

整合到 Agent:

1from strands import Agent
2from strands_weather import get_weather  # 你的套件名稱
3
4# 將工具傳入 Agent,Agent 會在需要時自動呼叫
5agent = Agent(tools=[get_weather])
6response = agent("台北現在的天氣如何?")

關鍵觀念:

  • @tool 裝飾器會自動解析函式的 docstring、參數名稱和 Type Annotation (型別註解),產生符合 LLM Tool Calling 規範的 JSON Schema
  • 回傳值會被 Agent 框架序列化後傳回 LLM,LLM 再根據結果產生自然語言回覆
  • 參數的 strintfloatboollistdict 等基本型別都可直接使用

4.2 範例二:建立 Model Provider (TypeScript)

Model Provider 讓你整合任何 LLM API。以下示範 TypeScript 版本的結構:

 1/**
 2 * 自訂 Model Provider 範例 — 整合假想的 "MyLLM" API
 3 */
 4import {
 5  Model,
 6  type BaseModelConfig,
 7  type Message,
 8  type ModelStreamEvent,
 9  type StreamOptions,
10} from '@strands-agents/sdk'
11
12// 1. 定義設定介面,繼承 BaseModelConfig
13export interface MyLLMConfig extends BaseModelConfig {
14  apiKey: string          // API 金鑰
15  endpoint: string        // API 端點 URL
16  temperature?: number    // 生成溫度(可選)
17}
18
19// 2. 繼承 Model 基礎類別,實作必要方法
20export class MyLLMModel extends Model<MyLLMConfig> {
21  private _config: MyLLMConfig
22
23  constructor(config: MyLLMConfig) {
24    super()
25    this._config = { ...config }
26  }
27
28  // 3. 實作 updateConfig:讓 Agent 能在執行期間調整設定
29  override updateConfig(modelConfig: Partial<MyLLMConfig>): void {
30    this._config = { ...this._config, ...modelConfig }
31  }
32
33  // 4. 實作 getConfig:回傳目前設定的副本
34  override getConfig(): MyLLMConfig {
35    return { ...this._config }
36  }
37
38  // 5. 核心方法:實作 stream() 來處理與 LLM 的串流通訊
39  //    必須依序 yield 以下事件:
40  //    ModelMessageStartEvent → ContentBlockStart/Delta/Stop → ModelMessageStopEvent
41  override async *stream(
42    messages: Message[],
43    options?: StreamOptions
44  ): AsyncIterable<ModelStreamEvent> {
45    // 呼叫你的 LLM API
46    const response = await fetch(this._config.endpoint, {
47      method: 'POST',
48      headers: {
49        'Authorization': `Bearer ${this._config.apiKey}`,
50        'Content-Type': 'application/json',
51      },
52      body: JSON.stringify({
53        messages,
54        temperature: this._config.temperature ?? 0.7,
55        tools: options?.toolSpecs,
56      }),
57    })
58
59    // 將 API 回應轉換為 Strands StreamEvent 格式
60    yield { messageStart: { role: 'assistant' } }
61    yield { contentBlockStart: { contentBlock: { type: 'text', text: '' } } }
62
63    // 處理串流回應...
64    const data = await response.json()
65    yield { contentBlockDelta: { delta: { type: 'text', text: data.content } } }
66
67    yield { contentBlockStop: {} }
68    yield { messageStop: { stopReason: 'end_turn' } }
69  }
70}

使用方式:

 1import { Agent } from '@strands-agents/sdk'
 2import { MyLLMModel } from 'strands-myllm'
 3
 4const model = new MyLLMModel({
 5  apiKey: process.env.MYLLM_API_KEY!,
 6  endpoint: 'https://api.myllm.com/v1/chat',
 7  temperature: 0.5,
 8})
 9
10const agent = new Agent({ model })
11const response = await agent.run('幫我寫一首詩')

4.3 範例三:建立 Plugin (Python)

Plugin 是最強大的擴充機制,它能同時包含 Hook (生命週期鉤子) 和 Tool (工具),並以裝飾器自動被發現和註冊。

 1"""Logging Plugin — 記錄所有工具呼叫的執行日誌。"""
 2
 3import logging
 4import time
 5from typing import TYPE_CHECKING
 6
 7from strands import tool
 8from strands.hooks import BeforeToolCallEvent, AfterToolCallEvent
 9from strands.plugins import Plugin, hook
10
11if TYPE_CHECKING:
12    from strands.agent.agent import Agent
13
14logger = logging.getLogger(__name__)
15
16
17class LoggingPlugin(Plugin):
18    """記錄 Agent 工具呼叫的 Plugin。
19
20    自動追蹤每次工具呼叫的:
21    - 工具名稱與輸入參數
22    - 執行耗時
23    - 成功/失敗狀態
24    """
25
26    # 1. name 屬性用於辨識 Plugin
27    name = "logging-plugin"
28
29    def __init__(self, log_level: int = logging.INFO) -> None:
30        super().__init__()
31        self._log_level = log_level
32        self._call_times: dict[str, float] = {}  # 追蹤呼叫開始時間
33
34    # 2. init_agent 在 Plugin 被 Agent 載入時執行
35    #    裝飾了 @hook 和 @tool 的方法會自動被註冊,
36    #    這裡可以做額外的初始化邏輯
37    def init_agent(self, agent: "Agent") -> None:
38        logger.log(self._log_level, "LoggingPlugin initialized for agent")
39
40    # 3. @hook 裝飾器:在工具呼叫前觸發
41    @hook  # type: ignore[call-overload]
42    def on_before_tool_call(self, event: BeforeToolCallEvent) -> None:
43        tool_name = event.tool_use.get("name", "unknown")
44        tool_input = event.tool_use.get("input", {})
45
46        # 記錄呼叫開始時間
47        call_id = event.tool_use.get("toolUseId", "")
48        self._call_times[call_id] = time.time()
49
50        logger.log(
51            self._log_level,
52            f"[TOOL CALL] {tool_name} | Input: {tool_input}"
53        )
54
55    # 4. @tool 裝飾器:Plugin 也可以攜帶自己的工具
56    #    注意:Plugin 的 tool 方法第一個參數是 self
57    @tool
58    def get_call_stats(self) -> str:
59        """取得目前的工具呼叫統計資訊。"""
60        total = len(self._call_times)
61        return f"Total tool calls tracked: {total}"

使用方式:

1from strands import Agent
2from strands_logging import LoggingPlugin
3
4# Plugin 透過 plugins 參數傳入,可以傳入多個
5plugin = LoggingPlugin(log_level=logging.DEBUG)
6agent = Agent(plugins=[plugin])
7
8# Agent 執行時,Plugin 的 hook 會自動在對應的時間點觸發
9response = agent("請查詢台北的天氣,然後顯示呼叫統計")

4.4 範例四:建立 Session Storage (TypeScript)

TypeScript SDK 中的 Session 持久化分為兩層:SessionManager (SDK 內建,負責時機控制) 和 SnapshotStorage (你要實作的,負責實際儲存)。

 1/**
 2 * Redis-based SnapshotStorage — 使用 Redis 持久化 Agent 對話
 3 */
 4import type {
 5  Snapshot,
 6  SnapshotLocation,
 7  SnapshotManifest,
 8  SnapshotStorage
 9} from '@strands-agents/sdk'
10import { createClient, type RedisClientType } from 'redis'
11
12export interface RedisStorageConfig {
13  connectionString: string  // Redis 連線字串
14  keyPrefix?: string        // Key 前綴(預設 "strands:")
15}
16
17export class RedisSnapshotStorage implements SnapshotStorage {
18  private client: RedisClientType
19  private prefix: string
20
21  constructor(private readonly config: RedisStorageConfig) {
22    // 1. 建立 Redis 連線
23    this.client = createClient({ url: config.connectionString })
24    this.prefix = config.keyPrefix ?? 'strands:'
25  }
26
27  // 2. 儲存快照:將 Snapshot 序列化為 JSON 存入 Redis
28  async saveSnapshot(params: {
29    location: SnapshotLocation
30    snapshotId: string
31    isLatest: boolean
32    snapshot: Snapshot
33  }): Promise<void> {
34    const key = `${this.prefix}${params.location.sessionId}:${params.snapshotId}`
35    await this.client.set(key, JSON.stringify(params.snapshot))
36
37    // 如果是最新的,額外存一份 "latest" 指標
38    if (params.isLatest) {
39      const latestKey = `${this.prefix}${params.location.sessionId}:latest`
40      await this.client.set(latestKey, params.snapshotId)
41    }
42  }
43
44  // 3. 載入快照:從 Redis 讀取並反序列化
45  async loadSnapshot(params: {
46    location: SnapshotLocation
47    snapshotId?: string
48  }): Promise<Snapshot | null> {
49    let snapshotId = params.snapshotId
50    if (!snapshotId) {
51      // 沒指定 ID 就載入最新的
52      const latestKey = `${this.prefix}${params.location.sessionId}:latest`
53      snapshotId = await this.client.get(latestKey) ?? undefined
54    }
55    if (!snapshotId) return null
56
57    const key = `${this.prefix}${params.location.sessionId}:${snapshotId}`
58    const data = await this.client.get(key)
59    return data ? JSON.parse(data) : null
60  }
61
62  // 4. 其他必要方法...
63  async listSnapshotIds(params: {
64    location: SnapshotLocation
65    limit?: number
66  }): Promise<string[]> {
67    const pattern = `${this.prefix}${params.location.sessionId}:*`
68    const keys = await this.client.keys(pattern)
69    return keys
70      .map(k => k.replace(`${this.prefix}${params.location.sessionId}:`, ''))
71      .filter(k => k !== 'latest' && k !== 'manifest')
72      .slice(0, params.limit)
73  }
74
75  async deleteSession(params: { sessionId: string }): Promise<void> {
76    const pattern = `${this.prefix}${params.sessionId}:*`
77    const keys = await this.client.keys(pattern)
78    if (keys.length > 0) await this.client.del(keys)
79  }
80
81  async loadManifest(params: {
82    location: SnapshotLocation
83  }): Promise<SnapshotManifest> {
84    const key = `${this.prefix}${params.location.sessionId}:manifest`
85    const data = await this.client.get(key)
86    return data ? JSON.parse(data) : { snapshotIds: [] }
87  }
88
89  async saveManifest(params: {
90    location: SnapshotLocation
91    manifest: SnapshotManifest
92  }): Promise<void> {
93    const key = `${this.prefix}${params.location.sessionId}:manifest`
94    await this.client.set(key, JSON.stringify(params.manifest))
95  }
96}

使用方式:

 1import { Agent, SessionManager } from '@strands-agents/sdk'
 2import { RedisSnapshotStorage } from 'strands-redis'
 3
 4// SessionManager 是 SDK 內建的,你只需提供 storage backend
 5const session = new SessionManager({
 6  sessionId: 'user-123-session',
 7  storage: {
 8    snapshot: new RedisSnapshotStorage({
 9      connectionString: 'redis://localhost:6379',
10      keyPrefix: 'myapp:',
11    }),
12  },
13})
14
15const agent = new Agent({ sessionManager: session })

5. 進階功能 (Advanced Features)

5.1 Setup Script 的 Hoist 機制

Setup Script 最獨特的功能之一是 Hoist (提升)。當你只需要一種語言時,它可以:

  1. 刪除另一語言的子目錄 (如選 Python 就刪 typescript/)
  2. 重寫 CI/CD Workflow
    • 移除 paths: filter (因為不再是 monorepo)
    • 移除 working-directory: python default
    • ci-python.yml 重命名為 ci.yml
    • 將 tag prefix python-v* 改為 v*
  3. 將檔案搬到 Repo Rootpython/src/src/
  4. 清理 Monorepo 專用檔案:移除 CODE_OF_CONDUCT.mdCONTRIBUTING.mdNOTICE

Hoist 後的 Repo 看起來就像一個全新的、標準的單語言專案。

5.2 pyproject.toml 的 hatch-vcs 整合

Python 模板使用 hatch-vcs 從 Git Tag 自動推導版本號:

1[tool.hatch.version]
2source = "vcs"
3
4[tool.hatch.version.raw-options]
5root = ".."                                    # Monorepo 模式:Git root 在上層
6tag_regex = "^python-v(?P<version>.+)$"        # 匹配 python-v0.1.0 格式
7relative_to = "pyproject.toml"                 # 參考點

Hoist 後,Setup Script 會自動移除 [tool.hatch.version.raw-options] 區塊,讓 hatch-vcs 使用預設的 v* tag 格式。

5.3 TypeScript 的 Zod Schema 驗證

TypeScript 模板的 Tool 定義使用 Zod v4 做參數驗證:

 1import { tool } from '@strands-agents/sdk'
 2import { z } from 'zod'
 3
 4// Zod schema 定義了工具的輸入參數
 5const inputSchema = z.object({
 6  param1: z.string().describe('Description of parameter 1.'),
 7})
 8
 9// tool() factory 接受 Zod schema,自動產生 JSON Schema 並做執行期驗證
10export const templateTool = tool({
11  name: 'template_tool',
12  description: 'Brief description of what your tool does.',
13  inputSchema,
14  callback: (input: z.infer<typeof inputSchema>) => {
15    // input 已經過 Zod 驗證,型別安全
16    return { result: input.param1.toUpperCase() }
17  },
18})

Zod 不僅在編譯期提供 TypeScript 型別推導,還在執行期驗證 LLM 傳入的參數是否符合預期格式。

5.4 Plugin 的 Hook 自動發現機制

Python 版本的 Plugin 使用裝飾器實現自動發現:

 1class TemplatePlugin(Plugin):
 2    # @hook 裝飾的方法會被 Plugin Registry 自動掃描並註冊
 3    @hook
 4    def on_before_tool_call(self, event: BeforeToolCallEvent) -> None:
 5        pass
 6
 7    # @tool 裝飾的方法同樣會被自動發現
 8    @tool
 9    def my_plugin_tool(self, param1: str) -> str:
10        pass

而 TypeScript 版本使用顯式的 initAgent()getTools() 方法:

 1class TemplatePlugin implements Plugin {
 2    // 在 initAgent 中顯式註冊 hook
 3    initAgent(agent: LocalAgent): void {
 4        agent.addHook(BeforeToolCallEvent, (event) => { ... })
 5    }
 6    // 在 getTools 中顯式回傳工具
 7    getTools(): Tool[] {
 8        return [templatePluginTool]
 9    }
10}

5.5 CI/CD Workflow 的雙語言發佈機制

Monorepo 設計中,Python 和 TypeScript 的發佈流程完全獨立:

Tag PrefixWorkflow發佈目標
python-v*publish-python.ymlPyPI
typescript-v*publish-typescript.ymlnpm

發佈前須設定 PyPI Trusted Publishing 或 npm 的 NPM_TOKEN Secret。Hoist 後統一使用 v* tag。

5.6 Python 與 TypeScript 的 Session 設計差異

值得注意的是,兩個語言的 Session 管理架構不同:

Python: 你直接繼承 SessionManager 基礎類別,實作 initialize()append_message()redact_latest_message()sync_agent() 四個方法。

TypeScript: Session 管理拆成兩層:

  • SessionManager — SDK 內建的 Plugin,負責在正確的時機觸發快照。你通常不需要覆寫。
  • SnapshotStorage — 你要實作的介面,只負責底層的讀/寫/列/刪操作。

這意味著 TypeScript 的關注點分離更徹底,你只需要管「怎麼存」,不需要管「什麼時候存」。


6. Agent 可呼叫性分析 (Agent Callability Analysis)

6.1 本專案的定位

extension-template 本身 不是一個可直接執行的工具或服務,而是一個 Scaffolding (鷹架) 專案。它的「產出」是其他開發者基於此模板建立的擴充套件。

6.2 可呼叫性

面向狀態說明
CLI 可呼叫有限python setup_template.py / npx tsx setup-template.ts 做初始化
MCP 整合不適用模板本身不提供 MCP Server,但產出的 Tool 可透過 SDK 作為 MCP 工具使用
API 端點不適用非 API 服務
程式庫匯入模板形式產出的套件可以 pip install / npm install 後匯入使用

6.3 從 Agent 呼叫的路徑

由此模板產出的套件,可以在以下情境中被 Agent 呼叫:

  1. Tool:Agent 在對話中根據使用者需求自動呼叫
  2. Model Provider:作為 Agent 的 LLM 後端
  3. Plugin:Agent 初始化時自動載入,Hook 在對應事件觸發
  4. Session Manager:Agent 自動在對話輪次間持久化狀態
  5. Conversation Manager:Agent 自動在 Context 溢出時觸發

7. 與生態系的關係 (Ecosystem Relationships)

7.1 在 Strands Agents 12 個倉庫中的定位

extension-template 是生態系的 開發者入口 (Developer Onramp)。它與其他倉庫的關係如下:

倉庫關係說明
sdk-python直接相依Python 模板的 strands-agents>=1.0.0 相依來自此 SDK
sdk-typescript直接相依TypeScript 模板的 @strands-agents/sdk>=1.0.0 相依來自此 SDK
tools平行參考官方工具集是 Tool 開發的範例來源 (sleep.pybrowser/ 等)
agent-builder互補Agent Builder 消費此模板產出的套件
docs文件來源strandsagents.com 的文件指導如何使用此模板
samples範例參考完整的 Agent 應用範例,展示如何整合多個擴充套件
multiagent消費者多 Agent 系統可使用此模板產出的 Model Provider 和 Plugin
sagemaker-extension同類AWS SageMaker 的擴充套件,是此模板「畢業」後的範例

7.2 依賴關係圖

1sdk-python / sdk-typescript (核心 SDK)
2    ↑ 相依
3extension-template (起始模板)
4    ↓ 產出
5社群套件 (如 strands-redis, strands-slack)
6    ↓ 被使用
7Agent 應用程式 (透過 agent-builder / samples / multiagent)

7.3 與 tools 倉庫的差異

  • tools:Strands 官方維護的預建工具集,直接安裝即用(如 file_readshellbrowser
  • extension-template:提供空白起始點,讓你建立自己的工具和元件

如果你需要的功能 tools 已經有了,直接用;如果沒有,就用 extension-template 建一個。


8. 優缺點分析 (Strengths & Limitations)

8.1 優點

優點說明
五大擴充點完整覆蓋Tool、Model、Plugin、Session、Conversation 全部有模板
互動式 Setup Script一鍵客製化,不需手動改名和刪檔
雙語言支援Python + TypeScript 同步提供,命名慣例一致
Hoist 機制可自動從 Monorepo 轉成單語言標準 Repo
CI/CD 預設配好GitHub Actions 的 CI 和 PyPI/npm 發佈 Workflow 都已就緒
測試骨架每個元件都有對應的測試檔案,使用 pytest / vitest
完善的 Linting 設定Python (ruff + mypy)、TypeScript (ESLint + Prettier + tsc) 都已配好
命名慣例自動轉換輸入一個名稱自動產生 snake_case / PascalCase / kebab-case

8.2 限制

限制說明
測試只是空殼測試檔案只有 ...(pass),沒有實際的測試範例
沒有整合測試範例缺少端到端的 Agent + Extension 整合測試
沒有 Docker 支援沒有提供 Dockerfile 或容器化開發環境
文件以連結為主模板本身的 README 主要指向外部文件,缺少內聯的教學
新專案(star 少)僅 2 stars,社群尚在早期階段
缺少進階範例沒有「完整實作的範例擴充套件」可以直接參考
Conversation Manager 差異大Python 和 TypeScript 的 API 差異明顯,增加跨語言學習成本

9. 實戰應用場景 (Real-world Use Cases)

9.1 場景一:整合企業內部 LLM

情境: 你的公司在內部部署了一個 vLLM / TGI 伺服器,需要讓 Strands Agent 使用這個內部模型。

做法:

  1. extension-template 建立專案,只選 “Model Provider”
  2. model.py 中實作 stream() 方法,將 Strands 的 Messages 格式轉換為 vLLM 的 OpenAI-compatible API 格式
  3. 處理串流回應,將 SSE (Server-Sent Events) 轉換為 Strands 的 StreamEvent
  4. 發佈為內部 PyPI 套件(strands-internal-llm
1from strands import Agent
2from strands_internal_llm import InternalModel
3
4model = InternalModel(
5    endpoint="https://llm.internal.company.com/v1",
6    api_key="internal-token",
7    model_id="meta-llama/Llama-3.1-70B",
8)
9agent = Agent(model=model)

9.2 場景二:建立 Slack Bot 工具集

情境: 你需要讓 Agent 能讀取和發送 Slack 訊息。

做法:

  1. extension-template 建立專案,選 “Tool” + “Plugin”
  2. tool.py 中實作 send_slack_messageread_slack_channel 兩個 Tool
  3. plugin.py 中實作 Hook,在每次工具呼叫前記錄 Audit Log
  4. 發佈到 PyPI 讓團隊共用
1from strands import Agent
2from strands_slack import SlackPlugin, send_slack_message, read_slack_channel
3
4agent = Agent(
5    tools=[send_slack_message, read_slack_channel],
6    plugins=[SlackPlugin(workspace="my-company")]
7)
8agent("幫我在 #general 頻道發送一則公告")

9.3 場景三:DynamoDB Session 持久化

情境: 你的 Agent 部署在 AWS Lambda 上,需要跨 invocation 持久化對話。

做法:

  1. extension-template 建立 TypeScript 專案,只選 “Session Storage”
  2. 實作 DynamoSnapshotStorage,使用 @aws-sdk/client-dynamodb
  3. 設計 DynamoDB Table 的 partition key = sessionId,sort key = snapshotId
 1import { Agent, SessionManager } from '@strands-agents/sdk'
 2import { DynamoSnapshotStorage } from 'strands-dynamodb'
 3
 4const session = new SessionManager({
 5  sessionId: req.headers['x-session-id'],
 6  storage: {
 7    snapshot: new DynamoSnapshotStorage({
 8      tableName: 'agent-sessions',
 9      region: 'ap-northeast-1',
10    }),
11  },
12})
13
14const agent = new Agent({ sessionManager: session })
15// 即使 Lambda cold start,也能從 DynamoDB 恢復完整對話歷史

附錄:快速參考

常用指令

動作PythonTypeScript
初始化模板python setup_template.pynpx tsx setup-template.ts
安裝相依pip install -e ".[dev]"npm install
執行測試hatch run testnpm run test
完整檢查hatch run preparenpm run check
格式化hatch run formatnpm run format
Linthatch run lintnpm run lint
型別檢查hatch run typechecknpm run type-check
建構hatch buildnpm run build

相關連結