EvoSpikeNet RAG システム - 日本語検索最適化仕様書(詳細版)
Version: 3.1
Date: 2026-05-23
Status: Implemented with Operational Hardening
Document ID: RAG-JP-SPEC-V3.1-001
Changes in v3.1: API 契約を現行実装へ同期。拡張クエリ間の RRF 加算、memory_boost を含む最終スコア再ソート、検索→フィードバックのテスト階層(Unit / Integration / System / E2E)を反映。
目次
- システム概要
- 要件定義
- 技術仕様詳細
- 3.5 EvoSpikeNet Core 記憶システム統合
- API 仕様
- 4.3 記憶統合 API フロー
- 検索アルゴリズム
- 5.3 記憶強化ランキング
- 評価・テスト
- デプロイメント
- トラブルシューティング
- 付録
1. システム概要
1.1 目的
EvoSpikeNet-Core RAG システムの日本語検索精度を向上させるため、 以下の課題に対応した統合ソリューション:
- 表記揺れ: 「盆休み」「お盆休み」の相互検索
- 固有名詞: プロジェクトID「EV-2024-001」の正確検索
- 同義語: 「会議」「ミーティング」「カンファレンス」の統一
1.2 スコープ
| 対象 | 含む | 含まない |
|---|---|---|
| 言語 | 日本語(メイン)、英語 | 中国語、韓国語 |
| 検索 | ハイブリッド(BM25 + Vector) | Full-text only |
| 機能 | RAG Pipeline | Generative Model |
| 規模 | 1M+ ドキュメント | Real-time Streaming |
1.3 アーキテクチャ概観 — Memory-Augmented RAG
v3.0 より EvoSpikeNet Core SDK の記憶層(青色ノード)と統合した 記憶強化型 RAG に移行。
flowchart TD
Q["User Query (Japanese)"]
Q --> LD["言語検出\nLanguageDetector"]
Q --> NER["固有表現抽出\nEntityRecognizer"]
LD --> QN["クエリ正規化 & 拡張\nSudachiTokenizer + QueryExpander"]
NER --> QN
NER --> SM_Q["SemanticMemory\nretrieve_semantic_knowledge()"]
SM_Q -->|"概念グラフ Boost 強化"| ENH["エンティティ Boost\n動的強化"]
QN --> KW["Elasticsearch BM25\nSudachi アナライザ"]
QN --> VEC["Milvus ベクター検索\nL2距離 384-dim"]
ENH --> KW
ENH --> VEC
KW -->|"BM25 スコア"| RRF["RRF 融合\nk=60 + Entity Boost"]
VEC -->|"L2 距離"| RRF
RRF --> EP_ENR["EpisodicMemory\nintegrate_episodic_semantic()"]
EP_ENR -->|"文脈強化"| TOPK["Top-K ドキュメント"]
TOPK --> EP_RET["EpisodicMemory\nretrieve_memories()"]
EP_RET -->|"過去セッション Rerank"| GEN["レスポンス生成"]
GEN --> FB{"{ユーザー評価\nrating 1-5"}
FB -->|"reward = rating/5"| STORE["EpisodicMemory\nstore_experience()"]
STORE --> LTM["LongTermMemoryModule\nSNN 重要度ゲーティング"]
LTM -->|"低重要度"| FORGET["ForgettingController\n自動削除"]
style SM_Q fill:#e3f2fd,stroke:#1976D2
style EP_ENR fill:#e3f2fd,stroke:#1976D2
style EP_RET fill:#e3f2fd,stroke:#1976D2
style STORE fill:#e3f2fd,stroke:#1976D2
style LTM fill:#e8eaf6,stroke:#3F51B5
style FORGET fill:#fce4ec,stroke:#C62828
凡例:
- 青色ノード (#e3f2fd): evospikenet.episodic_memory モジュール
- 紫色ノード (#e8eaf6): evospikenet.long_term_memory モジュール
- 赤色ノード (#fce4ec): evospikenet.forgetting_controller モジュール
1.4 2026-05-23 実装状況と運用メモ
この仕様のコア範囲は EvoSpikeNet-Core/evospikenet/api_modules/rag_v2_api.py と EvoSpikeNet-Core/evospikenet/rag_memory_integrator.py を中心に実装済みです。以下は現行実装に基づく運用メモです。
- API 実装:
POST /api/v2/rag/searchPOST /api/v2/rag/feedbackGET /api/v2/rag/preprocessing/health
- 前処理の責務分離:
SudachiTokenizerEntityRecognizerQueryExpander
- 本番系ポリシー:
production/staging判定時は依存未解決を fail-closed とし、疑似埋め込みやプレースホルダ応答へフォールバックしない。
- 埋め込み方針:
SentenceTransformer実エンコーダを必須とし、埋め込み次元は 384 に統一。
- QueryExpander:
rule|llm|hybridをサポート。- LLM 出力は
{"expansions": [...]}の JSON のみ受理。
- 品質ガード:
- 多様性と冗長性を評価し、低品質時は
ruleバックエンドへ自動フォールバック。
- 多様性と冗長性を評価し、低品質時は
- 可観測性:
debug_info.preprocessingに品質メトリクス、ガード統計、履歴、query_hashサマリを出力。- 起動時ウォームアップとヘルス API により前処理コンポーネントの稼働可否を確認。
- 主な運用設定:
RAG_V2_NER_*RAG_V2_PREPROCESSING_*RAG_V2_SUDACHI_*RAG_V2_QUERY_EXPANDER_*
- 今後の拡張:
- 文脈依存 RRF
- NER 微調整
- フィードバック自動最適化
- 多言語展開
- 詳細は RAG_REMAINING_FUNCTIONALITY.md を参照。
- 2026-05-23 検証反映:
- 拡張クエリごとの検索結果は RRF スコアを加算して統合する。
memory_boostは最終スコアへ加算され、最終順位はfinal_scoreで再ソートする。POST /api/v2/rag/feedbackはsession_idを必須とし、保存後にmemory_idとimportanceを返す。
2. 要件定義
2.1 機能要件
FR-1: ハイブリッド検索
要件: BM25 キーワード検索とベクター検索を統合
検索時間: < 500ms @ top-10
精度(NDCG@10): > 0.75 at production launch
処理スループット: 100 queries/sec
実装: RRF (k=60) によるスコア統合
FR-2: 日本語特化処理
要件: 日本語の表記揺れ・同義語に対応
Variation Coverage: > 80%
Entity Recognition Recall: > 80%
表記ゆれ正規化: < 5ms overhead
実装: - Sudachi によるトークナイズ - NER による固有表現抽出 - Query Expansion による同義語拡張
FR-3: 評価フレームワーク
要件: 検索精度を定期的に評価
テストケース数: >= 50
評価メトリクス: MRR, NDCG, Recall, Precision
自動評価スクリプト: 実装済み
2.2 非機能要件
NFR-1: パフォーマンス
| メトリクス | 要件 | 測定方法 |
|---|---|---|
| P95 レイテンシ | < 300ms | query latency monitor |
| P99 レイテンシ | < 800ms | query latency monitor |
| Throughput | >= 100 q/s | load testing |
| インデックス再構築 | < 4h | full rebuild time |
NFR-2: 可用性
Availability: >= 99.5%
MTTR (Mean Time To Recovery): < 5 min
Backup frequency: Daily
NFR-3: 拡張性
Document Scaling: Up to 10M docs
Language Support: Extensible to 3+ languages
Customization: ルール・辞書の外部管理
NFR-4: セキュリティ
Access Control: Role-based (RBAC)
Data Encryption: in-transit (TLS) and at-rest
Audit Logging: All query access
3. 技術仕様詳細
3.1 コンポーネント仕様
3.1.1 言語検出モジュール
Module: backend.text.LanguageDetector
class LanguageDetector:
def __init__(self):
self.hiragana_range = ('\u3040', '\u309f')
self.katakana_range = ('\u30a0', '\u30ff')
self.kanji_range = ('\u4e00', '\u9fff')
def detect_language(self, text: str) -> str:
"""
入力テキストから言語を判定
Args:
text: 入力テキスト
Returns:
"ja" | "en" | "mixed"
Algorithm:
1. 日本語文字の割合を計算
2. 日本語文字 > 10% の場合は "ja"
3. ASCII文字 > 90% の場合は "en"
4. その他は "mixed"
"""
japanese_count = sum(
1 for c in text
if (self.hiragana_range[0] <= c <= self.hiragana_range[1] or
self.katakana_range[0] <= c <= self.katakana_range[1] or
self.kanji_range[0] <= c <= self.kanji_range[1])
)
ja_ratio = japanese_count / len(text) if text else 0
if ja_ratio > 0.1:
return "ja"
elif ja_ratio == 0:
return "en"
else:
return "mixed"
@property
def precision(self):
"""言語検出精度: 98% @ standard test set"""
pass
性能要件: - 実行時間: < 1ms - 精度: 98%
3.1.2 トークナイズ・正規化モジュール
Module: backend.elasticsearch_client.SudachiTokenizer
class SudachiTokenizer:
"""Sudachi による日本語トークナイズ"""
def __init__(self):
from sudachipy.dictionary import Dictionary
from sudachipy.tokenizer import Tokenizer
self.dictionary = Dictionary.open()
self.tokenizer = Tokenizer(self.dictionary)
def tokenize(self, text: str, mode: str = "search") -> List[str]:
"""
テキストをトークンに分割
Args:
text: 入力テキスト
mode: "search" (細粒度) | "normal" (標準)
Returns:
トークンリスト
Examples:
>>> tokenize("お盆休み")
["盆", "休み"] # 接頭辞「お」は削除
>>> tokenize("サーバーの障害")
["サーバ", "の", "障害"] # 長音符が正規化
"""
morphemes = self.tokenizer.tokenize(text, sudachipy.tokenizer.Tokenizer.SudachiMode.SEARCH)
tokens = []
for m in morphemes:
# 基本形を取得(活用を正規化)
base_form = m.dictionary_form()
tokens.append(base_form)
return tokens
def normalize_query(self, query: str) -> str:
"""
クエリを正規化
処理:
1. Unicode NFC → NFKC
2. 長音符を統一(ー削除 or 短縮)
3. 半角・全角を統一
"""
import unicodedata
# NFKC 正規化
normalized = unicodedata.normalize("NFKC", query)
# 長音符の削除(設定可能)
normalized = normalized.replace("ー", "")
return normalized
性能要件: - Tokenize 実行時間: < 5ms/query - Normalization overhead: < 2ms/query
3.1.3 Named Entity Recognition (NER)
Module: backend.rag_milvus.EntityRecognizer
class EntityRecognizer:
"""固有表現認識(NER)"""
ENTITY_TYPES = {
"PROJECT_ID": {"boost": 5.0, "pattern": r"^[A-Z]{2,3}-\d{4}-\d{3,4}$"},
"PERSON": {"boost": 3.0, "pattern": None},
"PRODUCT": {"boost": 4.0, "pattern": r"^[A-Za-z0-9 ]+(Pro|Core|Basic)$"},
"DATE": {"boost": 2.0, "pattern": r"^\d{4}-\d{2}-\d{2}$"},
"ORG": {"boost": 2.5, "pattern": None}
}
def __init__(self, model_name: str = "tner/roberta-large-japanese-char-luw-ner"):
from transformers import pipeline
self.nlp = pipeline("ner", model=model_name)
def extract(self, text: str) -> Dict[str, List[Dict]]:
"""
テキストから固有表現を抽出
Returns:
{
"PROJECT_ID": [{"text": "EV-2024-001", "start": 3, "end": 13}],
"PERSON": [...]
}
"""
entities = self.nlp(text)
result = {}
for ent in entities:
label = ent["entity_group"]
if label not in result:
result[label] = []
result[label].append({
"text": ent["word"],
"start": ent["start"],
"end": ent["end"],
"score": ent["score"]
})
return result
def compute_boost_factor(self, entity_type: str) -> float:
"""固有表現タイプから boost 係数を計算"""
return self.ENTITY_TYPES.get(entity_type, {}).get("boost", 1.0)
性能要件: - NER 実行時間: < 100ms/query - Entity Recognition Recall: >= 85%
3.1.4 Query Expansion
Module: backend.query_expander.QueryExpander
class QueryExpander:
"""クエリ拡張(同義語展開)"""
def __init__(self, llm_backend, terms_dict: Dict[str, List[str]]):
self.backend = llm_backend
self.terms_dict = terms_dict
self.expansion_cache = {} # LRU cache
def expand(self, query: str, max_expansions: int = 3) -> List[str]:
"""
クエリを拡張(同義語を追加)
Returns:
[original_query, synonym_1, synonym_2, ...]
Strategy:
1. キャッシュをチェック
2. 辞書ベースの拡張
3. LLM ベースの拡張(遅い場合はスキップ)
"""
if query in self.expansion_cache:
return self.expansion_cache[query]
expansions = [query]
# 辞書ベース
for word in query.split():
if word in self.terms_dict:
for syn in self.terms_dict[word][:max_expansions-1]:
new_q = query.replace(word, syn)
if new_q not in expansions:
expansions.append(new_q)
# LLM ベース(オプション)
if len(expansions) < max_expansions + 1:
try:
llm_expansions = self._llm_expand(query, max_expansions)
expansions.extend(llm_expansions)
except TimeoutError:
pass # Skip on timeout
# キャッシュに保存
self.expansion_cache[query] = expansions[:max_expansions+1]
return self.expansion_cache[query]
def _llm_expand(self, query: str, max_count: int) -> List[str]:
"""LLM による拡張"""
prompt = f"""
クエリ「{query}」の同義表現を {max_count} つ提示してください。
JSON 形式: {{"expansions": ["表現1", "表現2", ...]}}
"""
response = self.backend.generate(prompt, context="")
import json
return json.loads(response)["expansions"]
性能要件: - 拡張処理時間: < 50ms (キャッシュ済みは < 1ms) - キャッシュヒット率: 60-70%
3.2 検索パイプライン詳細
3.2.1 入力処理
def preprocess_query(query: str) -> Dict[str, Any]:
"""
クエリの前処理
"""
# 1. 言語検出
lang = language_detector.detect(query)
# 2. クエリ正規化
normalized = sudachi_tokenizer.normalize_query(query)
# 3. 固有表現抽出
entities = entity_recognizer.extract(query)
# 4. クエリ拡張(オプション)
expansions = query_expander.expand(query) if ENABLE_EXPANSION else [query]
return {
"original": query,
"normalized": normalized,
"language": lang,
"entities": entities,
"expansions": expansions
}
3.2.2 キーワード検索(Elasticsearch)
Elasticsearch クエリ:
{
"query": {
"bool": {
"must": [
{
"match": {
"content": {
"query": "クエリテキスト",
"analyzer": "ja_sudachi_normalized",
"operator": "and",
"fuzziness": "AUTO"
}
}
}
],
"should": [
{
"term": {
"entity_project": {
"value": "EV-2024-001",
"boost": 5.0
}
}
},
{
"term": {
"entity_person": {
"value": "田中太郎",
"boost": 3.0
}
}
}
],
"filter": [
{
"range": {
"created_at": {
"gte": "now-6M"
}
}
}
]
}
},
"size": 10,
"track_scores": true
}
スコアリング:
BM25(d, q) = IDF(qi) * (f(qi, d) * (k1 + 1)) / (f(qi, d) + k1 * (1 - b + b * |d| / avgdl))
パラメータ:
k1 = 1.2 (term frequency saturation)
b = 0.75 (document length normalization)
3.2.3 ベクター検索(Milvus)
検索パラメータ:
search_params = {
"metric_type": "L2", # L2距離(コサイン類似度より安定)
"params": {"nprobe": 10} # プローブ数(精度と速度のトレード)
}
results = collection.search(
data=[query_embedding], # 384次元
anns_field="embedding",
param=search_params,
limit=top_k * 2, # オーバーサンプリング
output_fields=["id", "text", "source"]
)
埋め込みモデル:
Model: paraphrase-multilingual-MiniLM-L12-v2
Dim: 384
Language: 50+ languages
Performance:
- Multilingual Understanding
- Fast inference (< 20ms @ batch size 8)
3.2.4 RRF + Entity Boosting
def reciprocal_rank_fusion_with_entity_boost(
keyword_results: List[Dict],
vector_results: List[Dict],
entities: Dict[str, List[str]],
k: int = 60
) -> List[str]:
"""
RRF + 固有表現ブースト
"""
fused_scores = {}
# RRF スコアを計算
for i, result in enumerate(keyword_results):
doc_id = result["id"]
rrf_score = 1.0 / (k + i + 1)
fused_scores[doc_id] = fused_scores.get(doc_id, 0) + rrf_score
for i, result in enumerate(vector_results):
doc_id = result["id"]
rrf_score = 1.0 / (k + i + 1)
fused_scores[doc_id] = fused_scores.get(doc_id, 0) + rrf_score
# 固有表現マッチによるブースト
for doc_id, score in fused_scores.items():
entity_boost = 1.0
# ドキュメントの固有表現を確認
doc_entities = extract_entities_from_doc(doc_id)
for query_entity, entity_type in entities.items():
if query_entity in doc_entities:
boost_factor = ENTITY_BOOST_MAP.get(entity_type, 1.0)
entity_boost *= boost_factor
fused_scores[doc_id] = score * entity_boost
# スコアでソート
ranked = sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)
return [doc_id for doc_id, _ in ranked]
Entity Boost Factors:
| Entity Type | Boost | 理由 |
|---|---|---|
| PROJECT_ID | 5.0 | 完全一致で最優先 |
| PRODUCT | 4.0 | 製品名は重要 |
| PERSON | 3.0 | 人名は中程度 |
| ORG | 2.5 | 部門は参考情報 |
| DATE | 2.0 | 日付は補足情報 |
3.5 EvoSpikeNet Core 記憶システム統合
RAG 2.0 パイプラインは EvoSpikeNet-Core SDK の記憶層と統合し、Memory-Augmented RAG を実現します。
クエリのたびに過去の検索経験を活用し、セマンティック知識でエンティティ認識を補完、フィードバックを reward として長期記憶に蓄積します。
3.5.1 Core SDK 記憶コンポーネント一覧
| クラス / モジュール | ファイル | RAG での役割 |
|---|---|---|
SemanticMemoryEntry |
evospikenet/episodic_memory.py |
概念・定義・関連概念を保持するデータクラス |
EpisodicMemoryEntry |
evospikenet/episodic_memory.py |
検索セッション(context/action/outcome/reward)を保持 |
EpisodicMemory |
evospikenet/episodic_memory.py |
SemanticMemory + EpisodicMemory の中核エンジン (nn.Module) |
LongTermMemoryModule |
evospikenet/long_term_memory.py |
EpisodicMemory + SNN リザーバ + 忘却コントローラのオーケストレーター |
ForgettingController |
evospikenet/forgetting_controller.py |
低重要度エピソードの自動削除 |
/api/memory/episodic/store |
evospikenet/api_modules/memory_api.py |
経験の REST 永続化 |
/api/memory/semantic/store |
evospikenet/api_modules/memory_api.py |
概念の REST 永続化 |
/api/memory/episodic/retrieve |
evospikenet/api_modules/memory_api.py |
過去経験の検索 |
/api/memory/semantic/retrieve |
evospikenet/api_modules/memory_api.py |
概念グラフ検索 |
/api/memory/associate |
evospikenet/api_modules/memory_api.py |
エピソード ↔ セマンティック横断連想 |
3.5.2 統合フロー全体図
sequenceDiagram
actor User
participant RAG as RAG Pipeline
participant SM as SemanticMemory
participant EM as EpisodicMemory
participant LTM as LongTermMemoryModule
User->>RAG: クエリ送信
RAG->>SM: retrieve_semantic_knowledge(extracted_entities, top_k=5)
SM-->>RAG: [(concept, score), ...] 概念グラフ
note over RAG: エンティティ Boost を概念スコアで動的強化
RAG->>EM: integrate_episodic_semantic(query_context)
EM-->>RAG: enhanced_context (semantic_concepts, semantic_knowledge)
note over RAG: Elasticsearch / Milvus 二重検索 + RRF 融合
RAG->>EM: retrieve_memories(query_context, top_k=3)
EM-->>RAG: 関連する過去検索セッション
note over RAG: 過去セッション結果で Reranking
RAG->>User: レスポンス返却
User->>RAG: フィードバック (rating: 1-5)
RAG->>LTM: store_spike_episode(spike_seq, context, reward=rating/5)
LTM->>EM: store_experience(importance=SNN_gate)
note over LTM: RecurrentRetentionCircuit (GRU) が重要度を決定
LTM-->>RAG: RetentionSummary(memory_id, importance)
3.5.3 SemanticMemory 統合仕様
目的
NER で抽出したエンティティを SemanticMemory の概念グラフで拡張し、Entity Boost 係数を動的に強化します。
また、起動時にドメイン語彙辞書(YAML)を事前ロードすることで、NER モデルが拾えない社内固有語を補完します。
統合ポイント: クエリ前処理フェーズ
flowchart LR
ENT["抽出エンティティ\n{PROJECT_ID: ['EV-2024-001']}"]
ENT --> SM_R["EpisodicMemory\n.retrieve_semantic_knowledge\n(concept, top_k=5)"]
SM_R --> REL["関連概念 + スコア\n[('Q-PFC Loop', 0.82),\n ('AEG Phase4', 0.71),\n ('適応制御', 0.65)]"]
REL --> BOOST["動的 Boost 係数\nbase_boost × (1 + score × 0.5)"]
BOOST --> ES["Elasticsearch\nBoost 強化クエリ"]
BOOST --> MV["Milvus\n検索補助クエリ"]
SemanticMemoryEntry ↔ RAG フィールドマッピング
| フィールド | RAG での対応データ | 例 |
|---|---|---|
concept |
エンティティ名 | "EV-2024-001" |
definition |
プロジェクト/用語の概要 | "EvoSpikeNet フェーズ4 Q-PFC制御統合" |
related_concepts |
関連プロジェクト・技術用語 | ["Q-PFC Loop", "AEG-Comm", "Phase 4"] |
confidence |
概念の確信度 | 0.95 |
embedding |
概念ベクター (384-dim MiniLM) | 起動時 SentenceTransformer でエンコード |
access_count |
検索頻度 (キャッシュ優先度に利用) | 自動カウント |
ドメイン語彙ローダーと動的 Boost 強化
# config/domain_terms.yaml の形式
# EV-2024-001:
# definition: "EvoSpikeNet フェーズ4 Q-PFC制御統合プロジェクト"
# related: ["Q-PFC Loop", "AEG-Comm", "適応制御"]
class RAGSemanticLoader:
"""EvoSpikeNet ドメイン語彙を SemanticMemory に事前登録。"""
def __init__(self, episodic_memory: EpisodicMemory):
self.em = episodic_memory
def load_domain_vocabulary(self, path: str) -> int:
"""YAML 辞書から SemanticMemory に概念を一括登録。起動時 1 回実行。"""
import yaml
with open(path) as f:
terms = yaml.safe_load(f)
for concept, meta in terms.items():
self.em.add_semantic_concept(
concept=concept,
definition=meta["definition"],
related_concepts=meta.get("related", []),
)
return len(terms)
def enrich_entity_boost(
self,
entities: Dict[str, List[str]],
base_boost_map: Dict[str, float],
) -> Dict[str, float]:
"""
SemanticMemory の概念スコアで Entity Boost を動的強化。
例: PROJECT_ID 基本 5.0 × (1 + 0.82 × 0.5) = 7.05
Returns:
entity_text → 強化後 boost 係数
"""
enriched: Dict[str, float] = {}
for entity_type, entity_list in entities.items():
base = base_boost_map.get(entity_type, 1.0)
for text in entity_list:
knowledge = self.em.retrieve_semantic_knowledge(text, top_k=1)
if knowledge:
_, conf = knowledge[0]
enriched[text] = base * (1.0 + conf * 0.5)
else:
enriched[text] = base
return enriched
性能要件:
| 処理 | 目標時間 |
|---|---|
retrieve_semantic_knowledge() |
< 10ms (< 10,000 概念) |
起動時 load_domain_vocabulary() |
< 5s (< 5,000 用語) |
enrich_entity_boost() |
< 15ms (エンティティ数 × 10ms) |
3.5.4 EpisodicMemory 統合仕様
目的
検索セッションを EpisodicMemoryEntry として保存し、ユーザー評価 (rating) を reward として記録します。
蓄積された経験を用いて過去の類似セッションを参照し、Reranking に活用します。
EpisodicMemoryEntry ↔ RAG セッションマッピング
| フィールド | RAG での対応データ | 例 |
|---|---|---|
id |
セッション ID | "rag_20260520_001" |
timestamp |
クエリ実行時刻 | datetime.now() |
context |
クエリ + メタデータ dict | {"query": "EV-2024-001進捗", "language": "ja", "entities": {...}} |
action |
検索パラメータ | {"top_k": 5, "expansion": True, "entity_boost": True} |
outcome |
取得ドキュメント ID リスト | ["DOC_A", "DOC_B", "DOC_C"] |
reward |
ユーザー評価 / 5.0 | rating / 5.0 → 0.0 〜 1.0 |
importance |
SNN ゲーティング出力 | LongTermMemoryModule が自動計算 |
embedding |
クエリ埋め込み (384-dim) | MiniLM エンコード |
統合シーケンス: 検索時のコンテキスト強化
flowchart TD
QC["query_context\n{query, language, entities, normalized}"]
QC --> IES["EpisodicMemory\n.integrate_episodic_semantic(query_context)"]
IES --> EC["enhanced_context\n+ semantic_concepts: ['Q-PFC Loop']\n+ semantic_knowledge: ['Q-PCFはフェーズ4の...']"]
EC --> RM["EpisodicMemory\n.retrieve_memories(query_context, top_k=3)"]
RM --> PS["過去セッション\n[{doc_ids, reward=0.8, score=0.91}, ...]"]
PS --> RR["Reranking\n過去高評価ドキュメントのスコアを加算"]
EC --> RR
RR --> FINAL["最終 Top-K\n記憶強化後ランキング"]
検索セッション記録アダプター
class RAGEpisodicRecorder:
"""RAG 検索セッションを EpisodicMemory に記録するアダプター。"""
def __init__(self, episodic_memory: EpisodicMemory, encoder: SentenceTransformer):
self.em = episodic_memory
self.encoder = encoder
def record_session(
self,
query: str,
query_context: Dict[str, Any],
search_params: Dict[str, Any],
retrieved_doc_ids: List[str],
user_rating: Optional[float] = None,
) -> str:
"""
検索セッションをエピソード記憶に保存。
Args:
user_rating: ユーザー評価 1-5。None の場合は 0.5 (中立) を設定。
Returns:
memory_id: 保存した記憶の ID
"""
query_embedding = torch.tensor(
self.encoder.encode(query), dtype=torch.float32
)
reward = (user_rating / 5.0) if user_rating is not None else 0.5
return self.em.store_experience(
context={
"query": query,
"language": query_context.get("language"),
"entities": query_context.get("entities", {}),
"normalized_query": query_context.get("normalized"),
},
action=search_params,
outcome=retrieved_doc_ids,
reward=reward,
context_embedding=query_embedding,
)
def retrieve_similar_sessions(
self,
query_context: Dict[str, Any],
top_k: int = 3,
min_importance: float = 0.3,
) -> List[Dict[str, Any]]:
"""
現在のクエリに類似した過去の検索セッションを取得。
Reranking 用の参照データとして利用する。
"""
results = self.em.retrieve_memories(query_context, top_k=top_k)
return [
{
"doc_ids": entry.outcome if isinstance(entry.outcome, list) else [],
"reward": entry.reward,
"score": score,
"timestamp": entry.timestamp,
}
for entry, score in results
if entry.importance >= min_importance
]
性能要件:
| 処理 | 目標時間 |
|---|---|
store_experience() |
< 20ms (非同期推奨) |
retrieve_memories() |
< 30ms (< 10,000 エピソード) |
integrate_episodic_semantic() |
< 15ms |
3.5.5 LongTermMemoryModule 統合仕様
LongTermMemoryModule は EpisodicMemory + LargeScaleSpikeReservoir + RecurrentRetentionCircuit(GRU ベース)を統合し、
スパイキングニューラルネットワーク (SNN) による重要度ゲーティングで長期保持するセッションを自動選別します。
SNN 重要度ゲーティングフロー
flowchart TD
SESSION["RAG 検索セッション\n(query, docs, reward)"]
SESSION --> ENCODE["SentenceTransformer\nquery → 384-dim embedding"]
ENCODE --> SPIKE["spike_sequence\n[T=1, N=384]"]
SPIKE --> GRU["RecurrentRetentionCircuit\nGRU retention gate"]
GRU --> GATE["retention_importance ∈ [0.05, 1.0]"]
GATE --> HIGH{"importance\n>= 0.7?"}
HIGH -->|"Yes: 統合"| LT_STORE["EpisodicMemory\n長期保持"]
HIGH -->|"No: < 0.3"| FORGET["ForgettingController\n削除"]
HIGH -->|"0.3 〜 0.7"| COMPRESS["optimize_compression()\n圧縮保存(30日超)"]
GATE --> RESERVOIR["LargeScaleSpikeReservoir\nスパイク状態記録"]
style GRU fill:#fff3e0,stroke:#FF9800
style GATE fill:#fff3e0,stroke:#FF9800
style FORGET fill:#fce4ec,stroke:#F44336
style COMPRESS fill:#f3e5f5,stroke:#9C27B0
LongTermMemoryModule 統合アダプター
class RAGLongTermIntegrator:
"""
RAG セッションを LongTermMemoryModule 経由で保存。
SNN ゲーティングにより重要なセッションのみ長期保持する。
"""
def __init__(self, ltm: LongTermMemoryModule, encoder: SentenceTransformer):
self.ltm = ltm
self.encoder = encoder
def store_with_snn_gating(
self,
query: str,
query_context: Dict[str, Any],
retrieved_doc_ids: List[str],
reward: float,
semantic_tags: Optional[List[str]] = None,
) -> "RetentionSummary":
"""
SNN 重要度ゲーティングを経由してセッションを長期記憶に保存。
embedding → [T=1, N=384] の spike_sequence に変換して投入。
SNN が重要度を計算し、threshold 以下は ForgettingController が削除。
Returns:
RetentionSummary: memory_id, spike_records, importance
"""
embedding = self.encoder.encode(query)
spike_sequence = torch.tensor(embedding, dtype=torch.float32).unsqueeze(0)
return self.ltm.store_spike_episode(
spike_sequence=spike_sequence,
context=query_context,
action={"retrieved_docs": retrieved_doc_ids},
outcome=retrieved_doc_ids,
reward=reward,
semantic_tags=semantic_tags or [],
)
忘却・圧縮ポリシー
| パラメータ | 推奨値 | 説明 |
|---|---|---|
consolidation_threshold |
0.7 |
長期統合(完全保持)する重要度閾値 |
forget_threshold |
0.3 |
削除する重要度閾値 |
compression_ratio |
0.1 |
圧縮対象の割合(古い低重要度の 10%) |
max_memories |
10,000 |
最大保持エピソード数 |
compress_age_threshold_days |
30 |
30 日超のエピソードを圧縮候補とする |
3.5.6 Embedding 次元統一方針
| コンポーネント | 次元 | モデル / 備考 |
|---|---|---|
| Milvus ベクター検索 | 384 | paraphrase-multilingual-MiniLM-L12-v2 |
EpisodicMemory(embedding_dim=) |
384 | デフォルト 512 から変更して統一 |
LongTermMemoryModule(state_dim=) |
384 | 同上 |
SemanticMemoryEntry.embedding |
384 | semantic_encoder (384→192) |
memory_api.py placeholder |
→ 384 | np.random(...768) を MiniLM に置換 |
# 推奨初期化設定
episodic_memory = EpisodicMemory(
embedding_dim=384, # MiniLM-L12-v2 に統一
max_memories=10_000,
consolidation_threshold=0.7,
forget_threshold=0.3,
device="cpu",
)
ltm = LongTermMemoryModule(
state_dim=384,
time_steps=1,
embedding_dim=384,
device="cpu",
)
注意:
memory_api.pyのnp.random.default_rng(seed).random(768)はプレースホルダーです。
本番実装ではSentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2").encode(text)に置き換えてください。
3.5.7 RAGMemoryIntegrator — 統合ファサードクラス仕様
すべての記憶統合コンポーネントを一元管理するファサードです。
RAG パイプラインはこのクラスのみを使用し、内部の記憶層の詳細を隠蔽します。
classDiagram
class RAGMemoryIntegrator {
+EpisodicMemory em
+LongTermMemoryModule ltm
+SentenceTransformer encoder
+RAGSemanticLoader semantic_loader
+RAGEpisodicRecorder episodic_recorder
+RAGLongTermIntegrator ltm_integrator
+from_config(path) RAGMemoryIntegrator
+enrich_query_context(context) Dict
+record_search_session(session_id, query, ctx, params, docs)
+record_feedback(session_id, rating) str
}
class RAGSemanticLoader {
+load_domain_vocabulary(path) int
+enrich_entity_boost(entities, base_map) Dict
}
class RAGEpisodicRecorder {
+record_session(query, ctx, params, docs, rating) str
+retrieve_similar_sessions(ctx, top_k) List
}
class RAGLongTermIntegrator {
+store_with_snn_gating(query, ctx, docs, reward, tags) RetentionSummary
}
RAGMemoryIntegrator --> RAGSemanticLoader
RAGMemoryIntegrator --> RAGEpisodicRecorder
RAGMemoryIntegrator --> RAGLongTermIntegrator
RAGMemoryIntegrator --> EpisodicMemory
RAGMemoryIntegrator --> LongTermMemoryModule
class RAGMemoryIntegrator:
"""EvoSpikeNet Core 記憶システムと RAG を統合するファサード。"""
@classmethod
def from_config(cls, config_path: str) -> "RAGMemoryIntegrator":
"""
memory_config.yaml から初期化。
memory_config.yaml:
embedding_dim: 384
max_memories: 10000
encoder_model: paraphrase-multilingual-MiniLM-L12-v2
domain_dict_path: config/domain_terms.yaml
"""
import yaml
with open(config_path) as f:
cfg = yaml.safe_load(f)
em = EpisodicMemory(
embedding_dim=cfg.get("embedding_dim", 384),
max_memories=cfg.get("max_memories", 10_000),
)
ltm = LongTermMemoryModule(
state_dim=cfg.get("embedding_dim", 384),
time_steps=1,
embedding_dim=cfg.get("embedding_dim", 384),
)
encoder = SentenceTransformer(
cfg.get("encoder_model", "paraphrase-multilingual-MiniLM-L12-v2")
)
return cls(em, ltm, encoder, cfg.get("domain_dict_path"))
def enrich_query_context(self, query_context: Dict[str, Any]) -> Dict[str, Any]:
"""
クエリコンテキストを SemanticMemory + EpisodicMemory で強化。
1. integrate_episodic_semantic() でセマンティック知識融合
2. enrich_entity_boost() でエンティティ Boost を動的強化
3. retrieve_similar_sessions() で過去類似セッション取得
"""
enhanced = self.em.integrate_episodic_semantic(query_context)
enhanced["enriched_boost"] = self.semantic_loader.enrich_entity_boost(
query_context.get("entities", {}), ENTITY_BOOST_MAP
)
enhanced["past_sessions"] = self.episodic_recorder.retrieve_similar_sessions(
query_context, top_k=3
)
return enhanced
def record_search_session(
self,
session_id: str,
query: str,
query_context: Dict[str, Any],
search_params: Dict[str, Any],
retrieved_doc_ids: List[str],
) -> None:
"""検索セッションを一時記録(フィードバック待機)。"""
self._pending_sessions[session_id] = {
"query": query,
"query_context": query_context,
"search_params": search_params,
"retrieved_doc_ids": retrieved_doc_ids,
}
def record_feedback(self, session_id: str, rating: int) -> Optional[str]:
"""
ユーザーフィードバックを受け取り LongTermMemoryModule 経由で保存。
Args:
rating: 1〜5(5 が最高評価、reward = rating / 5.0 に変換)
Returns:
memory_id(セッション不明の場合は None)
"""
session = self._pending_sessions.pop(session_id, None)
if not session:
return None
summary = self.ltm_integrator.store_with_snn_gating(
query=session["query"],
query_context=session["query_context"],
retrieved_doc_ids=session["retrieved_doc_ids"],
reward=rating / 5.0,
semantic_tags=list(session["query_context"].get("entities", {}).keys()),
)
return summary.memory_id
4. API 仕様
4.1 検索 API
Endpoint: POST /api/v2/rag/search
Request:
{
"query": "プロジェクトEV-2024-001の進捗報告",
"top_k": 5,
"language": "auto",
"enable_query_expansion": true,
"enable_entity_boosting": true,
"return_debug_info": false
}
Response:
{
"status": "success",
"query": "プロジェクトEV-2024-001の進捗報告",
"language": "ja",
"session_id": "rag_20260520_143022_001",
"results": [
{
"id": "DOC_001",
"rank": 1,
"score": 0.8234,
"memory_boost": 0.12,
"source": "rrf+memory",
"text": "プロジェクトEV-2024-001は...",
"metadata": {
"project_id": "EV-2024-001",
"updated_at": "2026-05-20"
}
}
],
"memory_context": {
"semantic_concepts": ["Q-PFC Loop", "AEG Phase4"],
"past_sessions_used": 2,
"entity_boost_enriched": {"EV-2024-001": 7.05}
},
"execution_time_ms": 123,
"debug_info": null
}
エラーレスポンス:
{
"status": "error",
"code": "INVALID_QUERY",
"message": "Query is too short (min 2 characters)",
"details": {}
}
4.2 フィードバック API
Endpoint: POST /api/v2/rag/feedback
Request:
{
"session_id": "rag_20260520_143022_001",
"query": "プロジェクトEV-2024-001の進捗報告",
"doc_id": "DOC_001",
"rating": 5,
"comment": "期待通りの結果",
"user_id": "user_123"
}
Response:
{
"status": "success",
"feedback_id": "fb_12345",
"memory_id": "ep_20260520_143155",
"importance": 0.72,
"recorded_at": "2026-05-20T10:30:00Z"
}
4.3 記憶統合 API フロー
現行実装では、RAG v2 API は REST 経由の /api/memory/* 呼び出しではなく、RAGMemoryIntegrator を同一プロセス内で呼び出して記憶層と統合します。/api/memory/* は外部クライアント向けの管理 API として残し、RAG v2 の高速パスではインプロセス連携を優先します。
統合呼び出しシーケンス
sequenceDiagram
participant CLI as RAG Client
participant RAGAPI as POST /api/v2/rag/search
participant INTEG as RAGMemoryIntegrator
participant EM as EpisodicMemory
participant LTM as LongTermMemoryModule
CLI->>RAGAPI: POST /api/v2/rag/search\n{query, top_k, enable_entity_boosting}
RAGAPI->>INTEG: enrich_query_context(query_context)
INTEG->>EM: integrate_episodic_semantic()\nretrieve_memories(top_k=3)
EM-->>INTEG: semantic_concepts + past_sessions
INTEG-->>RAGAPI: {enriched_boost, past_sessions}
note over RAGAPI: 拡張クエリごとに Elasticsearch + Milvus 検索\n→ RRF スコアを doc_id ごとに加算
RAGAPI->>INTEG: compute_memory_boost(doc_id, past_sessions)
INTEG-->>RAGAPI: memory_boost
note over RAGAPI: final_score = rrf_score × entity_boost + memory_boost\nで再ソートして rank を確定
RAGAPI-->>CLI: {results: [...], session_id: "rag_xxx", execution_time_ms: 180}
CLI->>RAGAPI: POST /api/v2/rag/feedback\n{session_id, rating: 4}
RAGAPI->>INTEG: record_feedback(session_id, rating)
INTEG->>LTM: store_with_snn_gating(reward=rating/5)
LTM-->>INTEG: RetentionSummary(memory_id, importance)
INTEG-->>RAGAPI: {memory_id, importance}
RAGAPI-->>CLI: {status: "success", feedback_id: "fb_xxx", memory_id, importance}
現行実装の統合マトリクス
| RAG 処理フェーズ | 実装呼び出し | 目的 |
|---|---|---|
| クエリ前処理(エンティティ拡張) | RAGMemoryIntegrator.enrich_query_context() |
概念グラフでエンティティ Boost 強化 |
| 検索結果 Reranking | RAGEpisodicRecorder.retrieve_similar_sessions() |
過去の類似セッションで結果補正 |
| 拡張クエリ統合 | rag_v2_api 内の RRF 加算 |
同一 doc_id が複数拡張で出た場合に加点 |
| 記憶スコア反映 | RAGMemoryIntegrator.compute_memory_boost() |
final_score へ memory_boost を加算 |
| フィードバック記録 | RAGMemoryIntegrator.record_feedback() |
reward = rating/5 で長期記憶へ保存 |
| ドメイン語彙更新 | RAGSemanticLoader.load_domain_vocabulary() |
新規概念・プロジェクトの追加 |
| 外部管理 API | /api/memory/* |
REST クライアント向けの記憶管理 |
拡張レスポンス形式(v3.0 追加フィールド)
POST /api/v2/rag/search のレスポンスに記憶統合情報を追加します:
{
"status": "success",
"query": "プロジェクトEV-2024-001の進捗報告",
"language": "ja",
"session_id": "rag_20260520_143022_001",
"results": [
{
"id": "DOC_001",
"rank": 1,
"score": 0.8234,
"memory_boost": 0.12,
"source": "rrf+memory",
"text": "プロジェクトEV-2024-001は...",
"metadata": {"project_id": "EV-2024-001"}
}
],
"memory_context": {
"semantic_concepts": ["Q-PFC Loop", "AEG Phase4"],
"past_sessions_used": 2,
"entity_boost_enriched": {"EV-2024-001": 7.05}
},
"execution_time_ms": 187
}
新フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
session_id |
string |
フィードバック紐付け用のセッション ID |
results[].memory_boost |
float |
記憶強化によるスコア加算値 |
results[].source |
string |
"rrf" / "rrf+memory" |
memory_context.semantic_concepts |
List[str] |
SemanticMemory から取得した関連概念 |
memory_context.past_sessions_used |
int |
Reranking に使用した過去セッション数 |
memory_context.entity_boost_enriched |
Dict |
動的強化後の Boost 係数マップ |
フィードバック API(v3.0 拡張)
Endpoint: POST /api/v2/rag/feedback
{
"session_id": "rag_20260520_143022_001",
"query": "プロジェクトEV-2024-001の進捗報告",
"doc_id": "DOC_001",
"rating": 4,
"comment": "期待通りの結果",
"user_id": "user_123"
}
Response(v3.0):
{
"status": "success",
"feedback_id": "fb_12345",
"memory_id": "epi_20260520_143155",
"importance": 0.72,
"recorded_at": "2026-05-20T14:31:55Z"
}
追加フィールド:
| フィールド | 説明 |
|---|---|
memory_id |
EpisodicMemory に保存した記憶の ID |
importance |
SNN ゲーティングが計算した重要度 (0.05〜1.0) |
5. 検索アルゴリズム
5.1 言語別の検索戦略
日本語クエリの場合(v3.0: 記憶強化フロー)
Query: "プロジェクトEV-2024-001の進捗報告"
↓
[Normalize] → "プロジェクトEV-2024-001の進捗報告"
↓
[Tokenize with Sudachi] → ["プロジェクト", "EV-2024-001", "進捗", "報告"]
↓
[Entity Extract] → {PROJECT_ID: ["EV-2024-001"]}
↓
[SemanticMemory.retrieve_semantic_knowledge("EV-2024-001", top_k=5)]
→ 関連概念: [("Q-PFC Loop", 0.82), ("AEG Phase4", 0.71), ("適応制御", 0.65)]
→ Boost 強化: PROJECT_ID = 5.0 × (1 + 0.82 × 0.5) = 7.05
↓
[EpisodicMemory.integrate_episodic_semantic(query_context)]
→ semantic_concepts: ["EV-2024-001", "Q-PFC Loop"]
→ semantic_knowledge: ["EV-2024-001 はフェーズ4のQ-PFC制御統合プロジェクト"]
↓
[Query Expansion (Optional)]
- Dictionary + LLM: ["EV-2024-001進捗", "フェーズ4状況報告"]
↓
[Dual Search]
- Elasticsearch: BM25 (Sudachi + 強化 Boost 7.05)
- Milvus: L2 384-dim vector search
↓
[RRF Fusion + Entity Boost (7.05)] → ランキング
↓
[EpisodicMemory.retrieve_memories(query_context, top_k=3)]
→ 過去セッション: [{doc_ids:["DOC_A","DOC_B"], reward:0.8, score:0.91}, ...]
→ 過去高評価ドキュメントのスコアを +memory_boost で加算
↓
[Top-5 ドキュメント] → レスポンス生成
↓
[ユーザー評価: rating=4] → reward = 0.8
↓
[LongTermMemoryModule.store_spike_episode(spike_seq, context, reward=0.8)]
→ RecurrentRetentionCircuit (GRU) → importance = 0.72
→ EpisodicMemory.store_experience(importance=0.72)
↓
[長期記憶に保存: memory_id="epi_20260520_001"] (以後の類似クエリで活用)
英語クエリの場合
Query: "project report"
↓
[Language Detect] → "en"
↓
[Normalize] → No Japanese normalization
↓
[Tokenize] → ["project", "report"]
↓
[Entity Extract] → {}
↓
[Dual Search with English Analyzer]
- Elasticsearch: Standard analyzer (stop words, stemming)
- Milvus: Same as above
↓
[RRF Fusion] → Merged ranking
5.2 Ranking Algorithm
Score Computation:
rrf_score(doc) = Σ 1 / (60 + rank_i)
base_score = rrf_score * entity_multiplier
Where:
rank_i は BM25 / Vector / query expansion 経路ごとの順位
entity_multiplier は SemanticMemory 強化後の最大 Boost 係数
詳細:
def compute_final_score(
bm25_score: float,
vector_score: float,
entity_multiplier: float = 1.0,
rrf_position: int = 0
) -> float:
"""
基礎スコア = RRFスコア × 固有表現ブースト
"""
# RRF スコア
rrf_score = 1.0 / (60 + rrf_position + 1)
# 固有表現ブースト
final = rrf_score * entity_multiplier
return final
5.3 記憶強化ランキング
v3.0 で追加されたランキング層。SemanticMemory と EpisodicMemory からの情報を RRF スコアに加算します。
スコア計算式(v3.0)
final_score = rrf_score * entity_boost_enriched + memory_boost
| 変数 | 説明 | 範囲 |
|---|---|---|
rrf_score |
RRF 融合スコア (k=60) | 0 〜 1 |
entity_boost_enriched |
SemanticMemory 強化後の Boost 係数 | 1.0 〜 10.0 |
memory_boost |
過去セッションからの補正スコア(内部で alpha=0.2 を適用済み) |
0 〜 0.3 |
memory_boost 計算
def compute_memory_boost(
doc_id: str,
past_sessions: List[Dict[str, Any]],
alpha: float = 0.2,
) -> float:
"""
過去の類似セッションにおける高評価ドキュメントのスコアを加算。
Args:
doc_id: 対象ドキュメント ID
past_sessions: retrieve_similar_sessions() の結果
alpha: 記憶スコアの重み
Returns:
memory_boost: 加算スコア (0.0 〜 0.3)
"""
boost = 0.0
for session in past_sessions:
if doc_id in session.get("doc_ids", []):
# 過去の評価 (reward) × 類似度 (score) で加重
boost += session["reward"] * session["score"] * alpha
return min(boost, 0.3) # 上限 0.3 でキャップ
記憶強化ランキング Mermaid フロー
flowchart TD
RRF_SCORES["RRF スコア\n{DOC_A: 0.016, DOC_B: 0.015, DOC_C: 0.012}"]
SM_BOOST["SemanticMemory\nenriched_boost\n{EV-2024-001: 7.05}"]
EP_SESSIONS["EpisodicMemory\npast_sessions\n[{doc_ids:[DOC_A,DOC_B], reward:0.8, score:0.91}]"]
RRF_SCORES --> APPLY_BOOST["entity_boost 適用\nDOC_A: 0.016 × 7.05 = 0.113"]
SM_BOOST --> APPLY_BOOST
APPLY_BOOST --> APPLY_MEM["memory_boost 加算\nDOC_A: 0.113 + (0.8×0.91×0.2) = 0.259\nDOC_B: 0.106 + (0.8×0.91×0.2) = 0.252"]
EP_SESSIONS --> APPLY_MEM
APPLY_MEM --> FINAL["最終ランキング\n1位: DOC_A (0.259)\n2位: DOC_B (0.252)\n3位: DOC_C (0.085)"]
6. 評価・テスト
6.1 テストケース
テストセット構成:
| カテゴリ | ケース数 | 例 |
|---|---|---|
| 表記揺れ | 15 | 「盆休み」「お盆休み」 |
| 固有名詞 | 30+ | 「EV-2024-001」「田中太郎」 |
| 合計 | 50+ | - |
6.2 評価メトリクス
Variation Cases
case: var_001
canonical_form: "盆休み"
variations: ["盆休み", "お盆休み", "盆期間"]
ground_truth_doc_id: "DOC_001"
評価:
- MRR = 1/rank (正解が出現した順位の逆数)
- 1位: MRR = 1.0
- 3位: MRR = 0.33
- 見つからない: MRR = 0.0
目標: MRR_mean > 0.7
Entity Cases
case: ent_001
query: "プロジェクトEV-2024-001の進捗報告"
ground_truth_doc_ids: ["DOC_A", "DOC_B"]
should_not_match: ["DOC_C"]
評価:
- Recall = found_count / total_ground_truth
- Precision = found_count / returned_count
- F1 = 2 * (Precision * Recall) / (Precision + Recall)
目標: Recall > 0.8, Precision > 0.8, F1 > 0.75
6.3 自動評価スクリプト
# 表記揺れ評価
python backend/evaluate_robustness.py --variation-only
# 固有名詞評価
python backend/evaluate_robustness.py --entity-only
# 全体評価
python backend/evaluate_robustness.py --all
# 出力
$ csv: variation_results.csv, entity_results.csv
$ report: evaluation_report.txt
7. デプロイメント
7.1 環境要件
サーバー:
CPU: 8 cores (or equivalent)
Memory: 32GB (NER model + Milvus + Elasticsearch)
Storage: 500GB (index + backups)
GPU: Optional (CUDA 11.8+ for faster NER)
ソフトウェア:
Python: 3.10+
Elasticsearch: 8.0+
Milvus: 2.3+
Sudachi: Latest
7.2 インストール手順
# 1. 依存パッケージ
pip install -r requirements.txt
# 2. Elasticsearch 設定更新
curl -X PUT "localhost:9200/rag_kb_index_v2" \
-H "Content-Type: application/json" \
-d @elasticsearch_settings.json
# 3. Milvus インデックス再構築
python scripts/rebuild_milvus_index.py
# 4. テスト実行
python backend/evaluate_robustness.py --test-sample
# 5. デプロイ
docker build -t rag-v2:latest .
docker push registry/rag-v2:latest
7.3 ロールバック計画
もし問題が発生した場合:
1. トラフィック を Blue (v1) に切り替え
2. Green (v2) のログ確認
3. バグ修正 → 再テスト
4. 再デプロイ
ロールバック時間: 5 min
8. トラブルシューティング
Q1: MRR が 0.5 以下の場合
症状: 表記揺れに対応できていない
対策:
1. Sudachi の辞書が古い可能性
→ pip install --upgrade sudachi sudachipy_dict_small
2. 正規化ルールが不完全
→ elasticsearch_settings.json の char_filter を確認
3. ベクター検索の重みが低い
→ RRF の w_vector を 0.6 から 0.7 に上げる
Q2: Entity Recall が 80% 未満の場合
症状: 固有名詞が検出されない
対策:
1. NER モデルの精度確認
→ python -c "import tner; print(tner.__version__)"
2. Entity Boost が機能していない
→ Elasticsearch のマッピングを確認
→ entity_project フィールドが keyword 型か?
3. 固有表現辞書が不完全
→ internal_entities.yaml を追加
Q3: クエリが遅い場合(> 500ms)
症状: P95 レイテンシが SLA を超える
対策:
1. Query Expansion を無効化
→ ENABLE_EXPANSION = False
2. Milvus のプローブ数を減らす
→ nprobe: 10 → 5
3. Elasticsearch のクエリを単純化
→ entity_boosting のレイヤー数を減らす
9. 付録
9.1 依存関係一覧
RAG パイプライン依存:
sudachipy==0.7.5
sudachipy-dict-small==20240716
tner==1.8.0
elasticsearch==8.6.2
pymilvus==2.4.1
sentence-transformers==2.2.2
transformers==4.34.0
pandas==2.0.0
numpy==1.23.0
pyyaml==6.0
torch>=2.0.0
EvoSpikeNet Core SDK 依存(v3.0 追加):
evospikenet/episodic_memory.py # EpisodicMemory, SemanticMemoryEntry
evospikenet/long_term_memory.py # LongTermMemoryModule
evospikenet/forgetting_controller.py # ForgettingController
evospikenet/snn_memory_extension.py # LargeScaleSpikeReservoir
evospikenet/api_modules/memory_api.py # REST API エンドポイント
設定ファイル(新規追加):
config/memory_config.yaml # RAGMemoryIntegrator 設定
config/domain_terms.yaml # ドメイン語彙辞書(SemanticMemory ロード用)
9.2 関連ドキュメント
- RAG_SYSTEM_DETAILED.md - RAG システム詳細設計
- RAG_REMAINING_FUNCTIONALITY.md - 将来フェーズ (Phase 6-11)
- RAG_JAPANESE_EVALUATE_GUIDE.md - 評価ガイド
- EPISODIC_MEMORY_IMPLEMENTATION.md - EvoSpikeNet エピソード記憶実装詳細
Core SDK ソース参照:
- evospikenet/episodic_memory.py - EpisodicMemory, SemanticMemoryEntry, EpisodicMemoryEntry
- evospikenet/long_term_memory.py - LongTermMemoryModule, RetentionSummary
- evospikenet/api_modules/memory_api.py - REST エンドポイント実装
9.3 参考リソース
9.4 FAQ
Q: Kuromoji から Sudachi への移行は直前にできるのか?
A: はい、Elasticsearch のアナライザ設定変更のみです。既存インデックスは新規インデックスへの移行を推奨(数時間)。
Q: Query Expansion は必須か?
A: オプションです。無効化すると +30ms 高速になります。
Q: NER の微調整は必要か?
A: Phase 1 では標準モデルで十分です。精度が 85% 未満の場合は Phase 7 で検討。
Document Version: 3.0
Last Updated: 2026-05-22
Approved By: RAG Development Team
Status: Implemented with Operational Hardening
v3.0 Changes: EvoSpikeNet Core 記憶システム(SemanticMemory / EpisodicMemory / LongTermMemoryModule)統合仕様を追加。Memory-Augmented RAG アーキテクチャへ移行。
2026-05-22 Update: RAG v2 の前処理ヘルス API、fail-closed 運用方針、QueryExpander 品質ガード、監視項目を反映。