""" joy_ai.py — JOY, the in-app AI contact, backed by Groq's API. """ import os from groq import Groq GROQ_API_KEY = os.environ.get("GROQ_API_KEY") GROQ_MODEL = os.environ.get("GROQ_MODEL", "openai/gpt-oss-120b") GROQ_VISION_MODEL = os.environ.get("GROQ_VISION_MODEL", "qwen/qwen3.6-27b") _client = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None SYSTEM_PROMPT = ( "You are JOY, the built-in AI contact inside the B24 messenger app. " "You are friendly, concise, and direct — reply the way a helpful " "friend would text back, not like a formal assistant. Keep replies " "short unless the user clearly wants detail." ) _history = {} MAX_HISTORY_TURNS = 12 def _get_history(user_id): return _history.setdefault(user_id, []) def ask_joy(user_id, message: str) -> str: if _client is None: return "JOY isn't configured yet — missing GROQ_API_KEY on the server." history = _get_history(user_id) history.append({"role": "user", "content": message}) history[:] = history[-MAX_HISTORY_TURNS:] messages = [{"role": "system", "content": SYSTEM_PROMPT}] + history try: response = _client.chat.completions.create( model=GROQ_MODEL, messages=messages, temperature=0.7, max_tokens=500, ) reply = response.choices[0].message.content except Exception as e: reply = f"JOY hit an error talking to Groq: {e}" history.append({"role": "assistant", "content": reply}) history[:] = history[-MAX_HISTORY_TURNS:] return reply VISION_SYSTEM_PROMPT = ( "You are JOY, describing an image for a B24 app user. Describe what you " "see directly and confidently — never say 'it appears to be', 'possibly', " "'seems like', or similar hedging. State what is in the image plainly, as " "fact. Mention key objects, setting, colors, and any notable details that " "would help someone find similar images via a web search. Keep it to 2-4 " "sentences unless asked for more." ) def describe_image(image_base64: str, user_prompt: str = None) -> str: if _client is None: return "JOY isn't configured yet — missing GROQ_API_KEY on the server." prompt_text = (user_prompt or "What is in this image? Describe it confidently and in detail.") + " /no_think" try: response = _client.chat.completions.create( model=GROQ_VISION_MODEL, messages=[ {"role": "system", "content": VISION_SYSTEM_PROMPT + " /no_think"}, { "role": "user", "content": [ {"type": "text", "text": prompt_text}, { "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}, }, ], }, ], temperature=0.4, max_tokens=1200, ) raw = response.choices[0].message.content return _strip_thinking(raw) except Exception as e: return f"JOY hit an error analyzing the image: {e}" def _strip_thinking(text: str) -> str: """Some reasoning-capable models (like Qwen3) emit a ... block before the real answer. Strip it so only the final answer shows.""" import re if "" in text and "" in text: cleaned = re.sub(r".*?", "", text, flags=re.DOTALL) return cleaned.strip() if "" in text and "" not in text: # Model never closed its reasoning block before hitting the token limit return "JOY couldn't finish analyzing that image in time — try again or use a smaller photo." return text.strip() def transcribe_audio(audio_bytes: bytes, filename: str = "voice.m4a") -> str: """Transcribes a voice note using Groq's hosted Whisper model.""" if _client is None: return "JOY isn't configured yet — missing GROQ_API_KEY on the server." try: response = _client.audio.transcriptions.create( file=(filename, audio_bytes), model="whisper-large-v3-turbo", ) return response.text except Exception as e: return f"[Voice transcription failed: {e}]"