全文検索サーバーFessのAIモード(RAGチャット機能)について、大規模なリファクタリングを行いました。LLMプロバイダーの実装をコアから切り出してfess-llm-*プラグインとして独立させたほか、セキュリティ強化、設定の柔軟性向上、信頼性改善など多くの改善を加えています。
LLMクライアントのプラグイン化
これまでGemini、Ollama、OpenAIの各LLMクライアントはFessコアに組み込まれていましたが、プラグインアーキテクチャに切り出しました。
変更内容
PluginHelperにLLM("fess-llm")プラグインタイプを追加AbstractLlmClientにregister()メソッドを追加し、LlmClientManagerへの自動登録を実現LlmClientManagerのclientListをCopyOnWriteArrayListに変更し、スレッドセーフに- コアから
GeminiLlmClient、OllamaLlmClient、OpenAiLlmClientとそのテストクラスを削除
これにより、コアから約6,600行が削減され、LLMプロバイダーの追加・更新がFess本体のリリースに依存しなくなりました。新しいLLMプロバイダーを追加する場合は、fess-llm-*プラグインとして実装するだけで利用できます。
パラメータ設定の統一
各LLMプロバイダーで散在していたパラメータ設定を、統一的な設定方式に変更しました。
getTemperature()、getMaxTokens()等の個別メソッドを廃止し、getConfigPrefix()で設定プレフィックスを返す方式に{configPrefix}.{promptType}.{paramName}のパターンでプロンプトタイプごとにtemperature、max.tokens、thinking.budgetを設定可能にLlmChatRequestにextraParamsフィールドを追加し、プロバイダー固有のパラメータ(OpenAIのreasoning_effort等)にも対応
たとえば、意図検出には低いtemperatureを、回答生成には高いトークン上限を設定するといった細かいチューニングが、コード変更なしで可能になりました。
セキュリティ強化
RAGチャットパイプラインに対して、複数のセキュリティ対策を追加しました。
- メッセージ長制限:
rag.chat.message.max.length(デフォルト4000文字)で入力長を制限 - プロンプトインジェクション対策: ユーザー入力を
<user_input>デリミタで囲み、タグエスケープを適用 - クエリバリデーション: LLMが生成したクエリに対して、1000文字超や危険なLuceneパターン(
*:*)を拒否 - セッションオーナーシップ検証: セッションクリア時にuserId所有権を検証し、他ユーザーのセッション操作を防止
エラーハンドリングの改善
LlmExceptionに構造化されたエラーコードを導入しました。
rate_limit、auth_error、service_unavailable、unknown、model_not_found、timeoutの6種類- HTTPステータスコードからエラーコードへのマッピング(
resolveErrorCode()) - チャットUIでエラーコードに応じたローカライズ済みメッセージを表示(21言語対応)
- 推論モデル(o1、o3、DeepSeek R1等)でトークンを使い切った場合のフォールバック処理を追加
並行制御
LLMリクエストにセマフォベースの並行制御を追加しました。
rag.llm.{provider}.max.concurrent.requests(デフォルト5)で同時リクエスト数を制限rag.llm.{provider}.concurrency.wait.timeout(デフォルト30秒)でタイムアウトを設定- 上限超過時は
ERROR_RATE_LIMITを返却
コンテキスト管理の改善
LLMに渡すコンテキストの管理を改善しました。
- 履歴バジェット管理:
addHistoryWithBudget()で会話履歴の総文字数をrag.chat.total.context.max.chars以内に制御 - アシスタントメッセージ設定:
rag.chat.history.assistant.contentで履歴中のアシスタントメッセージの形式を選択可能(full、source_titles、source_titles_and_urls、truncated、noneの5モード) - ハイライト設定: RAGチャット用のハイライトでHTMLタグを除去し、フラグメントサイズと数を設定可能に
- プロンプトタイプ別コンテキストサイズ: プロンプトの種類ごとにデフォルトのコンテキストサイズを設定可能に
管理画面の改善
管理画面の一般設定にRAG LLMプロバイダーの選択UIを追加しました。
- 登録済みのLLMクライアントからプロバイダーを選択可能
rag.llm.name設定値の検証- RAG機能が有効かつLLMクライアントが登録されている場合のみ表示
その他の改善
- 国際化: チャットのウェルカムタイトルを全16言語で簡潔なタグラインに更新(例: JA「聞けば、見つかる。」、EN「Ask and Discover.」)
- レート制限設定の削除: チャットのレート制限設定(
rag.chat.rate.limit.*)をFessConfigから削除(並行制御に移行) - 履歴付き意図検出: 直近の会話履歴を考慮した意図検出で、コンテキストに基づいた検索クエリを生成
- ドキュメント順序の保持: 検索スコア順でドキュメントの順序を維持
まとめ
今回のリファクタリングにより、FessのAIモードはプラグインベースの拡張可能なアーキテクチャになりました。LLMプロバイダーの追加・更新がFess本体から独立し、セキュリティや信頼性も大幅に向上しています。新しいLLMプロバイダーに対応する場合は、fess-llm-*プラグインを作成してインストールするだけで利用可能です。
関連リンク
- PR #3043: feat(chat): add AI chat mode with busy error page and UI improvements
- PR #3048: refactor(llm): extract provider-specific clients to plugin architecture
- PR #3049: refactor(llm): unify per-prompt-type parameter config
- PR #3050: feat(llm): add structured error codes to LlmException
- PR #3051: i18n(chat): update chat_welcome_title to concise taglines
- PR #3052: feat(chat): add configurable assistant message content for conversation history
- PR #3053: refactor(chat): remove rate limit config from FessConfig
- PR #3054: feat(admin/general): add RAG LLM provider selection to general settings
- PR #3055: feat(rag): add custom highlight tags and improve evaluation content processing
- PR #3057: refactor(llm): extract constant and add resolveErrorCode
- PR #3058: fix(chat): add security hardening and reliability improvements
- PR #3059: fix(chat): simplify DANGEROUS_QUERY_PATTERN
- PR #3060: fix(chat): improve security, concurrency, and context handling
- PR #3061: fix(llm): handle empty content with length finish reason for reasoning models