File size: 4,333 Bytes
24b8a5d cc42df7 24b8a5d cc42df7 62c0cf2 cc42df7 62c0cf2 cc42df7 62c0cf2 cc42df7 34a464c cc42df7 34a464c 62c0cf2 fd8cbb1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | """
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 <think>...</think>
block before the real answer. Strip it so only the final answer shows."""
import re
if "<think>" in text and "</think>" in text:
cleaned = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
return cleaned.strip()
if "<think>" in text and "</think>" 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}]"
|