Files
playchat/docs/discord-bot.md
colcaroli 347230e3c8 Reorganize core into subpackages, add local KB, ruff/pyright tooling
- Split backend/app/core into weather/, discord/, memory_kb/, search/
  subpackages by responsibility, keeping config.py and llm.py as the
  shared foundation; updated all import sites accordingly.
- Add local Markdown knowledge base support (local_knowledge.py,
  knowledge_base/fgo.md, LOCAL_KB_* settings) that embeds and injects
  relevant docs into the system prompt by topic similarity.
- Add ruff and pyright as backend dev dependencies with basic config
  in pyproject.toml for linting and type checking.
- Document local machine quirks in CLAUDE.md (Postgres on port 8080,
  SSLKEYLOGFILE breaking litellm/pyright via OPENSSL_Uplink crashes).
- Add backend/run_dev.ps1 dev launcher and docs/discord-bot.md.
2026-06-21 11:44:11 +09:00

112 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 雑談Bot(Discord連携)仕様
`docs/architecture.md` のチャットアプリ本体に対する拡張機能群。Discord上での雑談応答・天気案内・定期的などうでもいい豆知識配信、およびそれらを支えるタグ付け/ワードプール/知識ベース機構をまとめる。
## 全体像
```
Discord ⇄ discord_bot.py ⇄ chat.py (REST/SSE) ⇄ LiteLLM
│ │
├─ scheduler.py ├─ tagging.py(メッセージへタグ付け)
│ (定期実行) ├─ word_memory.py(話題語彙プール)
│ └─ knowledge.py / websearch.py(Web検索→知識ベース)
└─ JMA天気API
```
## 1. Discord bot(`backend/app/core/discord_bot.py`)
- Bot宛てメンション(`on_message`)に応答。メンション以外は無視
- **天気キーワード**検出時: 気象庁(JMA) APIと連携し天気・洗濯指数を案内
- **雑談キーワード**検出時:
1. ユーザー発言をチャット応答へリダイレクト(`_generate_useless_talk_redirect`)
2. `tagging.generate_tags()` で発言からタグを3つ生成
3. `tagging.find_related_tag()` で関連トピックを1つ選定
4. `scheduler.send_useless_tip(topic_override=related_tag)` を即時実行し、その話題に絡めた豆知識を配信
- 受信メッセージは `word_memory.record_message()` で語彙プールにも記録される
## 2. スケジューラ(`backend/app/core/scheduler.py`)
APSchedulerによる定期ジョブ2種:
| ジョブ | 頻度 | 内容 |
|---|---|---|
| 洗濯天気通知 | 毎日22時 | JMA天気を基に翌日の洗濯指数を配信 |
| どうでもいい豆知識 (`send_useless_tip`) | `USELESS_TIPS_INTERVAL_HOURS`時間ごと(既定3h、`USELESS_TIPS_ENABLED`で有効化) | ランダム/指定トピックでLLMに60文字以内の「どうでもいい豆知識」を生成させ配信 |
`send_useless_tip(topic_override=None)`:
- AM3時台は配信スキップ(静音時間)
- `topic_word = topic_override or word_memory.pick_random_word()` でトピックを決定
- `topic_word`が決まれば、プロンプトに「〇〇という話題にこじつけてネタを絡めて」という指示を追加
- 生成文を `💡 今日のお役立ち情報` として Discord Webhook へ送信
## 3. メッセージタグ付け(`backend/app/core/tagging.py`)
- `generate_tags(content)`: LLMでメッセージ本文から最大3タグを生成(JSON出力を期待し、崩れていればカンマ区切りにフォールバック)
- `tag_message(db, message)`: 1メッセージにタグを生成し `MessageTag` テーブルへ保存。**ロール非依存**で、ユーザー発言・アシスタント応答の両方に使われる
- `tag_message_by_id(message_id)`: バックグラウンドタスク用。`chat.py` のSSE応答処理内で、ユーザーメッセージ保存後・アシスタント応答保存後の両方で `asyncio.create_task()` 経由で呼ばれる(応答ストリームをブロックしない)
- `tag_memory(db, memory)`: 要約(`Memory`)にもタグ付け可能(`MemoryTag`テーブル)
- `backfill_tags(db, limit=None)`: 未タグのuser/assistantメッセージを一括タグ付けする保守用関数
### `find_related_tag(seed_tags)` — 関連トピック選定とフォールバックチェーン
雑談リダイレクト時に呼ばれ、最終的に「豆知識のネタ」となる文字列を返す。
1. `seed_tags`が空 → `None` を返す(呼び出し元の`send_useless_tip`側で`word_memory.pick_random_word()`にフォールバック)
2. `seed_tags`と同じメッセージ/要約に付いている他のタグ(=関連タグ)を探す。見つかればその中からランダムに1つ選択。見つからなければ`seed_tags`自体からランダムに1つ選択 → `chosen_tag`
3. `_pick_word_for_tag(db, chosen_tag)` で、`chosen_tag`が付いた `Message.content` / `Memory.summary` の本文から名詞を抽出(`word_memory.extract_words`)し、現在のワードプールと重なる単語を優先選択(`word_memory.pick_word_near`)。重なりが無ければ抽出語からランダム選択
4. 本文から単語が1つも抽出できなければ `chosen_tag`(タグ文字列そのもの)を返す
→ 「関連タグが見つかった場合でも、ただタグ文字列を使うのではなく、その話題の実際の発言内容とワードプールの今ホットな語彙を突き合わせて、より自然な単語をネタに選ぶ」という多段フォールバック構成。
## 4. ワードプール(`backend/app/core/word_memory.py`)
- Janomeで形態素解析し、名詞(一般・固有名詞・サ変接続)のみを抽出。2文字未満・ストップワードは除外
- Markdownの記法(コードブロック・見出し・リンク等)は事前に除去
- `_recent_words: deque(maxlen=200)` がインメモリの揮発性プール(DB非永続、再起動でリセット。起動時は `word_memory.seed_from_history()` で直近メッセージから再構築)
- 主な関数:
- `record_message(content)`: 本文を解析しプールに追記
- `extract_words(content)`: プールに追記せず単語リストのみ返す(タグ紐づけ用本文の解析に利用)
- `pick_random_word()`: プールからランダムに1語
- `pick_word_near(words)`: 渡した単語群とプールの積集合からランダムに1語(無ければNone)
- `get_word_pool_with_counts()`: 出現回数つきでプール全体を返す(`WordPool.vue`表示用)
- `chat.py`のSSE処理・`discord_bot.py`のメッセージ受信、双方で`record_message()`が呼ばれ語彙が蓄積される
## 5. 知識ベース/Web検索(`websearch.py` / `knowledge.py`)
- `websearch.py`: Tavily APIで検索を実行
- `knowledge.py`: 検索結果(クエリ・要約・出典URL)を `KnowledgeEntry` / `KnowledgeSource` テーブルへ永続化し、以後の会話コンテキストへ再注入できる知識ベースとして機能
- `chat.py` の `POST /chat/stream` 内で、LLMが追加情報を必要とした場合(`llm.resolve_web_search`)に検索が走り、結果は `sources` イベントとしてフロントへ、要約は `knowledge.save_knowledge()` でDBへ保存される
## 6. DBスキーマ(拡張分)
`backend/app/db/models.py` で追加されたテーブル:
| テーブル | 主なカラム | 用途 |
|---|---|---|
| `message_tags` | id, message_id (FK), tag, created_at | メッセージ単位のタグ |
| `memory_tags` | id, memory_id (FK), tag, created_at | 要約(長期メモリ)単位のタグ |
| `knowledge_entries` | id, query, summary, created_at | Web検索結果の要約 |
| `knowledge_sources` | id, entry_id (FK), title, url | 知識エントリの出典 |
## 7. 環境変数(追加分、`backend/.env.example`)
| 変数 | 説明 |
|---|---|
| `USELESS_TIPS_ENABLED` | どうでもいい豆知識ジョブの有効化フラグ |
| `USELESS_TIPS_INTERVAL_HOURS` | 豆知識配信の間隔(時間、既定3) |
| `USELESS_TIPS_MODEL` | 豆知識生成に使うLLMモデル(未指定時は`DEFAULT_MODEL`) |
| `TAGGING_MODEL` | タグ生成に使うLLMモデル |
| Discord Bot Token / Webhook URL 系 | Discord連携用 |
| 天気API関連 | JMA連携用 |
| Tavily APIキー | Web検索用 |
| Bedrock関連 | LiteLLM経由でのAWS Bedrock利用設定 |
## 8. フロントエンド: WordPool(`frontend/src/components/WordPool.vue`)
- `word_memory.get_word_pool_with_counts()` を参照し、現在の話題語彙をタグクラウド的に表示
- `App.vue` のサイドバーに `SessionList` / `MemoryMonitor` と並んで配置
---
最終更新: 2026-06-20(タグ付けの双方向化・`find_related_tag`へのword_memory連携を反映、コミット `234ebba`)