Validate, repair, retry LLM structured output (JSON/YAML/TOML/python-literal)
Project description
arcanada-output-guard
Validate, repair, and retry LLM structured output (JSON / YAML / TOML / Python literal) — battle-tested 15-strategy repair chain with two-pass orchestrator. Python sibling of @arcanada/output-guard with shared golden fixtures and byte-equal parity gate.
Install
pip install arcanada-output-guard
# or
uv add arcanada-output-guard
# or
poetry add arcanada-output-guard
Requires Python ≥ 3.11. Runtime deps: pyyaml, tomli-w, pydantic, jsonschema, json-repair.
Quick start
from output_guard import repair
# Functional API — format-only repair, no schema
result = repair('{"name": "Alice", "age": 30,}', "json")
print(result.data) # {'name': 'Alice', 'age': 30}
print(result.repaired) # True
print(result.pass_) # 'A'
print(result.strategies_applied) # ['fix-commas']
Stateful API with pydantic schema
from pydantic import BaseModel
from output_guard import OutputGuard
class User(BaseModel):
name: str
age: int
guard = OutputGuard(format="json", schema=User)
result = guard.repair('```json\n{"name": "Bob", "age": 25}\n```')
print(result.data) # User(name='Bob', age=25)
print(result.pass_) # 'A' or 'B'
Output format support
- JSON — strict + relaxed parsing with common error recovery (trailing commas, unquoted keys, single→double quote, true/false casing, fenced blocks).
- YAML — structural repair for indentation + missing keys.
- TOML — fixing malformed tables and value assignments.
- Python literal —
dict/listliterals via safeast.literal_eval. - Auto-detect — pass
format="auto"to infer from content.
RepairResult semantics
@dataclass(frozen=True, slots=True)
class RepairResult:
repaired: bool # False when raw was already valid
raw: str # original input as supplied
pass_: Literal["A", "B"] # "A" = combined-apply; "B" = isolating fallback
data: Any = None # parsed + (optionally) schema-validated payload
strategies_applied: list[str] = [] # ordered list of repair strategies invoked
MAX_RETRIES exhaustion signals via ParseError raise, not via a pass_ value.
Async wrapper for LLM calls
import asyncio
from pydantic import BaseModel
from output_guard import guarded_generate, GuardedGenerateOptions
class Answer(BaseModel):
result: int
async def my_llm(prompt: str, history=None) -> str:
# call your LLM client here (OpenAI / Anthropic / OpenRouter / MC / etc.)
return '{"result": 42}'
async def main():
out = await guarded_generate(GuardedGenerateOptions(
generate=my_llm,
prompt='Return JSON {"result": <integer>}',
schema=Answer,
fmt="json",
max_retries=3,
))
print(out.data) # Answer(result=42)
print(out.pass_) # 'A' | 'B'
print(out.strategies_applied)
asyncio.run(main())
Error handling
from output_guard import (
ParseError,
SchemaValidationError,
RepairError,
GuardedGenerationError,
)
try:
result = guard.repair(bad_input)
except SchemaValidationError:
... # input parsed but doesn't satisfy schema
except ParseError:
... # MAX_RETRIES exhausted — all strategies failed
except RepairError:
... # a repair strategy itself raised
except GuardedGenerationError:
... # guarded_generate gave up after max_retries LLM round-trips
Schema adapters
from output_guard import pydantic_adapter, jsonschema_adapter
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
# pydantic v2
adapter = pydantic_adapter(User)
# JSON Schema (dict)
adapter = jsonschema_adapter({
"type": "object",
"properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
"required": ["name", "age"],
})
Batch API
from output_guard import repair_batch
results = repair_batch(
['{"a": 1,}', '{"b": 2,}', '{"c": 3,}'],
fmt="json",
)
# returns list[RepairResult]; per-item failure surfaces as RepairResult with repaired=False
Documentation
- Source: https://github.com/Arcanada-one/output-guard/tree/main/packages/python
- Design (two-pass orchestrator, strategy ordering): https://github.com/Arcanada-one/datarim/blob/main/datarim/creative/creative-CONN-0087-algorithm-strategy-ordering.md
- TypeScript sibling: https://www.npmjs.com/package/@arcanada/output-guard
License
MIT — see LICENSE.
Жизнь одного человека имеет значение / One human life matters
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file arcanada_output_guard-0.1.1.tar.gz.
File metadata
- Download URL: arcanada_output_guard-0.1.1.tar.gz
- Upload date:
- Size: 23.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
deabb6f0f921cdc3952020e088a45acb7ea6aa1ceea46708004662c892f58d2e
|
|
| MD5 |
324560a66b1a7a901dfb9363ae83fbfc
|
|
| BLAKE2b-256 |
999335722be1aaae34b43ca00579cc809bcb528c6f65640fb12c7dc45f5d7a21
|
File details
Details for the file arcanada_output_guard-0.1.1-py3-none-any.whl.
File metadata
- Download URL: arcanada_output_guard-0.1.1-py3-none-any.whl
- Upload date:
- Size: 26.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65d761cd038c7a5c66000eea753565ea32549e92585b89fc20846d2fb4d317b6
|
|
| MD5 |
0d885291b9f73d93f5aa7c17972825d0
|
|
| BLAKE2b-256 |
808c563d933cb362c5178ab498cccb8b3e00b20473f1852ab667d5cc9397de44
|