後端 SDK 使用指南
後端 SDK 是讓你的伺服器以可信端的角色與 Asgard Bot Provider 溝通的工具, 是 Backend Relay 模式的核心。本指南介紹後端語言 SDK 用法、以及後端中繼層的整體實作範式。
本頁專為採用 Backend Relay 模式的讀者撰寫 — 也就是「你的後端作為前端與 Asgard Edge Server 之間中繼層」的情境。其他 Pattern(Direct Connect / Workflow Auth / Hosted Embed)不需要後端 SDK。
如果你對 Pattern 還不熟悉, 請先閱讀 總覽與選型, 確認確實需要 Backend Relay 再回到本頁。
後端 SDK 是什麼
當你採用 Backend Relay 模式時,你的後端會成為前端與 Asgard Edge Server 之間的中繼層, 流程簡化如下:
後端 SDK 就是上方藍色節點 ③④ 用來與 Edge Server 溝通的工具(其餘步驟由你的後端自行實作)。完整流程的時序圖見 Pattern: Backend Relay — 架構圖。
語言支援現況
| 語言 | 套件 | 狀態 |
|---|---|---|
| Go | go.asgard-ai.com/asgard-sdk-go | ✅ 可用 |
| Node.js | @asgard-js/nodejs | ✅ 可用 |
| Python | asgard-sdk | ✅ 可用 |
| .NET | AsgardAi.Sdk | ✅ 可用 |
| Java | com.asgard-ai:asgard-sdk-java | ✅ 可用 |
若你使用的語言尚未有原生 SDK, 可以直接呼叫 Bot Provider 的 HTTP API(本質是 SSE / REST / Multipart 等標準協定)。請參考 Send Message API 介紹 取得完整的 endpoint、事件型別與 payload schema。
Asgard 的「後端 Node.js SDK」(@asgard-js/nodejs)是跑在你的伺服器(例如 Express / Fastify / Next.js Route Handler 的伺服器側), 用途是中繼前端到 Asgard。
這跟「前端 Javascript SDK」(@asgard-js/core / @asgard-js/react)完全不同。前端 SDK 跑在使用者瀏覽器中, 用途是渲染聊天 UI 與直連 Asgard 或你的後端。
兩者套件名稱、執行位置、用途都不同, 使用時請確認上下文。詳見 前端 SDK 使用指南。
SDK 完整指南
安裝
- Go
- Node.js
- Python
- .NET
- Java
go get go.asgard-ai.com/asgard-sdk-go
npm install @asgard-js/nodejs
需要 Node.js 18+。後續所有 Node.js 範例皆以 TypeScript 撰寫; 接收 HTTP 請求的範例以 Express 樣式 (req, res) 示意 — Fastify / Next.js Route Handler / Hono 等 framework 寫法類似。
pip install asgard-sdk
# 或使用 uv
uv add asgard-sdk
需要 Python 3.11+。
dotnet add package AsgardAi.Sdk
需要 .NET 8+。
<dependency>
<groupId>com.asgard-ai</groupId>
<artifactId>asgard-sdk-java</artifactId>
<version><!-- 最新版本見下方連結 --></version>
</dependency>
請至 Maven Central — asgard-sdk-java versions 查詢最新版本後填入 <version> 標籤。
需要 Java 17+、Maven 或 Gradle。後續所有 Java 範例皆使用標準 Checked Exception (AsgardException)。
建立 Bot Provider Client
Bot Provider client 是與單一 Bot Provider 互動的入口, 提供 streaming、訊息發送、檔案上傳、自訂工具觸發等能力。
整個後端服務共用同一份 client 即可(client 設計為執行緒/併發安全), 無需每次請求都重建。Bot Provider API Key 從環境變數 / secrets manager 讀取, 絕對不要 hardcode 進程式碼。
- Go
- Node.js
- Python
- .NET
- Java
import (
"os"
"go.asgard-ai.com/asgard-sdk-go/pkg/client"
)
// 從環境變數讀取設定,請勿 hardcode 機敏資訊到程式碼
var (
edgeServerHost = getEnvOrDefault("EDGE_SERVER_HOST", "https://api.asgard-ai.com")
namespace = os.Getenv("NAMESPACE")
botProviderName = os.Getenv("BOT_PROVIDER_NAME")
botProviderApiKey = os.Getenv("BOT_PROVIDER_API_KEY") // 從 secrets manager 注入
)
func getEnvOrDefault(key, defaultVal string) string {
if v := os.Getenv(key); v != "" {
return v
}
return defaultVal
}
// 建立 Bot Provider Client(整個後端共用一份即可)
bpClient := client.NewBotProviderClientWithConfig(&client.BotProviderConfig{
EdgeServerHost: edgeServerHost,
Namespace: namespace,
BotProviderName: botProviderName,
BotProviderApiKey: botProviderApiKey,
})
import { BotProviderClient } from '@asgard-js/nodejs';
// 從環境變數讀取設定,請勿 hardcode 機敏資訊到程式碼
const edgeServerHost = process.env.EDGE_SERVER_HOST ?? 'https://api.asgard-ai.com';
const namespace = process.env.NAMESPACE ?? '';
const botProviderName = process.env.BOT_PROVIDER_NAME ?? '';
const botProviderApiKey = process.env.BOT_PROVIDER_API_KEY ?? ''; // 從 secrets manager 注入
// 建立 Bot Provider Client(整個後端共用一份即可)
const bpClient = new BotProviderClient({
edgeServerHost,
namespace,
botProviderName,
botProviderApiKey,
});
import os
from asgard import BotProviderClient
# 從環境變數讀取設定,請勿 hardcode 機敏資訊到程式碼
edge_server_host = os.getenv("EDGE_SERVER_HOST", "https://api.asgard-ai.com")
namespace = os.getenv("NAMESPACE", "")
bot_provider_name = os.getenv("BOT_PROVIDER_NAME", "")
bot_provider_api_key = os.getenv("BOT_PROVIDER_API_KEY", "")
# 建立 Bot Provider Client(整個後端共用一份即可)
bp_client = BotProviderClient(
host=edge_server_host,
namespace=namespace,
bot_provider_name=bot_provider_name,
api_key=bot_provider_api_key,
)
using Asgard;
// 從環境變數讀取設定,請勿 hardcode 機敏資訊到程式碼
var config = new BotProviderConfig
{
EdgeServerHost = Environment.GetEnvironmentVariable("EDGE_SERVER_HOST") ?? "https://api.asgard-ai.com",
Namespace = Environment.GetEnvironmentVariable("NAMESPACE") ?? "",
BotProviderName = Environment.GetEnvironmentVariable("BOT_PROVIDER_NAME") ?? "",
ApiKey = Environment.GetEnvironmentVariable("BOT_PROVIDER_API_KEY") ?? "",
};
// 建立 Bot Agent(整個後端共用一份即可)
IBotAgent agent = AsgardClient.NewBotAgent(config);
import com.asgard.sdk.client.BotProviderClient;
import com.asgard.sdk.client.BotProviderConfig;
import com.asgard.sdk.client.Client;
// 從環境變數讀取設定,請勿 hardcode 機敏資訊到程式碼
String edgeServerHost = System.getenv().getOrDefault("EDGE_SERVER_HOST", "https://api.asgard-ai.com");
String namespace = System.getenv("NAMESPACE");
String botProviderName = System.getenv("BOT_PROVIDER_NAME");
String botProviderApiKey = System.getenv("BOT_PROVIDER_API_KEY"); // 從 secrets manager 注入
// 建立 Bot Provider Client(整個後端共用一份即可)
BotProviderConfig config = BotProviderConfig.builder()
.edgeServerHost(edgeServerHost)
.namespace(namespace)
.botProviderName(botProviderName)
.botProviderApiKey(botProviderApiKey)
.build();
Client bpClient = new BotProviderClient(config);
NewStreamer 對單則訊息建立 SSE 串流, 逐一接收事件, 使用結束務必 Close。事件型別與 payload schema 見 Send Message API 介紹。
MessageRequestOptions 提供兩個可選參數:
isDebug: 開啟 debug 模式, 在 Asgard Console 可見詳細 tracebypassToolCallConsent: 跳過 Tool Call Consent 流程, 直接執行工具呼叫。僅適用於 SSE 串流
- Go
- Node.js
- Python
- .NET
- Java
import "go.asgard-ai.com/asgard-sdk-go/pkg/models"
// 組裝送往 Edge Server 的訊息
msg := &models.GenericBotMessage{
CustomChannelId: "channel-abc-123", // 對話識別,後續同 channel 訊息會串成一個 Conversation
CustomMessageId: "msg-001", // 訊息識別,用於去重 / idempotency
Text: "Hello",
Action: models.PostBackActionNone,
Payload: map[string]interface{}{ // 任意 JSON-serializable 資料,Workflow 內可透過 prevPayload.* 讀取
"user_id": "user-456",
},
}
// 建立串流(第三個參數可傳 options,通常 nil 即可)
stream, err := bpClient.NewStreamer(ctx, msg, nil)
// 若需啟用 debug 或跳過 Tool Call Consent:
// opts := &client.MessageRequestOptions{IsDebug: true, BypassToolCallConsent: true}
// stream, err := bpClient.NewStreamer(ctx, msg, opts)
if err != nil { /* ... */ }
defer stream.Close() // 必須關閉,避免連線洩漏
// 逐一處理事件。Next() 在串流結束或 ctx 取消時回傳 false
// 注意:asgard.run.error 不會走進 switch,而是讓 Next() 直接回 false;
// 發生錯誤時跳出迴圈後,從 stream.Err() 取得錯誤詳情
for stream.Next() {
event := stream.Current()
switch event.EventType {
case models.SseEventTypeRunInit: // Workflow 開始
case models.SseEventTypeMessageDelta: // AI 逐字回應
case models.SseEventTypeMessageComplete: // AI 一則訊息完成
case models.SseEventTypeToolCallComplete: // 工具呼叫完成
case models.SseEventTypeCompletionModelUsage: // LLM 用量
case models.SseEventTypeRunDone: // Workflow 結束
}
}
// 迴圈結束務必檢查 error,避免上游錯誤被吞掉
if err := stream.Err(); err != nil { /* ... */ }
import {
AsgardError,
PostBackActionNone,
SseEventTypeRunInit,
SseEventTypeMessageDelta,
SseEventTypeMessageComplete,
SseEventTypeToolCallComplete,
SseEventTypeCompletionModelUsage,
SseEventTypeRunDone,
type GenericBotMessage,
} from '@asgard-js/nodejs';
// 組裝送往 Edge Server 的訊息
const msg: GenericBotMessage = {
customChannelId: 'channel-abc-123', // 對話識別,後續同 channel 訊息會串成一個 Conversation
customMessageId: 'msg-001', // 訊息識別,用於去重 / idempotency
text: 'Hello',
action: PostBackActionNone,
payload: { // 任意 JSON-serializable 資料,Workflow 內可透過 prevPayload.* 讀取
user_id: 'user-456',
},
};
// 建立串流
const streamer = await bpClient.newStreamer(msg);
// 若需啟用 debug 或跳過 Tool Call Consent:
// const streamer = await bpClient.newStreamer(msg, { isDebug: true, bypassToolCallConsent: true });
// 逐一處理事件。
// 注意:asgard.run.error 不會走進 switch,而是讓 for await 直接 throw;
// 發生錯誤時在 catch 區塊取得錯誤詳情(也可呼叫 streamer.err() 拿到同樣的物件)
try {
for await (const event of streamer) {
switch (event.eventType) {
case SseEventTypeRunInit: // Workflow 開始
case SseEventTypeMessageDelta: // AI 逐字回應
case SseEventTypeMessageComplete: // AI 一則訊息完成
case SseEventTypeToolCallComplete: // 工具呼叫完成
case SseEventTypeCompletionModelUsage: // LLM 用量
case SseEventTypeRunDone: // Workflow 結束
}
}
} catch (e) {
if (e instanceof AsgardError) { /* ... */ }
} finally {
streamer.close(); // 必須關閉,避免連線洩漏
}
from asgard import AsgardStreamError, GenericBotMessage, PostBackAction, SseEventType
# 組裝送往 Edge Server 的訊息
msg = GenericBotMessage(
custom_channel_id="channel-abc-123", # 對話識別,後續同 channel 訊息會串成一個 Conversation
custom_message_id="msg-001", # 訊息識別,用於去重 / idempotency
text="Hello",
action=PostBackAction.NONE,
payload={ # 任意 JSON-serializable 資料,Workflow 內可透過 prevPayload.* 讀取
"user_id": "user-456",
},
)
# 建立串流(context manager 自動關閉連線)
try:
with bp_client.stream(msg) as stream:
for event in stream:
match event.event_type:
case SseEventType.RUN_INIT: # Workflow 開始
pass
case SseEventType.MESSAGE_DELTA: # AI 逐字回應
pass
case SseEventType.MESSAGE_COMPLETE: # AI 一則訊息完成
pass
case SseEventType.TOOL_CALL_COMPLETE: # 工具呼叫完成
pass
case SseEventType.COMPLETION_MODEL_USAGE: # LLM 用量
pass
case SseEventType.RUN_DONE: # Workflow 結束
pass
except AsgardStreamError as e:
# asgard.run.error 事件會在迴圈結束後拋出,在此捕捉錯誤詳情
print(e.detail.message)
using Asgard;
using Asgard.Models;
// 組裝送往 Edge Server 的訊息
var msg = new GenericBotMessage
{
CustomChannelId = "channel-abc-123", // 對話識別,後續同 channel 訊息會串成一個 Conversation
CustomMessageId = "msg-001", // 訊息識別,用於去重 / idempotency
Text = "Hello",
Action = PostBackAction.None,
Payload = new Dictionary<string, object?> // 任意 JSON-serializable 資料,Workflow 內可透過 prevPayload.* 讀取
{
["user_id"] = "user-456",
},
};
// 建立串流;傳入 CancellationToken 可在客戶端斷線時取消,避免 LLM 繼續燒 token
try
{
await foreach (var evt in agent.StreamMessageAsync(msg, ct: cancellationToken))
{
switch (evt.EventType)
{
case SseEventType.RunInit: // Workflow 開始
break;
case SseEventType.MessageDelta: // AI 逐字回應
break;
case SseEventType.MessageComplete: // AI 一則訊息完成
break;
case SseEventType.ToolCallComplete: // 工具呼叫完成
break;
case SseEventType.CompletionModelUsage: // LLM 用量
break;
case SseEventType.RunDone: // Workflow 結束
break;
}
}
}
catch (AsgardException ex)
{
// asgard.run.error 事件會在迴圈結束後拋出,在此捕捉錯誤詳情
Console.WriteLine(ex.Message);
Console.WriteLine(ex.ErrorCode);
}
import com.asgard.sdk.models.*;
import com.asgard.sdk.client.*;
// 組裝送往 Edge Server 的訊息
GenericBotMessage msg = new GenericBotMessage();
msg.setCustomChannelId("channel-abc-123"); // 對話識別,後續同 channel 訊息會串成一個 Conversation
msg.setCustomMessageId("msg-001"); // 訊息識別,用於去重 / idempotency
msg.setText("Hello");
msg.setAction(PostBackAction.NONE);
msg.setPayload(Map.of("user_id", "user-456")); // 任意 JSON-serializable 資料
// 建立串流(第二個參數可傳 options,null 代表預設)
BotProviderStreamer stream = bpClient.newStreamer(msg, null);
// 若需啟用 debug 或跳過 Tool Call Consent:
// MessageRequestOptions opts = new MessageRequestOptions();
// opts.setDebug(true);
// opts.setBypassToolCallConsent(true);
// BotProviderStreamer stream = bpClient.newStreamer(msg, opts);
try {
// 逐一處理事件。next() 在串流結束時回傳 false
// 注意:asgard.run.error 不會走進 switch,而是讓 next() 直接回 false;
// 發生錯誤時跳出迴圈後,從 stream.err() 取得錯誤詳情
while (stream.next()) {
GenericBotSseEvent event = stream.current();
switch (event.getEventType()) {
case RUN_INIT: // Workflow 開始
case MESSAGE_DELTA: // AI 逐字回應
case MESSAGE_COMPLETE: // AI 一則訊息完成
case TOOL_CALL_COMPLETE: // 工具呼叫完成
case COMPLETION_MODEL_USAGE: // LLM 用量
case RUN_DONE: // Workflow 結束
}
}
// 迴圈結束務必檢查 error
if (stream.err() != null) { /* 處理錯誤 */ }
} finally {
stream.close(); // 必須關閉,避免連線洩漏
}
把 SSE 串流接到 HTTP handler、轉發給前端、攔截事件做 side effects(寫 history、扣點、Human Handoff…)等完整 use case 整合範例, 見 Pattern: Backend Relay — 快速起步。
檔案上傳
UploadBlob 把檔案串流上傳到 Edge Server, 回傳含 blobId 的物件; 後續可將 blobId 放入 GenericBotMessage 的 blob 欄位讓 Bot 看到該檔案。
- Go
- Node.js
- Python
- .NET
- Java
// 參數:ctx、channelId、檔案 reader、filename、MIME type 指標(nil 代表讓 Edge Server 自行偵測)
blob, err := bpClient.UploadBlob(ctx, channelId, fileReader, "filename.png", &mime)
if err != nil { /* ... */ }
// 取得 blob.BlobId 後,可放入後續訊息的 blob 欄位引用
import { createReadStream } from 'node:fs';
// 參數:channelId、檔案 { stream, filename, mime }(mime 省略則讓 Edge Server 自行偵測)
const blob = await bpClient.uploadBlob(channelId, {
stream: createReadStream('filename.png'),
filename: 'filename.png',
mime: 'image/png',
});
// 取得 blob.blobId 後,可放入後續訊息的 blobIds 欄位引用
# 參數:custom_channel_id、file(file-like object)、filename、mime
with open("filename.png", "rb") as f:
blobs = bp_client.upload_blob(
custom_channel_id=channel_id,
file=f,
filename="filename.png",
mime="image/png",
)
# 取得 blobs[0].blob_id 後,可放入後續訊息的 blob_ids 欄位引用
// 參數:channelId、Stream、filename、mime(null 讓 Edge Server 自行偵測)
await using var fileStream = File.OpenRead("filename.png");
var blob = await agent.UploadBlobAsync(channelId, fileStream, "filename.png", "image/png");
// 取得 blob.BlobId 後,可放入後續訊息的 BlobIds 欄位引用
import java.io.FileInputStream;
// 參數:customChannelId、InputStream、filename、MIME type
try (FileInputStream fis = new FileInputStream("filename.png")) {
Blob blob = bpClient.uploadBlob(channelId, fis, "filename.png", "image/png");
// 取得 blob.getBlobId() 後,可放入後續訊息的 blobIds 欄位引用
}
把 HTTP
multipart/form-data解析後接到此 API 的完整 endpoint 範例, 見 Pattern: Backend Relay — 快速起步。
單發訊息(非串流)
SendMessage 同步等待 Workflow 跑完最終結果再回傳, 適合非面對使用者的場景(批次任務、server-side 排程):
- Go
- Node.js
- Python
- .NET
- Java
// 注意:LLM 回應可能耗時數十秒,呼叫端要設好足夠的 timeout
resp, err := bpClient.SendMessage(ctx, msg, nil)
// resp 等同於 SSE 串流跑完後的彙整結果
// 注意:LLM 回應可能耗時數十秒,建構 client 時用 timeoutMs 設好足夠的 timeout(預設 300_000)
const reply = await bpClient.sendMessage(msg);
// reply 等同於 SSE 串流跑完後的彙整結果
# 注意:LLM 回應可能耗時數十秒,建構 client 時用 timeout 設好足夠的 timeout(預設 300.0 秒)
reply = bp_client.send_message(msg)
# reply 等同於 SSE 串流跑完後的彙整結果
// 注意:LLM 回應可能耗時數十秒,呼叫端要傳入適當的 CancellationToken 控制 timeout
var reply = await agent.SendMessageAsync(msg, ct: cancellationToken);
// reply 等同於 SSE 串流跑完後的彙整結果
// 注意:LLM 回應可能耗時數十秒,建構 config 時用 httpClient 設好足夠的 timeout(預設 300s)
GenericBotReply reply = bpClient.sendMessage(msg, null);
// reply 等同於 SSE 串流跑完後的彙整結果
自訂 HTTP Client
Bot Provider client 可自訂底層 HTTP 連線(用於分散式追蹤、metrics、retry policy 等), 把客製化的 HTTP client 實例傳進建構設定即可:
- Go
- Node.js
- Python
- .NET
- Java
import (
"net/http"
"time"
"go.asgard-ai.com/asgard-sdk-go/pkg/client"
)
httpClient := &http.Client{
Transport: yourCustomTransport, // 例如 otelhttp.NewTransport(...) 加上 OpenTelemetry tracing
Timeout: 30 * time.Second, // 注意:SSE 是長連線,Timeout 不可設太短
}
bpClient := client.NewBotProviderClientWithConfig(&client.BotProviderConfig{
HTTPClient: httpClient,
EdgeServerHost: edgeServerHost,
Namespace: namespace,
BotProviderName: botProviderName,
BotProviderApiKey: botProviderApiKey,
})
Node.js SDK 底層使用全域 fetch,未提供 Go SDK 那樣的 HTTPClient 注入點。現階段可調整的選項只有:
headers: 靜態自訂 headertimeoutMs: 一般 REST 請求 timeout(SSE 串流不適用此 timeout, SSE 為長連線設計)
如需 OpenTelemetry tracing、metrics、retry policy 等 HTTP-level instrumentation, 請在呼叫端(例如 undici 全域 dispatcher)wrap, 或在外層 patch globalThis.fetch。
const bpClient = new BotProviderClient({
edgeServerHost,
namespace,
botProviderName,
botProviderApiKey,
headers: { 'x-trace-id': 'abc-123' }, // 靜態自訂 header
timeoutMs: 30_000, // 一般 REST 請求的 timeout
});
import httpx
from asgard import BotProviderClient
http_client = httpx.Client(
timeout=30.0, # 注意:SSE 是長連線,此 timeout 僅作用於一般 REST 請求
headers={"x-trace-id": "abc-123"},
)
bp_client = BotProviderClient(
host=edge_server_host,
namespace=namespace,
bot_provider_name=bot_provider_name,
api_key=bot_provider_api_key,
http_client=http_client, # 注入自訂 httpx.Client(用於 tracing、retry 等)
)
// 注入自訂 HttpClient(用於 OpenTelemetry tracing、retry handler 等)
var httpClient = new HttpClient(yourDelegatingHandler)
{
// 注意:SSE 是長連線,Timeout 不可設太短;建議用 CancellationToken 控制個別請求
Timeout = Timeout.InfiniteTimeSpan,
};
IBotAgent agent = AsgardClient.NewBotAgent(new BotProviderConfig
{
EdgeServerHost = edgeServerHost,
Namespace = namespaceName,
BotProviderName = botProviderName,
ApiKey = apiKey,
HttpClient = httpClient,
Headers = new Dictionary<string, string> { ["x-trace-id"] = "abc-123" },
});
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(yourTracingInterceptor) // 例如加上 OpenTelemetry tracing interceptor
.callTimeout(300, TimeUnit.SECONDS) // 注意:SSE 是長連線,不宜設太短
.build();
BotProviderConfig config = BotProviderConfig.builder()
.httpClient(httpClient)
.edgeServerHost(edgeServerHost)
.namespace(namespace)
.botProviderName(botProviderName)
.botProviderApiKey(botProviderApiKey)
.build();
Client bpClient = new BotProviderClient(config);
Error Handling
所有 HTTP 方法在收到 non-2xx 回應或 envelope isSuccess=false 時, 都會拋出具型別例外。部分 SDK 另外提供 predicate helper; 若未提供, 也可直接從錯誤物件讀取 HTTP status code 或 server errorCode 做條件分支:
- Go
- Node.js
- Python
- .NET
- Java
import (
"errors"
"go.asgard-ai.com/asgard-sdk-go/pkg/client"
)
_, err := bpClient.SandboxHeartbeat(ctx, sandboxName)
if err != nil {
if client.IsPreconditionFailed(err) {
// HTTP 412: Sandbox 尚未啟動,請先透過 Message API 啟動
} else if client.IsNotFound(err) {
// HTTP 404
} else {
// 取得 statusCode
code := client.StatusCode(err)
var apiErr *client.APIError
if errors.As(err, &apiErr) {
// apiErr.ErrorCode: server 回傳的 errorCode 字串
// apiErr.Message: server 回傳的錯誤訊息
}
}
}
import {
AsgardError,
isBadRequest,
isUnauthorized,
isForbidden,
isNotFound,
isConflict,
isPreconditionFailed,
statusCodeOf,
} from '@asgard-js/nodejs';
try {
await bpClient.sandboxHeartbeat(sandboxName);
} catch (err) {
if (isPreconditionFailed(err)) {
// HTTP 412: Sandbox 尚未啟動,請先透過 Message API 啟動
} else if (isNotFound(err)) {
// HTTP 404
} else if (err instanceof AsgardError) {
const code = statusCodeOf(err); // HTTP status code
const serverErrCode = err.errorCode; // server 回傳的 errorCode 字串
}
}
from asgard import AsgardError
try:
bp_client.send_message(msg)
except AsgardError as e:
# Python SDK 目前直接暴露 server 回傳的 errorCode / message
server_err_code = e.detail.code
server_err_msg = e.detail.message
if server_err_code == "YOUR_PRECONDITION_FAILED_CODE":
# 依你的 server errorCode 做條件分支
pass
using Asgard;
try
{
await agent.SandboxHeartbeatAsync(sandboxName, ct: cancellationToken);
}
catch (AsgardException ex)
{
if (ex.HttpStatusCode == 412)
{
// HTTP 412: Sandbox 尚未啟動,請先透過 Message API 啟動
}
else if (ex.HttpStatusCode == 404)
{
// HTTP 404
}
else
{
var code = ex.HttpStatusCode; // HTTP status code
var serverErrCode = ex.ErrorCode; // server 回傳的 errorCode 字串
}
}
import com.asgard.sdk.AsgardException;
try {
bpClient.sandboxHeartbeat(sandboxName);
} catch (AsgardException e) {
if (AsgardException.isPreconditionFailed(e)) {
// HTTP 412: Sandbox 尚未啟動,請先透過 Message API 啟動
} else if (AsgardException.isNotFound(e)) {
// HTTP 404
} else {
int code = AsgardException.statusCode(e); // HTTP status code
String serverErrCode = e.getErrorCode(); // server 回傳的 errorCode 字串
}
}
安全與部署注意事項
1. API Key 安全保管
- Bot Provider API Key 放在 secrets manager(AWS Secrets Manager / Vault / GCP Secret Manager)
- 絕對不要 寫死在程式碼或 commit 到版本控制
- 環境變數注入時也要避免在 Docker image / log 暴露
2. SSE 雙向長連線基礎設施
- 避免緩衝: 某些 API Gateway / CDN 預設會緩衝回應, 要明確關閉(
X-Accel-Buffering: nofor nginx) - 連線數: 預估同時上線使用者數量, 設定足夠的 file descriptor 上限與 Worker 數
- Idle Timeout: Load Balancer 的 timeout 要長於對話可能的閒置時間, 或實作 keepalive ping
3. 客戶端斷線清理
當使用者關閉頁面、切換頁籤、網路中斷, 你的後端應監聽請求層級的取消訊號(例如 request context / cancellation token / abort signal), 把它傳給 NewStreamer 並在偵測到取消時呼叫 stream.Close(), 避免上游 LLM 還在燒 token 但已無人接收。實作範例見 Pattern: Backend Relay — 快速起步。
4. 重試與冪等性
- 重試建立 SSE 串流時要考慮
customMessageId是否重複, 避免重複處理同一事件 - side effects(扣點、寫 history)需設計冪等, 使用
customMessageId或 SSE event ID 作為去重 key
延伸閱讀
- Pattern 完整流程: Pattern 4: Backend Relay
- 前端 SDK(配合使用): 前端 SDK 使用指南
- Auth 設計: Authentication & Access Control
- History 持久化: Conversation History
- Memory & Payload 注入: Memory & Payload 注入
- Audit Log 標準實作: Audit Logging
- Go SDK GitHub: asgard-sdk-go
- Node.js SDK npm:
@asgard-js/nodejs - Python SDK PyPI:
asgard-sdk - .NET SDK NuGet:
AsgardAi.Sdk - Java SDK Maven Central:
com.asgard-ai:asgard-sdk-java