""" Chatbot logic — context-stuffs the extracted study material into the system prompt and streams the response back. """ from prompts import CHAT_SYSTEM_PROMPT from llm import groq, CHAT_MODEL from rag import retrieve_relevant_chunks, find_direct_reference, clean_math_output def extract_text_content(content) -> str: """ Gradio's Chatbot can hand back message content as either a plain string or a list of content-part dicts (e.g. [{"type": "text", "text": "..."}]) depending on version/message type. Normalize to a plain string either way. """ if isinstance(content, str): return content if isinstance(content, list): return " ".join( part.get("text", "") for part in content if isinstance(part, dict) and part.get("type") == "text" ) return str(content) def chat(history, study_context, vector_store): """ Streams a response from the LLM based on the chat history and study context. """ latest_message = extract_text_content(history[-1]["content"]) relevant_chunks = retrieve_relevant_chunks(vector_store, latest_message, k=4) direct_match = find_direct_reference(study_context, latest_message) pieces = relevant_chunks + ([direct_match] if direct_match else []) retrieved_context = "\n\n".join(pieces) system_message = CHAT_SYSTEM_PROMPT.format(study_context=retrieved_context) history_for_api = [{"role": h["role"], "content": h["content"]} for h in history] messages = [{"role": "system", "content": system_message}] + history_for_api stream = groq.chat.completions.create(model=CHAT_MODEL, messages=messages, stream=True) response = "" for chunk in stream: response += chunk.choices[0].delta.content or "" yield history + [{"role": "assistant", "content": clean_math_output(response)}]