# 機能設計書 210-LLMフォーマッター

## 概要

本ドキュメントは、ChatwootのLLMフォーマッター機能について記載する。Chatwootの各種オブジェクト（会話、連絡先、記事）をLLM（大規模言語モデル）が理解しやすいテキスト形式に変換するサービス群である。

### 本機能の処理概要

LLMフォーマッター機能は、Captain AIなどのLLM統合機能で使用するために、Chatwootのデータオブジェクト（Conversation、Contact、Article）を構造化されたテキスト形式に変換するサービスである。Strategy パターンを使用し、オブジェクトの型に応じて適切なフォーマッターを自動選択する。

**業務上の目的・背景**：LLMベースのAIアシスタント機能（Captain）では、会話の履歴や連絡先情報、ヘルプセンターの記事をAIに渡す必要がある。しかし、データベースのオブジェクトをそのままLLMに渡すことはできないため、人間が読める形式かつAIが理解しやすい構造化テキストに変換する必要がある。

**機能の利用シーン**：
- Captain AIが会話のコンテキストを理解する場合
- AIアシスタントが連絡先情報を参照する場合
- AIがヘルプセンターの記事を検索・参照する場合
- Copilot機能でAIに会話情報を渡す場合

**主要な処理内容**：
1. オブジェクトの型に基づいてフォーマッターを自動選択
2. 会話をメッセージ履歴付きのテキストに変換
3. 連絡先を属性・ノート付きのテキストに変換
4. 記事をメタデータ・コンテンツ付きのテキストに変換
5. オプションでトークン制限やプライベートメッセージの包含を制御

**関連システム・外部連携**：Captain AI統合機能、Copilot機能。LLM APIへの入力データ生成に使用。

**権限による制御**：本機能はサービス層で使用される内部機能のため、直接的な権限制御は適用されない。呼び出し元の機能で権限が制御される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | 内部機能 | Captain AI/Copilotから間接的に利用 |

## 機能種別

データ変換 / テキスト生成

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| record | Object | Yes | 変換対象オブジェクト（Conversation/Contact/Article） | - |
| config | Hash | No | 変換オプション（token_limit, include_contact_details等） | - |

### 入力データソース

- conversationsテーブル
- contactsテーブル
- articlesテーブル
- messagesテーブル
- notesテーブル
- custom_attribute_definitionsテーブル

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| formatted_text | String | LLM向けにフォーマットされたテキスト |

### 出力先

呼び出し元サービス（LLM API入力として使用）

## 処理フロー

### 処理シーケンス

```
1. LlmTextFormatterService初期化
   └─ recordを受け取る
2. フォーマッター選択
   └─ record.class.nameからフォーマッタークラスを特定
3. フォーマット実行
   └─ 選択されたフォーマッターのformatメソッド呼び出し
4. テキスト生成
   └─ 各フォーマッターが構造化テキストを生成
```

### フローチャート

```mermaid
flowchart TD
    A[record受け取り] --> B[find_formatter]
    B --> C{フォーマッターあり?}
    C -->|Yes| D[フォーマッター生成]
    C -->|No| E[FormatterNotFoundError]
    D --> F[format実行]
    F --> G[テキスト返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 自動フォーマッター選択 | オブジェクト型に応じたフォーマッターを自動選択 | 常時 |
| BR-002 | トークン制限 | token_limitオプションでメッセージ数を制限可能 | config[:token_limit]指定時 |
| BR-003 | プライベートメッセージ除外 | デフォルトでプライベートノートは除外 | config[:include_private_messages]なし |
| BR-004 | 活動・テンプレート除外 | activity, templateタイプのメッセージは除外 | 会話フォーマット時 |

### 計算ロジック

フォーマッター選択ロジック：
```ruby
def find_formatter
  formatter_name = "LlmFormatter::#{@record.class.name}LlmFormatter"
  formatter_class = formatter_name.safe_constantize
  raise FormatterNotFoundError unless formatter_class
  formatter_class
end
```

トークン制限ロジック（ConversationLlmFormatter）：
```ruby
def build_limited_messages(messages, config)
  selected = []
  character_count = 0
  messages.reorder(created_at: :desc).each do |message|
    formatted = format_message(message)
    break if character_count + formatted.length > config[:token_limit]
    selected.prepend(formatted)
    character_count += formatted.length
  end
  selected.join
end
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 会話フォーマット | conversations | SELECT | 会話情報取得 |
| 会話フォーマット | messages | SELECT | メッセージ履歴取得 |
| 連絡先フォーマット | contacts | SELECT | 連絡先情報取得 |
| 連絡先フォーマット | notes | SELECT | ノート取得 |
| 共通 | custom_attribute_definitions | SELECT | カスタム属性定義取得 |

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

本機能は参照のみで、データベースへの書き込みは行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | FormatterNotFoundError | 対応するフォーマッターが存在しない | 例外発生 |

### リトライ仕様

本機能にリトライ処理は実装されていない。

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

参照のみのためトランザクション制御は不要。

## パフォーマンス要件

- 大量のメッセージを持つ会話でもtoken_limitで制御可能
- メッセージは逆順（新しい順）から制限を適用

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

- プライベートノートはデフォルトで除外される
- LLMに送信されるデータにはPII（個人識別情報）が含まれる可能性あり
- 外部LLM APIへのデータ送信に注意

## 備考

- Strategy パターンで実装
- safe_constantizeで安全にクラス名を解決
- DefaultLlmFormatterを継承したフォーマッターを実装

---

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

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

### 推奨読解順序

#### Step 1: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | llm_text_formatter_service.rb | `app/services/llm_formatter/llm_text_formatter_service.rb` | フォーマッター選択とディスパッチ |

**読解のコツ**: Strategyパターンの実装。find_formatterでオブジェクト型からフォーマッターを動的に選択。

**主要処理フロー**:
1. **2-4行目**: initializeでrecordを受け取る
2. **6-8行目**: formatメソッドでフォーマッターを取得して実行
3. **12-18行目**: find_formatterでクラス名からフォーマッターを解決

#### Step 2: 基底クラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | default_llm_formatter.rb | `app/services/llm_formatter/default_llm_formatter.rb` | 共通インターフェース |

**読解のコツ**: シンプルな基底クラス。サブクラスでformatメソッドをオーバーライド。

#### Step 3: 会話フォーマッターを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | conversation_llm_formatter.rb | `app/services/llm_formatter/conversation_llm_formatter.rb` | 会話のテキスト変換 |

**主要処理フロー**:
1. **3-22行目**: formatメソッドでセクションを構築
2. **26-35行目**: build_messagesでメッセージ履歴構築
3. **49-65行目**: build_limited_messagesでトークン制限対応
4. **67-78行目**: format_messageで個別メッセージフォーマット
5. **80-85行目**: build_attributesでカスタム属性フォーマット

#### Step 4: 連絡先フォーマッターを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | contact_llm_formatter.rb | `app/services/llm_formatter/contact_llm_formatter.rb` | 連絡先のテキスト変換 |

**主要処理フロー**:
1. **3-14行目**: formatメソッドで属性とノートを含むテキスト生成
2. **18-20行目**: build_notesでノート一覧生成
3. **22-33行目**: build_attributesで属性一覧生成

#### Step 5: 記事フォーマッターを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | article_llm_formatter.rb | `app/services/llm_formatter/article_llm_formatter.rb` | 記事のテキスト変換 |

**主要処理フロー**:
1. **8-21行目**: formatメソッドでヒアドキュメントを使用したテキスト生成

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

```
LlmFormatter::LlmTextFormatterService#format(config)
    │
    ├─ find_formatter
    │      └─ "LlmFormatter::#{record.class.name}LlmFormatter".safe_constantize
    │
    └─ formatter.format(config)
           │
           ├─ ConversationLlmFormatter#format
           │      ├─ build_messages(config)
           │      │      ├─ build_all_messages
           │      │      └─ build_limited_messages (token_limit時)
           │      │             └─ format_message
           │      ├─ @record.contact.to_llm_text (include_contact_details時)
           │      └─ build_attributes
           │
           ├─ ContactLlmFormatter#format
           │      ├─ build_attributes
           │      └─ build_notes
           │
           └─ ArticleLlmFormatter#format
                  └─ ヒアドキュメントでテキスト生成
```

### データフロー図

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

record ────────────▶ LlmTextFormatterService ──▶ formatted_text
(Conversation/            │
 Contact/Article)    ┌────┴────┐
                     ▼         ▼
              find_formatter  config適用
                     │
              ┌──────┼──────┐
              ▼      ▼      ▼
         Conversation Contact Article
         Formatter    Formatter  Formatter
              │           │         │
              ▼           ▼         ▼
         メッセージ履歴  属性・ノート  メタデータ・
         カスタム属性              コンテンツ
              │           │         │
              └─────┬─────┴────┬────┘
                    ▼          ▼
              構造化テキスト出力
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| llm_text_formatter_service.rb | `app/services/llm_formatter/llm_text_formatter_service.rb` | サービス | フォーマッター選択・ディスパッチ |
| default_llm_formatter.rb | `app/services/llm_formatter/default_llm_formatter.rb` | 基底クラス | 共通インターフェース |
| conversation_llm_formatter.rb | `app/services/llm_formatter/conversation_llm_formatter.rb` | フォーマッター | 会話のテキスト変換 |
| contact_llm_formatter.rb | `app/services/llm_formatter/contact_llm_formatter.rb` | フォーマッター | 連絡先のテキスト変換 |
| article_llm_formatter.rb | `app/services/llm_formatter/article_llm_formatter.rb` | フォーマッター | 記事のテキスト変換 |
