Skip to content

Provider Message Formats

strahl.analyze() accepts transcripts in OpenAI and Anthropic message formats. You do not need to convert between them — pass the messages as you received them from your provider client.

OpenAI

Pass the messages list directly from your OpenAI conversation history:

from openai import OpenAI
import strahl

client = OpenAI()
messages = [{"role": "user", "content": "Find my order."}]

response = client.chat.completions.create(model="gpt-4o", messages=messages, tools=[...])
messages.append(response.choices[0].message.to_dict())

analysis = strahl.analyze(messages)
analysis.raise_if_denied()

OpenAI tool calls appear in the assistant message under tool_calls, with arguments as a JSON string:

{
    "role": "assistant",
    "tool_calls": [{
        "id": "call_abc123",
        "function": {
            "name": "lookup_order",
            "arguments": '{"order_id": "ord_123"}',
        },
    }],
}

Tool results are role "tool" messages referencing the call by tool_call_id:

{"role": "tool", "tool_call_id": "call_abc123", "content": "Order found: ..."}

Anthropic

Pass the messages list from your Anthropic conversation:

from anthropic import Anthropic
import strahl

client = Anthropic()
messages = [{"role": "user", "content": "Find my order."}]

response = client.messages.create(model="claude-opus-4-7", messages=messages, tools=[...])
messages.append({"role": "assistant", "content": response.content})

analysis = strahl.analyze(messages)
analysis.raise_if_denied()

Anthropic tool calls appear as tool_use blocks in list-style message content:

{
    "role": "assistant",
    "content": [
        {"type": "text", "text": "I will look that up."},
        {
            "type": "tool_use",
            "id": "toolu_abc123",
            "name": "lookup_order",
            "input": {"order_id": "ord_123"},
        },
    ],
}

Tool results are tool_result blocks in the following user message:

{
    "role": "user",
    "content": [
        {"type": "tool_result", "tool_use_id": "toolu_abc123", "content": "Order found: ..."},
    ],
}

Requirements

  • The transcript must end with an assistant message. Call analyze() after the model responds, before executing tools.
  • Every non-tool-result message role must have a corresponding role label set via set_role_labels(). Tool result messages (role "tool" in OpenAI, or pure tool_result list content in Anthropic) are exempt.
  • Every tool referenced in the transcript must be registered with add_tool() or @tool().

Registering provider tool schemas

You can register tools using your provider's schema format directly, without rewriting them:

import strahl
from strahl import Label

openai_tool = {
    "type": "function",
    "function": {
        "name": "lookup_order",
        "description": "Look up an order by ID.",
        "parameters": {
            "type": "object",
            "properties": {"order_id": {"type": "string"}},
            "required": ["order_id"],
        },
    },
}

anthropic_tool = {
    "name": "lookup_order",
    "description": "Look up an order by ID.",
    "input_schema": {
        "type": "object",
        "properties": {"order_id": {"type": "string"}},
        "required": ["order_id"],
    },
}

requires = Label(source={"user"}, visibility={"user"})
produces = Label(source={"orders"}, visibility={"user"})

strahl.add_tool(fn=openai_tool, requires=requires, produces=produces)
strahl.add_tool(fn=anthropic_tool, requires=requires, produces=produces)

The SDK detects the format from the schema structure.