From 00a1896bd96ce43e53dafd4f97be4eae8a902074 Mon Sep 17 00:00:00 2001 From: Junyan Chin Date: Mon, 22 Jun 2026 11:15:07 +0800 Subject: [PATCH] docs(platforms): HTTP Bot adapter tutorial (zh/en/ja) (#98) * docs(platforms): add HTTP Bot adapter tutorial (zh/en/ja) Server-to-server HTTP integration guide for the new http_bot platform adapter: signed inbound webhook, signed outbound callbacks, N->1 aggregation, 1->M multi-reply, session reset, sync convenience mode, error codes, and a 5-minute reference-client demo. Registered in docs.json nav for all three locales; adds the platform icon. * docs(http-bot): link the interactive playground in all locales Add a section pointing at examples/http-bot/playground.py (browser debug console: sign -> POST -> 202 -> live callbacks) ahead of the CLI reference clients, in zh/en/ja. * style(platforms): use code icon for http-bot logo --- docs.json | 5 +- en/usage/platforms/http-bot.mdx | 232 ++++++++++++++++++++++++++++++++ images/platforms/http-bot.svg | 9 ++ ja/usage/platforms/http-bot.mdx | 232 ++++++++++++++++++++++++++++++++ zh/usage/platforms/http-bot.mdx | 232 ++++++++++++++++++++++++++++++++ 5 files changed, 709 insertions(+), 1 deletion(-) create mode 100644 en/usage/platforms/http-bot.mdx create mode 100644 images/platforms/http-bot.svg create mode 100644 ja/usage/platforms/http-bot.mdx create mode 100644 zh/usage/platforms/http-bot.mdx diff --git a/docs.json b/docs.json index 19e945216..0ca2dbe54 100644 --- a/docs.json +++ b/docs.json @@ -270,6 +270,7 @@ "en/usage/platforms/slack", "en/usage/platforms/line", "en/usage/platforms/webpage", + "en/usage/platforms/http-bot", "en/usage/platforms/kook", "en/usage/platforms/lark", "en/usage/platforms/dingtalk", @@ -526,7 +527,8 @@ "zh/usage/platforms/telegram", "zh/usage/platforms/slack", "zh/usage/platforms/line", - "zh/usage/platforms/webpage" + "zh/usage/platforms/webpage", + "zh/usage/platforms/http-bot" ] }, { @@ -719,6 +721,7 @@ "ja/usage/platforms/slack", "ja/usage/platforms/line", "ja/usage/platforms/webpage", + "ja/usage/platforms/http-bot", "ja/usage/platforms/kook", "ja/usage/platforms/lark", "ja/usage/platforms/dingtalk", diff --git a/en/usage/platforms/http-bot.mdx b/en/usage/platforms/http-bot.mdx new file mode 100644 index 000000000..640e68dc0 --- /dev/null +++ b/en/usage/platforms/http-bot.mdx @@ -0,0 +1,232 @@ +--- +title: "HTTP Bot" +description: "Integrate any backend with LangBot over plain HTTP: push messages in via a signed webhook, receive replies on a callback URL. Server-to-server, no long-lived connection." +icon: "/images/platforms/http-bot.svg" +--- + +The `HTTP Bot` adapter integrates **any backend system** with a LangBot pipeline over plain HTTP. Ticketing systems, CRMs, internal tools, custom web apps — all can drive a pipeline through it: + +- **Inbound**: your backend POSTs a signed message to a fixed LangBot URL; +- **Outbound**: LangBot POSTs replies to a callback URL you configure. + +It needs **no long-lived connection** and fully preserves two native pipeline capabilities: + +- **Message aggregation (N→1)**: a user fires several messages in a row, merged into one turn; +- **Multi-part replies (1→M)**: one turn may produce several replies (function calls, multi-message plugins, streamed chunks). + + +If you want an **in-browser**, real-time chat widget, use the [Web Page Bot](/en/usage/platforms/webpage) instead. HTTP Bot is designed for backend-to-backend integration. + + +## How it works + +``` +Your backend ──(1) POST signed message──► LangBot /bots/ + (pipeline: aggregate → think → reply) +Your callback ◄─(2) POST signed reply(s)── LangBot one POST per reply part +``` + +- **(1) Inbound** is *fire-and-collect*: LangBot returns `202 Accepted` immediately and does **not** carry the pipeline result on that response; +- **(2) Outbound** replies arrive later as separate signed POSTs to your `callback_url`; a single turn may produce several callbacks; +- Everything is keyed by a **`session_id` you choose** (e.g. a ticket number); each `session_id` maps to one isolated session. + +## Create the bot + +In the LangBot WebUI, go to **Bots > Create Bot**, fill in a name, and pick `HTTP Bot` as the platform/adapter. + +## Configuration + +| Field | Required | Notes | +|-------|----------|-------| +| Inbound Signing Secret | yes | Your backend signs inbound requests with this; LangBot verifies every inbound request. | +| Outbound Callback URL | yes | Where LangBot POSTs replies. **Config-only** — cannot be overridden per message (SSRF protection). | +| Outbound Signing Secret | no | LangBot signs callbacks with this; defaults to the inbound secret if blank. | +| Default Session Type | no | `person` (default) or `group`. | +| Require Inbound Signature | no | Keep enabled in production. | +| Callback Timeout (seconds) | no | Per-callback HTTP timeout, default 15. | +| Callback Max Retries | no | Retries on timeout or 5xx with exponential backoff, default 3. | + +After binding a **pipeline** and **enabling** the bot, the config page shows the **Inbound Webhook URL**, like `https://your-langbot/bots/`. Copy it. + +## Signature scheme + +Both directions use the same dependency-free HMAC-SHA256 scheme: + +``` +signing_string = "{timestamp}." + raw_body_bytes +signature = "sha256=" + hex(HMAC_SHA256(secret, signing_string)) +``` + +Sent as headers: + +| Header | Meaning | +|--------|---------| +| `X-LB-Timestamp` | Unix seconds. Rejected if more than **±300s** from server time. | +| `X-LB-Signature` | `sha256=` over `"{timestamp}." + body`. | +| `X-LB-Idempotency-Key` | *(optional, inbound)* dedup key; a repeat returns `409`. | + +Verify outbound callbacks the same way, using the **outbound** secret (or the inbound secret if left blank). + +## Send your first message (curl) + +```bash +BOT="https://your-langbot/bots/" +SECRET="your-inbound-secret" +BODY='{"session_id":"ticket-10293","message":[{"type":"Plain","text":"Export keeps failing on the dashboard."}]}' +TS=$(date +%s) +SIG="sha256=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)" + +curl -sS -X POST "$BOT" \ + -H "Content-Type: application/json" \ + -H "X-LB-Timestamp: $TS" \ + -H "X-LB-Signature: $SIG" \ + -d "$BODY" +# -> 202 {"code":0,"msg":"accepted","data":{"session_id":"ticket-10293","accepted_message_id":"in_...","aggregating":true}} +``` + +Replies will be POSTed to your configured callback URL shortly after. + +## Inbound request format + +`POST /bots/{bot_uuid}` + +```json +{ + "session_id": "ticket-10293", + "session_type": "person", + "sender": { "id": "user-5567", "name": "Alice" }, + "message": [ + { "type": "Plain", "text": "Export keeps failing on the dashboard." }, + { "type": "Image", "url": "https://example.com/screenshot.png" } + ] +} +``` + +- `session_id` (**required**): your stable id, mapped 1:1 to a LangBot session; +- `message` (**required**): a LangBot message chain. Text uses `{"type":"Plain","text":"..."}`, images use `{"type":"Image","url":"..."}` (or `base64`); other types: `Voice`, `File`, `At`, `Quote`. + + +The callback URL is **not** accepted in the body — it is taken only from bot config. This is deliberate: even if the inbound secret leaks, an attacker cannot redirect replies to an arbitrary host. + + +### Aggregation (N → 1) + +If the pipeline has **message aggregation** enabled, send several messages with the **same `session_id`** inside the aggregation window and they merge into **one** turn. No special flag — just reuse the `session_id`. + +## Outbound callback format + +LangBot POSTs each reply part to your callback URL: + +```json +{ + "session_id": "ticket-10293", + "reply_to": "in_01H...", + "sequence": 1, + "is_final": false, + "stream": false, + "message": [ { "type": "Plain", "text": "Looking into it…" } ], + "timestamp": "2026-06-22T09:00:01Z" +} +``` + +Your endpoint should return `2xx` quickly. Non-2xx / timeout → LangBot retries with exponential backoff. + +### Multi-part replies (1 → M) + +One turn may emit multiple callbacks, delivered **in `sequence` order** for a given session: + +``` +seq=1 is_final=false "Checking your export logs…" +seq=2 is_final=false "Found 2 failed exports." +seq=3 is_final=true "Fixed — please try again." +``` + +Stitch by `session_id` + `sequence`; the turn is complete when `is_final: true` arrives. + +## Reset a session + +Start a fresh conversation for a `session_id` (drops history): + +``` +POST /bots/{bot_uuid}/reset +{ "session_id": "ticket-10293", "session_type": "person" } +→ 200 { "code":0, "msg":"reset", "data": { "session_id":"ticket-10293", "removed": true } } +``` + +Signed exactly like an inbound message. + +## Synchronous convenience mode + +If you don't need streaming/multi-part and just want one reply back on the same HTTP call, POST to `/sync`. LangBot waits for the turn to finish and returns all reply parts **collapsed** into one array: + +``` +POST /bots/{bot_uuid}/sync +{ "session_id": "ticket-10293", "message": [ { "type":"Plain", "text":"hi" } ] } +→ 200 { "code":0, "msg":"ok", + "data": { "session_id":"ticket-10293", "reply_to":"in_...", "message": [ ... ] } } +``` + + +Sync mode is **lossy** (you lose `sequence` and streaming boundaries) and blocks up to `callback_timeout × 4` seconds. Prefer the callback model for anything real-time or multi-part. Only one in-flight `/sync` per `session_id`. + + +## Error codes + +```json +{ "code": 40101, "msg": "invalid signature: signature_mismatch", "data": null } +``` + +| HTTP | code | meaning | +|------|------|---------| +| 202 | 0 | accepted | +| 400 | 40001 | malformed body / missing `session_id` or `message` | +| 401 | 40101 | bad/expired signature | +| 409 | 40901 | duplicate idempotency key | +| 413 | 41301 | message too large (>1 MiB) | +| 500 | 50001 | internal error | + +## Reference clients & 5-minute demo + +The main repo's [`examples/http-bot/`](https://github.com/langbot-app/LangBot/tree/master/examples/http-bot) ships an **interactive playground** plus Python and TypeScript reference clients. + +### Interactive playground (run this first) + +[`playground.py`](https://github.com/langbot-app/LangBot/blob/master/examples/http-bot/playground.py) is a single-file web app: type a message in your browser → it is signed and POSTed to a **running** `http_bot` bot → replies stream back into the page, with a debug panel showing the signature, the `202` ack, and each callback's `sequence` / verification. + +```bash +# From the LangBot repo root, with the backend running: +PUBLIC_IP= ./.venv/bin/python examples/http-bot/playground.py +# then open http://:8920/ +``` + +On startup it reads the API key + the `http_bot` bot from `data/langbot.db` and points that bot's `callback_url` + secrets back at itself via the LangBot API (live reload, no restart). Requires an enabled `http_bot` bot bound to a working pipeline. + +### Command-line reference clients + +[`examples/http-bot/`](https://github.com/langbot-app/LangBot/tree/master/examples/http-bot) also ships Python and TypeScript reference clients (with a callback receiver): + +```bash +cd examples/http-bot +pip install flask requests + +# Terminal 1: callback receiver (point the bot's callback_url here; use cloudflared / ngrok locally) +python client.py serve --port 8900 --secret SHARED_SECRET + +# Terminal 2: push a message +python client.py push \ + --url https://your-langbot/bots/ \ + --secret SHARED_SECRET \ + --session ticket-1 \ + --text "hello" +``` + +Terminal 1 prints each reply part (`[part ]` / `[FINAL]`) with its sequence number — that's 1→M multi-reply with signature verification, live. + +A machine-readable contract is in the main repo at [`docs/http-bot-openapi.json`](https://github.com/langbot-app/LangBot/blob/master/docs/http-bot-openapi.json). + +## Security checklist + +- Keep **Require Inbound Signature** on in production; +- Use **HTTPS** callback URLs, set only in config (no per-message override); +- Treat secrets like passwords; rotate via the dashboard; +- The inbound route is unauthenticated at the framework level **by design** — security comes entirely from the HMAC signature, so never disable it on a public deployment. diff --git a/images/platforms/http-bot.svg b/images/platforms/http-bot.svg new file mode 100644 index 000000000..e092e755f --- /dev/null +++ b/images/platforms/http-bot.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ja/usage/platforms/http-bot.mdx b/ja/usage/platforms/http-bot.mdx new file mode 100644 index 000000000..439263743 --- /dev/null +++ b/ja/usage/platforms/http-bot.mdx @@ -0,0 +1,232 @@ +--- +title: "HTTP ボット" +description: "任意のバックエンドを HTTP で LangBot に接続:署名付き Webhook でメッセージを送信し、コールバック URL で返信を受信。サーバー間連携、長時間接続不要。" +icon: "/images/platforms/http-bot.svg" +--- + +`HTTP ボット`アダプターは、**任意のバックエンドシステム**を LangBot パイプラインに接続します。チケットシステム、CRM、社内ツール、自作 Web アプリなど、すべてこれを通じてパイプラインを駆動できます。 + +- **受信**:バックエンドが署名付きメッセージを LangBot の固定 URL に `POST` します; +- **送信**:LangBot が返信を設定済みのコールバック URL に `POST` します。 + +**長時間接続は不要**で、パイプラインの 2 つのネイティブ機能を完全に保持します。 + +- **メッセージ集約(N→1)**:ユーザーが連続して複数のメッセージを送ると、1 ターンにまとめて処理; +- **マルチパート返信(1→M)**:1 ターンで複数の返信が発生し得る(関数呼び出し、複数メッセージのプラグイン、ストリーミング分割)。 + + +**ブラウザ内**のリアルタイムチャットウィジェットが必要な場合は、[ページボット](/ja/usage/platforms/webpage)を使用してください。HTTP ボットはバックエンド間連携のために設計されています。 + + +## 仕組み + +``` +バックエンド ──(1) 署名付きメッセージを POST──► LangBot /bots/ + (パイプライン:集約 → 推論 → 返信) +コールバック ◄─(2) 署名付き返信を POST────── LangBot 返信パートごとに 1 回 POST +``` + +- **(1) 受信**は「まず受け取り、後で処理」:LangBot は即座に `202 Accepted` を返し、そのレスポンスにパイプライン結果は**含めません**; +- **(2) 送信**返信は後で、独立した署名付き POST としてあなたの `callback_url` に届きます。1 ターンで複数回のコールバックが発生し得ます; +- すべては**あなたが指定する `session_id`**(例:チケット番号)をキーとし、各 `session_id` は 1 つの独立したセッションに対応します。 + +## ボットの作成 + +LangBot WebUI で**ボット > ボット作成**に進み、名前を入力し、プラットフォーム/アダプターに `HTTP ボット` を選択します。 + +## 設定項目 + +| 設定項目 | 必須 | 説明 | +|----------|------|------| +| 受信署名シークレット | はい | バックエンドが受信リクエストの HMAC-SHA256 署名に使用。LangBot は受信ごとに検証します。 | +| 送信コールバック URL | はい | LangBot が返信を POST する先。**設定からのみ**取得し、メッセージ単位で上書き不可(SSRF 対策)。 | +| 送信署名シークレット | いいえ | LangBot がコールバックの署名に使用。空の場合は受信シークレットを使用。 | +| デフォルトセッションタイプ | いいえ | `person`(既定)または `group`。 | +| 受信署名を必須にする | いいえ | 本番環境では有効のままに。 | +| コールバックタイムアウト(秒) | いいえ | コールバックごとの HTTP タイムアウト、既定 15。 | +| コールバック最大リトライ回数 | いいえ | タイムアウトまたは 5xx 時に指数バックオフでリトライ、既定 3。 | + +**パイプライン**にバインドして**有効化**すると、設定ページに**受信 Webhook URL**(`https://your-langbot/bots/` の形式)が表示されます。コピーしてください。 + +## 署名スキーム + +両方向で同じ依存関係なしの HMAC-SHA256 スキームを使用します。 + +``` +signing_string = "{timestamp}." + 生のリクエストボディのバイト列 +signature = "sha256=" + hex(HMAC_SHA256(secret, signing_string)) +``` + +ヘッダーで送信します。 + +| ヘッダー | 意味 | +|----------|------| +| `X-LB-Timestamp` | Unix 秒。サーバー時刻と **±300 秒**以上離れていると拒否。 | +| `X-LB-Signature` | `"{timestamp}." + body` に対する `sha256=`。 | +| `X-LB-Idempotency-Key` | (任意、受信)重複排除キー。再送は `409` を返す。 | + +送信コールバックの検証も同様に、**送信シークレット**(空なら受信シークレット)を使用します。 + +## 最初のメッセージを送る(curl) + +```bash +BOT="https://your-langbot/bots/" +SECRET="あなたの受信シークレット" +BODY='{"session_id":"ticket-10293","message":[{"type":"Plain","text":"ダッシュボードでエクスポートが失敗し続けます。"}]}' +TS=$(date +%s) +SIG="sha256=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)" + +curl -sS -X POST "$BOT" \ + -H "Content-Type: application/json" \ + -H "X-LB-Timestamp: $TS" \ + -H "X-LB-Signature: $SIG" \ + -d "$BODY" +# -> 202 {"code":0,"msg":"accepted","data":{"session_id":"ticket-10293","accepted_message_id":"in_...","aggregating":true}} +``` + +返信はまもなく設定済みのコールバック URL に POST されます。 + +## 受信リクエスト形式 + +`POST /bots/{bot_uuid}` + +```json +{ + "session_id": "ticket-10293", + "session_type": "person", + "sender": { "id": "user-5567", "name": "Alice" }, + "message": [ + { "type": "Plain", "text": "ダッシュボードでエクスポートが失敗し続けます。" }, + { "type": "Image", "url": "https://example.com/screenshot.png" } + ] +} +``` + +- `session_id`(**必須**):あなたの安定した識別子、1 つの LangBot セッションに 1:1 対応; +- `message`(**必須**):LangBot メッセージチェーン。テキストは `{"type":"Plain","text":"..."}`、画像は `{"type":"Image","url":"..."}`(または `base64`);その他 `Voice`、`File`、`At`、`Quote` をサポート。 + + +コールバック URL はボディでは**受け付けず**、ボット設定からのみ取得します。これは意図的な設計です:受信シークレットが漏洩しても、攻撃者が返信を任意のホストにリダイレクトすることはできません。 + + +### メッセージ集約(N → 1) + +パイプラインで**メッセージ集約**が有効な場合、**同じ `session_id`** で集約ウィンドウ内に複数のメッセージを送ると、**1 ターン**にまとめられます。特別なフラグは不要、`session_id` を再利用するだけです。 + +## 送信コールバック形式 + +LangBot は各返信パートをコールバック URL に POST します。 + +```json +{ + "session_id": "ticket-10293", + "reply_to": "in_01H...", + "sequence": 1, + "is_final": false, + "stream": false, + "message": [ { "type": "Plain", "text": "調査中です…" } ], + "timestamp": "2026-06-22T09:00:01Z" +} +``` + +受信側は速やかに `2xx` を返してください。2xx 以外やタイムアウトの場合、LangBot は指数バックオフでリトライします。 + +### マルチパート返信(1 → M) + +1 ターンで複数のコールバックが発生し得て、同一セッションには `sequence` 順で届きます。 + +``` +seq=1 is_final=false "エクスポートログを確認中…" +seq=2 is_final=false "失敗したエクスポートが 2 件見つかりました。" +seq=3 is_final=true "修正しました。再試行してください。" +``` + +`session_id` + `sequence` で結合します。`is_final: true` が届いたらそのターンは完了です。 + +## セッションのリセット + +ある `session_id` の会話を新規に開始(履歴を破棄)します。 + +``` +POST /bots/{bot_uuid}/reset +{ "session_id": "ticket-10293", "session_type": "person" } +→ 200 { "code":0, "msg":"reset", "data": { "session_id":"ticket-10293", "removed": true } } +``` + +署名方法は受信メッセージと同じです。 + +## 同期便利モード + +ストリーミング/マルチパートが不要で、同じ HTTP 呼び出しで返信を受け取りたい場合は `/sync` に POST します。LangBot はターンの終了を待ち、すべての返信パートを 1 つの配列に**まとめて**返します。 + +``` +POST /bots/{bot_uuid}/sync +{ "session_id": "ticket-10293", "message": [ { "type":"Plain", "text":"こんにちは" } ] } +→ 200 { "code":0, "msg":"ok", + "data": { "session_id":"ticket-10293", "reply_to":"in_...", "message": [ ... ] } } +``` + + +同期モードは**ロスあり**(`sequence` とストリーミング境界が失われる)で、最大 `コールバックタイムアウト × 4` 秒ブロックします。リアルタイムやマルチパートの場合はコールバックモードを優先してください。`session_id` ごとに進行中の `/sync` は 1 つのみです。 + + +## エラーコード + +```json +{ "code": 40101, "msg": "invalid signature: signature_mismatch", "data": null } +``` + +| HTTP | code | 意味 | +|------|------|------| +| 202 | 0 | 受理 | +| 400 | 40001 | ボディ不正 / `session_id` または `message` 欠如 | +| 401 | 40101 | 署名が不正または期限切れ | +| 409 | 40901 | 冪等キーの重複 | +| 413 | 41301 | メッセージが大きすぎる(>1 MiB) | +| 500 | 50001 | 内部エラー | + +## リファレンスクライアントと 5 分デモ + +メインリポジトリの [`examples/http-bot/`](https://github.com/langbot-app/LangBot/tree/master/examples/http-bot) には**インタラクティブなプレイグラウンド**と Python / TypeScript のリファレンスクライアントがあります。 + +### インタラクティブ・プレイグラウンド(まずこれを実行) + +[`playground.py`](https://github.com/langbot-app/LangBot/blob/master/examples/http-bot/playground.py) は単一ファイルの Web アプリです:ブラウザでメッセージを入力 → 署名して**稼働中**の `http_bot` ボットへ POST → 返信がページにストリーム表示され、右側のデバッグパネルに署名・`202` 確認・各コールバックの `sequence` と検証結果が表示されます。 + +```bash +# LangBot リポジトリのルートで、バックエンドが稼働している状態で: +PUBLIC_IP=<あなたのホストIP> ./.venv/bin/python examples/http-bot/playground.py +# その後 http://<あなたのホストIP>:8920/ を開く +``` + +起動時に `data/langbot.db` から API Key と `http_bot` ボットを読み取り、LangBot API 経由でそのボットの `callback_url` とシークレットを自分自身に向けます(ボットはライブリロード、再起動不要)。有効化され、動作するパイプラインにバインドされた `http_bot` ボットが必要です。 + +### コマンドラインのリファレンスクライアント + +[`examples/http-bot/`](https://github.com/langbot-app/LangBot/tree/master/examples/http-bot) には Python と TypeScript のリファレンスクライアント(コールバックレシーバー付き)もあります。 + +```bash +cd examples/http-bot +pip install flask requests + +# ターミナル 1:コールバックレシーバー(ボットの callback_url をここに向ける。ローカルは cloudflared / ngrok で) +python client.py serve --port 8900 --secret SHARED_SECRET + +# ターミナル 2:メッセージを送信 +python client.py push \ + --url https://your-langbot/bots/ \ + --secret SHARED_SECRET \ + --session ticket-1 \ + --text "こんにちは" +``` + +ターミナル 1 が各返信パート(`[part ]` / `[FINAL]`)をシーケンス番号付きで表示します——これが署名検証済みの 1→M マルチパート返信のライブ動作です。 + +機械可読の契約はメインリポジトリの [`docs/http-bot-openapi.json`](https://github.com/langbot-app/LangBot/blob/master/docs/http-bot-openapi.json) にあります。 + +## セキュリティチェックリスト + +- 本番環境では**受信署名を必須にする**を有効に; +- コールバック URL は **HTTPS** を使用し、設定でのみ指定(メッセージ単位の上書き不可); +- シークレットはパスワードと同様に扱い、ダッシュボードでローテーション; +- 受信ルートはフレームワークレベルで未認証です(**意図的な設計**)。セキュリティは完全に HMAC 署名に依存するため、公開デプロイでは絶対に無効化しないでください。 diff --git a/zh/usage/platforms/http-bot.mdx b/zh/usage/platforms/http-bot.mdx new file mode 100644 index 000000000..318c16065 --- /dev/null +++ b/zh/usage/platforms/http-bot.mdx @@ -0,0 +1,232 @@ +--- +title: "HTTP 通用接入" +description: "通过 HTTP 将任意后端系统接入 LangBot:以签名 Webhook 推入消息,在回调地址接收回复。面向服务间集成,无需长连接。" +icon: "/images/platforms/http-bot.svg" +--- + +`HTTP 通用接入`(HTTP Bot)是一个面向**服务间集成**的通用适配器。任意后端系统(工单系统、CRM、内部工具、自研 Web 应用等)都可以通过它驱动 LangBot 流水线: + +- **入站**:你的后端将消息以签名方式 `POST` 到 LangBot 的固定地址; +- **出站**:LangBot 把回复 `POST` 到你配置的回调地址。 + +它**无需维持长连接**,并且完整保留流水线的两项原生能力: + +- **消息聚合(多条合一,N→1)**:用户连发多条消息,合并成一轮处理; +- **多段回复(一问多答,1→M)**:一轮对话可产生多条回复(函数调用、插件多条消息、流式分片)。 + + +如果你要的是**浏览器内**的实时聊天组件,请使用[页面机器人](/zh/usage/platforms/webpage)。HTTP 通用接入是为后端到后端的集成设计的。 + + +## 工作原理 + +``` +你的后端 ──(1) 推入签名消息──► LangBot /bots/ + (流水线运行:聚合 → 推理 → 回复) +你的回调 ◄─(2) 推回签名回复── LangBot 每段回复一次 POST +``` + +- **(1) 入站**是「先收下、后处理」:LangBot 立即返回 `202 Accepted`,**不会**在该响应里带流水线结果; +- **(2) 出站**回复随后以独立的签名 POST 发到你的 `回调地址`,一轮对话可能产生多次回调; +- 一切都以**你自定义的 `session_id`**(如工单号)为键,每个 `session_id` 对应一个独立会话。 + +## 创建机器人 + +打开 LangBot WebUI,进入**机器人 > 创建机器人**,填写名称,平台/适配器选择 `HTTP 通用接入`。 + +## 配置项 + +| 配置项 | 必填 | 说明 | +|--------|------|------| +| 入站签名密钥 | 是 | 你的后端用它对入站请求做 HMAC-SHA256 签名,LangBot 据此校验每个入站请求。 | +| 出站回调地址 | 是 | LangBot 将回复 POST 到此地址。**仅取自此配置**,不允许在消息中覆盖(防 SSRF)。 | +| 出站签名密钥 | 否 | LangBot 用它对回调签名,供你的接收端校验。留空则回退使用入站密钥。 | +| 默认会话类型 | 否 | `person`(默认)或 `group`。 | +| 强制入站签名校验 | 否 | 生产环境请保持开启。 | +| 回调超时(秒) | 否 | 单次回调的 HTTP 超时,默认 15。 | +| 回调最大重试次数 | 否 | 超时或 5xx 时按指数退避重试,默认 3。 | + +创建并绑定**流水线**、**启用**后,配置页会显示**入站 Webhook 地址**,形如 `https://your-langbot/bots/`,复制备用。 + +## 签名方案 + +两个方向都使用同一套零依赖的 HMAC-SHA256 方案: + +``` +signing_string = "{timestamp}." + 原始请求体字节 +signature = "sha256=" + hex(HMAC_SHA256(secret, signing_string)) +``` + +通过请求头传递: + +| 请求头 | 含义 | +|--------|------| +| `X-LB-Timestamp` | Unix 秒级时间戳。与服务端相差超过 **±300 秒**则拒绝。 | +| `X-LB-Signature` | `sha256=`,对 `"{timestamp}." + body` 计算。 | +| `X-LB-Idempotency-Key` | (可选,入站)幂等键;相同键重复提交返回 `409`。 | + +校验出站回调时同理,使用**出站密钥**(留空则用入站密钥)。 + +## 发送第一条消息(curl) + +```bash +BOT="https://your-langbot/bots/" +SECRET="你的入站密钥" +BODY='{"session_id":"ticket-10293","message":[{"type":"Plain","text":"导出功能在控制台一直失败。"}]}' +TS=$(date +%s) +SIG="sha256=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)" + +curl -sS -X POST "$BOT" \ + -H "Content-Type: application/json" \ + -H "X-LB-Timestamp: $TS" \ + -H "X-LB-Signature: $SIG" \ + -d "$BODY" +# -> 202 {"code":0,"msg":"accepted","data":{"session_id":"ticket-10293","accepted_message_id":"in_...","aggregating":true}} +``` + +回复将稍后 POST 到你配置的回调地址。 + +## 入站消息格式 + +`POST /bots/{bot_uuid}` + +```json +{ + "session_id": "ticket-10293", + "session_type": "person", + "sender": { "id": "user-5567", "name": "Alice" }, + "message": [ + { "type": "Plain", "text": "导出功能在控制台一直失败。" }, + { "type": "Image", "url": "https://example.com/screenshot.png" } + ] +} +``` + +- `session_id`(**必填**):你的稳定标识,1:1 映射到一个 LangBot 会话; +- `message`(**必填**):LangBot 消息链。文本用 `{"type":"Plain","text":"..."}`,图片用 `{"type":"Image","url":"..."}`(或 `base64`);其余支持 `Voice`、`File`、`At`、`Quote`。 + + +回调地址**不接受**写在消息体里,只取自机器人配置。这是有意为之:即便入站密钥泄露,攻击者也无法把回复重定向到任意地址。 + + +### 消息聚合(N → 1) + +若流水线启用了**消息聚合**,用**相同的 `session_id`**在聚合窗口内连发多条消息,它们会被合并为**一轮**处理。无需任何特殊标记,复用 `session_id` 即可。 + +## 出站回调格式 + +LangBot 将每段回复 POST 到你的回调地址: + +```json +{ + "session_id": "ticket-10293", + "reply_to": "in_01H...", + "sequence": 1, + "is_final": false, + "stream": false, + "message": [ { "type": "Plain", "text": "正在排查……" } ], + "timestamp": "2026-06-22T09:00:01Z" +} +``` + +你的接收端应尽快返回 `2xx`。非 2xx 或超时,LangBot 会按指数退避重试。 + +### 多段回复(1 → M) + +一轮对话可能产生多次回调,对同一会话按 `sequence` 顺序送达: + +``` +seq=1 is_final=false "正在查看导出日志……" +seq=2 is_final=false "发现 2 次失败的导出。" +seq=3 is_final=true "已修复,请重试。" +``` + +用 `session_id` + `sequence` 拼接;收到 `is_final: true` 表示本轮结束。 + +## 重置会话 + +为某个 `session_id` 开启全新会话(清空历史): + +``` +POST /bots/{bot_uuid}/reset +{ "session_id": "ticket-10293", "session_type": "person" } +→ 200 { "code":0, "msg":"reset", "data": { "session_id":"ticket-10293", "removed": true } } +``` + +签名方式与入站消息一致。 + +## 同步便利模式 + +若你不需要流式/多段,只想在同一次 HTTP 调用里拿回复,可 POST 到 `/sync`。LangBot 会等待本轮结束,把所有回复段**折叠**成一个数组返回: + +``` +POST /bots/{bot_uuid}/sync +{ "session_id": "ticket-10293", "message": [ { "type":"Plain", "text":"你好" } ] } +→ 200 { "code":0, "msg":"ok", + "data": { "session_id":"ticket-10293", "reply_to":"in_...", "message": [ ... ] } } +``` + + +同步模式是**有损的**(丢失 `sequence` 与流式边界),且会阻塞最长 `回调超时 × 4` 秒。实时或多段场景请优先使用回调模式。每个 `session_id` 同一时刻只允许一个进行中的 `/sync`。 + + +## 错误码 + +```json +{ "code": 40101, "msg": "invalid signature: signature_mismatch", "data": null } +``` + +| HTTP | code | 含义 | +|------|------|------| +| 202 | 0 | 已接受 | +| 400 | 40001 | 请求体不合法 / 缺少 `session_id` 或 `message` | +| 401 | 40101 | 签名错误或过期 | +| 409 | 40901 | 幂等键重复 | +| 413 | 41301 | 消息过大(>1 MiB) | +| 500 | 50001 | 内部错误 | + +## 参考客户端与 5 分钟体验 + +LangBot 主仓的 [`examples/http-bot/`](https://github.com/langbot-app/LangBot/tree/master/examples/http-bot) 提供了一个**交互式调试台**和 Python / TypeScript 参考客户端。 + +### 交互式调试台(推荐先跑这个) + +[`playground.py`](https://github.com/langbot-app/LangBot/blob/master/examples/http-bot/playground.py) 是一个单文件网页应用:在浏览器里打字 → 自动签名后真实发往运行中的 `http_bot` 机器人 → 回复经回调实时显示在页面上,右侧调试面板展示签名、`202` 确认、以及每条回调的 `sequence` 与验签结果。 + +```bash +# 在 LangBot 仓库根目录,后端已在运行的前提下: +PUBLIC_IP=<你的公网IP> ./.venv/bin/python examples/http-bot/playground.py +# 然后打开 http://<你的公网IP>:8920/ +``` + +启动时它会从 `data/langbot.db` 读取 API Key 与 `http_bot` 机器人,并通过 LangBot API 把该机器人的回调地址和密钥指向自身(机器人热加载,无需重启)。前提:有一个已启用、且绑定了可用流水线的 `http_bot` 机器人。 + +### 命令行参考客户端 + +[`examples/http-bot/`](https://github.com/langbot-app/LangBot/tree/master/examples/http-bot) 还提供了 Python 与 TypeScript 参考客户端(含回调接收器): + +```bash +cd examples/http-bot +pip install flask requests + +# 终端 1:回调接收器(把机器人回调地址指向它,本地可用 cloudflared / ngrok 隧道) +python client.py serve --port 8900 --secret SHARED_SECRET + +# 终端 2:推送一条消息 +python client.py push \ + --url https://your-langbot/bots/ \ + --secret SHARED_SECRET \ + --session ticket-1 \ + --text "你好" +``` + +终端 1 会逐段打印回复(`[part ]` / `[FINAL]`)并带序号——这就是 1→M 多段回复、签名校验通过的实时效果。 + +机器可读契约见主仓 [`docs/http-bot-openapi.json`](https://github.com/langbot-app/LangBot/blob/master/docs/http-bot-openapi.json)。 + +## 安全清单 + +- 生产环境保持**强制入站签名校验**开启; +- 回调地址使用 **HTTPS**,且只能在配置中设置(不可逐条覆盖); +- 密钥按密码对待,通过控制台轮换; +- 入站路由在框架层是无鉴权的(**有意设计**),安全完全依赖 HMAC 签名——公网部署绝不要关闭它。