> For the complete documentation index, see [llms.txt](https://docs.maiagent.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.maiagent.ai/tech/api-integration/api-tool-engineering-guide.md).

# API 工具工程指南

把你的 REST API 接給 MaiAgent AI 助理當工具：請求怎麼組、認證怎麼帶、逾時與重試、錯誤怎麼回給模型、記錄怎麼查。

本頁寫給要「讓 AI 助理反向呼叫自家系統」的工程師：MaiAgent 這一端怎麼把 AI 助理決定的參數組成 HTTP 請求、你的 API 要長什麼樣子才好被模型使用、失敗時模型會看到什麼。

後台的逐步操作（在哪個畫面新增工具、每個欄位填什麼）請看使用者手冊的 [建立 API 工具](https://docs.maiagent.ai/tools/setup)，本頁不重複操作步驟。工具也可以用 REST API 建立與管理，欄位與本頁一致，見 API 文件的「工具和連接器」。

## 一次呼叫的流程 <a href="#call-flow" id="call-flow"></a>

```mermaid
sequenceDiagram
    participant U as 使用者
    participant A as MaiAgent AI 助理
    participant P as MaiAgent 平台
    participant S as 你的 API

    U->>A: 「幫我查訂單 A123 的狀態」
    A->>A: 依工具描述與參數結構決定呼叫哪個工具、填什麼參數
    A->>P: tool call（工具名稱＋JSON 參數）
    P->>P: 套用參數預設值、渲染標頭與 Query 參數的 {{變數}}
    P->>S: HTTP 請求（GET 帶 query string / POST 帶 JSON body）
    S-->>P: 回應（任何文字，建議 JSON）
    P->>P: 遮罩敏感值、依 context window 截斷、寫入工具執行記錄
    P-->>A: 回應原文（或錯誤訊息）
    A-->>U: 用自然語言整理結果
```

AI 助理只負責「決定呼叫與參數」，實際的 HTTP 請求由平台送出；你的 API 不需要知道任何模型相關的事，只要是可從公網存取的 HTTP 端點即可。

## 工具定義對照 <a href="#tool-definition" id="tool-definition"></a>

一個 API 工具對應**一個端點＋一種 HTTP 方法**。平台目前不匯入 OpenAPI 文件；若你的 API 有 OpenAPI 規格，把每個要開放給助理的 operation 各建成一個工具，把 requestBody 或 query 的 schema 貼進「參數結構」即可。

| 欄位（後台名稱／API 欄位）                 | 給誰看 | 說明                                                                                                                                           |
| ------------------------------- | --- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| 工具名稱 `name`                     | 模型  | 模型呼叫時用的識別字。只能用英文字母、數字、底線、連字符；送給模型前會再正規化成 `^[a-zA-Z][a-zA-Z0-9_]*$`、最長 64 字元，連字符會變底線，超長會截斷並補上雜湊尾碼。建議一開始就用 `snake_case`，例如 `get_order_status`。 |
| 顯示名稱 `displayName`              | 人   | 後台列表與工具執行記錄顯示用，沒有格式限制。                                                                                                                       |
| 工具描述 `description`、提示詞 `prompt` | 模型  | 模型判斷「何時該用這個工具」的唯一依據。`prompt` 有值就用 `prompt`，否則用 `description`。寫清楚做什麼、什麼情況用、回傳什麼。                                                              |
| API URL `apiUrl`                | 平台  | 完整網址，含 `https://`。路徑是固定的；需要依參數變動的值放在 query string 或 body。                                                                                    |
| HTTP 方法 `httpMethod`            | 平台  | `get`、`post`、`put`、`patch`、`delete`。決定模型參數走 query string 還是 JSON body，見下節。                                                                   |
| 標頭 `rawHeaders`                 | 平台  | JSON 物件，每次請求都會帶。值支援 `{{變數}}`。                                                                                                                |
| Query 參數 `rawQueryParams`       | 平台  | JSON 物件，每次請求都會附在 query string。值支援 `{{變數}}`；與模型參數同名時**以這裡的值為準**。                                                                              |
| 參數結構 `rawParametersSchema`      | 模型  | JSON Schema，宣告模型可以（或必須）提供哪些參數。                                                                                                               |

## 參數結構（JSON Schema） <a href="#parameters-schema" id="parameters-schema"></a>

平台會把參數結構轉成模型的 function-calling schema，模型回傳的參數再依它驗證。

```json
{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string",
      "description": "訂單編號，格式如 A123456"
    },
    "include_items": {
      "type": "boolean",
      "description": "是否一併回傳品項明細",
      "default": false
    },
    "status": {
      "type": "string",
      "enum": ["pending", "shipped", "delivered"],
      "description": "只查特定狀態時填"
    }
  },
  "required": ["order_id"]
}
```

規則與限制：

* 頂層必須是 `type: "object"`；支援 `properties`、`required`、`default`、`enum`、巢狀 `object` 與 `array`。
* 參數名稱只能用英文字母、數字、底線、點、連字符與中文，長度 1–64；不符合的名稱在儲存時就會被拒絕。
* **`default` 會由平台補值**：模型沒有提供該參數時，平台把 `default` 填進去再送出。要固定帶某個值、又不想讓模型決定，用 `default` 或直接寫在 Query 參數。
* 每個參數都寫 `description`。模型填參數的品質幾乎完全取決於這段文字，寫上格式範例（「格式如 A123456」）比只寫型別有效得多。
* 參數名稱 `timeout` 有保留意義：模型若提供它，平台會拿來當這次請求的逾時秒數，**不會**送給你的 API。不要用這個名字當業務參數。

{% hint style="warning" %}
不要把 API 金鑰宣告成參數再靠 `default` 帶入。模型看得到參數結構，金鑰會進入模型上下文。金鑰請放在標頭或 Query 參數，見下一節。
{% endhint %}

## 請求怎麼組 <a href="#request-assembly" id="request-assembly"></a>

平台依 HTTP 方法決定模型參數的位置：

| 方法                   | 模型參數放哪                                      | 「Query 參數」欄位                    | Body          |
| -------------------- | ------------------------------------------- | ------------------------------- | ------------- |
| `GET`                | query string                                | 一併放進 query string，**同名時覆蓋模型參數** | 無             |
| `POST`、`PUT`、`PATCH` | JSON body（`Content-Type: application/json`） | query string                    | 模型參數的 JSON 物件 |
| `DELETE`             | 不送                                          | query string                    | 無             |

要點：

* `DELETE` 不會帶模型參數。需要指定刪除對象時，改用 `POST`，或把對象放進 Query 參數的 `{{變數}}`。
* Body 一律是 JSON 物件，不支援 form-data、檔案上傳或 XML。需要這些格式的 API 請在你這端包一層 JSON 介面。
* 模型參數與「Query 參數」同名時以設定值為準，這是刻意設計：把必須走 query string 的金鑰放在 Query 參數，模型就無法覆蓋它。

### 標頭與 Query 參數裡的 `{{變數}}` <a href="#template-variables" id="template-variables"></a>

標頭與 Query 參數的值可以引用兩類變數，平台在每次呼叫時代入：

| 變數                    | 內容                                              |
| --------------------- | ----------------------------------------------- |
| `{{contact_id}}`      | 目前對話的聯絡人 ID（MaiAgent 內部 ID）                     |
| `{{source_id}}`       | 聯絡人的外部 ID，也就是你在 Web Chat 嵌入或聯絡人同步時給的 `sourceId` |
| `{{contact_name}}`    | 聯絡人名稱                                           |
| `{{conversation_id}}` | 對話 ID                                           |
| `{{inbox_id}}`        | 收件匣 ID                                          |
| `{{organization_id}}` | 組織 ID                                           |
| `{{參數名}}`             | 參數結構中同名參數的值（模型填的）                               |

`{{source_id}}` 是 ISV 最常用的一個：你的系統只認自己的使用者 ID，把它放進標頭（例如 `X-User-Id: {{source_id}}`）或 query string，你的 API 就能以該使用者的身份查資料，不必在參數結構裡叫模型「猜」使用者是誰。

代入以字面文字進行，不做型別轉換；模型沒有提供被引用的參數時，這次呼叫會以錯誤中止，不會把 `{{參數名}}` 原樣送出。變數只在有對話上下文時可用，後台「測試工具」這類沒有對話的呼叫無法渲染情境變數。

## 認證方式 <a href="#authentication" id="authentication"></a>

三種方式可以並用，優先序由低到高：

1. **工具層固定憑證**：`Authorization: Bearer ...` 或 `X-API-Key: ...` 寫在標頭，或把金鑰寫在 Query 參數。代表「整個組織」呼叫你的 API。儲存後在後台以 `****` 遮罩顯示，工具執行記錄與 API 請求記錄裡的 Query 參數值也會被遮掉。
2. **情境變數**：如上節，用 `{{source_id}}` 等把「是誰在問」帶給你的 API，由你這端做授權。憑證仍是工具層的。
3. **聯絡人層憑證（API Credential）**：為特定聯絡人綁一組標頭，呼叫時**覆蓋同名的工具層標頭**。適合每位終端使用者各自持有 token 的情境（你的系統要以使用者本人的身份呼叫，而不是以整合帳號）。

聯絡人層憑證由你的後端在使用者登入後寫入：

```http
POST /api/contacts/{contact_id}/api-credentials/
Authorization: Api-Key <組織 API 金鑰>
Content-Type: application/json

{
  "tool": "<工具 ID>",
  "headers": {
    "Authorization": "Bearer <該使用者在你系統的 token>"
  }
}
```

同一聯絡人對同一工具重送會更新既有憑證；token 到期時再打一次即可。憑證只在「該聯絡人的對話」裡生效，其他聯絡人呼叫同一工具仍走工具層憑證。

{% hint style="info" %}
把 token 寫進聯絡人憑證的時機，通常是 Web Chat 嵌入時的身份同步流程，見 [聯絡人身份同步與 Token 更新](/tech/authorization-integration/contact-credentials-sync.md)。
{% endhint %}

## 逾時、重試與 TLS <a href="#timeout-retry-tls" id="timeout-retry-tls"></a>

| 項目  | 行為                                                                                                              |
| --- | --------------------------------------------------------------------------------------------------------------- |
| 逾時  | 每次 HTTP 請求的逾時設定預設 30 秒（平台層設定，地端可調），不是整次工具呼叫的總時限。超時的呼叫記為 `timeout`，模型收到「API 請求超時」。                               |
| 重試  | 首次請求之外最多重試 3 次，共最多 4 次嘗試，退避等待為 1、2、4 秒。對包含逾時在內的請求錯誤與 `408`、`409`、`429`、`500`、`502`、`503`、`504` 重試；其他 4xx 不重試。   |
| TLS | 一律驗證憑證鏈與主機名稱。若你的憑證鏈使用缺少 Subject Key Identifier 的舊版根憑證（部分本地 CA），可在工具設定開啟「Relax RFC 5280 strict」，只放寬該項檢查。自簽憑證不支援。 |

對你的 API 的意義：

* 寫入型端點要**冪等**。重試會讓同一筆 `POST` 送到兩次，請用參數裡的業務鍵（訂單編號、外部 ID）去重，或改回 `409` 讓平台重試時仍是同一筆。
* 整次工具呼叫會累積各次請求及退避時間；連續逾時時可能超過 120 秒，不能把 30 秒當成模型等待的總上限。查詢型端點請控制在幾秒內，長工作改成「建立任務→回任務 ID」再提供查詢工具。
* 對 `429` 回 `Retry-After` 沒有作用，平台固定退避；請以 3 次重試的量估算限流。

## 回應怎麼回給模型 <a href="#response-to-model" id="response-to-model"></a>

模型看到的是你的**回應 body 原文**，不解析結構：

| 情況              | 模型看到                                          |
| --------------- | --------------------------------------------- |
| 2xx             | body 原文（`response.text`）。空 body 會變成字串 `None`。 |
| 非 2xx（重試後仍失敗）   | `API 請求失敗` 開頭，接 `HTTP <狀態碼>: <body 原文>`。      |
| 逾時              | `API 請求超時` 開頭，接錯誤說明。                          |
| 連線錯誤、DNS、TLS 失敗 | `API 請求失敗` 開頭，接例外訊息。                          |
| 組織 Credits 不足   | 呼叫不會送出，模型收到額度不足的說明。                           |

回應超過模型 context window 的一定比例（預設 80%）時，平台保留頭尾兩段並在最前面加上「已截斷」提示；被截掉的是中間段。

因此你的 API 設計建議：

* **回精簡 JSON**。只回模型需要的欄位，避免整包 ORM 物件。一次列表控制在幾十筆內，多的提供分頁參數讓模型再查。
* **錯誤用可讀訊息**，不要只回錯誤碼。`{"error": "order_id A123 不存在，請確認編號"}` 比 `{"code": 40401}` 更能讓模型修正參數或向使用者說明。
* **4xx 是給模型的回饋**：參數缺漏或格式錯回 `400` 並說明是哪個欄位，模型有機會重新填參數再呼叫；不要用 `200` 包錯誤，模型會把它當成功結果整理給使用者。
* 回應裡若會回顯 query string（部分框架的錯誤頁會），平台會把工具設定的 Query 參數值遮掉再給模型，但標頭不會回顯到 body 的前提是你的 API 自己不把它印出來。
* 要讓模型能對使用者說「查無資料」，就明確回 `404` 加說明，而不是 `200` 空陣列。

## 記錄與除錯 <a href="#records-and-debugging" id="records-and-debugging"></a>

每次呼叫都會寫入一筆**工具執行記錄**，後台在「AgentOps → 工具執行記錄」，也可用 API 查：

```http
GET /api/tool-execution-records/?tool=<工具 ID>&status=failure&start_date=2026-09-01&end_date=2026-09-11
Authorization: Api-Key <組織 API 金鑰>
X-Organization-Id: <組織 ID>
```

可用篩選：`tool`、`tool_type=api`、`status`（`success`、`failure`、`pending`、`timeout`）、`chatbot`、`start_date`、`end_date`、`query`（全文）。每筆記錄包含：

* 模型填的輸入參數 `inputParameters`，以及平台實際送出的 `requestMethod`、`requestHeaders`（敏感值遮罩）、`requestBody`。
* 回應原文 `outputResult`（截斷後的版本）或 `errorMessage`。
* 執行時間與狀態，關聯的訊息與 AI 助理。

讀取這個 API 需要成員具備 AgentOps 的「工具執行記錄」權限，組織 owner 不受限。

除錯順序建議：

1. 記錄裡的 `requestHeaders`／`requestBody` 是否是你預期的形狀。`{{變數}}` 沒被代入通常是變數名拼錯或參數結構裡沒有同名參數。
2. `errorMessage` 開頭是 `HTTP <碼>` 就是你的 API 回的錯，body 原文就在後面。
3. `status=timeout` 而你的 log 顯示已回應，代表回應時間超過 30 秒；先看你這端的 p95。
4. 模型「沒有呼叫工具」不會產生記錄。這時要改的是工具描述與參數 `description`，不是 API。

## 計費 <a href="#billing" id="billing"></a>

每次成功送出的 API 工具呼叫扣一次「API 工具呼叫」的 Credits，額度不足時呼叫不會送出。呼叫失敗或逾時的重試不另外計次。詳細單價依組織方案，見使用者手冊的 Credits 說明。

## 上線前檢查清單 <a href="#checklist" id="checklist"></a>

* [ ] 端點可從公網以 HTTPS 存取，憑證鏈完整。
* [ ] 查詢型端點 p95 在數秒內；寫入型端點冪等。
* [ ] 參數結構每個參數都有 `description` 與格式範例；`required` 只列真正必填的。
* [ ] 金鑰放在標頭或 Query 參數，不在參數結構裡。
* [ ] 需要「以使用者身份」呼叫時，決定用 `{{source_id}}` 還是聯絡人層憑證，並在你的後端完成授權。
* [ ] 錯誤回應是可讀訊息，`4xx` 說明缺什麼，不用 `200` 包錯誤。
* [ ] 回應精簡，列表有分頁。
* [ ] 用一個真實對話走完一次，到工具執行記錄確認送出的請求與回應都是預期的。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.maiagent.ai/tech/api-integration/api-tool-engineering-guide.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
