iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Output Parsers

Output parsers turn raw LLM text into structured Python objects. Without one, your pipeline depends on string substring tricks that crack as soon as the model phrases its answer slightly differently.

StrOutputParser, Pydantic, JSON, fixing

EXAMPLE
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import List, Literal

llm = ChatOpenAI(model='gpt-4o-mini', temperature=0)

# 1) StrOutputParser — just the text content
chain = ChatPromptTemplate.from_template('Translate to French: {text}') | llm | StrOutputParser()
chain.invoke({'text': 'hello'})    # 'bonjour'
# Without a parser you'd get an AIMessage object — annoying for pipelines.

# 2) Pydantic — structured, validated output
class Recipe(BaseModel):
    title: str        = Field(description='dish name')
    servings: int     = Field(description='number of servings', ge=1)
    ingredients: List[str]
    steps: List[str]
    cuisine: Literal['italian', 'french', 'thai', 'mexican', 'other']

parser = PydanticOutputParser(pydantic_object=Recipe)

prompt = ChatPromptTemplate.from_messages([
    ('system', 'Return a recipe as JSON.\n{format_instructions}'),
    ('user', '{request}'),
]).partial(format_instructions=parser.get_format_instructions())

chain = prompt | llm | parser
recipe = chain.invoke({'request': 'A quick pasta dinner'})
print(type(recipe))                  # <class 'Recipe'>
print(recipe.title, recipe.servings) # validated, typed

# 3) JsonOutputParser — just a dict, no schema enforcement
parser = JsonOutputParser()
chain  = prompt | llm | parser
data   = chain.invoke({'request': '…'})  # dict

# JsonOutputParser with Pydantic for schema docs
parser = JsonOutputParser(pydantic_object=Recipe)

# 4) Modern: with_structured_output (recommended for new code)
structured_llm = llm.with_structured_output(Recipe)
recipe = structured_llm.invoke('A quick pasta dinner')
# No prompt instructions or parser needed — uses tool calling under the hood.

# 5) Streaming JSON — JsonOutputParser streams partial dicts
chain = prompt | llm | JsonOutputParser()
for partial in chain.stream({'request': '…'}):
    print(partial)  # {} → {'title': 'Pasta'} → {'title': 'Pasta', 'servings': 4} …

# 6) OutputFixingParser — wrap a parser so failures retry through the LLM
from langchain.output_parsers import OutputFixingParser

fixing = OutputFixingParser.from_llm(parser=parser, llm=llm)
try:
    recipe = fixing.parse(messy_text)
except Exception as e:
    log.error('still failed after retry', exc_info=e)

# 7) RetryOutputParser — re-runs original prompt with the bad output as context
from langchain.output_parsers import RetryOutputParser
retrying = RetryOutputParser.from_llm(parser=parser, llm=llm)

# 8) CommaSeparatedListOutputParser — simple list of strings
from langchain_core.output_parsers import CommaSeparatedListOutputParser
parser = CommaSeparatedListOutputParser()
chain  = ChatPromptTemplate.from_template('Five colors:\n{format_instructions}').partial(
    format_instructions=parser.get_format_instructions()
) | llm | parser
chain.invoke({})              # ['red', 'blue', 'green', 'yellow', 'purple']

# 9) Custom parser
from langchain_core.output_parsers import BaseOutputParser

class YesNoParser(BaseOutputParser[bool]):
    def parse(self, text: str) -> bool:
        t = text.strip().lower()
        if t.startswith(('yes', 'true', '1')):  return True
        if t.startswith(('no',  'false', '0')): return False
        raise ValueError(f'Could not parse {text!r}')

chain = prompt | llm | YesNoParser()

# 10) Tool calling — the most reliable structured output
from langchain_core.tools import tool

@tool
def record_recipe(title: str, servings: int, ingredients: List[str], steps: List[str]):
    '''Save a recipe.'''
    return f'saved {title}'

llm_with_tools = llm.bind_tools([record_recipe])
result = llm_with_tools.invoke('Pasta dinner for 4')
if result.tool_calls:
    args = result.tool_calls[0]['args']  # already a dict matching the schema

# 11) Common bugs
#   • Putting format instructions ABOVE the user question → model forgets them
#   • Letting temperature > 0 with strict JSON → invalid JSON randomly
#   • Pydantic parser without format_instructions in prompt → model freelances
#   • Trying to parse markdown code-fence JSON without stripping ``` → fails
#   • Not using with_structured_output when the model supports it → harder to maintain

Why it matters

For new code on models that support tool calling, with_structured_output(MySchema) is the cleanest path — it bypasses fragile prompt instructions and gives you Pydantic objects directly. Reserve manual parsers for older models or unusual schemas.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int

parser = JsonOutputParser(pydantic_object=User)
chain = prompt | llm | parser
Try it Yourself »

Exercise

Parse model output as plain text.

chain = prompt | llm | ()

Discussion

Loading…