Files
kb-app/app/topic_extractor.py
T
2026-05-10 23:07:43 +00:00

67 lines
2.4 KiB
Python

"""Extract topic labels for chunks using a local LLM."""
import requests
import os
from .config import CONFIG
# Use a fast model — chosen at config time
TOPIC_MODEL = os.getenv("KB_TOPIC_MODEL", "qwen2.5-coder:7b")
def extract_topic(chunk_text, doc_title=None, doc_category=None):
"""Generate a brief topic label for a chunk.
Returns a single-line description of what specific entities,
concepts, and topics this chunk discusses.
"""
# Truncate very long chunks for the LLM
if len(chunk_text) > 3000:
chunk_text = chunk_text[:3000] + "..."
context_hint = ""
if doc_title:
context_hint += f"\nThe broader document is titled: {doc_title}"
if doc_category:
context_hint += f"\nThe document category: {doc_category}"
prompt = (
f"<|im_start|>system\n"
f"You extract topic labels for retrieval. Given a passage, identify the specific "
f"entities, concepts, names, and topics it discusses. Output a single concise line "
f"of 10-30 words listing what this passage is specifically about. Include proper "
f"nouns, technical terms, and metric values. Do not summarize the content; just "
f"label what it covers.<|im_end|>\n"
f"<|im_start|>user\n"
f"Passage:{context_hint}\n\n{chunk_text}<|im_end|>\n"
f"<|im_start|>assistant\n"
)
try:
resp = requests.post(
f"{CONFIG.ollama_url}/api/generate",
json={
"model": TOPIC_MODEL,
"prompt": prompt,
"stream": False,
"options": {
"num_predict": 60,
"temperature": 0.0,
"top_p": 1.0,
},
"raw": True,
},
timeout=60,
)
resp.raise_for_status()
text = resp.json().get("response", "").strip()
# Take only the first line, drop any "Labels:" prefix
first_line = text.split("\n")[0].strip()
for prefix in ("Topics:", "Topic:", "Labels:", "Label:", "Subject:"):
if first_line.lower().startswith(prefix.lower()):
first_line = first_line[len(prefix):].strip()
return first_line[:300] # cap length
except Exception as e:
print(f"Topic extraction error: {e}", flush=True)
return ""