# 機能設計書 181-ボット応答

## 概要

本ドキュメントは、Chatwootにおける「ボット応答」機能の設計仕様を定義する。本機能は、AIボットがWebhook経由で会話イベントを受信し、自動で返答を行うための基盤機能である。

### 本機能の処理概要

本機能は、インボックスに配置されたエージェントボットが会話イベント（メッセージ作成、会話オープン、会話解決など）を検知し、外部のWebhookエンドポイントにペイロードを送信することで、ボットによる自動応答を可能にする。

**業務上の目的・背景**：カスタマーサポートにおいて、24時間365日対応や、定型的な問い合わせへの即座な回答が求められている。人的リソースには限界があり、ボットによる自動応答を導入することで、初期対応の効率化、応答時間の短縮、エージェントの負荷軽減を実現する。また、ボット応答により、営業時間外でも顧客対応が可能となり、顧客満足度の向上にも寄与する。

**機能の利用シーン**：顧客がWebウィジェットやSNSチャネル経由でメッセージを送信した際、ボットが最初に応答して基本的な情報収集を行う。FAQへの誘導、担当エージェントへのエスカレーション判断、営業時間外の自動応答メッセージ送信などに活用される。

**主要な処理内容**：
1. 会話イベント（message_created, conversation_opened, conversation_resolved等）の検知
2. インボックスに配置されたアクティブなボットの特定
3. イベントペイロードの構築
4. 外部WebhookエンドポイントへのHTTPリクエスト送信
5. ボットからの返答メッセージの処理

**関連システム・外部連携**：外部のWebhookサーバー（ボットロジックを実装した外部システム）、Sidekiqによる非同期ジョブ処理、Redisによるジョブキュー管理

**権限による制御**：ボットの作成・編集・削除にはアカウント管理者権限が必要。システムボット（account_idがnull）は全アカウントで利用可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 74 | エージェントボット一覧画面 | 主画面 | エージェントボットの一覧表示・管理 |
| 50 | インボックス設定画面 | 参照画面 | インボックスへのボット配置設定 |

## 機能種別

イベント駆動処理 / 外部システム連携

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| event | String | Yes | イベント種別 | message_created, message_updated, conversation_opened, conversation_resolved, webwidget_triggered のいずれか |
| conversation | Object | No | 会話データ | webhook_data形式のオブジェクト |
| message | Object | No | メッセージデータ | webhook_data形式のオブジェクト |
| contact_inbox | Object | No | コンタクトインボックスデータ | webwidget_triggered時のみ |

### 入力データソース

- 会話イベント: Wisperによるイベントブロードキャスト
- ボット設定: agent_botsテーブル、agent_bot_inboxesテーブル

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| HTTP Request | POST | 外部WebhookへのHTTPリクエスト |
| payload | JSON | イベント情報を含むJSONペイロード |

### 出力先

- 外部Webhookエンドポイント（agent_bot.outgoing_url）
- Sidekiqジョブログ

## 処理フロー

### 処理シーケンス

```
1. イベント発生（メッセージ作成等）
   └─ Wisperによるイベントブロードキャスト

2. AgentBotListenerがイベントを受信
   └─ 該当するイベントハンドラが呼び出される

3. 対象ボットの特定
   └─ インボックスに配置されたアクティブなボット + 会話に割り当てられたボットを取得

4. ペイロード構築
   └─ conversation.webhook_data または message.webhook_data とイベント名をマージ

5. WebhookJobのエンキュー
   └─ AgentBots::WebhookJob.perform_later でジョブを非同期実行

6. 外部Webhookへのリクエスト送信
   └─ HTTPクライアントでPOSTリクエストを送信
```

### フローチャート

```mermaid
flowchart TD
    A[イベント発生] --> B{イベント種別}
    B -->|message_created| C[メッセージ送信可能か確認]
    B -->|conversation_opened| D[会話・アカウント抽出]
    B -->|conversation_resolved| D
    B -->|webwidget_triggered| E[contact_inbox抽出]

    C --> F{webhook_sendable?}
    F -->|No| G[処理終了]
    F -->|Yes| H[ボット一覧取得]

    D --> H
    E --> H

    H --> I{ボットが存在するか}
    I -->|No| G
    I -->|Yes| J[各ボットに対して処理]

    J --> K{outgoing_urlが設定されているか}
    K -->|No| L[スキップ]
    K -->|Yes| M[WebhookJob実行]

    L --> N{次のボット}
    N -->|あり| J
    N -->|なし| G

    M --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-181-01 | ボットアクティブ判定 | agent_bot_inbox.statusがactiveの場合のみWebhookを送信 | インボックス配置ボット |
| BR-181-02 | 送信可能メッセージ判定 | message.webhook_sendable?がtrueの場合のみ処理 | メッセージイベント |
| BR-181-03 | URL必須チェック | outgoing_urlが空の場合はWebhook送信をスキップ | 全イベント |
| BR-181-04 | ボット重複排除 | 同一ボットへの重複送信を防止（uniq） | 全イベント |

### 計算ロジック

特になし

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ボット取得 | agent_bots | SELECT | ボット情報の取得 |
| インボックスボット取得 | agent_bot_inboxes | SELECT | インボックスに配置されたボットの取得 |
| 会話データ取得 | conversations | SELECT | Webhook送信用の会話データ取得 |
| メッセージデータ取得 | messages | SELECT | Webhook送信用のメッセージデータ取得 |

### テーブル別操作詳細

#### agent_bots

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, name, outgoing_url, bot_config | 会話の割り当てボット or インボックス配置ボット | |

#### agent_bot_inboxes

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | status | inbox_id で検索、status = 'active' | アクティブなボットのみ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ネットワークエラー | 外部Webhookへの接続失敗 | Sidekiqによるリトライ |
| - | タイムアウト | 外部Webhookが応答しない | Sidekiqによるリトライ |
| - | HTTPエラー | 4xx/5xxレスポンス | ログ出力、リトライ |

### リトライ仕様

WebhookJobはSidekiqのリトライ機構を使用。デフォルトのリトライ回数・間隔に従う。

## トランザクション仕様

本機能はイベント駆動の非同期処理であり、明示的なトランザクション制御は行わない。Sidekiqのジョブ実行単位で管理される。

## パフォーマンス要件

- Webhook送信はhighキューで処理（優先度高）
- ジョブのエンキューは即座に完了すること
- 外部Webhookへのリクエストはタイムアウト設定あり

## セキュリティ考慮事項

- outgoing_urlはURLの長さ制限あり（Limits::URL_LENGTH_LIMIT）
- Webhookペイロードには顧客情報が含まれるため、HTTPS通信を推奨
- ボットのアクセストークンはAccessTokenable concernで管理

## 備考

- ボットタイプは現在「webhook」のみ対応（enum: webhook: 0）
- システムボット（account_id = null）は複数アカウントで共有可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、ボット関連のデータモデルを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | agent_bot.rb | `app/models/agent_bot.rb` | ボットの属性、リレーション、スコープを理解 |
| 1-2 | agent_bot_inbox.rb | `app/models/agent_bot_inbox.rb` | ボットとインボックスの関連付け、ステータス管理 |

**読解のコツ**:
- AgentBotの`accessible_to`スコープは、アカウント固有ボット + システムボット（account_id = null）を返す
- **20-36行目**: AgentBotクラスの定義、リレーション宣言
- **37行目**: bot_typeのenum定義（現在はwebhookのみ）

#### Step 2: イベントリスナーを理解する

イベント駆動の処理起点となるリスナーを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | agent_bot_listener.rb | `app/listeners/agent_bot_listener.rb` | 各種イベントハンドラの実装 |

**主要処理フロー**:
- **2-8行目**: conversation_resolvedハンドラ - 会話解決時の処理
- **10-16行目**: conversation_openedハンドラ - 会話オープン時の処理
- **18-25行目**: message_createdハンドラ - メッセージ作成時の処理
- **47-53行目**: agent_bots_forメソッド - 対象ボットを特定するロジック

#### Step 3: 非同期ジョブを理解する

Webhook送信を行うジョブ処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | webhook_job.rb | `app/jobs/agent_bots/webhook_job.rb` | ボット専用のWebhookジョブ |

**主要処理フロー**:
- **1-7行目**: WebhookJobを継承し、highキューで実行
- 親クラスのWebhookJobで実際のHTTP送信を実行

### プログラム呼び出し階層図

```
Wisperイベント（message_created等）
    │
    ├─ AgentBotListener
    │      ├─ message_created / message_updated
    │      │      └─ extract_message_and_account
    │      │      └─ webhook_sendable? チェック
    │      │      └─ agent_bots_for(inbox, conversation)
    │      │             ├─ conversation.assignee_agent_bot
    │      │             └─ active_inbox_agent_bot(inbox)
    │      │
    │      ├─ conversation_opened / conversation_resolved
    │      │      └─ extract_conversation_and_account
    │      │      └─ agent_bots_for(inbox, conversation)
    │      │
    │      └─ webwidget_triggered
    │             └─ agent_bots_for(inbox)
    │
    └─ process_webhook_bot_event(agent_bot, payload)
           └─ AgentBots::WebhookJob.perform_later
                  └─ WebhookJob#perform (親クラス)
                         └─ HTTPリクエスト送信
```

### データフロー図

```
[入力]                          [処理]                              [出力]

会話イベント ───────────▶ AgentBotListener ─────────────▶ Sidekiq Queue (high)
(Wisper)                      │                                    │
                              ▼                                    ▼
                     ペイロード構築                        外部Webhook
                     (webhook_data)                        (HTTP POST)
                              │
                              ▼
                     対象ボット特定
                     (agent_bots_for)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| agent_bot.rb | `app/models/agent_bot.rb` | モデル | ボットエンティティの定義 |
| agent_bot_inbox.rb | `app/models/agent_bot_inbox.rb` | モデル | ボット-インボックス関連付け |
| agent_bot_listener.rb | `app/listeners/agent_bot_listener.rb` | リスナー | イベントハンドリング |
| webhook_job.rb | `app/jobs/agent_bots/webhook_job.rb` | ジョブ | 非同期Webhook送信 |
| base_listener.rb | `app/listeners/base_listener.rb` | リスナー | リスナー基底クラス |
