"""FluentGerman.ai — LLM service (provider-agnostic via LiteLLM).""" import logging from collections.abc import AsyncGenerator import litellm from app.config import get_settings logger = logging.getLogger("fluentgerman.llm") def _resolve_model(model: str, provider: str) -> str: """Ensure Gemini models have the 'gemini/' prefix for Google AI Studio.""" if provider == "gemini" and not model.startswith("gemini/"): resolved = f"gemini/{model}" logger.info("Auto-prefixed model: %s → %s (Google AI Studio)", model, resolved) return resolved return model async def chat_stream( messages: list[dict[str, str]], model: str | None = None, ) -> AsyncGenerator[str, None]: """Stream chat completion tokens from the configured LLM provider.""" settings = get_settings() model = _resolve_model(model or settings.llm_model, settings.llm_provider) logger.info("LLM request: model=%s messages=%d", model, len(messages)) response = await litellm.acompletion( model=model, messages=messages, api_key=settings.llm_api_key, stream=True, ) async for chunk in response: delta = chunk.choices[0].delta if delta.content: yield delta.content async def summarize_instruction(raw_text: str) -> str: """Ask the LLM to distill raw voice transcript into a structured instruction.""" settings = get_settings() model = _resolve_model(settings.llm_model, settings.llm_provider) meta_prompt = ( "You are an expert assistant for a language teacher. " "The following text is a raw transcript of the teacher describing a learning instruction, " "homework, or teaching method. Distill it into a clear, concise, structured instruction " "that can be used as a system prompt for a language-learning LLM assistant. " "Output ONLY the instruction text, no preamble.\n\n" f"Transcript:\n{raw_text}" ) response = await litellm.acompletion( model=model, messages=[{"role": "user", "content": meta_prompt}], api_key=settings.llm_api_key, ) return response.choices[0].message.content.strip()