Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions examples/reasoning_content/gpt_oss_stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import asyncio
import os

from openai import AsyncOpenAI
from openai.types.shared import Reasoning

from agents import (
Agent,
ModelSettings,
OpenAIChatCompletionsModel,
Runner,
set_tracing_disabled,
)

set_tracing_disabled(True)

# import logging
# logging.basicConfig(level=logging.DEBUG)

gpt_oss_model = OpenAIChatCompletionsModel(
model="openai/gpt-oss-20b",
openai_client=AsyncOpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.getenv("OPENROUTER_API_KEY"),
),
)


async def main():
agent = Agent(
name="Assistant",
instructions="You're a helpful assistant. You provide a concise answer to the user's question.",
model=gpt_oss_model,
model_settings=ModelSettings(
reasoning=Reasoning(effort="high", summary="detailed"),
),
)

result = Runner.run_streamed(agent, "Tell me about recursion in programming.")
print("=== Run starting ===")
print("\n")
async for event in result.stream_events():
if event.type == "raw_response_event":
if event.data.type == "response.reasoning_text.delta":
print(f"\033[33m{event.data.delta}\033[0m", end="", flush=True)
elif event.data.type == "response.output_text.delta":
print(f"\033[32m{event.data.delta}\033[0m", end="", flush=True)

print("\n")
print("=== Run complete ===")


if __name__ == "__main__":
asyncio.run(main())
98 changes: 81 additions & 17 deletions src/agents/models/chatcmpl_stream_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@
ResponseTextDeltaEvent,
ResponseUsage,
)
from openai.types.responses.response_reasoning_item import Summary
from openai.types.responses.response_reasoning_item import Content, Summary
from openai.types.responses.response_reasoning_summary_part_added_event import (
Part as AddedEventPart,
)
from openai.types.responses.response_reasoning_summary_part_done_event import Part as DoneEventPart
from openai.types.responses.response_reasoning_text_delta_event import (
ResponseReasoningTextDeltaEvent,
)
from openai.types.responses.response_reasoning_text_done_event import (
ResponseReasoningTextDoneEvent,
)
from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails

from ..items import TResponseStreamEvent
Expand Down Expand Up @@ -95,7 +101,7 @@ async def handle_stream(

delta = chunk.choices[0].delta

# Handle reasoning content
# Handle reasoning content for reasoning summaries
if hasattr(delta, "reasoning_content"):
reasoning_content = delta.reasoning_content
if reasoning_content and not state.reasoning_content_index_and_output:
Expand Down Expand Up @@ -138,10 +144,55 @@ async def handle_stream(
)

# Create a new summary with updated text
current_summary = state.reasoning_content_index_and_output[1].summary[0]
updated_text = current_summary.text + reasoning_content
new_summary = Summary(text=updated_text, type="summary_text")
state.reasoning_content_index_and_output[1].summary[0] = new_summary
current_content = state.reasoning_content_index_and_output[1].summary[0]
updated_text = current_content.text + reasoning_content
new_content = Summary(text=updated_text, type="summary_text")
state.reasoning_content_index_and_output[1].summary[0] = new_content

# Handle reasoning content from 3rd party platforms
if hasattr(delta, "reasoning"):
reasoning_text = delta.reasoning
if reasoning_text and not state.reasoning_content_index_and_output:
state.reasoning_content_index_and_output = (
0,
ResponseReasoningItem(
id=FAKE_RESPONSES_ID,
summary=[],
content=[Content(text="", type="reasoning_text")],
type="reasoning",
),
)
yield ResponseOutputItemAddedEvent(
item=ResponseReasoningItem(
id=FAKE_RESPONSES_ID,
summary=[],
content=[Content(text="", type="reasoning_text")],
type="reasoning",
),
output_index=0,
type="response.output_item.added",
sequence_number=sequence_number.get_and_increment(),
)

if reasoning_text and state.reasoning_content_index_and_output:
yield ResponseReasoningTextDeltaEvent(
delta=reasoning_text,
item_id=FAKE_RESPONSES_ID,
output_index=0,
content_index=0,
type="response.reasoning_text.delta",
sequence_number=sequence_number.get_and_increment(),
)

# Create a new summary with updated text
if state.reasoning_content_index_and_output[1].content is None:
state.reasoning_content_index_and_output[1].content = [
Content(text="", type="reasoning_text")
]
current_text = state.reasoning_content_index_and_output[1].content[0]
updated_text = current_text.text + reasoning_text
new_text_content = Content(text=updated_text, type="reasoning_text")
state.reasoning_content_index_and_output[1].content[0] = new_text_content

# Handle regular content
if delta.content is not None:
Expand Down Expand Up @@ -344,17 +395,30 @@ async def handle_stream(
)

if state.reasoning_content_index_and_output:
yield ResponseReasoningSummaryPartDoneEvent(
item_id=FAKE_RESPONSES_ID,
output_index=0,
summary_index=0,
part=DoneEventPart(
text=state.reasoning_content_index_and_output[1].summary[0].text,
type="summary_text",
),
type="response.reasoning_summary_part.done",
sequence_number=sequence_number.get_and_increment(),
)
if (
state.reasoning_content_index_and_output[1].summary
and len(state.reasoning_content_index_and_output[1].summary) > 0
):
yield ResponseReasoningSummaryPartDoneEvent(
item_id=FAKE_RESPONSES_ID,
output_index=0,
summary_index=0,
part=DoneEventPart(
text=state.reasoning_content_index_and_output[1].summary[0].text,
type="summary_text",
),
type="response.reasoning_summary_part.done",
sequence_number=sequence_number.get_and_increment(),
)
elif state.reasoning_content_index_and_output[1].content is not None:
yield ResponseReasoningTextDoneEvent(
item_id=FAKE_RESPONSES_ID,
output_index=0,
content_index=0,
text=state.reasoning_content_index_and_output[1].content[0].text,
type="response.reasoning_text.done",
sequence_number=sequence_number.get_and_increment(),
)
yield ResponseOutputItemDoneEvent(
item=state.reasoning_content_index_and_output[1],
output_index=0,
Expand Down