Concepts

On this page

Output formats (OutputFormat)

Defined in schema.py. Represents the detected or expected shape of an LLM output:

ValueMeaning
JSONA JSON object or array.
YAMLYAML mapping/sequence.
CSVDelimited tabular text with a header row.
MARKDOWN_TABLEA GitHub-flavored Markdown pipe table.
NUMBERED_LIST1. item, 2) item, a. item, etc.
BULLETED_LIST- item, * item, • item, etc.
KEY_VALUEFreeform key: value or key = value lines.
XMLPaired <tag>value</tag> elements.
TOOL_CALLReAct-style (Action: ... / Action Input: ...) or OpenAI-style function-call JSON ({"name": ..., "arguments": ...}).
PLAIN_TEXTNo detectable structure — wrapped as {"answer": text} by the orchestrator's _parse_plain_text.
MIXEDMultiple formats present in one output (falls back to plain-text handling in the orchestrator's _FORMAT_PARSERS map — there is currently no dedicated mixed-format extraction logic).
UNKNOWNDetector could not confidently classify the text.

Parse modes (ParseMode)

ModeEffect inside OutputParser._run_pipeline
STRICTIf every parse/fix/retry attempt fails, parser.parse() raises ParseError instead of falling back. Use when a bad result is worse than an exception (e.g. you want your caller to explicitly handle failure).
LENIENT (default)On total failure, returns a graceful_fallback() dict ({"answer": raw_text, "sources": [], "confidence": 0.0, "_parse_error": True, ...}) instead of raising.
SEMANTICBefore attempting structural parsing, tries _semantic_parse(), which asks llm_fn to extract structured JSON from freeform text via a purpose-built prompt. Requires llm_fn to be set — if llm_fn is None, semantic mode silently falls through to structural parsing (see _attempt_parse).
TOOL_CALLIntended for tool/function-call outputs. Note: this enum value does not currently change control flow inside _run_pipeline beyond being stored in trace.parse_mode — actual tool-call parsing is driven by setting expected_format=OutputFormat.TOOL_CALL (as create_tool_call_parser() does), not by mode alone.

Format detection scoring

FormatDetector (in format_detector.py) does not try parsers one at a time and catch exceptions — it runs independent regex/structural scorers for every format and assigns each a confidence score in [0.0, 1.0]:

  • _score_json — 0.99 if json.loads() succeeds directly; 0.65 if the text merely starts/ends with {}/[]; 0.45 for an embedded {"key": pattern.
  • _score_yaml — scores on count of key: value lines (≥3 lines → +0.5) and list items (≥2 → +0.25), with a 0.3 penalty if the text starts with {/[ (to avoid misclassifying JSON as YAML, since both use colons).
  • _score_csv — checks each of , ; \t | as a candidate delimiter, requires ≥75% row-to-row consistency in delimiter count across the first 4 data rows.
  • _score_markdown_table — 0.92 if a proper | header | + |---| separator row is found; 0.5 for merely pipe-delimited lines.
  • _score_numbered_list / _score_bulleted_list — 0.85/0.82 for ≥3 matched items, 0.4/0.38 for 1–2.
  • _score_key_value — 0.60 if ≥2 key: value matches and no YAML list markers present (to avoid double-counting with YAML).
  • _score_xml — 0.88 for ≥2 paired tags, 0.35 for a bare self-closing/opening tag.
  • _score_tool_call — 0.82 with an Action:/Tool:/Function: line plus an argument block, 0.55 without; 0.90 for OpenAI-style "name": ..., "arguments" JSON.
  • _score_plain_text — always returns a baseline score (max(0.1, 0.55 - 0.1 * structure_signal_count)) so there is always at least one fallback candidate.

A code fence with an explicit language tag (```json, ```yaml, ```csv, ```xml) short-circuits with high confidence (0.90–0.95) via _detect_from_code_fence, and is combined with — not simply replacing — the structural scores.

detect() returns just the top candidate; rank() returns all candidates above _MIN_CONFIDENCE = 0.15, deduplicated by format and sorted descending; detect_with_confidence() returns (format, confidence) for the top candidate.

Fix strategies (FixStrategy)

Applied in order by OutputFixer.fix() (see api/fixer.md for full detail):

  1. REGEX_REPAIR — strips markdown fences, fixes trailing commas, converts single→double quotes, quotes bare keys; if still invalid, tries extracting the first complete JSON block, then progressively truncating from the right to find a valid JSON prefix.
  2. FIELD_INJECTION — if the text is valid-enough JSON to parse into a dict, injects None/default values for any required_fields that are missing.
  3. FALLBACK_PARSE — extracts key: value pairs from plain text via regex and re-serializes as JSON.
  4. LLM_REFORMAT — (only if llm_fn is set) sends the malformed text to the LLM with an explicit "reformat this as X" prompt.

If none succeed, fix() returns the original text unchanged with FixStrategy.NONE.

Retry strategies (RetryStrategy)

Applied in order by RetryHandler.run() (see api/retry_handler.md):

  1. STRICT_FORMAT — re-sends the original question plus the previous error, a preview of the bad response, and the parser's format instructions.
  2. JSON_STRICT — demands JSON-only output and includes a generated schema example (from the Pydantic model if available, else from required_fields).
  3. SIMPLIFIED — asks a stripped-down version of the question, requesting just {"answer": "...", "confidence": ...}.
  4. GRACEFUL_FAIL — terminal state; no further LLM call is made, and RetryResult.success = False is returned.

Each attempt sleeps backoff_seconds * attempt_idx before calling llm_fn (linear backoff, default 0.5s per step).

Validation layers

OutputValidator.validate() (see api/validator.md) runs up to four layers, all independent and all additive to the results list:

  1. Field completeness/typing — only runs if fields were provided and data is a dict. Checks required fields present, then soft type-coerces mismatches to WARNING where possible (e.g. "3" for an int field), else FAILED. Also emits WARNING for any unexpected extra keys not declared in fields.
  2. Business rules — arbitrary ValidationRule predicates you supply.
  3. Safety checks — regex scans for hallucination-admission phrases (WARNING), sensitive data leakage patterns like credit-card-shaped numbers, SSNs, emails, password=/api_key= strings, and Bearer tokens (FAILED), and prompt-injection markers like "ignore previous instructions" (FAILED). Only runs if enable_safety=True (default).
  4. Pydantic structural validation — only runs if a pydantic_model was supplied and data is a dict; re-validates by instantiating the model.

Thread safety

None of the classes in this package (OutputParser, FormatDetector, OutputValidator, OutputFixer, RetryHandler, or any BaseOutputParser subclass) implement explicit locking. Specifically:

  • OutputParser._cache is a plain dict — concurrent parse() calls from multiple threads on the same instance with overlapping text can race on cache writes (a lost update is possible, not a crash, since dict assignment is atomic in CPython, but you should not rely on the cache being consistent under concurrent write-heavy load).
  • All other components hold only configuration state set at __init__ time and are read-only during parse()/detect()/validate()/fix()/run(), making them safe to share across threads as long as you don't mutate their config after construction.
  • Async: BaseOutputParser.async_parse() (in base_parser.py) runs the synchronous parse() in a thread-pool executor via asyncio.get_event_loop().run_in_executor(None, self.parse, text). This makes it safe to call from an async context, but does not make parse() itself non-blocking — it just moves the blocking call off the event loop thread. The OutputParser orchestrator (parser.py) does not define an async entry point at all; only BaseOutputParser subclasses (the single-format toolkit) get async_parse() for free.

Related pages