Main Application¶
FastAPI application bootstrap and compatibility routing surface.
app.main now owns application setup, simple endpoints, import handlers, and router mounting for the split route modules. Keep this page because it remains the public entrypoint for uvicorn app.main:app.
app/main.py¶
FastAPI application entrypoint for the Axis Descriptor Lab.
This module is a thin routing layer — each route handler orchestrates calls to domain modules and returns the result. All business logic lives in dedicated modules:
Domain modules¶
pipeworks_ipc– IPC normalisation and SHA-256 hash functions (shared library).app.schema– Pydantic v2 request / response models.app.chat_renderer– Synchronous HTTP wrapper around Ollama.app.signal_isolation– NLP pipeline for content-word delta.app.transformation_map– Clause-level sentence alignment and diffing.app.micro_indicators– Structural pattern indicators for transformation map rows.app.save_package– Manifest builder, zip archive, import/export.app.relabel_policy– Policy table and score-to-label mapping.app.save_formatting– Markdown builders and folder-name generator.app.file_loaders– Example and prompt file loading/listing.app.mud_server_client– Synchronous HTTP client for the mud server lab API.
- Run with:
uvicorn app.main:app –reload –host 127.0.0.1 –port 8242
Endpoints¶
GET / → serves index.html HEAD / → returns the SPA shell headers without a body GET /pipeline-build → serves index.html with Pipeline Build preselected GET /api/examples → list of available example names GET /api/examples/{name} → returns a single example JSON payload GET /api/prompts → list of available prompt names (optionally by purpose) GET /api/prompts/{name} → returns a single prompt’s text content GET /api/models → returns locally available Ollama models POST /api/generate → send axis payload to Ollama, return description POST /api/log → persist a run log entry under the configured log root POST /api/relabel → (optional) recompute labels from policy rules POST /api/analyze-delta → content-word delta between two texts POST /api/transformation-map → clause-level replacement pairs GET /api/system-prompt → return the default system prompt as plain text POST /api/save → save session state under the configured writable save root GET /api/save/{name}/export → download a save package as a zip POST /api/import → import a save package from a zip upload POST /api/import_chat → import a chat save package from a zip upload POST /api/mud/login → proxy login to mud server POST /api/mud/logout → clear mud server session GET /api/mud/mode → return runtime chat mode + available options POST /api/mud/mode → switch runtime chat mode GET /api/mud/session → return auth status + translation mode GET /api/mud/worlds → proxy list worlds from mud server GET /api/mud/world-config/{id}→ proxy world config from mud server GET /api/mud/world-prompts/{id} → proxy world prompt templates from mud server GET /api/mud/world-image-policy-bundle/{id} → proxy canonical image policy bundle metadata POST /api/mud/compile-image-prompt → proxy canonical image prompt compilation POST /api/mud/select-world → store selected world_id
Architecture notes¶
All blocking I/O (file reads, Ollama HTTP calls) lives in regular
defroute handlers. FastAPI automatically runs those in a threadpool so the async event loop is never blocked.Static files are served by Starlette’s StaticFiles middleware.
Jinja2Templates renders index.html (single page — the JS takes over).
A simple JSONL log file under the configured writable log root provides the Pipe-Works audit trail without any database dependency.
- app.main.close_runtime_clients()[source]¶
Close all shared HTTP clients created by the application runtime.
- app.main.index(request)[source]¶
Serve the single-page application shell.
Passes the default model name and the list of locally available Ollama models into the Jinja2 template so the frontend can pre-populate its model selector without an extra API round-trip.
- app.main.pipeline_build_page(request)[source]¶
Serve the shared SPA shell with the Pipeline Build page preselected.
- app.main.list_examples()[source]¶
Return a sorted list of example names (without the .json extension) that are stored in app/examples/.
The frontend uses this to populate its example dropdown.
- app.main.get_example(name)[source]¶
Return the parsed JSON for a named example.
- Parameters:
name (Example stem, e.g. "proud_operator".)
- Returns:
The raw example JSON object (validated loosely by Pydantic when the
frontend loads it into the textarea).
- Return type:
- app.main.list_prompts(purpose=None)[source]¶
Return a sorted list of prompt names (without the .txt extension) that are stored in the grouped
app/prompts/tree.The frontend uses this to populate its prompt library dropdown, allowing users to browse only the prompt family relevant to the current page.
- Parameters:
purpose ({"character_description", "chat_translation"} | None) – Optional prompt-group filter. When omitted, prompt names from every local prompt group are returned.
- app.main.get_prompt(name, purpose=None)[source]¶
Return the text content of a named prompt file as plain text.
Uses
PlainTextResponseto match the existing/api/system-promptendpoint pattern. The frontend loads this into the system prompt override textarea.- Parameters:
name (Prompt stem, e.g. "system_prompt_v01".)
purpose ({"character_description", "chat_translation"} | None) – Optional prompt-group filter. When provided, lookup is limited to that prompt family.
- Return type:
The raw prompt text (plain text, not JSON).
- app.main.get_models(host=None)[source]¶
Query an Ollama instance and return all model names it has pulled.
- Parameters:
host (Optional Ollama server URL (query param). When omitted the) – server-side default (
OLLAMA_HOSTenv var) is used.unreachable (Returns an empty list if Ollama is)
to (allowing the frontend)
input. (fall back to a manual text)
- app.main.generate(req)[source]¶
Core endpoint: serialise the axis payload to JSON, attach the system prompt, call Ollama, and return the generated paragraph.
The system prompt is taken from the request body if provided; otherwise the default Character Description prompt is loaded from
app/prompts/character_description/. This lets the frontend experiment with custom prompts without restarting the server.- Parameters:
req (GenerateRequest – full request body (payload + model settings).)
- Return type:
GenerateResponse containing the generated text and metadata.
- Raises:
HTTPException(502) if Ollama returns an HTTP error. –
HTTPException(504) if the Ollama request times out. –
HTTPException(500) for any other unexpected error. –
- app.main.log_run(payload, output, model, temperature, max_tokens, system_prompt=None)[source]¶
Append a structured log entry to the configured run log file.
Each line in the JSONL file is one complete LogEntry serialised as compact JSON. The file can be opened in any JSONL-aware tool (jq, pandas, etc.) for drift analysis.
When
system_promptis provided, the entry includes IPC hashes (system_prompt_hash,output_hash,ipc_id). When omitted,output_hashis still computed but the prompt-dependent fields are set to None for backward compatibility with older frontend versions.- Parameters:
payload (The AxisPayload used in the run.)
output (The LLM-generated text.)
model (Ollama model identifier.)
temperature (Sampling temperature used.)
max_tokens (Token budget used.)
system_prompt (The system prompt used (optional). When provided,) – enables full IPC chain in the log entry.
- Return type:
The complete LogEntry that was persisted.
- app.main.relabel(payload)[source]¶
Optional “auto-label” endpoint (Strategy 2 from spec §9.1).
Applies a simple piecewise score → label mapping for each known axis so the lab can demonstrate policy drift as well as LLM drift. Unknown axes are left unchanged.
Delegates to
app.relabel_policy.apply_relabel_policy()which owns the policy table and mapping logic.- Parameters:
payload (Current AxisPayload (axes with scores, possibly stale labels).)
- Return type:
Updated AxisPayload with labels recomputed from scores.
- app.main.analyze_delta(req)[source]¶
Signal Isolation Layer endpoint.
Takes two text strings (baseline A and current B), runs both through the NLP pipeline (tokenise, lemmatise, filter stopwords), and returns the set difference as alphabetically sorted word lists.
This endpoint surfaces meaningful lexical pivots by filtering structural noise. It is deterministic: same inputs always produce the same outputs.
The LLM is not involved — this is pure programmatic text analysis.
- Parameters:
req (DeltaRequest) – Contains
baseline_textandcurrent_text.- Returns:
Alphabetically sorted
removedandaddedcontent-lemma lists.- Return type:
- app.main.transformation_map(req)[source]¶
Transformation Map endpoint with micro-indicators.
Takes two text strings (baseline A and current B), runs sentence-aware alignment followed by token-level diffing, and returns clause-level replacement pairs. Each row is then classified with structural micro-indicators (compression, embodiment shift, intensity change, etc.) using deterministic heuristics.
This fills the gap between word-level diff (too granular) and content-word delta (structure-blind) by showing what chunk of text was replaced by what chunk at the clause scale, annotated with structural pattern labels.
The LLM is not involved — this is pure programmatic text analysis.
- Parameters:
req (TransformationMapRequest) – Contains
baseline_text,current_text, and optionalindicator_configfor tuning heuristic thresholds.- Returns:
Ordered list of
TransformationMapRowreplacement pairs, each annotated with zero or more micro-indicator labels.- Return type:
- async app.main.import_save(file)[source]¶
Accept an uploaded save-package zip, validate it, and return structured state for the frontend to restore a complete session.
The endpoint reads the uploaded file, enforces a maximum upload size, validates the zip structure and manifest checksums (if present), then extracts plain text from the Markdown files so the frontend can populate the UI directly without parsing.
- Parameters:
file (The uploaded zip file (multipart form data).)
- Returns:
ImportResponse
- Return type:
Everything the frontend needs to restore session state.
:raises HTTPException(400) : If the file is not a valid zip, exceeds size limits,: or fails checksum validation. :raises HTTPException(422) : If required files (metadata.json, payload.json,: system_prompt.md) are missing from the zip.
- async app.main.import_chat_save(file)[source]¶
Accept an uploaded chat save-package zip and return structured state for the frontend to restore a complete chat translation session.
Extracts character payloads, model settings, system prompt, and the historical game log so the frontend can rebuild sliders, settings, and the in-game log panel without any further parsing.
- Parameters:
file (The uploaded zip file (multipart form data).)
- Returns:
ChatImportResponse
- Return type:
Everything the frontend needs to restore chat state.
:raises HTTPException(400) : If the file is not a valid zip, exceeds size limits,: or fails checksum validation. :raises HTTPException(422) : If metadata.json is missing from the zip.: