From 5830fda5165bc4ab99485120c435628588cd5146 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Tue, 26 May 2026 03:31:01 +0000 Subject: [PATCH] Add HTTP and OpenAI integration documentation and rewrite Narratio contract --- README.md | 2 + docs/cli.md | 5 + docs/config.md | 5 + docs/integrations/http-api.md | 186 +++++++++++ docs/integrations/narratio.md | 336 ++++---------------- docs/integrations/openai-compatible-chat.md | 110 +++++++ 6 files changed, 372 insertions(+), 272 deletions(-) create mode 100644 docs/integrations/http-api.md create mode 100644 docs/integrations/openai-compatible-chat.md diff --git a/README.md b/README.md index c6064c0..13fd396 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ This command renders the prepared prompt and effective runtime settings without - [Configuration reference](docs/config.md) - [Operations guide](docs/operations.md) - [Troubleshooting](docs/troubleshooting.md) +- [HTTP API integration](docs/integrations/http-api.md) +- [OpenAI-compatible chat integration](docs/integrations/openai-compatible-chat.md) - [Narratio subprocess integration](docs/integrations/narratio.md) - [Architecture policy](docs/policy/architecture.md) - [Documentation roadmap](docs/roadmap/documentation.md) diff --git a/docs/cli.md b/docs/cli.md index b5bbe5e..21c9a74 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -18,6 +18,11 @@ go run ./cmd/scriptorium render \ - `scriptorium render`: prepare prompt only; write prepared-run output as `text` or `json`. - `scriptorium serve`: start the HTTP server. +Integration references: + +- HTTP contract: `docs/integrations/http-api.md` +- Narratio subprocess contract: `docs/integrations/narratio.md` + ## Common Argument Rules - `--config` is supported by `run`, `render`, and `serve`. diff --git a/docs/config.md b/docs/config.md index 798be04..3109dd9 100644 --- a/docs/config.md +++ b/docs/config.md @@ -204,3 +204,8 @@ Supported artifact reference types for request inputs are `file` and `inline`. - Profile examples: `profiles/` - Schema examples: `schemas/` - Input fixtures: `examples/fixtures/` + +## Integration References + +- Inbound HTTP contract: `docs/integrations/http-api.md` +- Outbound OpenAI-compatible contract: `docs/integrations/openai-compatible-chat.md` diff --git a/docs/integrations/http-api.md b/docs/integrations/http-api.md new file mode 100644 index 0000000..b281dae --- /dev/null +++ b/docs/integrations/http-api.md @@ -0,0 +1,186 @@ +# HTTP API Integration + +## Scope + +This document defines the implemented inbound HTTP contract for Scriptorium. + +Current scope is only: + +- `POST /v1/runs` + +For CLI behavior, see `docs/cli.md`. + +## Endpoint + +- Method: `POST` +- Path: `/v1/runs` +- Content type: JSON request/response + +Route behavior: + +- unknown path: `404 not_found` +- unsupported method on `/v1/runs`: `405 method_not_allowed` + +## Request Body + +```json +{ + "prompt_id": "generic.structured_events", + "profile_id": "local-quality", + "prompt_version": "1.0.0", + "inputs": { + "transcript": {"type": "file", "uri": "./examples/fixtures/transcript.md"}, + "glossary": {"type": "inline", "body": "party:\n - Rin"} + }, + "vars": { + "session_date": "2026-05-04" + }, + "model": { + "endpoint": "http://localhost:8000/v1", + "model": "gpt-4o-mini", + "temperature": 0.0, + "max_tokens": 800, + "top_p": 1.0, + "timeout_seconds": 120, + "reasoning_effort": "medium", + "api_key_env": "SCRIPTORIUM_API_KEY", + "extra_params": { + "route": "primary" + } + }, + "include_raw_output": false +} +``` + +Required fields: + +- `prompt_id` +- `inputs` (must contain at least one named input) + +Input reference types currently supported by runtime artifact loading: + +- `file` +- `inline` + +## Strict JSON Rules + +Request decoding uses strict JSON field checks: + +- unknown request fields are rejected with `400 invalid_json` +- unknown `model` fields are rejected with `400 invalid_json` +- raw API-key payload fields such as `api_key` are rejected as unknown fields + +## Success Response + +Status: `200 OK` + +Response shape: + +```json +{ + "artifact": { + "name": "output", + "content_type": "application/json", + "body": "{\"summary\":\"...\"}", + "uri": "", + "size": 123, + "hash": "..." + }, + "validation": { + "status": "passed", + "mode": "json_schema", + "errors": [], + "schema_path": "structured_events.schema.json", + "repair_attempts": 0, + "is_valid": true + }, + "metadata": { + "run_id": "...", + "prompt_id": "generic.structured_events", + "prompt_version": "1.0.0", + "prompt_hash": "...", + "rendered_prompt_hash": "...", + "selected_profile_id": "local-quality", + "model_name": "gpt-4o-mini", + "endpoint": "http://localhost:8000/v1", + "model_params": { + "endpoint": "http://localhost:8000/v1", + "model": "gpt-4o-mini", + "temperature": 0, + "max_tokens": 800, + "top_p": 1, + "timeout_seconds": 120, + "reasoning_effort": "medium", + "api_key_env": "SCRIPTORIUM_API_KEY", + "extra_params": { + "route": "primary" + } + }, + "input_hashes": { + "transcript": "..." + }, + "usage": { + "prompt_tokens": 11, + "completion_tokens": 22, + "total_tokens": 33 + }, + "start_time": "2026-05-04T12:00:00Z", + "end_time": "2026-05-04T12:00:01Z", + "duration_ms": 1000, + "validation_mode": "json_schema", + "validation_status": "passed", + "repair_attempts_used": 0 + } +} +``` + +`raw_model_output` is omitted by default. + +To include it, send: + +- `"include_raw_output": true` + +## Validation Failure Behavior + +Validation content failures do not map to HTTP error status. + +Behavior: + +- status remains `200 OK` +- `validation.status` is `failed` +- validation errors are returned in `validation.errors` + +## Error Responses + +Error body shape: + +```json +{ + "error": { + "code": "invalid_request", + "message": "prompt_id is required" + } +} +``` + +Current error mapping (non-exhaustive): + +- `400 invalid_json`: malformed JSON or unknown JSON fields +- `400 invalid_request`: missing/invalid request fields +- `400 profile_required`: no explicit `profile_id` and prompt has no `default_profile` +- `400 prompt_load_failed`: prompt definition invalid/unloadable +- `400 profile_load_failed`: profile invalid/unloadable +- `400 artifact_read_failed`: input artifact loading failed +- `400 prompt_render_failed`: template render failed +- `400 api_key_env_missing`: named API-key environment variable is missing +- `404 prompt_not_found` +- `404 profile_not_found` +- `502 llm_failed`: outbound model request failed +- `500 validation_runtime_failed`: validator runtime/schema-load failure +- `500 internal_error` + +## Security And Deployment Note + +The HTTP adapter has no built-in authentication or authorization. + +Deploy behind trusted controls (for example authenticated gateway/reverse proxy and network boundaries). diff --git a/docs/integrations/narratio.md b/docs/integrations/narratio.md index c7d6bfb..fe6346e 100644 --- a/docs/integrations/narratio.md +++ b/docs/integrations/narratio.md @@ -1,322 +1,114 @@ -# Narratio -> Scriptorium CLI Integration +# Narratio Subprocess Integration -## 1. Purpose +## Purpose -This document defines how Narratio should invoke Scriptorium through the **public CLI**. +This document defines the supported subprocess contract for Narratio invoking Scriptorium through the public CLI. -This is a **subprocess integration contract**, not an internal Go API contract. +This is a CLI contract, not an internal Go package integration. -## 2. Assumptions +## Supported Commands -- `scriptorium` is installed and available on `PATH`. -- Scriptorium is configured with `config.yml`. -- `config.yml` provides `prompt_dir`, `profile_dir`, and `schema_dir` as needed. -- Prompt and profile libraries are already deployed for the environment. -- Narratio provides prepared artifact files (for example polished transcript, glossary, previous recap, campaign notes). -- Initial integration is synchronous subprocess execution. -- Narratio remains the orchestrator. - -In normal operation, Narratio does not need to pass `--prompt-dir` and `--profile-dir` if they are supplied by Scriptorium config. - -Narratio may pass `--config ` when it must use a non-default Scriptorium config file. - -## 3. Core Commands Narratio May Call - -Primary commands for subprocess integration: +Narratio should invoke: - `scriptorium run` - `scriptorium render` -For production generation, use `scriptorium run`. +Use `run` for generation. -`scriptorium render` is for debugging, dry-runs, test assertions, and validating command construction without LLM execution. +Use `render` for preflight/debug output without LLM execution. -Note: `scriptorium serve` and HTTP API exist, but they are not the initial integration path. +## Recommended Invocation Shapes -## 4. Command Selection Guidance - -- Use `run` to generate an output artifact. -- Use `render` to inspect the prepared prompt and effective settings without calling the LLM. -- Use `render --format json` when Narratio/tests need structured prepare output. - -## 5. Recommended `run` Invocation Shape - -Production shape: +Run: ```bash scriptorium run \ --prompt \ - --input transcript= \ - --out + --input transcript= \ + --out ``` -Common optional additions: - -- `--config `: use a specific Scriptorium config file. -- `--profile `: override prompt default profile. -- `--var name=value` (repeatable): small metadata values. -- `--input name=path` (repeatable): additional named artifacts. -- `--timeout `: per-run timeout override. -- Runtime model override flags (`--llm-base-url`, `--model`, etc.) only for exceptional/operator-directed cases. - -## 6. Recommended `render` Invocation Shape - -Human-readable debug shape: +Render: ```bash scriptorium render \ --prompt \ - --input transcript= \ - --format text + --input transcript= \ + --format json ``` -Structured debug/test shape: +Narratio may add: -```bash -scriptorium render \ - --prompt \ - --input transcript= \ - --format json \ - --out -``` +- `--config ` +- `--profile ` +- repeatable `--input name=path` +- repeatable `--var name=value` +- runtime overrides when explicitly needed (`--model`, `--llm-base-url`, `--timeout`, etc.) -`render` does **not** call the LLM, does **not** validate model output, and does **not** perform repair. +## Config And Directory Behavior -## 7. Inputs +Narratio can rely on resolved app config or pass explicit paths. -- Pass inputs as repeated `--input name=path` flags. -- `name` must match the Prompt Definition input name. -- Prefer absolute paths, or paths relative to a working directory controlled by Narratio. -- Pass Audita output as the primary transcript input. -- Additional inputs may include glossary, previous recap, campaign notes, event logs, final state maps, or other prompt-specific artifacts. -- Scriptorium reads input files directly; Narratio does not need to inline file content for CLI use. +- default config search order: + 1. `/usr/local/etc/scriptorium/config.yml` + 2. `/etc/scriptorium/config.yml` +- explicit `--config` requires file existence and valid syntax +- CLI flags override config values -## 8. Variables +## Profile Selection -Use repeated `--var name=value` for small metadata values. +Profile selection follows runner behavior: -Typical examples: +1. explicit `--profile` +2. prompt `default_profile` +3. error if neither is available -- `session_date` -- `session_id` -- `campaign_name` -- `previous_session_id` -- `output_kind` +Narratio should treat prompt/profile IDs as deployment configuration, not hardcoded logic. -Large content belongs in input files, not `--var` values. +## Input And Variable Contract -## 9. Prompt IDs and Output Artifact Types +- Inputs use repeated `--input name=path`. +- Input names must match prompt definition input names. +- Variables use repeated `--var name=value` for small metadata values. +- Prefer file inputs for large content. -Narratio should treat prompt IDs as configuration, not hardcoded business logic. - -Narratio config may map stage/output names to prompt IDs, for example: - -- session recap prompt -- structured event extraction prompt -- glossary suggestion prompt -- player-facing summary prompt - -Prompt IDs used by Narratio should come from the deployed Scriptorium prompt library. - -## 10. Profiles - -- Prompts may declare `default_profile`. -- Narratio may omit `--profile` to use prompt default profile. -- Narratio may pass `--profile` to force profile selection. -- This enables environment/profile selection like `local-fast`, `local-quality`, `frontier`, `batch`, or test profiles. -- Profile names should generally be Narratio configuration values. - -## 11. Runtime Overrides - -Supported runtime override flags: - -- `--llm-base-url` -- `--model` -- `--api-key-env` -- `--temperature` -- `--max-tokens` -- `--top-p` -- `--timeout` - -Guidance: - -- Keep normal model/runtime settings in Execution Profiles. -- Use runtime overrides only for explicit per-run exceptions, tests, or operator overrides. -- Never pass raw API keys on the command line. -- `--api-key-env` names an environment variable; Narratio must ensure that variable is set in subprocess environment. - -## 12. Config Behavior - -- Default config path: `/etc/scriptorium/config.yml`. -- `--config ` overrides default path. -- Missing default config is allowed by Scriptorium. -- If `--config` is provided explicitly, the file must exist and be valid. -- CLI flags override `config.yml`. -- `config.yml` overrides built-in application defaults. - -Narratio can either: - -- rely on system default config path, or -- carry an explicit config path and pass `--config`. - -## 13. Environment Handling - -Subprocess environment recommendations: +## Environment Contract - Pass through required API-key environment variables referenced by `api_key_env`. -- Do not pass raw API keys as CLI arguments. -- Avoid logging full environment dumps. -- Capture stdout and stderr separately. -- Use a controlled working directory. -- Prefer absolute artifact paths. +- Never pass raw API keys via CLI arguments. +- Keep subprocess environment scoped to required variables. -## 14. Output Handling +## Output And Error Handling -For `scriptorium run`: +`run`: -- Use `--out` when Narratio needs durable artifact files. -- Without `--out`, artifact content is written to stdout. -- Preferred orchestration pattern: always use `--out`, then treat the file as stage output artifact. -- Capture stderr for diagnostics. +- stdout: artifact body unless `--out` is used +- `--out`: writes artifact to file +- stderr: success summary and errors -For `scriptorium render`: +`render`: -- Use `--out` to store render diagnostics. -- Use `--format json` when tests need to inspect selected profile, effective runtime settings, input hashes, prompt hash, and rendered messages. +- stdout: prepared-run output unless `--out` is used +- stderr: errors -## 15. Exit Status and Errors +Narratio should capture stdout and stderr separately. -Current CLI behavior (verified from implementation/tests): +## Exit Status Contract -- `0`: success. -- `1`: runtime/parse/config/load/render/generation/IO error. -- `2`: run completed but output validation failed (`ValidationFailed`). +- `0`: success +- `1`: parse/config/load/render/generation/IO/runtime error +- `2`: run completed but validation failed -Additional details: +A `run` exit code `2` can still produce output (stdout or `--out`). -- On `run`, output artifact write happens before exit code selection. If validation fails, artifact may still be written and exit code is `2`. -- `stderr` carries both errors and normal run summary output; non-empty stderr alone does not imply failure. -- `render` returns `0` on success and `1` on failures. +## Security Notes -Narratio should treat non-zero exit codes as failed stage execution, but may record generated artifact paths if a run exited `2` and output file exists. +- Treat generated artifacts and stderr logs as potentially sensitive. +- Avoid logging full rendered prompts by default in production contexts. +- Use controlled output paths and access controls for persisted artifacts. -## 16. Recommended Narratio Integration Pattern +## Canonical References -1. Build CLI args from Narratio stage configuration. -2. Use subprocess context cancellation/timeout. -3. Pass absolute input paths. -4. Pass `--out` to a session-scoped artifact path. -5. Add `--var` metadata values. -6. Optionally add `--config`. -7. Optionally add `--profile`. -8. Ensure required API-key env vars are present. -9. Run subprocess synchronously. -10. Capture stdout/stderr separately. -11. On success, store output artifact path and invocation metadata in stage artifacts. -12. On failure, store exit code and stderr diagnostics in stage status. - -## 17. Suggested Narratio Configuration Shape - -Illustrative (not required schema): - -```yaml -scriptorium: - config_path: /etc/scriptorium/config.yml - stages: - session_recap: - prompt_id: dnd.session_recap - profile_id: local-quality # optional - inputs: [transcript, glossary, previous_recap] - vars: [session_id, session_date, campaign_name] - output_path_template: artifacts/{session_id}/session_recap.md - timeout: 2m - render_debug: false -``` - -The key idea: map Narratio stage/artifact names to prompt ID, optional profile, expected inputs, and output destination. - -## 18. Testing Strategy for Narratio Integration - -- Use `scriptorium render --format json` to verify command construction without LLM calls. -- Use dedicated test prompt/profile libraries for integration tests. -- Use small fixture transcripts. -- Verify missing-input failure behavior. -- Verify prompt `default_profile` behavior. -- Verify explicit `--profile` override behavior. -- Verify `--config` behavior (default and explicit). -- Verify output file creation when `--out` is used. -- Verify stderr capture on failures. -- Avoid real API keys in tests. - -## 19. Security and Privacy Notes - -- Never pass raw API keys on command line. -- Do not log full rendered prompts by default; transcripts may contain sensitive content. -- Avoid logging prompt content unless explicit debug mode is enabled. -- Treat generated artifacts as potentially sensitive. -- Use session-scoped, access-controlled output paths. -- `api_key_env` names should come from environment management, not embedded secrets. - -## 20. Initial D&D Artifact Generation Examples - -These are examples only. Use prompt IDs from the deployed prompt library. - -Session recap: - -```bash -scriptorium run \ - --prompt dnd.session_recap \ - --input transcript=/work/session-42/transcript.polished.md \ - --input glossary=/work/session-42/glossary.yml \ - --out /work/session-42/artifacts/session_recap.md -``` - -Structured events: - -```bash -scriptorium run \ - --prompt dnd.structured_events \ - --input transcript=/work/session-42/transcript.polished.md \ - --out /work/session-42/artifacts/structured_events.json -``` - -Glossary suggestions: - -```bash -scriptorium run \ - --prompt dnd.glossary_suggestions \ - --input transcript=/work/session-42/transcript.polished.md \ - --input previous_recap=/work/session-41/artifacts/session_recap.md \ - --out /work/session-42/artifacts/glossary_suggestions.md -``` - -Player-facing summary: - -```bash -scriptorium run \ - --prompt dnd.player_summary \ - --input transcript=/work/session-42/transcript.polished.md \ - --input structured_events=/work/session-42/artifacts/structured_events.json \ - --out /work/session-42/artifacts/player_summary.md -``` - -## 21. Non-Goals - -Initial Narratio integration should not: - -- call Scriptorium internal Go packages -- use HTTP API as the primary path -- expect Scriptorium to read S3 refs directly -- make Scriptorium responsible for Narratio stage state -- make Scriptorium responsible for notification -- require Scriptorium to understand D&D workflow semantics beyond prompt definitions - -## 22. Future Extension Notes - -Possible later extensions: - -- HTTP API integration -- S3 artifact references if Scriptorium adds S3 reader support -- storing render diagnostics alongside generated artifacts -- token budgeting/prompt-size checks -- batch execution if Scriptorium later adds batch support +- CLI behavior: `docs/cli.md` +- Config behavior: `docs/config.md` +- Operations and failure handling: `docs/operations.md`, `docs/troubleshooting.md` diff --git a/docs/integrations/openai-compatible-chat.md b/docs/integrations/openai-compatible-chat.md new file mode 100644 index 0000000..41e0792 --- /dev/null +++ b/docs/integrations/openai-compatible-chat.md @@ -0,0 +1,110 @@ +# OpenAI-Compatible Chat Integration + +## Scope + +This document defines the outbound LLM contract implemented by `internal/llm/openai_compatible_client.go`. + +It documents only fields and behaviors currently serialized by code. + +## Endpoint Construction + +Request endpoint is built as: + +1. choose base URL: + - `GenerateRequest.Target.Endpoint` if set + - otherwise client config `BaseURL` +2. trim trailing slash +3. append `/chat/completions` + +Example: + +- base URL: `http://localhost:8000/v1` +- final URL: `http://localhost:8000/v1/chat/completions` + +## Request Fields Sent + +Serialized JSON fields: + +- `model` (required after fallback resolution) +- `messages` (role/content pairs from rendered prompt) +- `temperature` (only when non-zero) +- `max_tokens` (only when non-zero) +- `top_p` (only when non-zero) +- `response_format` (only when structured output is provided) + +Structured output is currently `json_schema` only, serialized as: + +```json +{ + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "...", + "strict": true, + "schema": {"type": "object"} + } + } +} +``` + +## Authentication Header + +If `Target.APIKeyEnv` is set: + +- resolve environment variable value at request time +- set `Authorization: Bearer ` + +If the environment variable is unset/empty: + +- request fails before HTTP call (`ErrInvalidRequest`) + +If `Target.APIKeyEnv` is empty: + +- no `Authorization` header is sent + +## Timeout Behavior + +Base timeout comes from client configuration. + +Per-request override: + +- if `Target.TimeoutSeconds > 0`, use that value for request timeout +- if `Target.TimeoutSeconds < 0`, request is rejected (`ErrInvalidRequest`) + +## Response Expectations + +Expected successful response shape (subset used): + +- `choices[0].message.content` +- `usage.prompt_tokens` +- `usage.completion_tokens` +- `usage.total_tokens` + +Malformed response conditions include: + +- invalid JSON +- empty `choices` +- empty `choices[0].message.content` + +Malformed responses return `ErrMalformedResponse`. + +## Error Handling + +- network/request-construction failures: `ErrRequestFailed` +- non-2xx HTTP status: `ErrUnexpectedStatus` (includes status code and trimmed response body snippet) +- malformed response shape/content: `ErrMalformedResponse` + +## Unsupported Or Non-Serialized Fields + +The following fields may exist in profile/effective settings but are not currently serialized into outbound chat-completions payloads: + +- `reasoning_effort` +- `extra_params` + +No built-in retries, tool-calls, or multi-request payload modes are implemented in this client. + +## Relationship To Runner + +When prompt validation mode is `json_schema`, runner prepares a structured-output schema spec and passes it to the client as `StructuredOutput`. + +The client only serializes the provider request payload; it does not load schema files itself.