Skip to content

Preambles with structured output #2824

@AmazingHorsess

Description

@AmazingHorsess

Question

Are preambles supported when using structured output?

According to the documentation(Preamble topic), preambles are brief user-visible messages that GPT-5.4 generates before invoking a tool call. I'm trying to use preambles with the OpenAI Agents SDK, but they don't appear when output_type is set (which translates to text.format: json_schema in the Responses API).

Observed behavior:

The first response contains only:

ResponseReasoningItem (internal reasoning, not user-visible)
ResponseFunctionToolCall
There is no ResponseOutputMessage before the tool call — i.e., no preamble is generated.

Expected behavior:

A user-visible text message (preamble) should appear before the tool call, as described in the documentation.

Reproducible Example

from agents import Agent, ModelSettings, Runner, function_tool
class Weather(BaseModel):
    city: str
    temperature_range: str
    conditions: str

class QueryResult(BaseModel):
    check_tool_preambles: bool = Field(description="Whether this content is a tool preamble.")
    content: str

@function_tool
def get_weather(city: str) -> Weather:
    print("[debug] get_weather called")
    return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")


agent = Agent(
    name="Hello world",
    instructions="""You are a helpful agent. <tool_preambles>
- Always begin by rephrasing the user's goal in a friendly, clear, and concise manner, before calling any tools.
- Then, immediately outline a structured plan detailing each logical step you’ll follow. - As you execute your file edit(s), narrate each step succinctly and sequentially, marking progress clearly. 
- Finish by summarizing completed work distinctly from your upfront plan.
</tool_preambles>""",
    model="gpt-5.4-mini",
    tools=[get_weather],
    output_type=QueryResult
)

async def main():
    result = Runner.run_streamed(agent, input="What's the weather in Tokyo?")
    with open("stream_events_output.txt", "w") as f:
        async for event in result.stream_events():
            #print(event, flush=True)
            f.write(str(event) + "\n")
            f.write("--------------------------------\n")
    print(result.to_input_list())
    print("--------------------------------")
    print(result.final_output)
if __name__ == "__main__":
    asyncio.run(main())

Output

[debug] get_weather called
[{'content': "What's the weather in Tokyo?", 'role': 'user'}, {'id': 'rs_0fc4000fb9b8b38d0069ccc026c4708195a7eb7f2de5455747', 'summary': [], 'type': 'reasoning'}, {'arguments': '{"city":"Tokyo"}', 'call_id': 'call_PdvYnxzSBa0j4gUFAdmOSyaI', 'name': 'get_weather', 'type': 'function_call', 'id': 'fc_0fc4000fb9b8b38d0069ccc026ee7c8195b61ac903b0be407d', 'status': 'completed'}, {'call_id': 'call_PdvYnxzSBa0j4gUFAdmOSyaI', 'output': "city='Tokyo' temperature_range='14-20C' conditions='Sunny with wind.'", 'type': 'function_call_output'}, {'id': 'msg_0fc4000fb9b8b38d0069ccc028367081958aac269a1ab73ca7', 'content': [{'annotations': [], 'text': '{"check_tool_preambles":false,"content":"You’re asking for the current weather in Tokyo.\\n\\nPlan:\\n1. Check the weather for Tokyo.\\n2. Summarize the temperature and conditions clearly.\\n\\nCompleted: Tokyo is sunny and windy, with temperatures around 14–20°C."}', 'type': 'output_text', 'logprobs': []}], 'role': 'assistant', 'status': 'completed', 'type': 'message', 'phase': 'final_answer'}]
--------------------------------
check_tool_preambles=False content='You’re asking for the current weather in Tokyo.\n\nPlan:\n1. Check the weather for Tokyo.\n2. Summarize the temperature and conditions clearly.\n\nCompleted: Tokyo is sunny and windy, with temperatures around 14–20°C.'

Question: How to achieve follow-up messages before tool calls with structured output?

Preambles are critical for reducing perceived latency — users see an immediate acknowledgment ("Let me check the weather in Tokyo...") before the tool executes. Without preambles, there's a noticeable "dead zone" where nothing is streamed to the user while the tool runs.

When output_type is removed, preambles work correctly — the model emits a phase: "commentary" message before the tool call:

[
  {'content': "What's the weather in Tokyo?", 'role': 'user'},
  {'id': '...', 'summary': [], 'type': 'reasoning'},
  {'id': '...', 'content': [{'text': "You'd like to know the current weather in Tokyo.\n\nPlan:\n1. Check the latest weather for Tokyo.\n2. Share a concise summary...", 'type': 'output_text'}], 'role': 'assistant', 'status': 'completed', 'type': 'message', 'phase': 'commentary'},
  {'arguments': '{"city":"Tokyo"}', 'call_id': '...', 'name': 'get_weather', 'type': 'function_call', ...},
  ...
]

But as soon as output_type (i.e., text.format: json_schema) is set, the commentary message disappears entirely, and the model jumps straight to the tool call.

Is there a recommended pattern to combine structured output with preamble/commentary messages? For example:

  • Could phase: "commentary" messages be exempt from the json_schema format constraint, since they are not the final answer?
  • Is there a planned SDK-level or API-level solution for this use case?
  • Any suggested workaround that preserves both type-safe output and user-visible pre-tool-call messages?

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionQuestion about using the SDKstale

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions