> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bigdata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Structured Output Extraction

> Extract typed JSON data from Research Agent responses using structured_output_schema.

The Research Agent generates a Markdown answer by default. For programmatic
consumers -- screeners, dashboards, downstream pipelines -- a Markdown blob
is often the wrong shape. The `structured_output_schema` field on a request
asks the agent to produce a parallel JSON object that conforms to a schema you
provide, extracted from the same research material that produced the answer.
The schema acts as a typed contract between the agent and your code.

This page covers how `structured_output_schema` works end-to-end: when the
extraction runs, the schema constraints, a worked example, schema-design tips,
and how to combine structured output with the other Research Agent features.

## How it works

`structured_output_schema` is a JSON Schema document attached to the request.
When set, the agent runs a dedicated extraction step **after** the main
answer has been produced. The extracted object is emitted as a single
`STRUCTURED_OUTPUT` message before the final `COMPLETE` event:

```
... ANSWER chunks ...
STRUCTURED_OUTPUT      <- one event, contains the parsed JSON
COMPLETE
```

Because extraction runs after the answer, the agent has the full research
context (search results, intermediate reasoning, the answer text itself)
available when populating the schema. This makes structured output
fundamentally different from prompt-engineering "return JSON in your answer"
patterns -- the JSON does not have to be part of the visible answer, and the
agent uses native function-calling / JSON-mode to produce it.

Extraction is all-or-nothing: when it succeeds you get one schema-conformant
object, and in the rare case it fails the request still completes but no
`STRUCTURED_OUTPUT` event is emitted (see [Schema conformance](#schema-conformance)).

## Schema constraints

A few rules to keep in mind when authoring your schema:

* **Top-level must be a JSON object.** If you need a list of items, wrap them
  in an object with a single array property (see
  [List outputs](#list-outputs) below).
* **Nested objects, arrays, and primitives are all supported.** Use `type`,
  `properties`, `required`, `items`, and `enum` as you would in any JSON
  Schema.
* **Field descriptions matter.** The agent reads each property's
  `description` to know what to extract. Specific, instruction-shaped
  descriptions (`"Net income in millions of USD for the most recent
  fiscal year"`) produce dramatically better extractions than generic ones
  (`"net income"`).
* **Required fields should reflect what must always be present.** Marking
  every field as required can cause extraction failures when one piece of
  data is genuinely absent; mark only fields you can guarantee.

## Worked example: extract financial metrics

The request below asks for a one-paragraph credit summary and a parallel JSON
object with quantitative metrics. The Markdown answer is for human readers;
the JSON is for downstream code.

```python theme={null}
import json
import requests
from dotenv import load_dotenv
import os

load_dotenv()
api_key = os.getenv("BIGDATA_API_KEY")

schema = {
    "type": "object",
    "properties": {
        "company": {
            "type": "string",
            "description": "Common name of the company, e.g. 'NVIDIA'."
        },
        "fiscal_year": {
            "type": "string",
            "description": "The fiscal year the metrics refer to, e.g. 'FY2025'."
        },
        "revenue_billions_usd": {
            "type": "number",
            "description": "Total revenue for the fiscal year in billions of USD."
        },
        "gross_margin_pct": {
            "type": "number",
            "description": "Gross margin as a percentage (e.g. 69.85 for 69.85%)."
        },
        "operating_margin_pct": {
            "type": "number",
            "description": "Operating margin as a percentage."
        },
        "key_risk_factors": {
            "type": "array",
            "items": {"type": "string"},
            "description": "Top 3-5 risk factors disclosed in the period, each as a short noun phrase."
        }
    },
    "required": ["company", "fiscal_year"]
}

payload = {
    "message": "Write a one-paragraph credit summary of NVIDIA covering FY2025 results.",
    "research_effort": "standard",
    "structured_output_schema": schema,
}

headers = {"X-API-KEY": api_key, "Content-Type": "application/json"}

answer_text = ""
structured: dict | None = None

with requests.post(
    "https://agents.bigdata.com/v1/research-agent",
    headers=headers,
    json=payload,
    stream=True,
    timeout=300,
) as r:
    r.raise_for_status()
    for raw_line in r.iter_lines(decode_unicode=True):
        if not raw_line or not raw_line.startswith("data: "):
            continue
        try:
            event = json.loads(raw_line[6:])
        except json.JSONDecodeError:
            continue
        msg = event.get("message", {})
        if msg.get("type") == "ANSWER":
            answer_text += msg.get("content", "")
        elif msg.get("type") == "STRUCTURED_OUTPUT":
            structured = msg.get("content")
        elif msg.get("type") == "ERROR":
            raise RuntimeError(msg.get("error"))

print("Markdown answer:\n", answer_text)
print("\nStructured output:\n", json.dumps(structured, indent=2) if structured else "(extraction failed)")
```

The `structured` variable receives a dict matching the schema, ready to be
persisted or passed downstream.

## List outputs

JSON Schema's top level must be an object, so to return a list of items wrap
the array in a single property:

```python theme={null}
schema = {
    "type": "object",
    "properties": {
        "competitors": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "Competitor company name."},
                    "ticker": {"type": "string", "description": "Stock ticker, if public."},
                    "differentiator": {
                        "type": "string",
                        "description": "One-line summary of what makes this competitor distinct."
                    }
                },
                "required": ["name"]
            },
            "description": "Up to five direct competitors, ordered by competitive intensity."
        }
    },
    "required": ["competitors"]
}
```

A request using this schema returns `{"competitors": [...]}`. Downstream
code accesses the array via `structured["competitors"]`.

## Schema-design tips

* **Lead with the description.** A property's `description` is the single
  most important field for extraction quality. Write it like an instruction
  to the agent: *what* to find, *where* to find it, and the expected
  *unit or format*.
* **Use `enum` to constrain categorical fields.** For sentiment, ratings,
  or classification fields, list the allowed values explicitly:
  `{"type": "string", "enum": ["positive", "neutral", "negative"]}`. The
  agent will pick one rather than inventing an out-of-band value.
* **Prefer flat schemas.** Deep nesting (3+ levels) tends to reduce
  extraction reliability. If you need rich structure, split it across
  multiple requests rather than packing one massive schema.
* **Keep arrays short and bounded.** Specify expected length in the
  description ("up to five", "exactly three") so the agent does not
  over- or under-fill.
* **Mark `required` minimally.** Only fields you are certain the research
  will surface should be required. Optional fields gracefully degrade to
  missing keys when the data is absent.
* **Test on representative prompts.** Different prompts can elicit
  different shapes of evidence; an extraction schema that works for
  "summarize NVIDIA's earnings" may behave differently for "compare
  NVIDIA and AMD."

## Schema conformance

Structured output is produced with the model's native structured-output mode,
which enforces your schema. When a `STRUCTURED_OUTPUT` message is emitted, its
`content` already conforms to the schema you supplied -- you do not need to
re-validate it client-side, and you will not receive a partially-filled object.
Optional fields you did not mark `required` may simply be absent when the
research did not surface them; that is normal and expected.

Extraction is all-or-nothing. In the rare case the model cannot map the
research onto your schema, the request still completes but no
`STRUCTURED_OUTPUT` message is emitted. This is not a routine outcome to build
fallbacks around -- if you hit it repeatedly for a given schema, simplify the
schema (see the design tips above) and report it, rather than absorbing it
silently.

## Combining with other features

### With `tools_configs`

Use a tightly scoped search config alongside a strict schema to build
deterministic, narrow pipelines. For example, a "company snapshot" pipeline
might scope to one entity ID and extract a fixed financial-metrics schema:

```python theme={null}
payload = {
    "message": "Build a financial snapshot of the company.",
    "research_effort": "standard",
    "tools_configs": {
        "search": {"query_filters": {"entities": {"all_of": ["D8442A"]}}}
    },
    "structured_output_schema": snapshot_schema,
}
```

This pattern is the foundation for screener-style applications.

### With conversation continuity

Structured output is computed once per request. A follow-up turn with the
same `chat_id` runs its own extraction against its own answer; previous
turns' structured outputs are not re-emitted. If you need a running structured
state across turns, accumulate the values on the client side.

## Next steps

<CardGroup cols={2}>
  <Card title="Streaming responses" icon="bolt" href="/how-to-guides/agents/concepts/streaming-responses">
    See where STRUCTURED\_OUTPUT falls in the message lifecycle.
  </Card>

  <Card title="Search tool configuration" icon="filter" href="/how-to-guides/agents/research/search-tool-configuration">
    Pair a strict schema with a tightly scoped search for deterministic pipelines.
  </Card>

  <Card title="Multi-turn conversations" icon="comments" href="/how-to-guides/agents/research/multi-turn-conversations">
    Run structured extraction across multiple turns of a continuing conversation.
  </Card>

  <Card title="Conversation continuity" icon="bookmark" href="/how-to-guides/agents/concepts/conversation-continuity">
    `chat_id`, `checkpoint_id`, and the persistence model for follow-up turns.
  </Card>
</CardGroup>
