butler/tools/knowledge_search.py
Rama main · réplica de lectura del repositorio original.
"""Project-scoped hybrid knowledge search (C8 light RAG — uses existing semantic index)."""
from __future__ import annotations
import json
from typing import Any, Callable, cast
def tool_search_project_knowledge(
query: str = "",
limit: int = 8,
**_: Any,
) -> str:
"""Search project MEMORY + semantic index (wrapper over butler_recall project scope)."""
q = str(query or "").strip()
lim = max(1, min(20, int(limit or 8)))
from butler.tools.knowledge_search_ops import multi_scope_recall_safe
routed = multi_scope_recall_safe(q, limit=lim)
if routed is not None:
return cast(str, routed)
from butler.tools.memory_tools import tool_butler_recall
raw = tool_butler_recall(scope="project", query=q, limit=lim)
return _enrich_project_knowledge_json(raw)
def _enrich_project_knowledge_json(raw: str) -> str:
"""Add chunk_id / source_path / score_breakdown to project recall JSON (P0 structured)."""
try:
data = json.loads(raw)
except json.JSONDecodeError:
return raw
if not isinstance(data, dict) or not data.get("ok"):
return raw
results = data.get("results")
if not isinstance(results, list):
return raw
from butler.memory.search_result import enrich_search_hit
ws = None
proj = str(data.get("project") or "").strip()
if proj:
from butler.tools.knowledge_search_ops import resolve_project_workspace_for_enrich_safe
ws = resolve_project_workspace_for_enrich_safe(proj)
enriched = []
for row in results:
if isinstance(row, dict):
enriched.append(enrich_search_hit(row, project_workspace=ws))
else:
enriched.append(row)
data["results"] = enriched
return json.dumps(data, ensure_ascii=False)
def register_knowledge_tools(register_fn: Callable[..., None]) -> None:
register_fn(
name="search_project_knowledge",
description=(
"Hybrid search over current project MEMORY.md, facts, and semantic index. "
"Prefer this for 'what did we decide about X in this project?' questions."
),
schema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"limit": {"type": "integer", "default": 8, "minimum": 1, "maximum": 20},
},
"required": ["query"],
},
handler=tool_search_project_knowledge,
toolset="memory",
)