Designing Robust Parser Contracts for LLM Outputs

Authors
  • avatar
    Name
    Nino
    Occupation
    Senior Tech Editor

Most developers beginning their journey with Large Language Models (LLMs) follow a predictable path: they write a prompt, ask for JSON, perhaps provide a schema, and call it a day. While this covers the 'happy path,' it fails to address the fundamental instability of non-deterministic AI. The real challenge doesn't lie in the prompt—it lies in the code that must consume, trust, and act upon the model's output.

When building the import pipeline for a CRM like Anguardia, which processes AI-generated prospect research, it became clear that the prompt was only 20% of the solution. The remaining 80% was the Parser Contract. A parser contract is a set of deterministic rules that treat LLM output not as a reliable data source, but as a messy stream that requires rigorous validation, versioning, and graceful degradation.

The Markdown Dossier Approach

While JSON is the industry standard, many enterprise workflows benefit from a hybrid format like Markdown. It is human-readable, allows the LLM to 'think' in a structured way, and is surprisingly easy to parse with regular expressions or specialized libraries. In our CRM use case, we defined a 'Dossier' format:

<!-- anguardia-dossier v1 -->

# Dossier: <Company Name>

## Company

- Industry: <industry>
- Website: <url>
- Location: <city or region>
- Source: <cold | referral | inbound | research>

## People

| Name | Role | Email | Phone | LinkedIn |
| ---- | ---- | ----- | ----- | -------- |

## Suggested tasks

- [ ] <task title> | due: <YYYY-MM-DD, optional>

## Suggested outreach

<the first message, under 150 words>

To ensure high-speed processing and reliability across different models like DeepSeek-V3 or Claude 3.5 Sonnet, we use n1n.ai as our API aggregator. This allows us to switch models without breaking the parser, as long as the contract remains intact.

Rule 1: Never Throw on Malformed Input

Traditional software engineering teaches us to throw an error when a contract is violated. In the world of LLMs, this is a recipe for a broken user experience. If a model fails to close a bracket or skips a header, throwing an exception forces the user to 'try again,' wasting tokens and time.

Instead, the parseDossier function should always return a result object accompanied by a warnings array.

/** Deterministic Dossier v1 parse. Always returns a dossier object + warnings. */
export function parseDossier(text: string): ParseResult {
  const warnings: string[] = []
  const hasMarker = textContainsDossierMarker(text)

  if (!hasMarker) {
    warnings.push('Dossier marker not detected. Parsing best-effort.')
  }

  // Logic continues to extract whatever it can find...
  return { data: dossier, warnings }
}

By using n1n.ai, you can test how different models handle these partial failures and optimize your parser accordingly.

Rule 2: Reject and Report, Don't Coerce

One of the most dangerous things a parser can do is 'guess' what the model meant. If the LLM adds a field like Twitter: @handle when the schema only expects Website, a naive parser might try to shove the Twitter handle into the Website field. This leads to data corruption.

if (!KNOWN_COMPANY_KEYS.has(key)) {
  warnings.push(`Unknown company field ignored: ${bullet[1].trim()}`)
  continue
}

If the data is malformed (e.g., a date not in YYYY-MM-DD format), drop the data and log a warning. A missing date is a minor inconvenience; a wrong date is a business failure. This is especially critical when using high-performance APIs through n1n.ai where throughput is high and manual oversight is low.

Rule 3: Double-Layer Honesty Constraints

We often prompt models with instructions like "Never invent contact details." However, models hallucinate. The parser must act as the second layer of this honesty contract. If the prompt asks for a blank field when data is missing, the parser must strictly enforce that nullity.

If the model returns "Unknown" or "N/A" in a phone number field, the parser should recognize these as null values rather than valid strings. Two independent layers agreeing that "blank is better than plausible" is the only way to maintain a clean database.

Rule 4: Versioning at the Boundary

Products evolve. Your data schema will change. Instead of trying to migrate every LLM-generated record in your database when you update your prompt, use version markers. An HTML comment at the top of the output is a cheap, durable way to version your format.

export const DOSSIER_MARKER_LEGACY = '<!-- founder-os-dossier v1 -->'
export const DOSSIER_MARKER = '<!-- anguardia-dossier v1 -->'

const version = detectVersion(text) // Handle v1, v2, etc.

Implementation Guide: Building the Pipeline

To implement this effectively, follow these steps:

  1. Define the Shape: Choose Markdown for readability or JSON for strictness.
  2. Select a Multi-Model Gateway: Use n1n.ai to ensure your parser works across OpenAI, Anthropic, and DeepSeek models.
  3. Write the Regex/Parser: Build a state machine or regex-based extractor that populates a partial object.
  4. Diagnostic UI: Display the warnings array to the end-user so they know if the AI missed something.

By shifting your focus from 'better prompting' to 'better parsing,' you build systems that are resilient to the inherent quirks of artificial intelligence. The prompt gets the output roughly right; the parser makes it safe to automate.

Get a free API key at n1n.ai