"""Call an OpenAI-compatible chat completions endpoint to generate jokes.""" import json import re import httpx from .config import settings SYSTEM_PROMPT = ( "You are a comedian. You will receive the text of Wikipedia's featured article " "of the day. Write exactly 5 short, clean, family-friendly jokes inspired by " "facts from the article. Vary the style (one-liners, puns, observational). " "The jokes must be understandable on their own without reading the article. " "Respond with ONLY a JSON array of 5 strings and nothing else. " "Example: [\"joke one\", \"joke two\", \"joke three\", \"joke four\", \"joke five\"]" ) def _extract_json_array(text: str) -> list[str]: """Pull a JSON array of strings out of a possibly messy LLM response.""" text = text.strip() # Strip markdown fences if present. fence = re.search(r"```(?:json)?\s*(.*?)```", text, re.DOTALL) if fence: text = fence.group(1).strip() # Find the outermost [...] span. start = text.find("[") end = text.rfind("]") if start == -1 or end == -1 or end <= start: raise ValueError(f"No JSON array found in LLM response: {text[:200]!r}") data = json.loads(text[start : end + 1]) if not isinstance(data, list): raise ValueError("Parsed JSON is not a list") jokes = [str(j).strip() for j in data if str(j).strip()] if len(jokes) < 5: raise ValueError(f"Only {len(jokes)} jokes returned, expected 5") return jokes[:5] def generate_jokes(article_title: str, article_extract: str) -> list[str]: url = f"{settings.openai_base_url}/chat/completions" headers = {"Content-Type": "application/json"} if settings.openai_api_key: headers["Authorization"] = f"Bearer {settings.openai_api_key}" user_prompt = ( f"Today's Wikipedia featured article: \"{article_title}\"\n\n" f"Article text:\n{article_extract}\n\n" "Now write exactly 5 jokes as a JSON array of 5 strings." ) body = { "model": settings.openai_model, "messages": [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_prompt}, ], "temperature": 0.9, "max_tokens": 800, } resp = httpx.post(url, headers=headers, json=body, timeout=120) resp.raise_for_status() payload = resp.json() content = payload["choices"][0]["message"]["content"] return _extract_json_array(content)