f03721e801
Phase 1 of the multi-domain refactor. Adds a kb_topic table seeded with the existing 'ביטוח לאומי' domain (id=1) and pins all 6 existing kb_source rows to it, so search/ask can be scoped per domain without disturbing the current single-topic UX. - scripts/migrations/001_topics.sql: idempotent migration. Creates kb_topic, seeds national-insurance with the system_prompt_addendum copied verbatim from kb_public.py's hardcoded text, adds nullable kb_source.topic_id, backfills it to 1, then SET NOT NULL. GRANTs to shira_kb mirror its existing kb_source privileges. Wrapped in BEGIN /COMMIT with a sanity-check that raises if the backfill is incomplete. - api/services/kb/topics.py: list_topics / get_topic / resolve_topic_id. resolve_topic_id raises ValueError on unknown ids so the route layer can return 400 instead of silently mixing domains. - api/services/kb/search.py: search() + _retrieve_rrf accept topic_id; the SQL adds AND s.topic_id = $N when present. _expand_query is parametrized by topic name (was hardcoded "הביטוח הלאומי"). - api/routes/kb_public.py: new GET /kb/topics. /kb/search /kb/sources /kb/ask /kb/ask/stream all accept topic_id and call resolve_topic_id. _build_ask_runner_context loads system_prompt_addendum from the DB instead of the hardcoded insurance string. - mcp_server/tools/legal_kb_tools.py: tool renamed search_insurance_kb → search_legal_kb, takes topic_id at registration so each conversation is pinned to its caller-chosen domain. Tool description interpolates the topic name. - api/services/agent_runner.py: kb_topic_id + kb_topic_name flow through to register_legal_kb_tools. Hebrew progress labels updated. Refs Task Master #12 (espocrm-extensions/KnowledgeBase). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
74 lines
2.4 KiB
Python
74 lines
2.4 KiB
Python
"""kb_topic helpers — fetch topics for the public listing endpoint and
|
|
load a topic's full row (including system_prompt_addendum) for /kb/ask.
|
|
|
|
The DB schema is set up by scripts/migrations/001_topics.sql. As of Phase
|
|
1 there is exactly one seeded topic (id=1, slug='national-insurance').
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Optional
|
|
|
|
from api.services.kb.db import get_pool
|
|
|
|
logger = logging.getLogger("shira.kb.topics")
|
|
|
|
|
|
async def list_topics(active_only: bool = True) -> list[dict]:
|
|
"""Return topics ordered by id. Public payload — never includes the
|
|
system_prompt_addendum (admin-only data, not surfaced to end users).
|
|
"""
|
|
pool = await get_pool()
|
|
where = "WHERE is_active = TRUE" if active_only else ""
|
|
async with pool.acquire() as conn:
|
|
rows = await conn.fetch(
|
|
f"""
|
|
SELECT id, slug, name, description, is_active
|
|
FROM kb_topic
|
|
{where}
|
|
ORDER BY id
|
|
"""
|
|
)
|
|
return [dict(r) for r in rows]
|
|
|
|
|
|
async def get_topic(topic_id: int) -> Optional[dict]:
|
|
"""Fetch a single topic by id, including system_prompt_addendum."""
|
|
pool = await get_pool()
|
|
async with pool.acquire() as conn:
|
|
row = await conn.fetchrow(
|
|
"""
|
|
SELECT id, slug, name, description, system_prompt_addendum,
|
|
is_active, created_at, updated_at
|
|
FROM kb_topic
|
|
WHERE id = $1
|
|
""",
|
|
topic_id,
|
|
)
|
|
return dict(row) if row else None
|
|
|
|
|
|
async def resolve_topic_id(topic_id: Optional[int]) -> int:
|
|
"""Validate a caller-supplied topic_id, or fall back to the lowest
|
|
active topic. Raises ValueError if the caller supplied an unknown or
|
|
inactive id, so the route can return 400 instead of silently mixing
|
|
domains.
|
|
"""
|
|
pool = await get_pool()
|
|
async with pool.acquire() as conn:
|
|
if topic_id is not None:
|
|
row = await conn.fetchrow(
|
|
"SELECT id FROM kb_topic WHERE id = $1 AND is_active = TRUE",
|
|
topic_id,
|
|
)
|
|
if not row:
|
|
raise ValueError(f"unknown or inactive topic_id: {topic_id}")
|
|
return topic_id
|
|
row = await conn.fetchrow(
|
|
"SELECT id FROM kb_topic WHERE is_active = TRUE ORDER BY id LIMIT 1"
|
|
)
|
|
if not row:
|
|
raise ValueError("no active topics in kb_topic")
|
|
return row["id"]
|