Repository navigation
Preambles with structured output #2824
Description
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?
Activity
[-]Preambles with structure output[/-][+]Preambles with structured output[/+]seratch commentedon Apr 2, 2026
Thanks for the detailed repro. From the SDK side,
output_typeis currently translated intotext.format: { type: "json_schema", ... }on the Responses API request. Your example suggests that, with that response format enabled, the model no longer emits a pre-tool-call commentary/preamble message.One nuance: the runner can preserve a preamble
ResponseOutputMessageeven for a structured-output agent if the model returns one, so this does not look like the stream event layer dropping the message. It appears more likely to be model/API behavior whentext.format=json_schemais set.For now, if pre-tool-call commentary is a UX requirement, the safest workaround is probably to either:
output_typeand validate/parse only the final answer yourself,ihower commentedon Apr 2, 2026
@AmazingHorsess This is just my personal observation:
I noticed that model behavior has changed. With gpt-5, gpt-5.1, and gpt-5.2, preambles often still appear even when using structured output. But with gpt-5.4, preambles seem to no longer appear when structured output is enabled.
But I can also see why this change is reasonable, since this seems not a recommended pattern:
There is a mismatch between structured output and preambles. Structured output requires strict JSON, while preambles are free text. The model has to put that text somewhere in the schema. If there is a field like
content, it can work. But if the schema only allows numbers or strict types, there is no valid place to put that text. This leads to an awkward situation.So for my use case, I run them in parallel:
Also, about
check_tool_preambles, I don’t think it works well. Messages and tool calls are interleaved. When the model generates a message, it likely doesn’t know yet if it will be a preamble. The model can only tell that a message is a preamble after the tool call happens.github-actions commentedon Apr 10, 2026
This issue is stale because it has been open for 7 days with no activity.
github-actions commentedon Apr 13, 2026
This issue was closed because it has been inactive for 3 days since being marked as stale.