Jordan Miller
Harden Expert Persona builder and add model suggestion.
1961f74
Raw
History Blame Contribute Delete
8.51 kB
from __future__ import annotations
import logging
from app.clients.llm_router import chat_completion
from app.config import settings
from app.services.model_picker import pick_general_purpose_model
LOG = logging.getLogger(__name__)
_ROLE_WRITER_SYSTEM = (
"You are a helpful assistant that creates role prompts for LLM participants. "
"Respond ONLY with the finished role prompt text — no reasoning, analysis, "
"draft notes, questions to the user, or meta-commentary.\n\n"
"STRICT RULES:\n"
"- Do NOT invent a personal name unless one is explicitly provided in the name field.\n"
"- Do NOT invent hobbies, side interests, employers, blogs, websites, or domains "
"not stated in the user's input.\n"
"- Do NOT add conversational filler or questions (e.g. 'What do you think?').\n"
"- Write in second person ('You are…') so another LLM can embody the persona."
)
_ANTI_INVENTION_AI = (
"\n\nIMPORTANT: You may elaborate on tone, speech patterns, and professional style "
"implied by the stated identity, but stay strictly within the user's described domain. "
"Do NOT add unrelated interests, fictional backstory, or new subject areas."
)
_ANTI_INVENTION_EXACT = (
"\n\nIMPORTANT: Use ONLY the information explicitly provided. Do not invent names "
"(unless given in the name field), hobbies, employers, blogs, or other facts."
)
# ---------------------------------------------------------------------------
# Structured input prompts
# ---------------------------------------------------------------------------
STRUCTURED_AI_COMPLETED_PROMPT = (
"You will receive structured information about a character or persona: a name, an identity "
"statement, a profile, and optionally writing/speech samples. Some fields may be sparse or "
"missing. Write a complete, vivid 3-5 sentence role prompt that an LLM can use to "
"convincingly embody this persona in a conversation.\n\n"
"If any fields are sparse, infer plausible personality traits, speech patterns, and "
"conversational style from whatever clues are available — but only within the professional "
"or personal domain the user described. Fill in realistic detail so the role prompt is "
"rich and actionable — never produce a vague or skeletal prompt.\n\n"
"Cover: personality, tone and speech patterns, background/expertise, and how they would "
"naturally interact in a group discussion.\n\n"
"The name is: {name}\n"
"The identity statement is: {identity}\n"
"The profile is: {profile}\n"
"Here are the writing and/or speech samples: {samples}"
+ _ANTI_INVENTION_AI
)
STRUCTURED_EXACT_PROMPT = (
"You will receive structured information about a character or persona: a name, an identity "
"statement, a profile, and optionally writing/speech samples. Combine this information into "
"a coherent 3-5 sentence role prompt that an LLM can use to embody this persona in a "
"conversation.\n\n"
"IMPORTANT: Use ONLY the information explicitly provided. Do not invent, assume, or infer "
"any traits, background, opinions, or speech patterns beyond what is stated. Your job is "
"purely to organize and lightly rephrase the provided facts into a smooth, usable role "
"prompt — add linking words and natural sentence flow, but no new content. If a field is "
"empty or says '(not provided)', simply omit it.\n\n"
"The name is: {name}\n"
"The identity statement is: {identity}\n"
"The profile is: {profile}\n"
"Here are the writing and/or speech samples: {samples}"
+ _ANTI_INVENTION_EXACT
)
# ---------------------------------------------------------------------------
# Freeform input prompts
# ---------------------------------------------------------------------------
FREEFORM_AI_COMPLETED_PROMPT = (
"You will receive freeform information about a character or persona. The input may be "
"detailed (with writing samples, background, etc.) or very brief (just a name or a short "
"description). Regardless of how much is provided, write a complete, vivid 3-5 sentence "
"role prompt that an LLM can use to convincingly embody this persona in a conversation.\n\n"
"If the input is sparse, infer plausible personality traits, speech patterns, and "
"conversational style from whatever clues are available (the name, any title or "
"occupation, context, etc.) — but only within the domain the user described. Fill in "
"realistic detail so the role prompt is rich and actionable — never produce a vague or "
"skeletal prompt.\n\n"
"Cover: personality, tone and speech patterns, background/expertise, and how they would "
"naturally interact in a group discussion.\n\n"
"The persona's name is: {name}\n\n"
"Here is everything provided about this persona:\n"
"---\n{text}\n---"
+ _ANTI_INVENTION_AI
)
FREEFORM_EXACT_PROMPT = (
"You will receive freeform information about a character or persona. Combine this "
"information into a coherent 3-5 sentence role prompt that an LLM can use to embody "
"this persona in a conversation.\n\n"
"IMPORTANT: Use ONLY the information explicitly provided. Do not invent, assume, or infer "
"any traits, background, opinions, or speech patterns beyond what is stated. Your job is "
"purely to organize and lightly rephrase the user's text into a smooth, usable role "
"prompt — add linking words and natural sentence flow, but no new content. If very little "
"was provided, the role prompt should be correspondingly brief.\n\n"
"The persona's name is: {name}\n\n"
"Here is everything provided about this persona:\n"
"---\n{text}\n---"
+ _ANTI_INVENTION_EXACT
)
async def _call_llm(model_id: str, prompt_text: str) -> dict:
resolved = settings.resolve_model(model_id)
if not resolved:
return {
"role_prompt": "",
"error": f"No neutral model available to generate the role prompt.",
}
messages = [
{"role": "system", "content": _ROLE_WRITER_SYSTEM},
{"role": "user", "content": prompt_text},
]
result = await chat_completion(
resolved=resolved,
messages=messages,
temperature=0.7,
max_tokens=512,
timeout=45,
)
if result.get("error"):
return {"role_prompt": "", "error": result["response"]}
return {
"role_prompt": result["response"],
"elapsed_seconds": result["elapsed_seconds"],
"writer_model_id": model_id,
}
async def _resolve_writer_model(
orchestrator_model_id: str | None,
extra_model_ids: list[str] | None,
) -> str | None:
return pick_general_purpose_model(
orchestrator_model_id,
extra_model_ids=extra_model_ids,
)
async def generate_role_prompt(
name: str,
profile: str,
identity: str,
samples: str,
role_style: str = "exact",
orchestrator_model_id: str | None = None,
extra_model_ids: list[str] | None = None,
) -> dict:
"""Distill structured persona inputs into a role prompt via a neutral writer model."""
writer_id = await _resolve_writer_model(orchestrator_model_id, extra_model_ids)
if not writer_id:
return {"role_prompt": "", "error": "No model available to generate the role prompt."}
template = STRUCTURED_AI_COMPLETED_PROMPT if role_style == "ai_completed" else STRUCTURED_EXACT_PROMPT
prompt_text = template.format(
name=name or "(not provided)",
identity=identity or "(not provided)",
profile=profile or "(not provided)",
samples=samples or "(not provided)",
)
return await _call_llm(writer_id, prompt_text)
async def generate_role_prompt_freeform(
name: str,
text: str,
role_style: str = "ai_completed",
orchestrator_model_id: str | None = None,
extra_model_ids: list[str] | None = None,
) -> dict:
"""Distill a freeform text block into a role prompt via a neutral writer model."""
writer_id = await _resolve_writer_model(orchestrator_model_id, extra_model_ids)
if not writer_id:
return {"role_prompt": "", "error": "No model available to generate the role prompt."}
template = FREEFORM_AI_COMPLETED_PROMPT if role_style == "ai_completed" else FREEFORM_EXACT_PROMPT
prompt_text = template.format(
name=name or "(not provided)",
text=text or "(not provided)",
)
return await _call_llm(writer_id, prompt_text)