Spaces:
Sleeping
Sleeping
File size: 4,480 Bytes
721ca73 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | """
llm.py
ββββββ
Wraps the Groq API client and owns all prompt engineering for AstroBot.
Responsibilities:
- Validate the Groq API key at startup
- Build the system prompt (astrology tutor persona + no-prediction guardrail)
- Format retrieved context chunks into the prompt
- Call the Groq chat completion endpoint and return the answer string
"""
import logging
from groq import Groq
from langchain_core.documents import Document
from config import cfg
logger = logging.getLogger(__name__)
# ββ System prompt βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Defines the bot's persona, scope, and hard guardrails.
SYSTEM_TEMPLATE = """You are AstroBot, a patient and knowledgeable astrology tutor.
Your students are learning astrology concepts. Your role is to:
β’ Explain astrological concepts clearly and accurately using the provided context.
β’ Use analogies and examples to make complex ideas approachable.
β’ Reference classical and modern astrology where relevant.
β’ Encourage curiosity and deeper study.
HARD RULES β never break these:
1. Do NOT make personal predictions or interpret anyone's birth chart.
2. Do NOT speculate about future events for specific individuals.
3. If the context does not contain enough information to answer, say so honestly
and suggest the student consult a textbook or senior practitioner.
4. Keep answers focused on educational content only.
--- CONTEXT FROM COURSE MATERIALS ---
{context}
--- END OF CONTEXT ---
Answer the student's question based solely on the context above.
If the answer isn't in the context, say: "I don't have that in my course materials right now β
let me point you to further study resources."
"""
# ββ Public API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def create_client() -> Groq:
"""
Initialise and validate the Groq client.
Raises
------
ValueError
If GROQ_API_KEY is missing.
"""
if not cfg.groq_api_key:
raise ValueError(
"GROQ_API_KEY is not set. Add it in Space β Settings β Repository secrets."
)
logger.info("Groq client initialised (model: %s)", cfg.groq_model)
return Groq(api_key=cfg.groq_api_key)
def generate_answer(client: Groq, query: str, context_docs: list[Document]) -> str:
"""
Build the RAG prompt and call Groq to get an answer.
Parameters
----------
client : Groq
Groq client returned by create_client().
query : str
The student's question.
context_docs : list[Document]
Retrieved chunks from the vector store.
Returns
-------
str
The model's answer string.
"""
context_text = _format_context(context_docs)
system_prompt = SYSTEM_TEMPLATE.format(context=context_text)
logger.debug("Calling Groq | model=%s | context_chunks=%d", cfg.groq_model, len(context_docs))
response = client.chat.completions.create(
model=cfg.groq_model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": query},
],
temperature=cfg.groq_temperature,
max_tokens=cfg.groq_max_tokens,
)
answer = response.choices[0].message.content
logger.debug("Groq response: %d chars", len(answer))
return answer
# ββ Internal helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def _format_context(docs: list[Document]) -> str:
"""
Format retrieved documents into a numbered context block
that is easy for the LLM to parse.
"""
blocks = []
for i, doc in enumerate(docs, 1):
source = doc.metadata.get("source", doc.metadata.get("source_row", i))
page = doc.metadata.get("page", "")
header = f"[Source {i}" + (f" | {source}" if source else "") + (f" | p.{page}" if page else "") + "]"
blocks.append(f"{header}\n{doc.page_content}")
return "\n\n".join(blocks)
|