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 decorator | tool() factory |
| Model Provider (模型提供者) | 整合自訂 LLM API | Model base class | Model base class |
| Plugin (插件) | 將 Hook 與 Tool 打包成可組合的套件 | Plugin base class | Plugin interface |
| Session Manager (Session 管理器) | 跨重啟持久化對話 | SessionManager base class | SnapshotStorage interface |
| Conversation Manager (對話管理器) | 控制 Context Window 與訊息歷史 | ConversationManager base class | ConversationManager 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_case | PascalCase | kebab-case | 用途 |
|---|---|---|---|---|
google | google | Google | google | - |
my tool | my_tool | MyTool | my-tool | - |
CustomLLM | custom_llm | Customllm | custom-llm | - |
轉換後的結果用於:
| 項目 | 格式 | 範例 (輸入 slack) |
|---|---|---|
| PyPI 套件名 | strands-{kebab} | strands-slack |
| Python Module | strands_{snake} | strands_slack |
| npm 套件名 | strands-{kebab} | strands-slack |
| Model 類別 | {Pascal}Model | SlackModel |
| Plugin 類別 | {Pascal}Plugin | SlackPlugin |
| Tool 函式 (Python) | {snake}_tool | slack_tool |
| Tool 函式 (TypeScript) | {camel}Tool | slackTool |
3. 安裝與設定 (Installation & Setup)
3.1 先決條件
Python 版本:
- Python 3.10 以上
pip或hatch套件管理器- 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 再根據結果產生自然語言回覆
- 參數的
str、int、float、bool、list、dict等基本型別都可直接使用
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 (提升)。當你只需要一種語言時,它可以:
- 刪除另一語言的子目錄 (如選 Python 就刪
typescript/) - 重寫 CI/CD Workflow:
- 移除
paths:filter (因為不再是 monorepo) - 移除
working-directory: pythondefault - 將
ci-python.yml重命名為ci.yml - 將 tag prefix
python-v*改為v*
- 移除
- 將檔案搬到 Repo Root:
python/src/→src/ - 清理 Monorepo 專用檔案:移除
CODE_OF_CONDUCT.md、CONTRIBUTING.md、NOTICE
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 Prefix | Workflow | 發佈目標 |
|---|---|---|
python-v* | publish-python.yml | PyPI |
typescript-v* | publish-typescript.yml | npm |
發佈前須設定 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 呼叫:
- Tool:Agent 在對話中根據使用者需求自動呼叫
- Model Provider:作為 Agent 的 LLM 後端
- Plugin:Agent 初始化時自動載入,Hook 在對應事件觸發
- Session Manager:Agent 自動在對話輪次間持久化狀態
- 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.py、browser/ 等) |
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_read、shell、browser)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 使用這個內部模型。
做法:
- 用
extension-template建立專案,只選 “Model Provider” - 在
model.py中實作stream()方法,將 Strands 的Messages格式轉換為 vLLM 的 OpenAI-compatible API 格式 - 處理串流回應,將 SSE (Server-Sent Events) 轉換為 Strands 的
StreamEvent - 發佈為內部 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 訊息。
做法:
- 用
extension-template建立專案,選 “Tool” + “Plugin” - 在
tool.py中實作send_slack_message和read_slack_channel兩個 Tool - 在
plugin.py中實作 Hook,在每次工具呼叫前記錄 Audit Log - 發佈到 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 持久化對話。
做法:
- 用
extension-template建立 TypeScript 專案,只選 “Session Storage” - 實作
DynamoSnapshotStorage,使用@aws-sdk/client-dynamodb - 設計 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 恢復完整對話歷史
附錄:快速參考
常用指令
| 動作 | Python | TypeScript |
|---|---|---|
| 初始化模板 | python setup_template.py | npx tsx setup-template.ts |
| 安裝相依 | pip install -e ".[dev]" | npm install |
| 執行測試 | hatch run test | npm run test |
| 完整檢查 | hatch run prepare | npm run check |
| 格式化 | hatch run format | npm run format |
| Lint | hatch run lint | npm run lint |
| 型別檢查 | hatch run typecheck | npm run type-check |
| 建構 | hatch build | npm run build |
Comments