Retrieve relevant knowledge for a user message. Returns a formatted ## Reference Material block ready to inject into the system prompt, or "" if nothing relevant is found. Args: user_message: The user's raw message (used to build the search query) budget_chars: Max
(user_message: str, budget_chars: int = None)
| 58 | |
| 59 | |
| 60 | def retrieve(user_message: str, budget_chars: int = None) -> str: |
| 61 | """ |
| 62 | Retrieve relevant knowledge for a user message. |
| 63 | |
| 64 | Returns a formatted ## Reference Material block ready to inject into |
| 65 | the system prompt, or "" if nothing relevant is found. |
| 66 | |
| 67 | Args: |
| 68 | user_message: The user's raw message (used to build the search query) |
| 69 | budget_chars: Max characters of retrieved content (default from config) |
| 70 | |
| 71 | Returns: |
| 72 | Formatted retrieval block string, or "" if empty |
| 73 | """ |
| 74 | if not RETRIEVAL_CONFIG.get("enabled", True): |
| 75 | return "" |
| 76 | |
| 77 | budget_chars = budget_chars or RETRIEVAL_CONFIG.get("budget_chars", 2400) |
| 78 | max_chunks = RETRIEVAL_CONFIG.get("max_chunks", 4) |
| 79 | min_score = RETRIEVAL_CONFIG.get("min_score", 0.0) |
| 80 | use_semantic = RETRIEVAL_CONFIG.get("semantic_search", True) |
| 81 | |
| 82 | query = extract_query(user_message) |
| 83 | if not query.strip(): |
| 84 | return "" |
| 85 | |
| 86 | # Choose search backend |
| 87 | try: |
| 88 | if use_semantic: |
| 89 | from tools.kb_semantic import semantic_search, has_index |
| 90 | if has_index(): |
| 91 | results = semantic_search(query, top_k=max_chunks) |
| 92 | else: |
| 93 | from tools.kb_semantic import keyword_fallback |
| 94 | results = keyword_fallback(query, top_k=max_chunks) |
| 95 | else: |
| 96 | from tools.kb_semantic import keyword_fallback |
| 97 | results = keyword_fallback(query, top_k=max_chunks) |
| 98 | except Exception: |
| 99 | return "" # KB unavailable — silent fallback |
| 100 | |
| 101 | if not results: |
| 102 | return "" |
| 103 | |
| 104 | # Filter by semantic relevance when hybrid search ran. |
| 105 | # semantic_score is the cosine similarity (0–1) stored on each result that |
| 106 | # came from the vector search. BM25-only results have no semantic_score |
| 107 | # and are passed through unconditionally. |
| 108 | if use_semantic: |
| 109 | semantic_threshold = RETRIEVAL_CONFIG.get("semantic_threshold", 0.3) |
| 110 | results = [ |
| 111 | r for r in results |
| 112 | if r.get("semantic_score", semantic_threshold) >= semantic_threshold |
| 113 | ] |
| 114 | |
| 115 | # Relevance gate: if even the best chunk's cosine similarity doesn't |
| 116 | # clear the gate, the KB has nothing specifically relevant — inject |
| 117 | # nothing rather than padding the prompt with unrelated content. |
no test coverage detected