Architecture

On this page

Two independent layers

The package contains two things that can be used separately:

  1. Single-format parser toolkit (base_parser.py + json_parser.py, yaml_parser.py, csv_parser.py, list_parser.py, structured_parser.py, pydantic_parser.py) — each parser implements the BaseOutputParser abstract interface (parse(), get_format_instructions()) independently. They share no runtime state with the orchestrator; pydantic_parser.py and json_parser.py are the only two that depend on each other (PydanticOutputParser wraps a JSONOutputParser internally).
  2. The orchestrator (parser.py's OutputParser), which composes FormatDetector, OutputFixer, OutputValidator, and RetryHandler into one pipeline. It does not import or reuse the single-format parser classes — it has its own internal module-level parse functions (_parse_json, _parse_yaml, _parse_csv, _parse_markdown_table, _parse_list, _parse_xml, _parse_tool_call, _parse_key_value, _parse_plain_text) mapped by OutputFormat in the _FORMAT_PARSERS dict.

The orchestrator pipeline

OutputParser.parse() runs _run_pipeline(), which executes stages in a fixed order, short-circuiting as soon as a stage succeeds:

Data flow of types

Initialization flow of OutputParser

OutputParser.__init__ builds its sub-components eagerly (not lazily):

  1. Stores schema, fields, mode, expected_format, llm_fn, max_retries, enable_safety, enable_cache, original_prompt as instance attributes.
  2. Creates a FormatDetector() (stateless, no config).
  3. Calls self._build_validator() → constructs an OutputValidator(fields=self.fields, pydantic_model=self.schema, enable_safety=self.enable_safety).
  4. Calls self._build_fixer() → constructs an OutputFixer(required_fields=[required field names], field_defaults={name: default}, llm_fn=self.llm_fn).
  5. Constructs a RetryHandler only if both llm_fn is truthy and max_retries > 0; otherwise self._retry = None and the retry stage of the pipeline is skipped entirely.
  6. Initializes self._cache = {} (a plain in-process dict, keyed by MD5 hash of the input text via _cache_key).

Caching model

Caching is per-OutputParser-instance, in-memory, and keyed by hashlib.md5(text). It only stores results where result.ok is True (i.e. result.data is not None). There is no TTL, size cap, or eviction policy — cache entries live until clear_cache() is called or the instance is garbage collected. This means:

  • Long-running processes that pass many unique texts through the same OutputParser instance will grow the cache unboundedly.
  • Caching is not shared across threads/processes beyond normal Python object-sharing semantics — it is a plain dict, so concurrent writes from multiple threads are not synchronized (see Concepts → Thread safety).

Extension points

  • Custom schema/fields: pass a Pydantic schema class or a list of FieldSchema to OutputParser.
  • Custom business rules: build your own OutputValidator(rules=[ValidationRule(...), ...]) and either use it standalone, or subclass/monkeypatch OutputParser._build_validator (no built-in constructor argument exists on OutputParser for injecting custom ValidationRules directly — this is a determinable limitation of the current implementation).
  • Custom LLM backend: any Callable[[str], str] works as llm_fn — it's used both by OutputFixer (LLM_REFORMAT strategy) and RetryHandler.
  • Composable single-format parsers: ChainedParser([ParserA(), ParserB()]) tries each in order; AutoFixingParser(parser, fixer=callable) retries a single parser with a fixer callback between attempts.

Related pages