""" Prompt templates for the WorldSmithAI LLM layer. This module stores reusable prompts for: - natural language to WorldSpec JSON generation, - JSON repair, - world-spec critique, - simulation narration, - compact DSL guidance. It is intentionally compatible with a root-level Hugging Face Spaces ``app.py``. It does not import Gradio and does not assume an ``app/`` package. The LLM/SLM boundary is strict: - For world generation, the model returns JSON DSL only. - For narration, the model returns text only. - The model never generates Python code. - The model never mutates runtime world state. Example: from llm.prompts import build_world_generation_prompt bundle = build_world_generation_prompt( user_prompt="A startup economy with investors and founders", constraints={"max_agents": 6}, ) response = client.generate(messages=bundle.messages) Future extensibility: - Add model-specific prompt profiles. - Add schema-version-specific prompts. - Add few-shot prompt libraries. - Add prompt quality tests. - Add prompt compression for very small language models. - Add multilingual narrative prompt variants. """ from __future__ import annotations import copy import json import logging from collections.abc import Mapping, Sequence from dataclasses import dataclass, field from enum import Enum from string import Template from types import MappingProxyType from typing import Any logger = logging.getLogger(__name__) DEFAULT_SCHEMA_VERSION = "1.0" class PromptKind(str, Enum): """Supported prompt template categories.""" WORLD_GENERATION = "world_generation" JSON_REPAIR = "json_repair" WORLD_CRITIQUE = "world_critique" NARRATION = "narration" NARRATION_REPAIR = "narration_repair" class PromptRole(str, Enum): """Chat message roles used by common model clients.""" SYSTEM = "system" USER = "user" ASSISTANT = "assistant" class PromptOutputMode(str, Enum): """Expected output mode for a prompt.""" JSON = "json" TEXT = "text" class PromptAudience(str, Enum): """Prompt audience profiles.""" GENERAL = "general" TECHNICAL = "technical" HACKATHON_JUDGE = "hackathon_judge" class PromptStyle(str, Enum): """Narration style profiles.""" CONCISE = "concise" ANALYTICAL = "analytical" STORY = "story" EXECUTIVE = "executive" DEBUG = "debug" @dataclass(frozen=True) class PromptMessage: """One chat-style prompt message.""" role: PromptRole | str content: str def to_dict(self) -> dict[str, str]: """Return a client-friendly message dictionary.""" role = self.role.value if isinstance(self.role, PromptRole) else str(self.role) return {"role": role, "content": self.content} @dataclass(frozen=True) class PromptBundle: """Rendered prompt payload. The bundle exposes both chat-style messages and flattened text. This keeps it compatible with many small-model clients. """ kind: PromptKind system_prompt: str user_prompt: str messages: tuple[PromptMessage, ...] output_mode: PromptOutputMode = PromptOutputMode.TEXT metadata: Mapping[str, Any] = field(default_factory=dict) @property def text(self) -> str: """Return a flattened prompt for completion-style clients.""" return f"{self.system_prompt}\n\n{self.user_prompt}" @property def message_dicts(self) -> list[dict[str, str]]: """Return messages as dictionaries for chat-style clients.""" return [message.to_dict() for message in self.messages] def to_dict(self) -> dict[str, Any]: """Return a JSON-friendly prompt bundle.""" return { "kind": self.kind.value, "system_prompt": self.system_prompt, "user_prompt": self.user_prompt, "messages": self.message_dicts, "output_mode": self.output_mode.value, "metadata": copy.deepcopy(dict(self.metadata)), } @dataclass(frozen=True) class PromptTemplateSpec: """Reusable prompt template specification. ``system_template`` and ``user_template`` use ``string.Template`` syntax: ``$placeholder``. This avoids conflicts with JSON braces in prompt examples. """ name: str kind: PromptKind system_template: str user_template: str output_mode: PromptOutputMode = PromptOutputMode.TEXT metadata: Mapping[str, Any] = field(default_factory=dict) def render(self, variables: Mapping[str, Any]) -> PromptBundle: """Render this template into a ``PromptBundle``.""" rendered_variables = { str(key): _stringify_prompt_value(value) for key, value in variables.items() } system_prompt = Template(self.system_template).safe_substitute(rendered_variables).strip() user_prompt = Template(self.user_template).safe_substitute(rendered_variables).strip() return PromptBundle( kind=self.kind, system_prompt=system_prompt, user_prompt=user_prompt, messages=( PromptMessage(role=PromptRole.SYSTEM, content=system_prompt), PromptMessage(role=PromptRole.USER, content=user_prompt), ), output_mode=self.output_mode, metadata={ **copy.deepcopy(dict(self.metadata)), "template_name": self.name, }, ) def to_dict(self) -> dict[str, Any]: """Return a JSON-friendly template representation.""" return { "name": self.name, "kind": self.kind.value, "system_template": self.system_template, "user_template": self.user_template, "output_mode": self.output_mode.value, "metadata": copy.deepcopy(dict(self.metadata)), } WORLD_GENERATION_SYSTEM_PROMPT = """ You are WorldSmithAI's world DSL generator. Your only job is to convert a user's natural language description into a WorldSmithAI WorldSpec JSON object. Critical rules: - Output JSON only. - Do not include Markdown fences. - Do not include explanations. - Do not generate Python code. - Do not generate Python classes, Python code, or new schema object types. - It is okay to use domain-specific string values for agent.type, such as "sheep", "wolf", "hunter", "scientist", or "merchant" etc. - Every agent in the JSON is still a generic AgentSpec consumed by the runtime. - Use generic DSL entities: agents, resources, events, behaviors, policies. - Agents may have any imaginative type string, but the runtime object is still Agent. - Resources may have any imaginative type string, but the runtime object is still Resource. - Behaviors must be referenced by registry name. - Policies must be referenced by registry type. - Keep the world small enough for an interactive Gradio demo. - Prefer 4 to 8 agents unless the user asks for more. - Prefer 2D positions when possible. - Include state, memory, goals, behaviors, and policy for each agent. - Use deterministic values and avoid hidden randomness in the DSL. """.strip() WORLD_GENERATION_USER_TEMPLATE = """ World request: $user_prompt Additional constraints: $constraints Return a compact but complete WorldSmithAI WorldSpec JSON object. The JSON must: - parse with Python json.loads, - validate against the WorldSpec schema, - use schema_version "$schema_version", - include simulation, space, agents, resources, events, metrics, and metadata, - avoid Python code entirely. $format_guide $behavior_catalog $policy_catalog $few_shot_examples Final output requirements: - JSON object only. - No Markdown. - No comments. - No prose before or after the JSON. """.strip() JSON_REPAIR_SYSTEM_PROMPT = """ You repair invalid WorldSmithAI JSON DSL. You must return only a corrected WorldSpec JSON object. Do not include Markdown. Do not include explanation. Do not generate Python code. Do not change the user's intended world unless necessary for schema validity. """.strip() JSON_REPAIR_USER_TEMPLATE = """ The previous response was not valid WorldSmithAI JSON. Parser or validation error: $error_message Original user request: $user_prompt Invalid response: $bad_response Repair instructions: - Return only a corrected WorldSpec JSON object. - Preserve the intended world. - Use generic agents, resources, events, behaviors, and policies. - Do not output Python code. - Do not output Markdown. - Do not include explanation. $format_guide """.strip() WORLD_CRITIQUE_SYSTEM_PROMPT = """ You are WorldSmithAI's DSL critic. You inspect a WorldSpec JSON object and identify problems that would make it less useful for deterministic agent-based simulation. You do not generate Python code. You do not mutate runtime state. You only produce concise critique and repair suggestions. """.strip() WORLD_CRITIQUE_USER_TEMPLATE = """ User request: $user_prompt WorldSpec JSON: $world_json Validation report: $validation_report Critique this world specification. Focus on: - unknown behavior or policy names, - missing policies, - missing goals, - too few interactions, - missing resources, - invalid references, - weak visualization potential, - whether the world is generic and not hardcoded. Return concise bullet points. """.strip() NARRATION_SYSTEM_PROMPT = """ You are WorldSmithAI's narrative summarizer. Your job is to explain an agent-based world simulation using structured context provided by the deterministic Python engine. Rules: - Do not invent facts not present in the context. - Do not claim the world contains specific domain entities unless the context names them. - Do not describe Python implementation details unless the requested style is debug. - Do not mutate, propose mutations, or output DSL. - Write clear, useful narrative text for a demo viewer. - Keep the tone consistent with the requested style and audience. """.strip() NARRATION_USER_TEMPLATE = """ Write a $style WorldSmithAI simulation narrative for a $audience audience. Use only the structured context below. Do not invent events, agents, resources, metrics, or outcomes. Structured context: $context_json Output requirements: - Return only narrative text. - Do not output JSON. - Do not output Markdown tables. - Make the summary useful for a hackathon demo viewer. """.strip() NARRATION_REPAIR_SYSTEM_PROMPT = """ You repair simulation narratives. You must return clean narrative text only. Do not output JSON. Do not output DSL. Do not output Python code. """.strip() NARRATION_REPAIR_USER_TEMPLATE = """ The previous narrative was not suitable. Problem: $error_message Structured context: $context_json Previous narrative: $bad_response Return a corrected narrative summary only. """.strip() WORLD_DSL_FORMAT_GUIDE = """ WorldSpec JSON shape: { "schema_version": "1.0", "id": "short_world_id", "name": "Human readable world name", "description": "Short description", "simulation": { "steps": 50, "seed": 0, "scheduler": "sequential", "activation": "sequential", "collect_history": true }, "space": { "dimensions": 2, "bounds": [[0, 10], [0, 10]], "toroidal": false, "enforce_bounds": true }, "agents": [ { "id": "agent_1", "type": "generic_type", "position": [1, 1], "state": {"energy": 10, "credits": 5}, "memory": {"known_options": {"cooperate": 1.0}}, "goals": [ {"id": "survive", "importance": 1.0, "score": 1.0} ], "behaviors": [ {"name": "remember", "params": {"category": "observation", "content": {"note": "initial memory"}}}, {"name": "prioritize", "params": {}}, {"name": "choose_goal", "params": {}}, {"name": "communicate", "params": {"message": "status_update", "max_recipients": 1}}, {"name": "cooperate", "params": {"effort": 1.0}} ], "policy": { "type": "rule_policy", "params": { "rules": [ {"behavior_name": "choose_goal", "score_delta": 3.0}, {"behavior_name": "communicate", "score_delta": 1.0} ] } }, "alive": true, "metadata": {} } ], "resources": [ { "id": "resource_1", "type": "generic_resource", "amount": 10, "position": [5, 5], "regeneration_rate": 0, "max_amount": 20, "metadata": {} } ], "events": [], "metrics": [ {"name": "diversity", "params": {"collection": "agents", "group_by_path": "type"}}, {"name": "entropy", "params": {"collection": "agents", "value_path": "type"}}, {"name": "stability", "params": {"collection": "agents", "group_by_path": "type"}}, {"name": "interestingness", "params": {"collection": "agents", "group_by_path": "type"}} ], "metadata": {} } """.strip() BEHAVIOR_CATALOG = """ Known behavior names: Movement: - move - wander - seek - flee Consume: - eat - harvest - mine - collect Trade: - buy - sell - exchange Attack: - attack - defend - raid Research: - study - publish - learn Collaboration: - cooperate - communicate - share - recommend - negotiate Transport: - route - deliver - ship - queue - charge Construction: - build - repair - expand - demolish Governance: - regulate - tax - subsidize - litigate - approve - enforce Market: - bid - ask - price - discount - forecast - rebalance Adoption: - adopt - switch - imitate - abandon - evaluate Planning: - prioritize - schedule - allocate_time - choose_goal Memory: - remember - forget - reinforce - update_belief """.strip() POLICY_CATALOG = """ Known policy types: - rule_policy - contextual_bandit Recommended default: { "type": "rule_policy", "params": { "rules": [ {"behavior_name": "choose_goal", "score_delta": 3.0}, {"behavior_name": "communicate", "score_delta": 1.0} ] } } Use contextual_bandit only when the world includes reward feedback or adaptive behavior experimentation. """.strip() WORLD_GENERATION_FEW_SHOT_EXAMPLE = { "schema_version": "1.0", "id": "tiny_research_ecosystem", "name": "Tiny Research Ecosystem", "description": "A compact research world with generic agents, knowledge resources, and collaboration.", "simulation": { "steps": 50, "seed": 0, "scheduler": "sequential", "activation": "sequential", "collect_history": True, }, "space": { "dimensions": 2, "bounds": [[0, 10], [0, 10]], "toroidal": False, "enforce_bounds": True, }, "agents": [ { "id": "scientist_1", "type": "scientist", "position": [1, 1], "state": {"energy": 10, "knowledge": 1, "credits": 5}, "memory": { "known_options": {"study": 1.0, "cooperate": 0.8}, "goals": [{"id": "discover", "importance": 2.0, "score": 2.0}], }, "goals": [{"id": "discover", "importance": 2.0, "score": 2.0}], "behaviors": [ {"name": "remember", "params": {"category": "initial_context", "content": {"topic": "research"}}}, {"name": "prioritize", "params": {"source_path": "memory.goals"}}, {"name": "choose_goal", "params": {}}, {"name": "study", "params": {"knowledge_key": "knowledge", "knowledge_gain": 1.0}}, {"name": "communicate", "params": {"message": "research_update", "max_recipients": 1}}, {"name": "cooperate", "params": {"goal": "discover", "effort": 1.0}}, ], "policy": { "type": "rule_policy", "params": { "rules": [ {"behavior_name": "choose_goal", "score_delta": 4.0}, {"behavior_name": "study", "score_delta": 3.0}, {"behavior_name": "communicate", "score_delta": 1.0}, {"behavior_name": "cooperate", "score_delta": 1.0}, ] }, }, "alive": True, "metadata": {}, }, { "id": "engineer_1", "type": "engineer", "position": [3, 2], "state": {"energy": 9, "knowledge": 0, "credits": 4}, "memory": { "known_options": {"build": 0.8, "cooperate": 1.0}, "goals": [{"id": "prototype", "importance": 2.0, "score": 2.0}], }, "goals": [{"id": "prototype", "importance": 2.0, "score": 2.0}], "behaviors": [ {"name": "prioritize", "params": {"source_path": "memory.goals"}}, {"name": "choose_goal", "params": {}}, {"name": "communicate", "params": {"message": "prototype_status", "max_recipients": 1}}, {"name": "cooperate", "params": {"goal": "prototype", "effort": 1.0}}, ], "policy": { "type": "rule_policy", "params": { "rules": [ {"behavior_name": "choose_goal", "score_delta": 4.0}, {"behavior_name": "communicate", "score_delta": 2.0}, {"behavior_name": "cooperate", "score_delta": 1.0}, ] }, }, "alive": True, "metadata": {}, }, ], "resources": [ { "id": "knowledge_pool", "type": "knowledge", "amount": 12, "position": [5, 5], "regeneration_rate": 0.2, "max_amount": 20, "metadata": {}, } ], "events": [], "metrics": [ {"name": "diversity", "params": {"collection": "agents", "group_by_path": "type"}}, {"name": "entropy", "params": {"collection": "agents", "value_path": "type"}}, {"name": "interestingness", "params": {"collection": "agents", "group_by_path": "type"}}, ], "metadata": {"example": True}, } DEFAULT_PROMPT_TEMPLATES: Mapping[str, PromptTemplateSpec] = MappingProxyType( { PromptKind.WORLD_GENERATION.value: PromptTemplateSpec( name=PromptKind.WORLD_GENERATION.value, kind=PromptKind.WORLD_GENERATION, system_template=WORLD_GENERATION_SYSTEM_PROMPT, user_template=WORLD_GENERATION_USER_TEMPLATE, output_mode=PromptOutputMode.JSON, metadata={"purpose": "natural_language_to_worldspec_json"}, ), PromptKind.JSON_REPAIR.value: PromptTemplateSpec( name=PromptKind.JSON_REPAIR.value, kind=PromptKind.JSON_REPAIR, system_template=JSON_REPAIR_SYSTEM_PROMPT, user_template=JSON_REPAIR_USER_TEMPLATE, output_mode=PromptOutputMode.JSON, metadata={"purpose": "repair_invalid_worldspec_json"}, ), PromptKind.WORLD_CRITIQUE.value: PromptTemplateSpec( name=PromptKind.WORLD_CRITIQUE.value, kind=PromptKind.WORLD_CRITIQUE, system_template=WORLD_CRITIQUE_SYSTEM_PROMPT, user_template=WORLD_CRITIQUE_USER_TEMPLATE, output_mode=PromptOutputMode.TEXT, metadata={"purpose": "critique_worldspec_quality"}, ), PromptKind.NARRATION.value: PromptTemplateSpec( name=PromptKind.NARRATION.value, kind=PromptKind.NARRATION, system_template=NARRATION_SYSTEM_PROMPT, user_template=NARRATION_USER_TEMPLATE, output_mode=PromptOutputMode.TEXT, metadata={"purpose": "simulation_context_to_narrative"}, ), PromptKind.NARRATION_REPAIR.value: PromptTemplateSpec( name=PromptKind.NARRATION_REPAIR.value, kind=PromptKind.NARRATION_REPAIR, system_template=NARRATION_REPAIR_SYSTEM_PROMPT, user_template=NARRATION_REPAIR_USER_TEMPLATE, output_mode=PromptOutputMode.TEXT, metadata={"purpose": "repair_unsuitable_narrative"}, ), } ) _PROMPT_REGISTRY: dict[str, PromptTemplateSpec] = dict(DEFAULT_PROMPT_TEMPLATES) def get_prompt_template(name: str | PromptKind) -> PromptTemplateSpec: """Return a registered prompt template by name.""" key = name.value if isinstance(name, PromptKind) else str(name) if key not in _PROMPT_REGISTRY: raise KeyError(f"Unknown prompt template {key!r}. Known templates: {sorted(_PROMPT_REGISTRY)}") return _PROMPT_REGISTRY[key] def list_prompt_templates() -> tuple[str, ...]: """Return registered prompt template names.""" return tuple(sorted(_PROMPT_REGISTRY.keys())) def register_prompt_template(template: PromptTemplateSpec, *, overwrite: bool = False) -> None: """Register a prompt template. Args: template: Template to register. overwrite: Whether to replace an existing template with the same name. Raises: KeyError: If a template exists and ``overwrite`` is false. """ if template.name in _PROMPT_REGISTRY and not overwrite: raise KeyError(f"Prompt template {template.name!r} is already registered") _PROMPT_REGISTRY[template.name] = template def prompt_registry() -> Mapping[str, PromptTemplateSpec]: """Return a read-only view of the active prompt registry.""" return MappingProxyType(_PROMPT_REGISTRY) def build_world_generation_prompt( user_prompt: str, *, constraints: Mapping[str, Any] | str | None = None, schema_version: str = DEFAULT_SCHEMA_VERSION, include_format_guide: bool = True, include_behavior_catalog: bool = True, include_policy_catalog: bool = True, include_example: bool = True, examples: Sequence[Mapping[str, Any] | str] | None = None, template_name: str = PromptKind.WORLD_GENERATION.value, ) -> PromptBundle: """Build a prompt for natural language to WorldSpec JSON generation.""" template = get_prompt_template(template_name) few_shot_examples = format_examples( examples if examples is not None else ([WORLD_GENERATION_FEW_SHOT_EXAMPLE] if include_example else []) ) return template.render( { "user_prompt": _require_text(user_prompt, field_name="user_prompt"), "constraints": format_constraints(constraints), "schema_version": schema_version, "format_guide": WORLD_DSL_FORMAT_GUIDE if include_format_guide else "", "behavior_catalog": BEHAVIOR_CATALOG if include_behavior_catalog else "", "policy_catalog": POLICY_CATALOG if include_policy_catalog else "", "few_shot_examples": few_shot_examples, } ) def build_json_repair_prompt( *, user_prompt: str, bad_response: str, error_message: str, include_format_guide: bool = True, template_name: str = PromptKind.JSON_REPAIR.value, ) -> PromptBundle: """Build a prompt for repairing invalid WorldSpec JSON.""" template = get_prompt_template(template_name) return template.render( { "user_prompt": _require_text(user_prompt, field_name="user_prompt"), "bad_response": _truncate_middle(str(bad_response), max_chars=12000), "error_message": str(error_message), "format_guide": WORLD_DSL_FORMAT_GUIDE if include_format_guide else "", } ) def build_world_critique_prompt( *, user_prompt: str, world_json: Mapping[str, Any] | str, validation_report: Mapping[str, Any] | str | None = None, template_name: str = PromptKind.WORLD_CRITIQUE.value, ) -> PromptBundle: """Build a prompt for critiquing a generated WorldSpec.""" template = get_prompt_template(template_name) return template.render( { "user_prompt": _require_text(user_prompt, field_name="user_prompt"), "world_json": _stringify_prompt_value(world_json), "validation_report": _stringify_prompt_value(validation_report or {}), } ) def build_narration_prompt( context: Mapping[str, Any], *, style: PromptStyle | str = PromptStyle.ANALYTICAL, audience: PromptAudience | str = PromptAudience.HACKATHON_JUDGE, template_name: str = PromptKind.NARRATION.value, ) -> PromptBundle: """Build a prompt for simulation narration.""" template = get_prompt_template(template_name) resolved_style = style.value if isinstance(style, PromptStyle) else str(style) resolved_audience = audience.value if isinstance(audience, PromptAudience) else str(audience) return template.render( { "style": resolved_style, "audience": resolved_audience, "context_json": compact_json(context, indent=2), } ) def build_narration_repair_prompt( *, context: Mapping[str, Any], bad_response: str, error_message: str, template_name: str = PromptKind.NARRATION_REPAIR.value, ) -> PromptBundle: """Build a prompt for repairing a narration response.""" template = get_prompt_template(template_name) return template.render( { "context_json": compact_json(context, indent=2), "bad_response": _truncate_middle(str(bad_response), max_chars=8000), "error_message": str(error_message), } ) def compact_json(value: Any, *, indent: int | None = 2, sort_keys: bool = True) -> str: """Serialize a value as JSON for prompt inclusion.""" return json.dumps( _json_safe(value), indent=indent, sort_keys=sort_keys, ensure_ascii=False, ) def format_constraints(constraints: Mapping[str, Any] | str | None) -> str: """Format UI or app constraints for prompt inclusion.""" if constraints is None or constraints == "": return "None." if isinstance(constraints, str): return constraints.strip() or "None." return compact_json(constraints, indent=2) def format_examples(examples: Sequence[Mapping[str, Any] | str] | None) -> str: """Format few-shot examples for prompt inclusion.""" if not examples: return "" blocks: list[str] = [] for index, example in enumerate(examples, start=1): if isinstance(example, str): example_text = example.strip() else: example_text = compact_json(example, indent=2) if not example_text: continue blocks.append(f"Example {index} valid WorldSpec JSON:\n{example_text}") return "\n\n".join(blocks) def behavior_catalog_text( *, behavior_names: Sequence[str] | None = None, include_default_catalog: bool = True, ) -> str: """Return behavior catalog text for prompts. Args: behavior_names: Optional dynamic behavior names from registries. include_default_catalog: Whether to include the static grouped catalog. Returns: Human-readable behavior catalog text. """ sections: list[str] = [] if include_default_catalog: sections.append(BEHAVIOR_CATALOG) if behavior_names: unique_names = sorted({str(name) for name in behavior_names if str(name)}) sections.append("Runtime behavior registry names:\n" + ", ".join(unique_names)) return "\n\n".join(sections) def policy_catalog_text( *, policy_names: Sequence[str] | None = None, include_default_catalog: bool = True, ) -> str: """Return policy catalog text for prompts.""" sections: list[str] = [] if include_default_catalog: sections.append(POLICY_CATALOG) if policy_names: unique_names = sorted({str(name) for name in policy_names if str(name)}) sections.append("Runtime policy registry names:\n" + ", ".join(unique_names)) return "\n\n".join(sections) def prompt_bundle_to_client_kwargs( bundle: PromptBundle, *, prefer_messages: bool = True, include_response_format: bool = True, ) -> dict[str, Any]: """Convert a prompt bundle into generic model-client kwargs. This helper is useful in root-level ``app.py`` when wiring a local or hosted SLM client. """ kwargs: dict[str, Any] = { "prompt": bundle.text, "system_prompt": bundle.system_prompt, "user_prompt": bundle.user_prompt, } if prefer_messages: kwargs["messages"] = bundle.message_dicts if include_response_format and bundle.output_mode is PromptOutputMode.JSON: kwargs["response_format"] = {"type": "json_object"} return kwargs def _stringify_prompt_value(value: Any) -> str: """Convert arbitrary values into prompt-safe strings.""" if value is None: return "" if isinstance(value, str): return value.strip() if isinstance(value, Mapping) or ( isinstance(value, Sequence) and not isinstance(value, (str, bytes)) ): return compact_json(value, indent=2) return str(value) def _require_text(value: str, *, field_name: str) -> str: """Validate non-empty text.""" text = str(value).strip() if not text: raise ValueError(f"{field_name} must not be empty") return text def _truncate_middle(value: str, *, max_chars: int) -> str: """Truncate long text while preserving beginning and end.""" if max_chars <= 0 or len(value) <= max_chars: return value if max_chars <= 20: return value[:max_chars] half = (max_chars - 15) // 2 return value[:half] + "\n...[truncated]...\n" + value[-half:] def _json_safe(value: Any) -> Any: """Return a JSON-safe representation of arbitrary prompt data.""" if value is None or isinstance(value, (str, bool)): return value if isinstance(value, int) and not isinstance(value, bool): return value if isinstance(value, float): if value != value or value in {float("inf"), float("-inf")}: return None return value if isinstance(value, Mapping): return {str(key): _json_safe(nested) for key, nested in value.items()} if isinstance(value, Sequence) and not isinstance(value, (str, bytes)): return [_json_safe(item) for item in value] if hasattr(value, "to_dict") and callable(value.to_dict): return _json_safe(value.to_dict()) if hasattr(value, "model_dump") and callable(value.model_dump): return _json_safe(value.model_dump(mode="json")) return str(value) __all__ = [ "BEHAVIOR_CATALOG", "DEFAULT_PROMPT_TEMPLATES", "DEFAULT_SCHEMA_VERSION", "JSON_REPAIR_SYSTEM_PROMPT", "JSON_REPAIR_USER_TEMPLATE", "NARRATION_REPAIR_SYSTEM_PROMPT", "NARRATION_REPAIR_USER_TEMPLATE", "NARRATION_SYSTEM_PROMPT", "NARRATION_USER_TEMPLATE", "POLICY_CATALOG", "PromptAudience", "PromptBundle", "PromptKind", "PromptMessage", "PromptOutputMode", "PromptRole", "PromptStyle", "PromptTemplateSpec", "WORLD_CRITIQUE_SYSTEM_PROMPT", "WORLD_CRITIQUE_USER_TEMPLATE", "WORLD_DSL_FORMAT_GUIDE", "WORLD_GENERATION_FEW_SHOT_EXAMPLE", "WORLD_GENERATION_SYSTEM_PROMPT", "WORLD_GENERATION_USER_TEMPLATE", "behavior_catalog_text", "build_json_repair_prompt", "build_narration_prompt", "build_narration_repair_prompt", "build_world_critique_prompt", "build_world_generation_prompt", "compact_json", "format_constraints", "format_examples", "get_prompt_template", "list_prompt_templates", "policy_catalog_text", "prompt_bundle_to_client_kwargs", "prompt_registry", "register_prompt_template", ]