WorldSmithAI / app.py
Srishti280992's picture
Update app.py
a57fb0d verified
Raw
History Blame Contribute Delete
41.3 kB
"""
Root-level Gradio app for WorldSmithAI.
This file is intended to live directly beside callbacks.py in the repository
root. It does not assume an app/ package or folder.
The UI is intentionally thin:
- app.py defines the Gradio interface.
- callbacks.py runs generation, parsing, validation, world construction,
simulation, visualization, metrics, and narration.
- The simulation engine remains generic and DSL-driven.
Environment variables:
WORLDSMITHAI_MODEL_ID:
Optional Hugging Face model id for model-generated DSL.
HF_TOKEN:
Optional Hugging Face token for private or gated models.
WORLDSMITHAI_OUTPUT_DIR:
Optional directory for generated GIFs, MP4s, charts, and images.
WORLDSMITHAI_DEFAULT_STEPS:
Optional default simulation step count.
WORLDSMITHAI_MAX_FRAMES:
Optional maximum animation frame count.
WORLDSMITHAI_LOG_LEVEL:
Optional logging level. Defaults to INFO.
"""
from __future__ import annotations
import json
import logging
import os
import traceback
from collections.abc import Mapping
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import gradio as gr
from callbacks import (
CallbackConfig,
CallbackResult,
apply_step_override,
callback_failure_result,
generate_world_from_prompt,
load_example_callback,
parse_constraints,
run_worldsmith_pipeline,
simulate_dsl_pipeline,
validate_dsl_callback,
)
from dsl.validator import validate_world_spec
APP_TITLE = "WorldSmithAI"
APP_SUBTITLE = "Production-Level Agent-Based World Simulation Framework"
APP_VERSION = "0.1.0"
DEFAULT_PROMPT = (
"Create a compact research ecosystem where scientists, engineers, reviewers, "
"and funding agents exchange knowledge, compete for attention, collaborate, "
"and adapt their goals over time."
)
PROMPT_EXAMPLES = [
[
"A medieval civilization with merchants, rulers, artisans, scholars, guards, "
"taxes, trade, regulations, and resource pressure."
],
[
"A startup economy with founders, investors, customers, operators, competitors, "
"market bidding, adoption dynamics, and shifting goals."
],
[
"A fantasy world with mages, dragons, healers, merchants, mana resources, "
"alliances, communication, negotiation, and magical infrastructure."
],
[
"A transport network with hubs, carriers, dispatchers, chargers, passengers, "
"queues, routes, deliveries, and congestion."
],
[
"A power grid world with generators, storage nodes, consumers, regulators, "
"energy resources, subsidies, enforcement, and rebalancing."
],
[
"A space colony with colonists, engineers, botanists, navigators, miners, "
"oxygen resources, habitat expansion, and survival goals."
],
]
CUSTOM_CSS = """
#worldsmith-header {
padding: 1.1rem 1.25rem;
border-radius: 1rem;
background: linear-gradient(135deg, rgba(80, 80, 120, 0.12), rgba(120, 120, 160, 0.08));
border: 1px solid rgba(120, 120, 160, 0.18);
}
.worldsmith-small {
font-size: 0.92rem;
opacity: 0.82;
}
.worldsmith-status textarea {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
}
"""
logging.basicConfig(
level=os.getenv("WORLDSMITHAI_LOG_LEVEL", "INFO").upper(),
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
logger = logging.getLogger(__name__)
@dataclass(frozen=True)
class AppModelStatus:
"""Status of the optional model client used by the UI."""
enabled: bool
model_id: str | None
message: str
def to_markdown(self) -> str:
"""Return display Markdown for model status."""
if self.enabled:
return f"**Model client:** configured with `{self.model_id}`."
return f"**Model client:** not configured. {self.message}"
class OptionalHFInferenceClient:
"""Small adapter around huggingface_hub.InferenceClient.
This adapter tries both chat-completion and text-generation APIs and raises
detailed errors instead of hiding provider/model failures.
"""
def __init__(
self,
model_id: str,
token: str | None = None,
provider: str | None = None,
) -> None:
"""Initialize the optional Hugging Face inference adapter."""
from huggingface_hub import InferenceClient
self.model_id = model_id.strip().strip('"').strip("'").strip()
self.provider = None if provider is None or not str(provider).strip() else str(provider).strip()
api_key = (
token
or os.getenv("HF_TOKEN")
or os.getenv("HF_HUB_TOKEN")
or os.getenv("HUGGINGFACEHUB_API_TOKEN")
)
if not api_key:
raise RuntimeError(
"HF_TOKEN is required for Hugging Face Inference Providers. "
"Create a Hugging Face token with Inference Providers permission "
"and add it as a Space secret named HF_TOKEN."
)
if self.provider:
self.client = InferenceClient(
model=self.model_id,
provider=self.provider,
api_key=api_key,
)
else:
self.client = InferenceClient(
model=self.model_id,
api_key=api_key,
)
def generate(
self,
prompt: str | None = None,
messages: list[dict[str, str]] | None = None,
max_new_tokens: int = 2048,
temperature: float = 0.2,
**_: Any,
) -> str:
"""Generate text using Hugging Face chat or text-generation APIs."""
errors: list[str] = []
if messages:
text = self._try_chat_completion(
messages,
max_new_tokens=max_new_tokens,
temperature=temperature,
errors=errors,
)
if text:
return text
text = self._try_openai_compatible_chat(
messages,
max_new_tokens=max_new_tokens,
temperature=temperature,
errors=errors,
)
if text:
return text
if prompt:
text = self._try_text_generation(
prompt,
max_new_tokens=max_new_tokens,
temperature=temperature,
errors=errors,
)
if text:
return text
error_text = "\n".join(errors) if errors else "No inference method was attempted."
raise RuntimeError(
"InferenceClient did not return generated text.\n\n"
f"Model id: {self.model_id}\n\n"
f"Attempted methods and errors:\n{error_text}"
)
def _try_chat_completion(
self,
messages: list[dict[str, str]],
*,
max_new_tokens: int,
temperature: float,
errors: list[str],
) -> str | None:
"""Try InferenceClient.chat_completion()."""
chat_completion = getattr(self.client, "chat_completion", None)
if not callable(chat_completion):
errors.append("chat_completion: method not available on InferenceClient")
return None
try:
response = chat_completion(
messages=messages,
max_tokens=max_new_tokens,
temperature=temperature,
)
text = _extract_model_text(response)
if text:
return text
errors.append(
"chat_completion: response had no extractable text. "
f"response_type={response.__class__.__name__}, response={_short_repr(response)}"
)
return None
except Exception as exc:
errors.append(f"chat_completion: {exc.__class__.__name__}: {exc}")
return None
def _try_openai_compatible_chat(
self,
messages: list[dict[str, str]],
*,
max_new_tokens: int,
temperature: float,
errors: list[str],
) -> str | None:
"""Try InferenceClient.chat.completions.create()."""
chat = getattr(self.client, "chat", None)
completions = getattr(chat, "completions", None)
create = getattr(completions, "create", None)
if not callable(create):
errors.append("chat.completions.create: method not available on InferenceClient")
return None
call_variants = (
{
"model": self.model_id,
"messages": messages,
"max_tokens": max_new_tokens,
"temperature": temperature,
},
{
"messages": messages,
"max_tokens": max_new_tokens,
"temperature": temperature,
},
)
for kwargs in call_variants:
try:
response = create(**kwargs)
text = _extract_model_text(response)
if text:
return text
errors.append(
"chat.completions.create: response had no extractable text. "
f"kwargs_keys={list(kwargs.keys())}, "
f"response_type={response.__class__.__name__}, "
f"response={_short_repr(response)}"
)
except Exception as exc:
errors.append(
"chat.completions.create: "
f"kwargs_keys={list(kwargs.keys())}, "
f"{exc.__class__.__name__}: {exc}"
)
return None
def _try_text_generation(
self,
prompt: str,
*,
max_new_tokens: int,
temperature: float,
errors: list[str],
) -> str | None:
"""Try InferenceClient.text_generation()."""
text_generation = getattr(self.client, "text_generation", None)
if not callable(text_generation):
errors.append("text_generation: method not available on InferenceClient")
return None
call_variants = (
{
"prompt": prompt,
"max_new_tokens": max_new_tokens,
"temperature": temperature,
"return_full_text": False,
},
{
"prompt": prompt,
"max_new_tokens": max_new_tokens,
"return_full_text": False,
},
{
"prompt": prompt,
"max_new_tokens": max_new_tokens,
},
)
for kwargs in call_variants:
try:
response = text_generation(**kwargs)
text = _extract_model_text(response)
if text:
return text
errors.append(
"text_generation: response had no extractable text. "
f"kwargs_keys={list(kwargs.keys())}, "
f"response_type={response.__class__.__name__}, "
f"response={_short_repr(response)}"
)
except Exception as exc:
errors.append(
"text_generation: "
f"kwargs_keys={list(kwargs.keys())}, "
f"{exc.__class__.__name__}: {exc}"
)
return None
def build_optional_model_client() -> tuple[Any | None, AppModelStatus]:
"""Build the optional model client from environment variables."""
raw_model_id = (
os.getenv("WORLDSMITHAI_MODEL_ID")
or os.getenv("WORLD_SMITH_MODEL_ID")
or os.getenv("HF_MODEL_ID")
)
token = (
os.getenv("HF_TOKEN")
or os.getenv("HF_HUB_TOKEN")
or os.getenv("HUGGINGFACEHUB_API_TOKEN")
)
provider = (
os.getenv("WORLDSMITHAI_PROVIDER")
or os.getenv("HF_INFERENCE_PROVIDER")
or None
)
if not raw_model_id:
return None, AppModelStatus(
enabled=False,
model_id=None,
message="Set WORLDSMITHAI_MODEL_ID to enable model-generated DSL.",
)
model_id = raw_model_id.strip().strip('"').strip("'").strip()
logger.info(
"WorldSmithAI model configuration: raw_model_id=%r sanitized_model_id=%r",
raw_model_id,
model_id,
)
if not model_id:
return None, AppModelStatus(
enabled=False,
model_id=None,
message="WORLDSMITHAI_MODEL_ID was set but empty after sanitization.",
)
try:
client = OptionalHFInferenceClient(
model_id=model_id,
token=token,
provider=provider,
)
return client, AppModelStatus(
enabled=True,
model_id=model_id,
message="Model client configured.",
)
except Exception as exc:
logger.warning("Optional model client could not be configured: %s", exc)
return None, AppModelStatus(
enabled=False,
model_id=model_id,
message=(
f"WORLDSMITHAI_MODEL_ID={model_id} was found, but the client "
f"could not be initialized: {exc}. The app will use deterministic fallback."
),
)
MODEL_CLIENT, MODEL_STATUS = build_optional_model_client()
def model_health_check_ui() -> str:
"""Return a quick model-client health check for the Gradio UI."""
if MODEL_CLIENT is None:
return (
"Model client is NOT configured.\n\n"
f"Runtime status: {MODEL_STATUS.message}\n\n"
"Check that WORLDSMITHAI_MODEL_ID is set in Space settings."
)
provider_label = (
getattr(MODEL_CLIENT, "provider", None)
or os.getenv("WORLDSMITHAI_PROVIDER")
or os.getenv("HF_INFERENCE_PROVIDER")
or "auto"
)
try:
response = MODEL_CLIENT.generate(
prompt='Return exactly this JSON object and nothing else: {"ok": true}',
messages=[
{
"role": "system",
"content": "You return compact JSON only.",
},
{
"role": "user",
"content": 'Return exactly this JSON object and nothing else: {"ok": true}',
},
],
max_new_tokens=64,
temperature=0.0,
)
return (
"Model client is configured and returned text.\n\n"
f"Model id: {MODEL_STATUS.model_id}\n\n"
f"Provider: {provider_label}\n\n"
f"Response:\n{response}"
)
except Exception as exc:
logger.exception("Model health check failed")
return (
"Model client is configured but inference failed.\n\n"
f"Model id: {MODEL_STATUS.model_id}\n\n"
f"Provider: {provider_label}\n\n"
f"Error type: {exc.__class__.__name__}\n"
f"Error:\n{exc}"
)
def app_callback_config(
*,
steps: int | float | str | None,
animation_format: str,
) -> CallbackConfig:
"""Create callback configuration from UI values and environment defaults."""
default_steps = _env_int("WORLDSMITHAI_DEFAULT_STEPS", 60)
max_frames = _env_int("WORLDSMITHAI_MAX_FRAMES", 80)
output_dir = os.getenv("WORLDSMITHAI_OUTPUT_DIR") or None
return CallbackConfig(
output_dir=output_dir,
steps=_coerce_optional_int(steps) if steps is not None else default_steps,
max_animation_frames=max(1, max_frames),
animation_format=animation_format,
)
def run_from_prompt_ui(
prompt: str,
steps: int | float,
constraints: str,
animation_format: str,
use_configured_model: bool,
) -> tuple[Any, ...]:
"""Run the full prompt-to-simulation pipeline for the main UI tab."""
client = MODEL_CLIENT if use_configured_model else None
config = app_callback_config(steps=steps, animation_format=animation_format)
try:
result = run_worldsmith_pipeline(
prompt=prompt,
constraints=parse_constraints(constraints),
client=client,
config=config,
)
return result_to_ui_tuple(result)
except Exception as exc:
logger.exception("Prompt simulation failed")
return failure_to_ui_tuple(exc)
def generate_dsl_only_ui(
prompt: str,
constraints: str,
use_configured_model: bool,
steps: int | float,
) -> tuple[str, str, str]:
"""Generate DSL only, without running simulation."""
client = MODEL_CLIENT if use_configured_model else None
config = app_callback_config(steps=steps, animation_format="gif")
try:
generation = generate_world_from_prompt(
prompt=prompt,
constraints=parse_constraints(constraints),
client=client,
config=config,
)
spec = apply_step_override(generation.spec, _coerce_optional_int(steps))
report = validate_world_spec(
spec,
config=config.resolved_validation_config(),
)
status = (
f"Generated `{spec.id}` with {len(spec.agents)} agent(s), "
f"{len(spec.resources)} resource(s), and "
f"{len(spec.behavior_names)} behavior reference(s). "
f"Validation: {len(report.errors)} error(s), {len(report.warnings)} warning(s)."
)
return (
spec.to_json_string(indent=2, exclude_none=True),
_safe_json_dumps(report.to_dict()),
status,
)
except Exception as exc:
logger.exception("DSL generation failed")
return "", "{}", f"DSL generation failed: {exc}"
def simulate_dsl_ui(
dsl_json: str,
steps: int | float,
animation_format: str,
use_configured_model_for_narration: bool,
) -> tuple[Any, ...]:
"""Run simulation from user-supplied DSL JSON."""
client = MODEL_CLIENT if use_configured_model_for_narration else None
config = app_callback_config(steps=steps, animation_format=animation_format)
try:
result = simulate_dsl_pipeline(
dsl_json=dsl_json,
client=client,
config=config,
)
return result_to_ui_tuple(result)
except Exception as exc:
logger.exception("DSL simulation failed")
return failure_to_ui_tuple(exc, world_spec_json=dsl_json)
def validate_dsl_ui(dsl_json: str) -> tuple[str, str]:
"""Validate DSL JSON in the validation tab."""
return validate_dsl_callback(dsl_json)
def load_example_ui(example_name: str | None) -> tuple[str, str]:
"""Load an example JSON file into the DSL editor."""
if not example_name:
return "", "Choose an example first."
examples = discover_examples()
path = examples.get(example_name)
if path is None:
return "", f"Unknown example: {example_name}"
return load_example_callback(path)
def clear_main_outputs() -> tuple[Any, ...]:
"""Clear main-tab outputs."""
return (
"",
"",
gr.update(value=None, visible=False),
gr.update(value=None, visible=False),
None,
None,
None,
"",
"",
"",
)
def result_to_ui_tuple(result: CallbackResult) -> tuple[Any, ...]:
"""Map CallbackResult to the app output component order."""
animation_image, animation_video = animation_path_updates(result.animation_path)
return (
result.world_spec_json,
result.validation_json,
animation_image,
animation_video,
result.population_chart_path,
result.resource_chart_path,
result.final_image_path,
_markdown_narrative(result.narrative),
result.metrics_json,
result.status_message,
)
def failure_to_ui_tuple(
error: BaseException,
*,
world_spec_json: str = "",
) -> tuple[Any, ...]:
"""Map an exception to a safe UI output tuple."""
result = callback_failure_result(error, world_spec_json=world_spec_json)
details = {
"success": False,
"error_type": error.__class__.__name__,
"error": str(error),
"traceback": traceback.format_exc(),
"callback_result": result.to_dict(),
}
return (
result.world_spec_json,
result.validation_json,
gr.update(value=None, visible=False),
gr.update(value=None, visible=False),
None,
None,
None,
_markdown_narrative(result.narrative),
_safe_json_dumps(details),
result.status_message,
)
def animation_path_updates(path: str | None) -> tuple[Any, Any]:
"""Return Gradio updates for GIF/image and MP4/video animation components."""
if not path:
return gr.update(value=None, visible=False), gr.update(value=None, visible=False)
suffix = Path(path).suffix.lower()
if suffix in {".mp4", ".webm", ".mov", ".m4v"}:
return gr.update(value=None, visible=False), gr.update(value=path, visible=True)
return gr.update(value=path, visible=True), gr.update(value=None, visible=False)
def discover_examples() -> dict[str, Path]:
"""Discover JSON example files in the root-level examples directory."""
examples_dir = Path(__file__).resolve().parent / "examples"
if not examples_dir.exists():
return {}
return {
path.stem.replace("_", " ").title(): path
for path in sorted(examples_dir.glob("*.json"))
}
def build_demo() -> gr.Blocks:
"""Build and return the Gradio Blocks demo."""
examples = discover_examples()
example_names = list(examples.keys())
default_steps = _env_int("WORLDSMITHAI_DEFAULT_STEPS", 60)
with gr.Blocks(
title=f"{APP_TITLE} — Agent-Based World Simulation",
css=CUSTOM_CSS,
analytics_enabled=False,
) as demo:
gr.Markdown(
f"""
<div id="worldsmith-header">
# {APP_TITLE}
### {APP_SUBTITLE}
WorldSmithAI converts natural language into a validated JSON DSL, builds a deterministic Python
agent-based world, simulates emergent behavior, and returns animation, charts, metrics, and narration.
<span class="worldsmith-small">
No species or domains are hardcoded. A farmer, dragon, scientist, vehicle, startup, power node,
or civilization actor is a generic Agent with state, memory, behaviors, and policy.
</span>
</div>
"""
)
with gr.Accordion("Runtime status", open=False):
gr.Markdown(MODEL_STATUS.to_markdown())
gr.Markdown(
"If no model client is configured, the app still runs with the deterministic fallback DSL generator. "
"Set WORLDSMITHAI_MODEL_ID in the Space environment to enable model-generated DSL."
)
model_health_button = gr.Button("Check model connection")
model_health_output = gr.Textbox(
label="Model health check",
lines=8,
interactive=False,
)
model_health_button.click(
fn=model_health_check_ui,
inputs=[],
outputs=[model_health_output],
)
with gr.Tabs():
with gr.Tab("Generate + Simulate"):
with gr.Row():
with gr.Column(scale=1):
prompt = gr.Textbox(
label="World prompt",
value=DEFAULT_PROMPT,
lines=8,
placeholder=(
"Describe any agent-based world: farm, civilization, research lab, "
"startup economy, fantasy realm, transport system..."
),
)
constraints = gr.Textbox(
label="Optional constraints",
value='{"max_agents": 6, "demo_size": "small"}',
lines=4,
placeholder='Example: {"max_agents": 6, "steps": 80}',
)
with gr.Row():
steps = gr.Slider(
minimum=1,
maximum=250,
value=default_steps,
step=1,
label="Simulation steps",
)
animation_format = gr.Dropdown(
choices=["gif", "mp4"],
value="gif",
label="Animation format",
)
use_model = gr.Checkbox(
label="Use configured model client when available",
value=MODEL_CLIENT is not None,
interactive=True,
)
with gr.Row():
run_button = gr.Button("Generate world + run simulation", variant="primary")
clear_button = gr.Button("Clear outputs")
gr.Examples(
examples=PROMPT_EXAMPLES,
inputs=[prompt],
label="Prompt examples",
)
with gr.Column(scale=1):
status = gr.Textbox(
label="Status",
lines=3,
interactive=False,
elem_classes=["worldsmith-status"],
)
narrative = gr.Markdown(label="Narrative summary")
with gr.Tab("Visual outputs"):
with gr.Row():
animation_image = gr.Image(
label="Simulation animation GIF",
type="filepath",
visible=True,
)
animation_video = gr.Video(
label="Simulation animation MP4",
visible=False,
)
with gr.Row():
final_image = gr.Image(
label="Final world state",
type="filepath",
)
population_chart = gr.Image(
label="Population chart",
type="filepath",
)
resource_chart = gr.Image(
label="Resource chart",
type="filepath",
)
with gr.Tab("Generated DSL + diagnostics"):
world_spec_json = gr.Code(
label="Generated WorldSpec JSON",
language="json",
lines=24,
)
validation_json = gr.Code(
label="Validation report",
language="json",
lines=16,
)
metrics_json = gr.Code(
label="Metrics and run diagnostics",
language="json",
lines=24,
)
main_outputs = [
world_spec_json,
validation_json,
animation_image,
animation_video,
population_chart,
resource_chart,
final_image,
narrative,
metrics_json,
status,
]
run_button.click(
fn=run_from_prompt_ui,
inputs=[prompt, steps, constraints, animation_format, use_model],
outputs=main_outputs,
show_progress=True,
)
clear_button.click(
fn=clear_main_outputs,
inputs=[],
outputs=main_outputs,
)
with gr.Tab("Generate DSL only"):
with gr.Row():
with gr.Column(scale=1):
dsl_prompt = gr.Textbox(
label="World prompt",
value=DEFAULT_PROMPT,
lines=8,
)
dsl_constraints = gr.Textbox(
label="Optional constraints",
value='{"max_agents": 6}',
lines=4,
)
dsl_steps = gr.Slider(
minimum=1,
maximum=250,
value=default_steps,
step=1,
label="Simulation steps to put in DSL",
)
dsl_use_model = gr.Checkbox(
label="Use configured model client when available",
value=MODEL_CLIENT is not None,
)
generate_dsl_button = gr.Button("Generate DSL", variant="primary")
with gr.Column(scale=1):
generated_dsl_status = gr.Textbox(
label="Status",
lines=3,
interactive=False,
)
generated_dsl_validation = gr.Code(
label="Validation report",
language="json",
lines=16,
)
generated_dsl = gr.Code(
label="Generated WorldSpec JSON",
language="json",
lines=28,
)
generate_dsl_button.click(
fn=generate_dsl_only_ui,
inputs=[dsl_prompt, dsl_constraints, dsl_use_model, dsl_steps],
outputs=[generated_dsl, generated_dsl_validation, generated_dsl_status],
show_progress=True,
)
with gr.Tab("Simulate existing DSL"):
with gr.Row():
with gr.Column(scale=1):
example_dropdown = gr.Dropdown(
choices=example_names,
value=example_names[0] if example_names else None,
label="Load example JSON",
interactive=bool(example_names),
)
load_example_button = gr.Button("Load selected example")
example_status = gr.Textbox(
label="Example status",
lines=2,
interactive=False,
)
existing_dsl = gr.Code(
label="WorldSpec JSON",
language="json",
lines=28,
value="",
)
with gr.Row():
existing_steps = gr.Slider(
minimum=1,
maximum=250,
value=default_steps,
step=1,
label="Simulation steps override",
)
existing_animation_format = gr.Dropdown(
choices=["gif", "mp4"],
value="gif",
label="Animation format",
)
existing_use_model = gr.Checkbox(
label="Use configured model client for narration when available",
value=False,
)
simulate_dsl_button = gr.Button("Simulate DSL", variant="primary")
with gr.Column(scale=1):
existing_status = gr.Textbox(
label="Status",
lines=3,
interactive=False,
)
existing_narrative = gr.Markdown(label="Narrative summary")
with gr.Tab("DSL simulation visuals"):
with gr.Row():
existing_animation_image = gr.Image(
label="Simulation animation GIF",
type="filepath",
visible=True,
)
existing_animation_video = gr.Video(
label="Simulation animation MP4",
visible=False,
)
with gr.Row():
existing_final_image = gr.Image(
label="Final world state",
type="filepath",
)
existing_population_chart = gr.Image(
label="Population chart",
type="filepath",
)
existing_resource_chart = gr.Image(
label="Resource chart",
type="filepath",
)
with gr.Tab("DSL simulation diagnostics"):
existing_validation = gr.Code(
label="Validation report",
language="json",
lines=16,
)
existing_metrics = gr.Code(
label="Metrics and run diagnostics",
language="json",
lines=24,
)
load_example_button.click(
fn=load_example_ui,
inputs=[example_dropdown],
outputs=[existing_dsl, example_status],
)
simulate_dsl_button.click(
fn=simulate_dsl_ui,
inputs=[
existing_dsl,
existing_steps,
existing_animation_format,
existing_use_model,
],
outputs=[
existing_dsl,
existing_validation,
existing_animation_image,
existing_animation_video,
existing_population_chart,
existing_resource_chart,
existing_final_image,
existing_narrative,
existing_metrics,
existing_status,
],
show_progress=True,
)
with gr.Tab("Validate DSL"):
validation_input = gr.Code(
label="WorldSpec JSON to validate",
language="json",
lines=28,
)
validate_button = gr.Button("Validate DSL", variant="primary")
validation_output = gr.Code(
label="Validation report",
language="json",
lines=24,
)
validation_status = gr.Textbox(
label="Status",
lines=3,
interactive=False,
)
validate_button.click(
fn=validate_dsl_ui,
inputs=[validation_input],
outputs=[validation_output, validation_status],
)
with gr.Tab("About"):
gr.Markdown(
"""
## WorldSmithAI architecture
WorldSmithAI follows this pipeline:
Natural language prompt
→ SLM-generated JSON DSL
→ Pydantic schema validation
→ Semantic DSL validation
→ WorldFactory
→ World / Scheduler / Agents / Policies / Behaviors
→ Metrics / Visualization / Narration
→ Gradio UI
### Important design constraint
The simulation engine is deterministic Python. The model generates JSON only, never Python code.
### UI outputs
- Animation: GIF by default; MP4 if selected and ffmpeg is available.
- Population chart: grouped by agent type.
- Resource chart: grouped by resource type and weighted by amount.
- Narrative: deterministic by default, optionally model-assisted if a client is configured.
- Diagnostics: includes DSL validation, factory build report, metric history, artifact paths, and generation mode.
### Suggested root-level Space layout
app.py
callbacks.py
requirements.txt
core/
behaviors/
policies/
dsl/
factory/
metrics/
visualization/
llm/
examples/
"""
)
return demo
def _short_repr(value: Any, *, max_chars: int = 1200) -> str:
"""Return a bounded repr for diagnostics."""
text = repr(value)
if len(text) <= max_chars:
return text
return text[: max_chars - 3] + "..."
def _extract_model_text(response: Any) -> str | None:
"""Extract text from common model response shapes."""
if response is None:
return None
if isinstance(response, str):
return response.strip()
if isinstance(response, bytes):
return response.decode("utf-8").strip()
if isinstance(response, Mapping):
for key in ("text", "content", "generated_text", "output", "response"):
value = response.get(key)
if value is not None:
return _extract_model_text(value)
choices = response.get("choices")
if isinstance(choices, list) and choices:
return _extract_model_text(choices[0])
message = response.get("message")
if isinstance(message, Mapping):
return _extract_model_text(message.get("content"))
for attr in ("text", "content", "generated_text", "output", "response"):
value = getattr(response, attr, None)
if value is not None:
return _extract_model_text(value)
choices = getattr(response, "choices", None)
if choices:
return _extract_model_text(choices[0])
message = getattr(response, "message", None)
if message is not None:
return _extract_model_text(message)
return None
def _coerce_optional_int(value: int | float | str | None) -> int | None:
"""Coerce UI numeric values to optional int."""
if value is None or value == "":
return None
return int(float(value))
def _env_int(name: str, default: int) -> int:
"""Read an integer environment variable with a safe default."""
raw = os.getenv(name)
if raw is None or raw == "":
return default
try:
return int(float(raw))
except ValueError:
logger.warning("Invalid integer environment variable %s=%r", name, raw)
return default
def _safe_json_dumps(value: Any) -> str:
"""Serialize values as pretty JSON for Gradio Code components."""
return json.dumps(
_json_safe(value),
indent=2,
sort_keys=True,
ensure_ascii=False,
)
def _json_safe(value: Any) -> Any:
"""Return JSON-friendly representation of arbitrary values."""
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, (list, tuple)):
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)
def _markdown_narrative(text: str) -> str:
"""Return narrative text formatted for Markdown display."""
cleaned = str(text or "").strip()
if not cleaned:
return "No narrative was generated."
return cleaned
demo = build_demo()
app = demo
if __name__ == "__main__":
demo.queue().launch()