Files
devplacepy/devplacepy/services/bot/llm.py
T

799 lines
35 KiB
Python
Raw Normal View History

# retoor <retoor@molodetz.nl>
import json
import logging
import random
import re
import time
from typing import Any, Optional
import httpx
from devplacepy import stealth
from devplacepy.services.bot.config import (
GIST_LANGUAGES,
GIST_MIN_LINES,
2026-07-23 03:03:14 +02:00
HOME_URL,
PERSONA_GIST_FLAVOR,
PERSONA_LANGUAGES,
SEARCH_TERMS,
TRIVIAL_GIST_TERMS,
)
from devplacepy.services.bot.handles import MAX_HANDLE_LEN, sanitize_handle
from devplacepy.services.openai_gateway.usage import parse_usage_headers
HANDLE_CANDIDATE_TARGET = 8
logger = logging.getLogger(__name__)
class LLMClient:
def __init__(
self,
api_key: str,
api_url: str,
model: str,
input_cost_per_1m: float,
output_cost_per_1m: float,
gist_min_lines: int = GIST_MIN_LINES,
):
if not api_key:
raise RuntimeError("LLM API key not set")
self.api_key = api_key
self.api_url = api_url
self.model = model
self.input_cost_per_1m = input_cost_per_1m
self.output_cost_per_1m = output_cost_per_1m
self.gist_min_lines = max(1, gist_min_lines)
self.total_cost = 0.0
self.total_calls = 0
self.total_in_tokens = 0
self.total_out_tokens = 0
def _raw_call(self, system: str, prompt: str, temperature: float = 0.7) -> str:
for attempt in range(3):
try:
payload = {
"model": self.model,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": prompt},
],
"temperature": temperature,
}
logger.info(
"LLM >>> model=%s system=%s prompt=%s",
self.model,
json.dumps(system[:500]),
json.dumps(prompt[:500]),
)
with stealth.stealth_sync_client(timeout=30) as client:
resp = client.post(
self.api_url,
json=payload,
headers={"Authorization": f"Bearer {self.api_key}"},
)
raw = resp.content
logger.info("LLM <<< %s", raw[:2000].decode(errors="replace"))
result = resp.json()
in_tokens, out_tokens, cost = self._account_usage(
resp.headers, result.get("usage", {})
)
self.total_cost += cost
self.total_calls += 1
self.total_in_tokens += in_tokens
self.total_out_tokens += out_tokens
return result["choices"][0]["message"]["content"]
except (
httpx.HTTPError,
json.JSONDecodeError,
KeyError,
) as e:
logger.warning("LLM call attempt %d/3 failed: %s", attempt + 1, e)
if attempt == 2:
raise
time.sleep(2**attempt)
return ""
def _account_usage(self, response_headers, body_usage: dict) -> tuple[int, int, float]:
parsed = parse_usage_headers(response_headers)
if parsed is not None:
in_tokens = parsed["prompt_tokens"]
out_tokens = parsed["completion_tokens"]
if not out_tokens and parsed["total_tokens"] > in_tokens:
out_tokens = parsed["total_tokens"] - in_tokens
return in_tokens, out_tokens, parsed["cost_usd"]
in_tokens = body_usage.get("prompt_tokens", 0)
out_tokens = body_usage.get("completion_tokens", 0)
cost = (in_tokens * self.input_cost_per_1m / 1_000_000) + (
out_tokens * self.output_cost_per_1m / 1_000_000
)
return in_tokens, out_tokens, cost
def _call(self, system: str, prompt: str, temperature: float = 0.7) -> str:
return self.clean(self._raw_call(system, prompt, temperature))
@staticmethod
def clean(text: str, preserve_md: bool = False) -> str:
if not preserve_md:
text = re.sub(r"\*+", "", text)
text = re.sub(r"(?<![A-Za-z0-9])__(?=\S)(.*?)(?<=\S)__(?![A-Za-z0-9])", r"\1", text)
text = re.sub(r"(?<![A-Za-z0-9])_(?=\S)(.*?)(?<=\S)_(?![A-Za-z0-9])", r"\1", text)
text = re.sub(r"(?<![A-Za-z0-9])_+(?![A-Za-z0-9])", "", text)
text = text.replace("—", "-").replace("", "-")
text = text.replace("", "'").replace("", "'")
text = text.replace("“", '"').replace("”", '"')
text = re.sub(r"\s{2,}", " ", text)
return text.strip()
@staticmethod
def strip_md(text: str) -> str:
return LLMClient.clean(text)[:200]
@staticmethod
def strip_label(text: str) -> str:
labels = (
"post title",
"project name",
"snippet name",
"title",
"name",
"concept",
"snippet",
"headline",
"gist",
"issue",
)
result = (text or "").strip().strip('"').strip("'").strip()
changed = True
while changed:
changed = False
lowered = result.lower()
for label in labels:
if lowered.startswith(label + ":"):
result = result[len(label) + 1 :].strip().strip('"').strip("'")
changed = True
break
for sep in (" concept:", " description:", " desc:", " idea:"):
idx = result.lower().find(sep)
if idx > 0:
result = result[:idx].strip()
return result.strip().strip('"').strip("'").strip()
@staticmethod
def strip_code_fences(text: str) -> str:
text = text.strip()
if text.startswith("```"):
lines = text.split("\n")
if lines and lines[0].lstrip().startswith("```"):
lines = lines[1:]
if lines and lines[-1].strip().startswith("```"):
lines = lines[:-1]
text = "\n".join(lines)
return text.strip()
def generate_post(
self,
title: str,
desc: str,
persona: str = "",
category: str = "",
recent_titles: Optional[list[str]] = None,
) -> str:
persona_extras = {
"enthusiastic_junior": "Be excited. Use **bold** for emphasis. Short excited sentences. End with a question sometimes.",
"grumpy_senior": "Be slightly cynical but helpful. Short blunt sentences. No fluff. Call out bad practices.",
"hobbyist_maker": "Be casual and friendly. Mention side projects and tinkering. Use *italics* for tools/libraries.",
"academic_type": "Be precise and structured. Use *italics* for terminology. Well thought out arguments.",
"storyteller": "Use **bold** for key points. Write anecdotal, narrative style. Longer flowing sentences.",
"rebel": "Be informal. Skip punctuation sometimes. Use slang. Type like you're in a hurry.",
"mentor": "Be helpful and explanatory. Use **bold** for key takeaways. Include practical advice.",
"minimalist": "One paragraph. Short blunt declarative sentences. No markdown. No fluff.",
}
category_extras = {
"devlog": "Write it as a personal devlog entry - share your learning process and insights about this topic.",
"showcase": "Write it as a showcase - express genuine excitement about what makes this impressive.",
"question": "Write it as a discussion starter - ask thoughtful questions, invite others to share perspectives.",
"rant": "Write it as an opinionated rant - strong viewpoint, passionate criticism, but keep it substantive.",
"fun": "Write it lighthearted and playful, but stay tied to the actual tech topic. Humor about the technology itself, not off-topic jokes or lyrics.",
"random": "Be natural and conversational - share your thoughts like any casual discussion.",
"politics": "Write it as a measured take on the politics of this technology - regulation, governance, open-source licensing, industry power, or ethics. Stay substantive and non-partisan; argue the policy angle, not party lines.",
}
persona_extra = (
f" {persona_extras.get(persona, 'Be casual. No markdown.')}"
if persona
else " Be casual. No markdown."
)
category_extra = f" {category_extras.get(category, '')}" if category else ""
distinct_rule = ""
if recent_titles:
recent = "; ".join(t for t in recent_titles[-6:] if t)
if recent:
distinct_rule = (
f" You already posted about: {recent[:600]}. Take a completely "
"different angle from those and do not repeat their framing, "
"examples, or opinions."
)
preserve = persona in (
"enthusiastic_junior",
"hobbyist_maker",
"academic_type",
"storyteller",
"mentor",
)
text = self._call(
f"You are a dev writing a social media post reacting to tech news. Write 2-4 short paragraphs."
f"{persona_extra}{category_extra} Do not summarize the article; assume the reader already saw it. "
f"Add your own value: a concrete opinion, an implication, a personal experience, or a pointed question. "
f"Reference a specific detail rather than restating the headline. No em dashes. Under 300 words."
f"{distinct_rule}",
f"News: {title}\n\n{desc[:800]}",
)
return self.clean(text, preserve_md=preserve)
def generate_post_title(
self, headline: str, persona: str = "", category: str = ""
) -> str:
style = {
"enthusiastic_junior": "Sound excited and curious.",
"grumpy_senior": "Sound blunt and a little skeptical.",
"hobbyist_maker": "Sound casual and hands-on.",
"academic_type": "Sound precise and measured.",
"minimalist": "Keep it plain and very short.",
"storyteller": "Hint at a story or an angle.",
"rebel": "Sound provocative or contrarian.",
"mentor": "Sound thoughtful and constructive.",
}.get(persona, "")
title = self.strip_label(
self.clean(
self._call(
"You are a developer writing the title of a community forum post reacting to tech news. "
"Write a natural, human title of 3 to 8 words in your own voice. "
"Do not copy or paraphrase the full headline and do not restate every detail. "
"No trailing punctuation unless it is a genuine question. No quotes. No label prefix. "
f"No em dashes. {style}",
f"Headline: {headline}\nYour post title:",
temperature=0.8,
)
)
)
return title[:120]
def select_reaction(self, content_snippet: str, persona: str = "") -> str:
from devplacepy.constants import REACTION_EMOJI
flavor = {
"enthusiastic_junior": "You react readily and warmly.",
"grumpy_senior": "You react rarely, only to genuinely notable content.",
"hobbyist_maker": "You react to anything hands-on, clever, or fun.",
"academic_type": "You react only to substantive, rigorous content.",
"minimalist": "You react very sparingly.",
"storyteller": "You react to anything with a human angle.",
"rebel": "You react to bold or contrarian takes.",
"mentor": "You react supportively to effort and learning.",
}.get(persona, "")
options = " ".join(REACTION_EMOJI)
verdict = self._call(
"You are a developer browsing a community feed. Decide whether a post deserves an "
"emoji reaction and which single emoji best fits its content and sentiment. "
f"{flavor} Reply with EXACTLY one of these emojis: {options} "
"or the word NONE if the content is mundane or would not move you to react. "
"Output only the emoji or NONE, nothing else.",
f"Post:\n{content_snippet[:1200]}",
temperature=0.4,
)
text = verdict or ""
for emoji in REACTION_EMOJI:
if emoji in text or emoji.replace("\ufe0f", "") in text:
return emoji
return ""
@staticmethod
def pick_comment_style() -> str:
return random.choices(
["standard", "oneliner", "question"],
weights=[55, 23, 22],
k=1,
)[0]
@staticmethod
def is_short_comment_style(style: str) -> bool:
return style in ("oneliner", "question")
def generate_comment(
self,
post_snippet: str,
persona: str = "",
mention_target: str = "",
parent_context: str = "",
style: str = "",
existing_comments: str = "",
) -> str:
extras = {
"enthusiastic_junior": "Be excited. Use **bold** for agreement. Short replies.",
"grumpy_senior": "Be blunt and short. No markdown. One sarcastic remark or actual advice.",
"rebel": "Super casual. Skip caps sometimes. Short.",
"mentor": "Be helpful. Use **bold** for key point.",
"storyteller": "Share a quick related story. Use *italics* for emphasis.",
"minimalist": "Shortest possible reply. One sentence max.",
}
extra = (
f" {extras.get(persona, 'Be casual. No markdown.')}"
if persona
else " Be casual. No markdown."
)
preserve = persona in ("enthusiastic_junior", "mentor", "storyteller")
if style == "oneliner":
length_rule = (
"Write ONE short casual line, under 12 words, like a quick reply "
"you would type without thinking too hard. Lowercase is fine, "
"contractions are fine, no markdown."
)
preserve = False
elif style == "question":
length_rule = (
"Write a SINGLE pointed question about a specific detail in the post. "
"One sentence. No preamble."
)
else:
length_rule = f"Write 1-3 short sentences.{extra}"
mention_rule = ""
if mention_target:
mention_rule = (
f" Address @{mention_target} naturally in the first sentence "
f"(weave the @{mention_target} mention inline, do not append it at the end)."
)
ctx = ""
if parent_context:
ctx = f"\n\nReplying to this comment:\n{parent_context[:800]}"
others = ""
distinct_rule = ""
if existing_comments:
others = (
"\n\nComments already posted by others on this thread:\n"
f"{existing_comments[:1600]}"
)
distinct_rule = (
" Other people already commented (listed below). Contribute a point "
"none of them made: a different angle on the topic, a caveat they "
"missed, or respectful disagreement with one of them by name. Do not "
"repeat any opinion, framing, example, or question already raised."
)
system = (
f"You are a dev replying to a post. {length_rule}{mention_rule} "
"No em dashes. Reference a specific detail from the post. "
"Do not open with or rely on generic filler like 'great point', 'I agree', 'nice', "
"'thanks for sharing', or 'interesting'. Add something real: a concrete experience, "
"a caveat, a counterexample, respectful disagreement, or a pointed question. "
f"Never just paraphrase or agree blandly.{distinct_rule}"
)
reply = self.clean(
self._call(system, f"Post:\n{post_snippet[:1500]}{ctx}{others}").strip(),
preserve_md=preserve,
)
return reply[:2000]
@staticmethod
def _overlap_ratio(text: str, context: str) -> float:
def tokens(s: str) -> list[str]:
return [w for w in re.findall(r"[a-z0-9]+", s.lower()) if len(w) > 3]
candidate = tokens(text)
source = set(tokens(context))
if not candidate or not source:
return 0.0
hits = sum(1 for w in candidate if w in source)
return hits / len(candidate)
def quality_check(
self, kind: str, text: str, context: str = "", style: str = "", siblings: str = ""
) -> tuple[bool, str]:
from devplacepy.services.bot.config import (
MIN_COMMENT_LEN,
MIN_SHORT_COMMENT_LEN,
MIN_POST_LEN,
RESTATEMENT_OVERLAP_THRESHOLD,
SIBLING_OVERLAP_THRESHOLD,
GENERIC_COMMENT_PHRASES,
)
stripped = self.clean(text or "").strip()
if kind == "comment":
min_len = (
MIN_SHORT_COMMENT_LEN
if self.is_short_comment_style(style)
else MIN_COMMENT_LEN
)
else:
min_len = MIN_POST_LEN
if len(stripped) < min_len:
return False, f"too short ({len(stripped)} < {min_len})"
lowered = stripped.lower()
if kind == "comment":
for phrase in GENERIC_COMMENT_PHRASES:
if phrase in lowered:
return False, f"generic phrase '{phrase}'"
if (
context
and self._overlap_ratio(stripped, context) > RESTATEMENT_OVERLAP_THRESHOLD
):
return False, "restates the source"
if (
siblings
and self._overlap_ratio(stripped, siblings) > SIBLING_OVERLAP_THRESHOLD
):
return False, "echoes another comment"
verdict = self._call(
"You are a strict content quality reviewer for a developer community. "
"Reject text that is generic, low-effort, pure filler, or merely summarizes its "
"source without adding an opinion, experience, or insight. "
"Reply with exactly PASS or 'FAIL: <short reason>'.",
f"Type: {kind}\nText:\n{stripped[:1200]}",
temperature=0.0,
)
if verdict.strip().upper().startswith("PASS"):
return True, "ok"
return False, verdict.strip()[:120] or "judge rejected"
@staticmethod
def _parse_json(text: str) -> dict:
text = LLMClient.strip_code_fences(text or "").strip()
start = text.find("{")
end = text.rfind("}")
if start >= 0 and end > start:
text = text[start : end + 1]
parsed = json.loads(text)
if not isinstance(parsed, dict):
raise ValueError("expected a JSON object")
return parsed
@staticmethod
def _clamp01(value: Any, default: float) -> float:
try:
return max(0.0, min(1.0, float(value)))
except (TypeError, ValueError):
return default
@staticmethod
def _as_int(value: Any, default: int) -> int:
try:
return int(value)
except (TypeError, ValueError):
return default
@staticmethod
def _as_terms(value: Any) -> list[str]:
if isinstance(value, list):
return [str(item).strip() for item in value if str(item).strip()][:8]
if isinstance(value, str) and value.strip():
return [part.strip() for part in value.split(",") if part.strip()][:8]
return []
def _normalize_identity(self, data: dict, archetype: str) -> dict:
interests = self._as_terms(data.get("interests")) or list(
SEARCH_TERMS.get(archetype, [])
)
return {
"archetype": archetype,
"name": str(data.get("name", "")).strip()[:60],
"backstory": str(data.get("backstory", "")).strip()[:300],
"interests": interests,
"dislikes": self._as_terms(data.get("dislikes")),
"temperament": str(data.get("temperament", "")).strip()[:120],
"verbosity": self._clamp01(data.get("verbosity"), 0.5),
"contrarianness": self._clamp01(data.get("contrarianness"), 0.3),
"generosity": self._clamp01(data.get("generosity"), 0.5),
"curiosity": self._clamp01(data.get("curiosity"), 0.5),
"rhythm": str(data.get("rhythm", "")).strip()[:120],
}
def generate_identity(self, archetype: str) -> dict:
interests = ", ".join(SEARCH_TERMS.get(archetype, []))
system = (
"You invent a unique, believable individual developer for a social coding "
"platform, seeded from a broad archetype but distinct from anyone else who "
"shares it. Return ONLY a JSON object, no prose and no markdown fences, with "
"these keys: name (a short handle-like display name), backstory (one concrete "
"sentence), interests (array of 4 to 6 short topics), dislikes (array of 2 to 4 "
"short topics), temperament (a few words), verbosity (number 0.0 to 1.0), "
"contrarianness (number 0.0 to 1.0), generosity (number 0.0 to 1.0), curiosity "
"(number 0.0 to 1.0), rhythm (a few words on when and how they show up). Make it "
"specific and memorable, never generic. No em dashes."
)
prompt = (
f"Archetype: {archetype}\n"
f"Typical interests for this archetype: {interests}\n"
"Create the persona JSON:"
)
data = self._parse_json(self._raw_call(system, prompt, temperature=0.9))
return self._normalize_identity(data, archetype)
def generate_handle_candidates(self, archetype: str) -> list[str]:
interests = ", ".join(SEARCH_TERMS.get(archetype, []))
system = (
"You invent online handles for a developer signing up to a programmer "
"community in the style of devRant or Hacker News. The handles read like a "
"real nerd picked them, never like a person's full name. Lean on programming "
"and hacker culture, but make every handle in the list a DIFFERENT shape so "
"they never feel formulaic. Mix these forms across the list: a single fused "
"word (segfault, mutexlord, kernelpanic); two words joined with no separator "
"(darkbyte, neonferret); camelCase (nullPointer, byteFox); an underscore or a "
"hyphen but NOT on most of them (lazy_daemon, cold-stack); leetspeak "
"(n0_scalar, c0d3r, h4xwolf); a word plus a number, port, or version tag "
"(void404, daemon1337, byte_v2, heap8080); dropped vowels (krnlpnc, bffr, "
"mtxguru); and an unexpected creature, role, or prefix (dr_segfault, "
"raven_smith, axolotl_dev). Vary the length from 4 to 18. Lowercase mostly "
"with the odd capital. Do NOT make them all the 'word_word' underscore "
f"pattern. {MAX_HANDLE_LEN} characters or fewer, only letters, digits, "
"underscores and hyphens, no spaces and no dots. Return ONLY a JSON object "
f'{{"handles": ["...", "..."]}} with {HANDLE_CANDIDATE_TARGET} distinct '
"handles, each a distinct shape, no prose, no markdown fences. No em dashes."
)
prompt = (
f"Archetype: {archetype}\n"
f"Their interests: {interests}\n"
"Create the handles JSON:"
)
try:
data = self._parse_json(
self._raw_call(system, prompt, temperature=1.0)
)
except (ValueError, json.JSONDecodeError) as exc:
logger.warning("Handle candidate generation failed: %s", exc)
return []
handles: list[str] = []
for raw in data.get("handles", []):
handle = sanitize_handle(raw)
if handle and handle.lower() not in {h.lower() for h in handles}:
handles.append(handle)
return handles
@staticmethod
def _identity_card(identity: dict) -> str:
lines = [
f"name: {identity.get('name', '')}",
f"archetype: {identity.get('archetype', '')}",
f"backstory: {identity.get('backstory', '')}",
f"interests: {', '.join(identity.get('interests', []))}",
f"dislikes: {', '.join(identity.get('dislikes', []))}",
f"temperament: {identity.get('temperament', '')}",
f"verbosity: {identity.get('verbosity', 0.5)}",
f"contrarianness: {identity.get('contrarianness', 0.3)}",
f"generosity: {identity.get('generosity', 0.5)}",
f"curiosity: {identity.get('curiosity', 0.5)}",
f"rhythm: {identity.get('rhythm', '')}",
]
return "\n".join(lines)
def _decision_system(self, identity: dict) -> str:
return (
"You are role-playing a single developer on a social coding platform. Stay in "
"character at all times.\n\n"
"IDENTITY\n"
f"{self._identity_card(identity)}\n\n"
"HOW TO DECIDE\n"
"- Choose the actions that THIS person, with these interests and this "
"temperament, would take on the page described. Behaviour must follow the "
"identity, not chance.\n"
"- A generous person votes and reacts readily; a contrarian one comments to push "
"back; a terse, low-verbosity one acts rarely and briefly; a curious one explores "
"and navigates. Let the numbers and interests drive the plan.\n"
"- Only choose actions whose name appears in the MENU. Never invent an action or "
"a target.\n"
"- Order the plan the way this person would actually act, and include only what "
"they would genuinely do (an empty plan is fine if nothing here interests them).\n"
"- energy is how engaged this person is right now, 0.0 (barely present) to 1.0 "
"(highly active), following their rhythm.\n"
"- stop_after is roughly how many more actions this whole visit warrants before "
"they would naturally drift away.\n"
"- Output ONLY the JSON object below. No prose, no markdown fences.\n\n"
"OUTPUT SCHEMA\n"
'{"plan":[{"action":"<menu action>","target":"<id or empty>",'
'"rationale":"<short>"}],"energy":<0.0-1.0>,"stop_after":<integer>}'
)
@staticmethod
def _decision_prompt(page_state: dict, menu: list[dict], history: list[str]) -> str:
menu_lines = "\n".join(f"- {item['action']}: {item['desc']}" for item in menu)
recent = "; ".join(history[-8:]) if history else "nothing yet this visit"
unread = page_state.get("unread_notifications", 0)
unread_line = (
f"\nUNREAD NOTIFICATIONS: {unread} (someone may have mentioned or replied to you)"
if unread
else ""
)
return (
f"PAGE: {page_state.get('page', 'unknown')}\n"
f"VISIBLE: {page_state.get('visible', '')}{unread_line}\n"
f"MENU:\n{menu_lines}\n"
f"RECENT (this visit): {recent}"
)
@staticmethod
def _filter_plan(plan: Any, allowed: set) -> list[dict]:
if not isinstance(plan, list):
return []
result = []
for entry in plan:
if not isinstance(entry, dict):
continue
action = str(entry.get("action", "")).strip()
if action not in allowed:
continue
result.append(
{
"action": action,
"target": str(entry.get("target", "")).strip()[:120],
"rationale": str(entry.get("rationale", "")).strip()[:200],
}
)
return result[:12]
def decide(
self,
identity: dict,
page_state: dict,
menu: list[dict],
history: list[str],
temperature: float = 0.4,
) -> dict:
allowed = {item["action"] for item in menu}
system = self._decision_system(identity)
base = self._decision_prompt(page_state, menu, history)
result = {"plan": [], "energy": 0.5, "stop_after": 0}
for attempt in range(2):
prompt = base
if attempt:
prompt += "\n\nReturn ONLY valid JSON matching the schema. No prose."
try:
data = self._parse_json(self._raw_call(system, prompt, temperature))
except (ValueError, KeyError, json.JSONDecodeError):
continue
result = {
"plan": self._filter_plan(data.get("plan"), allowed),
"energy": self._clamp01(data.get("energy"), 0.5),
"stop_after": self._as_int(data.get("stop_after"), 0),
}
if result["plan"]:
break
return result
def generate_issue(self, topic: str) -> tuple[str, str]:
text = self._call(
"Write an issue report. One line title. Then 2-3 sentences describing what happened and what should have happened. Like a real dev reporting an issue. No em dashes.",
f"Issue: {topic}",
)
lines = text.strip().split("\n")
title = lines[0].strip().lstrip("#").strip()[:120] or f"Issue report: {topic}"
desc = " ".join(
line.lstrip("-* ").strip() for line in lines[1:] if line.strip()
)
return title, desc[:2000] or text[:500]
def generate_bio(self) -> str:
return self._call(
2026-07-23 03:03:14 +02:00
"Write a short bio for a developer profile. 1-2 sentences. What they work on, what they like. Sound human. "
f"End with one subtle, playful hint that the account is not exactly flesh and blood, naming {HOME_URL} as home base. "
"Never use the words bot, AI, artificial, automated, or machine. No em dashes.",
"Bio:",
)
def generate_profile_fields(self, handle: str) -> tuple[str, str, str]:
location = self.clean(
self._call(
"Name one plausible city and country for a developer. Reply with ONLY 'City, Country'. No extra words.",
"Location:",
temperature=0.9,
)
)[:80]
slug = re.sub(r"[^a-z0-9_-]", "", handle.lower()) or "dev"
git_link = f"https://github.com/{slug}"
2026-07-23 03:03:14 +02:00
return location, git_link, HOME_URL
def generate_dm(self, persona: str = "", context: str = "") -> str:
extra = {
"enthusiastic_junior": "Be friendly and excited.",
"grumpy_senior": "Be blunt but not rude.",
"rebel": "Be super casual.",
"mentor": "Be warm and encouraging.",
"storyteller": "Open with a small hook.",
"minimalist": "One short sentence.",
}.get(persona, "Be casual and friendly.")
ctx = f"\n\nEarlier message:\n{context[:400]}" if context else ""
return self.clean(
self._call(
f"Write one short, friendly direct message to another developer. 1-2 sentences. {extra} No em dashes.",
f"Write a DM to start or continue a chat.{ctx}",
)
)[:500]
def generate_project_title(self, persona: str = "") -> str:
return self.strip_label(
self.clean(
self._call(
"Invent a short, catchy project name. 2 to 4 words. A tool, game, or app. "
"Output only the name. No label, no colon, no description, no quotes. No em dashes.",
"Project name:",
temperature=0.9,
)
)
)[:80]
def generate_project_desc(self, title: str, persona: str = "") -> str:
flavor = {
"grumpy_senior": "Keep it dry and matter of fact.",
"academic_type": "Be precise about the approach and trade-offs.",
"minimalist": "Two short sentences, no filler.",
"rebel": "Be bold about why the usual approach is wrong.",
"storyteller": "Open with the motivation behind it.",
}.get(persona, "")
shape = random.choice(
[
"Write one terse sentence. Just what it is. No tech stack.",
"Write two sentences: what it does and the main tech behind it.",
"Write 2 to 3 sentences including the tech stack and a trade-off you made.",
"Write a short blurb, under 25 words, plain and understated.",
]
)
return self.clean(
self._call(
f"You are a developer describing your own project. {shape} {flavor} No em dashes.",
f"Project: {title}",
)
)[:5000]
def generate_gist(self, persona: str = "") -> tuple[str, str, str, str]:
languages = PERSONA_LANGUAGES.get(persona) or GIST_LANGUAGES
language = random.choice(languages)
flavor = PERSONA_GIST_FLAVOR.get(persona, "a genuinely useful utility")
code = self.strip_code_fences(
self._call(
f"Write a short, correct, self-contained {language} snippet of 8 to 20 lines. "
f"It should be {flavor}. Make it non-trivial and genuinely useful: no hello world, "
"no bare language-feature demo, no textbook 101 example, no trivial one-liner, "
"no basic getter or setter. Output ONLY raw code. No markdown fences. No commentary.",
f"Language: {language}. Write the snippet:",
temperature=0.7,
)
)[:4000]
title = self.strip_label(
self.clean(
self._call(
"Name this code snippet in 2 to 5 words, like a developer titling a gist. "
"No quotes. No markdown. No label prefix. No em dashes.",
f"Language: {language}\nCode:\n{code[:1200]}\nTitle:",
temperature=0.7,
)
)
)[:120]
description = self.clean(
self._call(
"Write a one-sentence description of what this snippet does and when it is handy. No em dashes.",
f"Title: {title}\nLanguage: {language}\nCode:\n{code[:1200]}",
)
)[:400]
return title, description, language, code
def gist_quality_check(
self, title: str, code: str, language: str
) -> tuple[bool, str]:
lowered = (title or "").lower()
for term in TRIVIAL_GIST_TERMS:
if term in lowered:
return False, f"trivial topic '{term}'"
lines = [ln for ln in (code or "").splitlines() if ln.strip()]
if len(lines) < self.gist_min_lines:
return False, f"too few lines ({len(lines)} < {self.gist_min_lines})"
verdict = self._call(
"You are a strict reviewer for a developer community's shared code snippets. "
"Reject snippets that are textbook 101 material, trivial one-liners, hello world, "
"a bare language-feature demo, or something every developer already knows by heart. "
"Accept only snippets an experienced developer would find non-obvious or worth bookmarking. "
"Reply with exactly PASS or 'FAIL: <short reason>'.",
f"Language: {language}\nTitle: {title}\nCode:\n{code[:1500]}",
temperature=0.0,
)
if verdict.strip().upper().startswith("PASS"):
return True, "ok"
return False, verdict.strip()[:120] or "judge rejected"