66 lines
2.2 KiB
Python
66 lines
2.2 KiB
Python
"""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()
|