Skip to content

Preambles with structured output #2824

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions