Files
playchat/backend/app/core/config.py
colcaroliandClaude Opus 5 c6108a1b29 Route reminders by tool availability, not keyword matching
「7時にモーニングコールをお願い。」のような、REMINDER_KEYWORDS のどれとも
一致しない言い回しがリマインド機能を素通りし、ただの雑談として処理されて
いた(本番の api_logs で実例を確認)。「モーニングコール」「起こして」を
キーワードに追加しても、次の言い回しでまた同じことが起きるだけなので、
キーワード判定そのものをやめる。

discord_bot.on_message は雑談・天気のキーワード分岐はそのまま残し、それ以外の
全メンションで create_reminder / list_reminders / cancel_reminder の3ツールを
常に同席させる(reminders.build_tool_hint() が現在時刻の文脈を添える)。
使うかどうかはLLM自身の判断に委ねるため、キーワード一覧を事前に尽くす必要が
なくなる。本番のBedrock Nova 2 Liteで、実際に失敗していた文言が
create_reminder を正しく呼ぶことを確認済み。

reminders.handle_reminder_request() は resolve_reminder_tools(messages, ctx,
model) に一般化し、discord_bot 側で組み立てたmessages(人格プロンプト+天気
コンテキスト+ツール文脈)をそのまま受け取れるようにした。tool calling
非対応のモデルが設定されていた場合は、1ラウンド目の例外を検知してツール
なしの通常応答にフォールバックする。ツール呼び出しが一般会話の呼び出しに
統合された以上、それだけ別モデルを割り当てる余地がなくなったため
REMINDER_MODEL は廃止する。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 01:17:03 +09:00

102 lines
6.8 KiB
Python
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.
from dotenv import load_dotenv
import os
import secrets
load_dotenv()
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./playchat.db")
DEFAULT_MODEL: str = os.getenv("DEFAULT_MODEL", "gpt-4o")
MEMORY_SUMMARIZE_THRESHOLD: int = int(os.getenv("MEMORY_SUMMARIZE_THRESHOLD", "20"))
CORS_ORIGINS: list[str] = os.getenv("CORS_ORIGINS", "http://localhost:5173").split(",")
DEBUG: bool = os.getenv("DEBUG", "false").lower() in ("1", "true", "yes")
# アプリ側ロガーの出力レベル。未設定だと logging.lastResort(WARNING以上・書式なし)に
# 落ちて logger.info が全部消えるため、main.py で明示的に設定する。
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO").upper()
# GUI設定画面(/api/settings)を保護する簡易パスワード。未設定の場合、設定画面は常に403で拒否する。
SETTINGS_PASSWORD: str | None = os.getenv("SETTINGS_PASSWORD")
# サイト全体(設定画面以外の全API)を保護する簡易パスワード。未設定の場合はこれまで通り無認証で動く。
SITE_PASSWORD: str | None = os.getenv("SITE_PASSWORD")
# セッションCookieの署名に使う秘密鍵。未設定なら起動毎に使い捨てキーを生成する(バックエンド再起動でログインし直しになる)。
SESSION_SECRET_KEY: str = os.getenv("SESSION_SECRET_KEY") or secrets.token_hex(32)
# ローカルのhttp開発環境ではSecure Cookieが送信されないため、その場合のみfalseにする。公開環境では必ずtrueのままにすること。
SESSION_COOKIE_SECURE: bool = os.getenv("SESSION_COOKIE_SECURE", "true").lower() in ("1", "true", "yes")
SESSION_MAX_AGE_DAYS: int = int(os.getenv("SESSION_MAX_AGE_DAYS", "30"))
# メッセージへの自動タグ付けに使うモデル(未設定ならDEFAULT_MODELを使用)
TAGGING_MODEL: str | None = os.getenv("TAGGING_MODEL")
# Tavily Web検索
# GUI設定画面から実行時に変更できるため、実際の参照は search/websearch._api_key() が os.environ から行う。
TAVILY_API_KEY: str | None = os.getenv("TAVILY_API_KEY")
# 蓄積した知識をシステムプロンプトに注入する際の最大件数
KNOWLEDGE_CONTEXT_LIMIT: int = int(os.getenv("KNOWLEDGE_CONTEXT_LIMIT", "15"))
# Web検索結果を構造的な要約にまとめる際に使うモデル(未設定ならDEFAULT_MODELを使用)
# GUI設定画面から実行時に変更できるため、実際の参照は memory_kb/knowledge._summary_model() が os.environ から行う。
SEARCH_SUMMARY_MODEL: str | None = os.getenv("SEARCH_SUMMARY_MODEL")
# ローカルMarkdownナレッジベース(話題に応じて関連ファイルをシステムプロンプトに注入)
LOCAL_KB_DIR: str = os.getenv("LOCAL_KB_DIR", "knowledge_base")
LOCAL_KB_EMBEDDING_MODEL: str = os.getenv("LOCAL_KB_EMBEDDING_MODEL", "text-embedding-3-small")
LOCAL_KB_TOP_K: int = int(os.getenv("LOCAL_KB_TOP_K", "3"))
LOCAL_KB_MIN_SCORE: float = float(os.getenv("LOCAL_KB_MIN_SCORE", "0.75"))
# 共有ノート(ユーザーとAIが共同編集するノート)
# read_note ツールが1回に返す本文の最大文字数
NOTES_TOOL_CONTENT_LIMIT: int = int(os.getenv("NOTES_TOOL_CONTENT_LIMIT", "4000"))
# アップロードで一度に取り込めるページ数と、1ファイル(ZIP内は1エントリ)あたりの最大バイト数
NOTES_IMPORT_MAX_PAGES: int = int(os.getenv("NOTES_IMPORT_MAX_PAGES", "100"))
NOTES_IMPORT_MAX_BYTES: int = int(os.getenv("NOTES_IMPORT_MAX_BYTES", "2000000"))
# セッションに紐付けたページ1枚あたり、システムプロンプトに載せる本文の最大文字数
SESSION_NOTES_CONTENT_LIMIT: int = int(os.getenv("SESSION_NOTES_CONTENT_LIMIT", "8000"))
# read_knowledge ツールが1回に返す蓄積知識の本文の最大文字数
KNOWLEDGE_TOOL_CONTENT_LIMIT: int = int(os.getenv("KNOWLEDGE_TOOL_CONTENT_LIMIT", "4000"))
# Amazon Bedrock
# 以下はすべて LiteLLM(boto3) が os.environ から直接読み取るため、.env に書くだけで自動的に有効になる。
# IAM経由(推奨): AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY を設定する。
# API key方式(旧): AWS_BEARER_TOKEN_BEDROCK のみを設定する。
# 両方設定されている場合はIAM認証(Access Key)が優先される。
AWS_ACCESS_KEY_ID: str | None = os.getenv("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY: str | None = os.getenv("AWS_SECRET_ACCESS_KEY")
AWS_SESSION_TOKEN: str | None = os.getenv("AWS_SESSION_TOKEN") # 一時的なIAMクレデンシャルを使う場合のみ
AWS_BEARER_TOKEN_BEDROCK: str | None = os.getenv("AWS_BEARER_TOKEN_BEDROCK")
AWS_REGION_NAME: str = os.getenv("AWS_REGION_NAME", "us-east-1")
# 天気予報 → Discord 通知
DISCORD_WEBHOOK_URL: str | None = os.getenv("DISCORD_WEBHOOK_URL")
WEATHER_AREA_CODE: str = os.getenv("WEATHER_AREA_CODE", "120000") # 千葉県
WEATHER_AREA_KEYWORD: str = os.getenv("WEATHER_AREA_KEYWORD", "北西部") # 市川市は千葉県北西部
WEATHER_NOTIFY_HOUR: int = int(os.getenv("WEATHER_NOTIFY_HOUR", "22"))
WEATHER_NOTIFY_MINUTE: int = int(os.getenv("WEATHER_NOTIFY_MINUTE", "0"))
# 通知メッセージをLLMで生成する場合に使うモデル(未設定ならDEFAULT_MODELを使用)
WEATHER_NOTIFY_MODEL: str | None = os.getenv("WEATHER_NOTIFY_MODEL")
# Discord Bot(メンションへの自動返信用)
DISCORD_BOT_TOKEN: str | None = os.getenv("DISCORD_BOT_TOKEN")
DISCORD_BOT_SYSTEM_PROMPT: str = os.getenv(
"DISCORD_BOT_SYSTEM_PROMPT",
"あなたは「お天気アプリ」という名前のDiscord Botです。"
"天気や洗濯指数の質問には親しみやすく簡潔に答え、"
"雑談にも気さくに応じてください。絵文字を適度に使って親しみやすい口調で話します。",
)
# どうでもいいTipsを定期的にDiscordへ投稿する機能
USELESS_TIPS_ENABLED: bool = os.getenv("USELESS_TIPS_ENABLED", "false").lower() in ("1", "true", "yes")
USELESS_TIPS_INTERVAL_HOURS: int = int(os.getenv("USELESS_TIPS_INTERVAL_HOURS", "3"))
USELESS_TIPS_MODEL: str | None = os.getenv("USELESS_TIPS_MODEL")
# 未設定なら天気通知と同じ DISCORD_WEBHOOK_URL を使う
DISCORD_WEBHOOK_URL_TIPS: str | None = os.getenv("DISCORD_WEBHOOK_URL_TIPS") or os.getenv("DISCORD_WEBHOOK_URL")
# Discord Bot のリマインド機能(メンションで登録し、時刻になったら同じチャンネルへ通知する)
# 期限切れのリマインドを探しにいく間隔。短いほど通知の遅れが減るがDBを引く回数は増える。
REMINDER_CHECK_INTERVAL_SECONDS: int = int(os.getenv("REMINDER_CHECK_INTERVAL_SECONDS", "30"))
# これより先の日時を指定されたら登録を拒否する(LLMの年の取り違えを弾くため)
REMINDER_MAX_AHEAD_DAYS: int = int(os.getenv("REMINDER_MAX_AHEAD_DAYS", "365"))
# list_reminders が一度に返す件数
REMINDER_LIST_LIMIT: int = int(os.getenv("REMINDER_LIST_LIMIT", "20"))