YourGymBuddy / app /utils /chat.py
PedroRuizCode's picture
Initial deploy to Hugging Face Space
3f00f74
Raw
History Blame Contribute Delete
4.04 kB
"""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))