This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
shira-hermes/api/services/kb/topics.py
T
chaim f03721e801 feat(kb): multi-topic foundation — kb_topic table, topic-scoped endpoints
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>
2026-04-25 14:15:19 +00:00

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"]