Skip to content

Commit 6959a6e

Browse files
xiehustjsamuel1
authored andcommitted
feat: Add reasoning content for openai model provider (strands-agents#187)
1 parent 1b00326 commit 6959a6e

File tree

4 files changed

+40
-11
lines changed

4 files changed

+40
-11
lines changed

src/strands/models/openai.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@ def stream(self, request: dict[str, Any]) -> Iterable[dict[str, Any]]:
107107
if choice.delta.content:
108108
yield {"chunk_type": "content_delta", "data_type": "text", "data": choice.delta.content}
109109

110+
if hasattr(choice.delta, "reasoning_content") and choice.delta.reasoning_content:
111+
yield {
112+
"chunk_type": "content_delta",
113+
"data_type": "reasoning_content",
114+
"data": choice.delta.reasoning_content,
115+
}
116+
110117
for tool_call in choice.delta.tool_calls or []:
111118
tool_calls.setdefault(tool_call.index, []).append(tool_call)
112119

src/strands/types/models/openai.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ def format_chunk(self, event: dict[str, Any]) -> StreamEvent:
262262
"contentBlockDelta": {"delta": {"toolUse": {"input": event["data"].function.arguments or ""}}}
263263
}
264264

265+
if event["data_type"] == "reasoning_content":
266+
return {"contentBlockDelta": {"delta": {"reasoningContent": {"text": event["data"]}}}}
267+
265268
return {"contentBlockDelta": {"delta": {"text": event["data"]}}}
266269

267270
case "content_stop":

tests/strands/models/test_openai.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,31 +73,45 @@ def test_stream(openai_client, model):
7373
mock_tool_call_1_part_1 = unittest.mock.Mock(index=0)
7474
mock_tool_call_2_part_1 = unittest.mock.Mock(index=1)
7575
mock_delta_1 = unittest.mock.Mock(
76-
content="I'll calculate", tool_calls=[mock_tool_call_1_part_1, mock_tool_call_2_part_1]
76+
reasoning_content="",
77+
content=None,
78+
tool_calls=None,
79+
)
80+
mock_delta_2 = unittest.mock.Mock(
81+
reasoning_content="\nI'm thinking",
82+
content=None,
83+
tool_calls=None,
84+
)
85+
mock_delta_3 = unittest.mock.Mock(
86+
content="I'll calculate", tool_calls=[mock_tool_call_1_part_1, mock_tool_call_2_part_1], reasoning_content=None
7787
)
7888

7989
mock_tool_call_1_part_2 = unittest.mock.Mock(index=0)
8090
mock_tool_call_2_part_2 = unittest.mock.Mock(index=1)
81-
mock_delta_2 = unittest.mock.Mock(
82-
content="that for you", tool_calls=[mock_tool_call_1_part_2, mock_tool_call_2_part_2]
91+
mock_delta_4 = unittest.mock.Mock(
92+
content="that for you", tool_calls=[mock_tool_call_1_part_2, mock_tool_call_2_part_2], reasoning_content=None
8393
)
8494

85-
mock_delta_3 = unittest.mock.Mock(content="", tool_calls=None)
95+
mock_delta_5 = unittest.mock.Mock(content="", tool_calls=None, reasoning_content=None)
8696

8797
mock_event_1 = unittest.mock.Mock(choices=[unittest.mock.Mock(finish_reason=None, delta=mock_delta_1)])
8898
mock_event_2 = unittest.mock.Mock(choices=[unittest.mock.Mock(finish_reason=None, delta=mock_delta_2)])
89-
mock_event_3 = unittest.mock.Mock(choices=[unittest.mock.Mock(finish_reason="tool_calls", delta=mock_delta_3)])
90-
mock_event_4 = unittest.mock.Mock()
99+
mock_event_3 = unittest.mock.Mock(choices=[unittest.mock.Mock(finish_reason=None, delta=mock_delta_3)])
100+
mock_event_4 = unittest.mock.Mock(choices=[unittest.mock.Mock(finish_reason=None, delta=mock_delta_4)])
101+
mock_event_5 = unittest.mock.Mock(choices=[unittest.mock.Mock(finish_reason="tool_calls", delta=mock_delta_5)])
102+
mock_event_6 = unittest.mock.Mock()
91103

92-
openai_client.chat.completions.create.return_value = iter([mock_event_1, mock_event_2, mock_event_3, mock_event_4])
104+
openai_client.chat.completions.create.return_value = iter(
105+
[mock_event_1, mock_event_2, mock_event_3, mock_event_4, mock_event_5, mock_event_6]
106+
)
93107

94108
request = {"model": "m1", "messages": [{"role": "user", "content": [{"type": "text", "text": "calculate 2+2"}]}]}
95109
response = model.stream(request)
96-
97110
tru_events = list(response)
98111
exp_events = [
99112
{"chunk_type": "message_start"},
100113
{"chunk_type": "content_start", "data_type": "text"},
114+
{"chunk_type": "content_delta", "data_type": "reasoning_content", "data": "\nI'm thinking"},
101115
{"chunk_type": "content_delta", "data_type": "text", "data": "I'll calculate"},
102116
{"chunk_type": "content_delta", "data_type": "text", "data": "that for you"},
103117
{"chunk_type": "content_stop", "data_type": "text"},
@@ -110,15 +124,15 @@ def test_stream(openai_client, model):
110124
{"chunk_type": "content_delta", "data_type": "tool", "data": mock_tool_call_2_part_2},
111125
{"chunk_type": "content_stop", "data_type": "tool"},
112126
{"chunk_type": "message_stop", "data": "tool_calls"},
113-
{"chunk_type": "metadata", "data": mock_event_4.usage},
127+
{"chunk_type": "metadata", "data": mock_event_6.usage},
114128
]
115129

116130
assert tru_events == exp_events
117131
openai_client.chat.completions.create.assert_called_once_with(**request)
118132

119133

120134
def test_stream_empty(openai_client, model):
121-
mock_delta = unittest.mock.Mock(content=None, tool_calls=None)
135+
mock_delta = unittest.mock.Mock(content=None, tool_calls=None, reasoning_content=None)
122136
mock_usage = unittest.mock.Mock(prompt_tokens=0, completion_tokens=0, total_tokens=0)
123137

124138
mock_event_1 = unittest.mock.Mock(choices=[unittest.mock.Mock(finish_reason=None, delta=mock_delta)])
@@ -145,7 +159,7 @@ def test_stream_empty(openai_client, model):
145159

146160

147161
def test_stream_with_empty_choices(openai_client, model):
148-
mock_delta = unittest.mock.Mock(content="content", tool_calls=None)
162+
mock_delta = unittest.mock.Mock(content="content", tool_calls=None, reasoning_content=None)
149163
mock_usage = unittest.mock.Mock(prompt_tokens=10, completion_tokens=20, total_tokens=30)
150164

151165
# Event with no choices attribute

tests/strands/types/models/test_openai.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@ def test_format_request(model, messages, tool_specs, system_prompt):
306306
},
307307
{"contentBlockDelta": {"delta": {"toolUse": {"input": ""}}}},
308308
),
309+
# Content Delta - Reasoning Text
310+
(
311+
{"chunk_type": "content_delta", "data_type": "reasoning_content", "data": "I'm thinking"},
312+
{"contentBlockDelta": {"delta": {"reasoningContent": {"text": "I'm thinking"}}}},
313+
),
309314
# Content Delta - Text
310315
(
311316
{"chunk_type": "content_delta", "data_type": "text", "data": "hello"},

0 commit comments

Comments
 (0)