> 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/serve/line-flex-message.md).

# LINE Flex Message 規格

MaiAgent 怎麼把 LINE Flex Message 卡片送出去：FlexMessage 工具的模板與 AI 欄位格式、驗證端點、回覆串流中的 flex 項目，以及送到 LINE 時的限制與失敗處理。

本頁寫給要讓 Agent 回覆 LINE Flex Message 卡片的工程師：卡片模板怎麼存、Agent 怎麼填欄位、平台怎麼把卡片送到 LINE，以及卡片在 API 回應裡長什麼樣子。

後台操作（在哪裡新增卡片、套用內建模板、綁定到 Agent）請看使用者手冊的 [Agent UI](https://docs.maiagent.ai/build/tools/agent-ui)；LINE 通道的串接步驟見 [串接對話平台：LINE](https://docs.maiagent.ai/build/choose-channel/line)。本頁不重複操作步驟。

## 運作方式 <a href="#how-it-works" id="how-it-works"></a>

MaiAgent 送出 Flex 卡片的入口是 **FlexMessage 工具**（工具類型 `flex`，後台稱為 Agent UI 的 FlexMessage）。一張卡片＝一個工具：

1. 你先存好一份 LINE 原生的 Flex JSON 模板（`flexTemplateJson`），並指定其中哪些欄位要由 AI 填寫（`flexSchema`）。
2. 工具綁定到 Agent 後，`flexSchema` 的每個欄位都會變成這個工具的一個字串參數，Agent 依你寫的欄位提示決定填什麼。
3. Agent 呼叫工具時，平台把參數值代入模板副本，產生完整的 Flex JSON，作為一個 `flex` 內容項目放進這則回覆。
4. 回覆送到 LINE 時，平台把 `flex` 項目轉成 LINE 的 Flex Message；Web Chat 直接渲染卡片；API 呼叫端在 `contentPayload.items` 收到同一份 JSON。

```mermaid
sequenceDiagram
    participant U as 使用者
    participant A as Agent
    participant P as MaiAgent 平台
    participant L as LINE

    U->>A: 「我的訂單到哪了？」
    A->>A: 先寫回覆文字，再呼叫卡片工具並填欄位
    A->>P: tool call（每個 AI 欄位一個字串參數）
    P->>P: 代入模板 → 完整 Flex JSON（flex 內容項目）
    P->>L: 文字訊息＋Flex Message（同一次回覆）
    L-->>U: 文字與卡片
```

卡片工具呼叫完就結束這一輪：工具的輸出不會再交回模型改寫，所以回覆文字要在呼叫卡片**之前**寫好。

{% hint style="info" %}
卡片只負責呈現。要顯示訂單、庫存等即時資料，請另外配置可取得資料的工具（例如 [API 工具](/tech/api-integration/api-tool-engineering-guide.md)），由 Agent 先查到資料，再把值填進卡片欄位。
{% endhint %}

## 模板與 AI 欄位 <a href="#template-and-fields" id="template-and-fields"></a>

### 模板 `flexTemplateJson` <a href="#template" id="template"></a>

* 存的是 LINE Flex Message 的 `contents`：一個 `bubble` 或 `carousel` 容器（不是外層含 `type: "flex"`、`altText` 的訊息物件）。平台用 LINE 官方 SDK 的 `FlexContainer` 解析它。
* 必須是 JSON 物件，FlexMessage 工具不能沒有模板。
* 巢狀 key 原樣保存。API 的欄位名稱一律 camelCase，但 `flexTemplateJson`、`flexSchema` 內部的 key（例如 `backgroundColor`、`paddingAll`）不會被轉換，請直接照 LINE 規格寫。

### AI 欄位 `flexSchema` <a href="#ai-fields" id="ai-fields"></a>

`flexSchema` 是一個 JSON 物件，key 是模板內欄位的路徑，value 是給 AI 的欄位提示：

| 寫法                                                            | 說明                                                   |
| ------------------------------------------------------------- | ---------------------------------------------------- |
| `"body.contents[0].text": "訂單編號"`                             | value 是字串：直接當作欄位提示                                   |
| `"body.contents[0].text": {"prompt": "訂單編號", "tag": "title"}` | value 是物件：`prompt` 為欄位提示，`tag` 會以 `[title]` 前綴附在提示前面 |

規則：

* **路徑語法**：用 `.` 分層、`[索引]` 取陣列元素，例如 `body.contents[0].text`、`footer.contents[0].action.uri`。
* **參數名稱**：路徑中非英數與底線的字元換成 `_` 並合併，例如 `body.contents[0].text` 變成參數 `body_contents_0_text`；重名時依序加 `_2`、`_3`。
* **型別**：每個 AI 欄位都是字串參數，預設為空字串。數字或布林欄位請不要開放給 AI 填。
* **空值沿用預設**：Agent 沒填或填空字串的欄位，保留模板裡原本的值。
* **路徑對不到就略過**：模板改過、路徑已不存在時，該欄位不會被寫入，也不會報錯。改模板後請一併檢查 `flexSchema`。

範例：一張訂單狀態卡，標題與查詢連結由 AI 填寫，其餘固定。

```json
{
  "flexTemplateJson": {
    "type": "bubble",
    "body": {
      "type": "box",
      "layout": "vertical",
      "contents": [
        { "type": "text", "text": "訂單 A000", "weight": "bold", "size": "lg" },
        { "type": "text", "text": "點下方按鈕查看配送進度", "size": "sm", "wrap": true }
      ]
    },
    "footer": {
      "type": "box",
      "layout": "vertical",
      "contents": [
        {
          "type": "button",
          "style": "primary",
          "action": { "type": "uri", "label": "查看進度", "uri": "https://example.com/orders" }
        }
      ]
    }
  },
  "flexSchema": {
    "body.contents[0].text": { "prompt": "「訂單 」加上訂單編號", "tag": "title" },
    "footer.contents[0].action.uri": "該筆訂單的查詢頁完整網址（https 開頭）"
  }
}
```

模型看到的是兩個參數 `body_contents_0_text`、`footer_contents_0_action_uri`。

## 用 API 建立與驗證卡片 <a href="#api" id="api"></a>

### 建立與管理 <a href="#create-and-manage" id="create-and-manage"></a>

FlexMessage 工具走 Agent UI 專用端點 `/api/v1/agent-ui-tools/`（列表、建立、修改、刪除），不能用一般工具端點建立（會回 `403`）。呼叫者需要 Agent UI 頁面的存取權限。

| 欄位                     | 說明                                          |
| ---------------------- | ------------------------------------------- |
| `toolType`             | `flex`。這個端點在省略時預設為 `flex`                   |
| `name`                 | 模型呼叫用的工具名稱，同一組織內不可重複，建議用 `snake_case`       |
| `displayName`          | 後台顯示名稱                                      |
| `description`／`prompt` | 至少填一個；`prompt` 優先作為給模型的工具說明，寫清楚**何時**該送這張卡片 |
| `flexTemplateJson`     | 模板，見[上一節](#template)                        |
| `flexSchema`           | AI 欄位，選填                                    |

儲存時平台會用 LINE SDK 對模板做結構檢查（不連線 LINE），不通過回 `400`，`detail` 為「Invalid FlexMessage template: …」加上錯誤原因（缺欄位時會列出欄位名稱）。只有模板內容與已存的不同時才會重新檢查，所以只改名稱或群組不會被舊模板擋下。

工具建好後，和其他工具一樣綁到 Agent（Agent 的 `tools` 欄位）才會生效；Agent 需使用 Agent 模式，見使用者手冊 [綁定到 Agent](https://docs.maiagent.ai/build/tools/agent-ui#bind-to-chatbot)。完整欄位見 [API 文件](https://docs.maiagent.ai/api)。

### 送 LINE 官方驗證 <a href="#validate" id="validate"></a>

儲存時的結構檢查只確認 LINE SDK 能否解析模板（缺欄位或 SDK 無法解析的欄位），看不出 LINE 會不會拒收（例如網址格式、欄位值超出範圍）。上線前請用驗證端點：

```http
POST /api/v1/agent-ui-tools/validate-flex/
Authorization: Api-Key <您的 API 金鑰>
Content-Type: application/json

{ "flexTemplateJson": { "type": "bubble", "body": { … } } }
```

回應：

```json
{
  "isValid": false,
  "details": [{ "property": "/footer/contents/0/action/uri", "message": "invalid uri" }],
  "source": "line",
  "tokenSource": "platform"
}
```

| 欄位            | 說明                                                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `isValid`     | 是否通過                                                                                                                     |
| `details`     | 逐欄位錯誤，`property` 是出錯的 JSON 路徑，格式沿用 LINE 的錯誤回應                                                                            |
| `source`      | `line`：由 LINE 官方驗證端點判定；`local`：未送到 LINE。可能是模板沒通過結構檢查（此時 `isValid` 為 `false`，有 token 也不會送 LINE），或沒有可用的 LINE token、只做了結構檢查 |
| `tokenSource` | 用哪一把 token 呼叫 LINE：`platform`（平台提供）或 `org_channel`（貴組織 LINE 通道的 token）；`source` 為 `local` 時是 `null`                      |

行為細節：

* 呼叫的是 LINE 的 `validate_push`：只驗證，不發送訊息，不消耗推播額度。
* 模板要先通過結構檢查才會送 LINE。
* 先用平台提供的驗證 token；沒有或被 LINE 拒絕（`401`）時，依序改用貴組織已串接的 LINE 通道 token。都沒有（或全部被拒絕）時回 `source: "local"`。
* 單次呼叫 LINE 的逾時是 10 秒。LINE 逾時、`5xx` 或網路錯誤時回 `503`，不會默默退回較弱的本地檢查，請稍後重試。
* 相同 JSON 的 LINE 判定結果快取 10 分鐘。
* 每位使用者每分鐘最多 30 次，超過回 `429`；API Key 本身的每分鐘上限同時適用。

## 回覆裡的 `flex` 內容項目 <a href="#content-item" id="content-item"></a>

用 API 收回覆（串流或同步，見 [對話與訊息回覆](/tech/api-integration/dui-hua-yu-xun-xi-hui-fu-chuan-liu-tong-bu.md)）時，卡片會出現在 `contentPayload.items`：

```json
{
  "type": "flex",
  "flexMessage": { "type": "bubble", "body": { … } },
  "isStreaming": false,
  "startTimestamp": "2026-10-12T10:00:00+08:00"
}
```

* `flexMessage` 是已代入 AI 欄位值的完整容器（`bubble` 或 `carousel`），可以直接交給 LINE SDK 或你的卡片渲染器。
* 只處理 `isStreaming: false` 且有 `flexMessage` 的項目。`isStreaming: true` 的項目只帶 `delta`（JSON 文字片段），等完成的項目出現再渲染。
* 一則回覆可以有多張卡片，依 `items` 順序呈現。
* `contentPayload.content`（各項目的純文字彙整）裡，卡片會以 JSON 字串出現；只想顯示文字時，請改取 `type: "text"` 的項目。

## 送到 LINE 的規則 <a href="#line-delivery" id="line-delivery"></a>

平台把一則回覆的 `contentPayload.items` 依序轉成 LINE 訊息物件：文字項目成為文字訊息，`flex` 項目成為 Flex Message，同一次回覆送出。

| 項目        | 規則                                                   |
| --------- | ---------------------------------------------------- |
| 訊息則數      | 一次最多 5 則（LINE 上限），超過的部分不送                            |
| 文字長度      | 單則文字超過 5,000 字元時切成多則，計入 5 則上限                        |
| `altText` | 取這則回覆所有文字項目串接後的前 400 字；回覆沒有文字時用「View details」（依語系翻譯） |
| 快速回覆按鈕    | 掛在最後一則訊息上                                            |
| 群組引用      | 群組對話的引用只掛在文字訊息上，Flex Message 不帶引用                    |

`altText` 會顯示在 LINE 的通知與聊天列表預覽，所以讓 Agent 在卡片前寫一句說明，預覽才有內容。

### Agent 只送卡片不寫字 <a href="#text-with-cards" id="text-with-cards"></a>

有些模型會直接呼叫卡片工具、不寫任何文字，使用者就只看到一張沒有說明的卡片。Agent 設定 `requireTextWithFlexCards` 設為 `true` 時，若這一輪只有卡片（或卡片加快速回覆按鈕）且完全沒有文字，平台會附上提示再向模型要一次回覆，要求先寫文字再送卡片。

* 每一輪最多重試一次；重試的結果照用，即使模型仍只送卡片。
* 重試是另一次計費的模型呼叫。
* 預設關閉，因為有些 Agent 本來就設計成只回一張選單卡片。

### 其他通道 <a href="#other-channels" id="other-channels"></a>

會把 `flex` 項目渲染成卡片的只有 Web Chat 與 LINE。其他通道（例如 Messenger、Instagram）沒有對應的轉換；Agent Teams 中由子 Agent 送出的卡片，在這些通道上不會送出，改由卡片的可見文字交給上游 Agent 轉述。要跨通道使用的 Agent，請不要讓卡片成為唯一的回覆內容。

## 失敗與錯誤處理 <a href="#errors" id="errors"></a>

| 情況                                | 平台行為                                              | 建議                                |
| --------------------------------- | ------------------------------------------------- | --------------------------------- |
| 建立或修改時模板結構不合法                     | `400`，`detail` 說明錯誤原因                             | 修正模板後重送                           |
| 驗證時 LINE 無法連線                     | `503`                                             | 稍後重試                              |
| 送出前 SDK 無法解析某張卡片                  | 該張卡片略過（記錄錯誤），其餘訊息照送；若整則回覆沒有任何可送的訊息，改送這則訊息的文字內容    | 上線前用[驗證端點](#validate)檢查模板         |
| LINE 拒收這次回覆（`400`，例如 AI 填入的網址不合法） | 改用推播重送同一批訊息；LINE 再次以 `4xx` 拒收時不重試，這則回覆（含同批文字）不會送達 | 讓 AI 只填不易出錯的欄位；網址類欄位寫清楚格式，或固定在模板裡 |

{% hint style="warning" %}
驗證端點只驗證模板本身。AI 填入的值是在對話當下才代入，例如 AI 填了一個不合法的網址，LINE 仍可能拒收整批訊息。對格式要求嚴格的欄位（`uri`、顏色、尺寸），建議固定在模板，不開放給 AI 填。
{% endhint %}

## 對話記憶中的卡片 <a href="#memory" id="memory"></a>

卡片送出後，寫入這一輪對話記憶的不是整份 JSON，而是卡片上看得到的文字：`text`、`span`、按鈕 `label` 與 `altText`，依 LINE 的顯示順序（header → hero → body → footer）排列，有長度上限。之後的對話裡，Agent 能引用卡片上的內容，但讀不到按鈕網址等不顯示的欄位。

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

* [ ] 模板是 `bubble` 或 `carousel` 容器，並通過[驗證端點](#validate)（`source` 為 `line`）。
* [ ] `flexSchema` 只開放文字類欄位給 AI；改過模板後確認路徑仍對得到。
* [ ] 工具的 `prompt` 寫清楚使用時機，並要求先寫文字再送卡片；必要時開啟 `requireTextWithFlexCards`。
* [ ] 一則回覆的文字加卡片不超過 5 則訊息。
* [ ] 在 LINE 實機測試：卡片顯示、按鈕動作、通知預覽文字。


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.maiagent.ai/tech/serve/line-flex-message.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
