Skip to main content

Documentation Index

Fetch the complete documentation index at: https://reagent-ai.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Tool output contracts validate the structure of what a tool returns. When a tool’s return shape changes — an upstream API renames a field, drops a key, or changes a type — assert_tool_output_matches catches it before the downstream agent sees wrong data.

assert_tool_output_matches

s.assert_tool_output_matches("extract_vendor_packet", schema={
    "vendor_name": str,
    "data_access": {"retention_days": int},
    "compliance": {"subprocessors": [str]},
})

What it checks

  • Finds all tool results for the named tool across all turns
  • Validates every result against the schema
  • Reports the first mismatch with the exact field path

Failure message

tool 'extract_vendor_packet' result field 'data_access.retention_days': expected int, got str

AGENT STACK TRACE — intake
================================
Turn 0: extract_vendor_packet(vendor_name="ClearVoice AI")
  → {"vendor_name": "ClearVoice AI", "data_access": {"retention_days": "30"}, ...}

Schema format

Schemas are dicts mapping field names to expected types:
# Flat schema
schema = {"name": str, "count": int, "active": bool}

# Nested schema
schema = {
    "user": {"id": str, "name": str},
    "scores": [float],
    "results": [{"id": str, "title": str}],
}
See Nested Schemas for the full schema language.

Adapter support

Tool output contracts work with all framework adapters:
AdapterHow results are captured
OpenAIExtracted from {"role": "tool", "tool_call_id": ...} messages on the next create() call
AnthropicExtracted from {"type": "tool_result", "tool_use_id": ...} content blocks on the next create() call
LangChainCaptured automatically via on_tool_end callback
LangGraphSame as LangChain (extends the callback handler)
CrewAICaptured via instrument() tool wrapping
ManualCall s.log_tool_result(name, result=...) directly

Example with manual logging

def test_tool_output_contract(tmp_path):
    with reagent_flow.session("agent", trace_dir=str(tmp_path)) as s:
        s.log_llm_call(
            tool_calls=[{"name": "search", "arguments": {"q": "test"}}],
        )
        s.log_tool_result("search", result={
            "results": [
                {"id": "doc-1", "score": 0.95, "title": "Getting Started"},
                {"id": "doc-2", "score": 0.87, "title": "API Reference"},
            ],
            "total": 2,
        })

    s.assert_tool_output_matches("search", schema={
        "results": [{"id": str, "score": float, "title": str}],
        "total": int,
    })

Example with OpenAI adapter

from openai import OpenAI
from reagent_flow_openai import patch
import reagent_flow

client = patch(OpenAI())

with reagent_flow.session("chat") as s:
    # Turn 1: model requests a tool call
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": "Look up order 123"}],
        tools=[...],
    )

    # User code runs the tool and sends the result back
    tool_result = run_tool(resp.choices[0].message.tool_calls[0])

    # Turn 2: model sees the tool result and responds
    client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "user", "content": "Look up order 123"},
            resp.choices[0].message,
            {"role": "tool", "tool_call_id": "call_abc", "content": tool_result},
        ],
    )

# The adapter captured the tool result — validate its shape
s.assert_tool_output_matches("lookup_order", schema={
    "status": str,
    "amount": float,
})