RandomZ / app /services /reference_style.py
StormShadow308's picture
feat: async pipeline, job queue, generation hardening, and docs
732b14f
Raw
History Blame Contribute Delete
3.61 kB
"""Reference style service β€” builds and caches the KB reference style profile.
The reference profile is extracted from Behrang's indexed reports (``__rics_kb__``
tenant) and stored in the style cache under the same tenant ID.
Used as a fallback in ``_get_or_build_style_profile`` when a user has not yet
uploaded any documents of their own.
"""
from __future__ import annotations
import logging
from app.async_executor import run_sync_in_executor
from app.cache import style_cache
from app.config import settings
from app.generator.style_analyzer import build_reference_style_profile
from app.retrieval.retriever import retrieve_async
logger = logging.getLogger(__name__)
# Diverse queries to sample a broad cross-section of survey writing from the KB
_REFERENCE_QUERIES = [
"building condition survey inspection findings",
"roof structure tiles condition recommendation",
"main walls external condition damp penetration",
"services electrical gas plumbing drainage condition",
"property description construction type age",
"chimney stack flashing pointing condition",
"floors structure condition movement settlement",
"windows doors frames condition repair",
]
async def seed_reference_style_profile() -> None:
"""Build and cache the reference style profile from the KB tenant.
1. Checks the style cache first β€” skips if already cached.
2. Retrieves representative chunks from the KB vectorstore using multiple
RICS-relevant queries.
3. Calls ``build_reference_style_profile`` (richer analyser with example
paragraph extraction).
4. Stores the result under the KB tenant key in the style cache.
Safe to call on every startup β€” the cache check makes it a no-op once seeded.
"""
if not settings.knowledge_base_enabled:
return
tenant = settings.knowledge_base_tenant_id
if style_cache.get(tenant) is not None:
logger.debug("Reference style cache hit for KB tenant β€” skipping seed")
return
if not settings.openai_api_key:
logger.info("No OpenAI key β€” reference style profile will use mock fallback")
return
logger.info("Building reference style profile from KB tenant '%s'", tenant)
all_texts: list[str] = []
seen: set[str] = set()
async def _collect() -> None:
for query in _REFERENCE_QUERIES:
try:
results = await retrieve_async(query=query, tenant_id=tenant, k=6)
for r in results:
text = r.text.strip()
if text and text not in seen:
seen.add(text)
all_texts.append(text)
except Exception as exc:
logger.debug("KB retrieve failed for query '%s': %s", query, exc)
await _collect()
if not all_texts:
logger.warning(
"No chunks found in KB tenant '%s' β€” reference style not seeded. "
"Upload reference documents or check knowledge_base_dirs config.",
tenant,
)
return
logger.info("Analysing reference style from %d unique KB chunks", len(all_texts))
profile = await run_sync_in_executor(
build_reference_style_profile,
sample_texts=all_texts,
openai_api_key=settings.openai_api_key,
chat_model=settings.chat_model,
)
style_cache.set(tenant, profile)
logger.info(
"Reference style cached: tone=%s formality=%s example_paragraphs=%d",
profile.tone,
profile.formality_level,
len(profile.example_paragraphs),
)