Structured Output Agent Ollama

Structured output agent ollama example.

Structured Output Agent using Flux agent() + Ollama.

Run it

python examples/ai/structured_output_agent_ollama.py
"""
Structured Output Agent using Flux agent() + Ollama.

This example demonstrates the agent() primitive with structured output — the agent
returns typed Pydantic models instead of raw strings, enabling reliable data extraction
and type-safe pipelines between agents.

Three use cases:
1. Data extraction — extract structured information from unstructured text
2. Classification — categorize input with typed labels and confidence scores
3. Typed pipeline — chain agents where the first returns structured data consumed by the second

Prerequisites:
    1. Install Ollama: https://ollama.ai
    2. Pull a model: ollama pull llama3
    3. Start Ollama service: ollama serve

Usage:
    flux workflow run structured_output_demo '{"text": "John Smith is a 35 year old software engineer at Google in Mountain View."}'
"""

from __future__ import annotations

from typing import Any

from pydantic import BaseModel

from flux import ExecutionContext, workflow
from flux.tasks.ai import agent


class PersonInfo(BaseModel):
    name: str
    age: int | None
    occupation: str | None
    company: str | None
    location: str | None


class SentimentResult(BaseModel):
    sentiment: str
    confidence: float
    reasoning: str


class BlogOutline(BaseModel):
    title: str
    sections: list[str]
    target_audience: str
    estimated_word_count: int


@workflow
async def structured_output_demo(ctx: ExecutionContext[dict[str, Any]]):
    """
    Demonstrates structured output with agent() in three use cases.

    Input format:
    {
        "text": "Text to analyze"
    }
    """
    input_data = ctx.input or {}
    text = input_data.get(
        "text",
        "Marie Curie was a physicist and chemist who conducted pioneering research on radioactivity.",
    )

    extractor = await agent(
        "You are a data extraction specialist. Extract structured information from text. "
        'Return a JSON object with fields: "name" (string), "age" (integer or null), '
        '"occupation" (string or null), "company" (string or null), "location" (string or null).',
        model="ollama/llama3",
        name="extractor",
        response_format=PersonInfo,
    )

    classifier = await agent(
        "You are a sentiment analysis specialist. Analyze the sentiment of the given text. "
        'Return a JSON object with fields: "sentiment" (one of "positive", "negative", "neutral"), '
        '"confidence" (float 0.0 to 1.0), "reasoning" (brief explanation).',
        model="ollama/llama3",
        name="classifier",
        response_format=SentimentResult,
    )

    planner = await agent(
        "You are a content planning specialist. Create a structured blog outline. "
        'Return a JSON object with fields: "title" (string), "sections" (list of section heading strings), '
        '"target_audience" (string), "estimated_word_count" (integer).',
        model="ollama/llama3",
        name="planner",
        response_format=BlogOutline,
    )

    writer = await agent(
        "You are a content writer. Write a blog post based on the outline provided.",
        model="ollama/llama3",
        name="writer",
    )

    # 1. Data extraction — returns a PersonInfo model
    person = await extractor(f"Extract person information from: {text}")

    # 2. Sentiment classification — returns a SentimentResult model
    sentiment = await classifier(f"Analyze the sentiment of: {text}")

    # 3. Typed pipeline — planner returns BlogOutline, writer uses it as context
    outline = await planner(
        f"Create a blog outline about: {person.name if isinstance(person, PersonInfo) else text}",
    )

    outline_text = (
        (
            f"Title: {outline.title}\n"
            f"Sections: {', '.join(outline.sections)}\n"
            f"Target audience: {outline.target_audience}\n"
            f"Estimated words: {outline.estimated_word_count}"
        )
        if isinstance(outline, BlogOutline)
        else str(outline)
    )

    blog_post = await writer("Write a blog post following this outline:", context=outline_text)

    return {
        "extraction": person.model_dump() if isinstance(person, PersonInfo) else str(person),
        "sentiment": sentiment.model_dump()
        if isinstance(sentiment, SentimentResult)
        else str(sentiment),
        "outline": outline.model_dump() if isinstance(outline, BlogOutline) else str(outline),
        "blog_post_preview": blog_post[:500] + "..." if len(blog_post) > 500 else blog_post,
        "execution_id": ctx.execution_id,
    }


if __name__ == "__main__":  # pragma: no cover
    import json

    text = "John Smith is a 35 year old software engineer at Google in Mountain View. He loves his job and finds it very rewarding."

    try:
        print("=" * 80)
        print("Structured Output Agent Demo")
        print("=" * 80 + "\n")

        result = structured_output_demo.run({"text": text})

        if result.has_failed:
            raise Exception(f"Workflow failed: {result.output}")

        output = result.output

        print("1. DATA EXTRACTION (PersonInfo)")
        print(f"   {json.dumps(output['extraction'], indent=2)}\n")

        print("2. SENTIMENT ANALYSIS (SentimentResult)")
        print(f"   {json.dumps(output['sentiment'], indent=2)}\n")

        print("3. CONTENT PLANNING (BlogOutline)")
        print(f"   {json.dumps(output['outline'], indent=2)}\n")

        print("4. TYPED PIPELINE (Outline -> Writer)")
        print(f"   {output['blog_post_preview']}\n")

        print("=" * 80)

    except Exception as e:
        print(f"Error: {e}")
        print("\nMake sure:")
        print("1. Ollama is running: ollama serve")
        print("2. Model is pulled: ollama pull llama3")

Last verified against Flux 0.56.0.