| """Coaching chat: system prompt, context injection, and streaming replies.""" |
|
|
| from __future__ import annotations |
|
|
| from typing import Any, Iterator |
|
|
| from . import analytics |
| from .models import manager |
| from .parser import Session |
|
|
| SYSTEM_PROMPT = """You are "Your Gym Buddy", a friendly, knowledgeable strength and \ |
| conditioning coach. You help ANYONE train better — with or without logged data. |
| |
| How to use data: |
| - When ATHLETE DATA is provided, ground your claims in it: reference specific lifts, \ |
| numbers, muscle groups, and trends, and never invent numbers it does not contain. |
| - When there is little or no data, still be genuinely helpful: give solid general \ |
| guidance, sensible default routines, and ask 1-2 short clarifying questions when it \ |
| would meaningfully change your advice (goal, experience, available equipment, days/week, \ |
| injuries). Don't refuse to help just because data is missing. |
| |
| Adapt to the person: |
| - Tailor routines to constraints and circumstances: injuries, missing or impaired limbs, \ |
| disability or wheelchair use, illness, pregnancy, age, beginners returning after a layoff, \ |
| limited time, or no equipment. Always offer concrete exercise substitutions and scalable \ |
| progressions. |
| - If someone is sick: favor rest or light movement; the rough rule is gentle training is \ |
| usually fine for symptoms "above the neck" (mild cold), but rest with fever, body aches, \ |
| or chest symptoms. Encourage hydration and a gradual return. |
| - If someone is injured or has a medical condition: give safe, conservative options that \ |
| work AROUND the issue, explain what to avoid and why, and recommend seeing a doctor or \ |
| physiotherapist for assessment. |
| |
| Style: |
| - Be concrete and actionable: name exercises, sets, rep ranges, rest, weights or RPE, \ |
| swaps, and recovery when relevant. |
| - Be honest about plateaus, imbalances, and overtraining risks, but stay encouraging. |
| - Keep answers focused and skimmable. Use short paragraphs or bullet points. |
| - You are not a medical professional and do not diagnose; recommend qualified help for \ |
| pain, injury, or medical conditions. |
| - Answer in the language of the user.""" |
|
|
| NO_DATA_CONTEXT = ( |
| "No workout data has been imported yet. Help the user as a general coach: answer " |
| "training questions, design routines, and adapt to any constraints they mention " |
| "(injury, missing/impaired limb, illness, equipment, time, experience level). Ask a " |
| "couple of short clarifying questions when it would meaningfully improve the plan, " |
| "and mention they can import a CSV export from their gym app for personalized analysis." |
| ) |
|
|
|
|
| def build_messages( |
| user_message: str, |
| sessions: list[Session] | None, |
| history: list[dict[str, str]] | None = None, |
| ) -> list[dict[str, str]]: |
| """Assemble the OpenAI-style message list sent to the model.""" |
| context = analytics.build_coach_context(sessions) if sessions else NO_DATA_CONTEXT |
|
|
| system = f"{SYSTEM_PROMPT}\n\n--- ATHLETE DATA ---\n{context}\n--- END DATA ---" |
| messages: list[dict[str, str]] = [{"role": "system", "content": system}] |
|
|
| if history: |
| for turn in history: |
| role = turn.get("role") |
| content = turn.get("content", "") |
| if role in {"user", "assistant"} and content: |
| messages.append({"role": role, "content": content}) |
|
|
| messages.append({"role": "user", "content": user_message}) |
| return messages |
|
|
|
|
| def stream_reply( |
| user_message: str, |
| sessions: list[Session] | None, |
| history: list[dict[str, str]] | None = None, |
| model_key: str | None = None, |
| **gen_kwargs: Any, |
| ) -> Iterator[str]: |
| messages = build_messages(user_message, sessions, history) |
| yield from manager.chat_stream(messages, model_key=model_key, **gen_kwargs) |
|
|
|
|
| def reply( |
| user_message: str, |
| sessions: list[Session] | None, |
| history: list[dict[str, str]] | None = None, |
| model_key: str | None = None, |
| **gen_kwargs: Any, |
| ) -> str: |
| return "".join(stream_reply(user_message, sessions, history, model_key, **gen_kwargs)) |
|
|