azure-functions-durable 1.3.3__py3-none-any.whl → 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. azure/durable_functions/__init__.py +8 -0
  2. azure/durable_functions/decorators/durable_app.py +64 -1
  3. azure/durable_functions/openai_agents/__init__.py +13 -0
  4. azure/durable_functions/openai_agents/context.py +194 -0
  5. azure/durable_functions/openai_agents/event_loop.py +17 -0
  6. azure/durable_functions/openai_agents/exceptions.py +11 -0
  7. azure/durable_functions/openai_agents/handoffs.py +67 -0
  8. azure/durable_functions/openai_agents/model_invocation_activity.py +268 -0
  9. azure/durable_functions/openai_agents/orchestrator_generator.py +67 -0
  10. azure/durable_functions/openai_agents/runner.py +103 -0
  11. azure/durable_functions/openai_agents/task_tracker.py +171 -0
  12. azure/durable_functions/openai_agents/tools.py +148 -0
  13. azure/durable_functions/openai_agents/usage_telemetry.py +69 -0
  14. {azure_functions_durable-1.3.3.dist-info → azure_functions_durable-1.4.0.dist-info}/METADATA +6 -1
  15. {azure_functions_durable-1.3.3.dist-info → azure_functions_durable-1.4.0.dist-info}/RECORD +24 -7
  16. tests/openai_agents/__init__.py +0 -0
  17. tests/openai_agents/test_context.py +466 -0
  18. tests/openai_agents/test_task_tracker.py +290 -0
  19. tests/openai_agents/test_usage_telemetry.py +99 -0
  20. tests/orchestrator/openai_agents/__init__.py +0 -0
  21. tests/orchestrator/openai_agents/test_openai_agents.py +316 -0
  22. {azure_functions_durable-1.3.3.dist-info → azure_functions_durable-1.4.0.dist-info}/LICENSE +0 -0
  23. {azure_functions_durable-1.3.3.dist-info → azure_functions_durable-1.4.0.dist-info}/WHEEL +0 -0
  24. {azure_functions_durable-1.3.3.dist-info → azure_functions_durable-1.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,316 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ import azure.durable_functions as df
4
+ import azure.functions as func
5
+ import json
6
+ import pydantic
7
+ from typing import TypedDict
8
+ from agents import Agent, Runner
9
+ from azure.durable_functions.models import OrchestratorState
10
+ from azure.durable_functions.models.actions import CallActivityAction
11
+ from azure.durable_functions.models.ReplaySchema import ReplaySchema
12
+ from openai import BaseModel
13
+ from tests.orchestrator.orchestrator_test_utils import get_orchestration_state_result, assert_valid_schema, \
14
+ assert_orchestration_state_equals
15
+ from tests.test_utils.ContextBuilder import ContextBuilder
16
+
17
+ app = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
18
+
19
+ @app.function_name("openai_agent_hello_world")
20
+ @app.orchestration_trigger(context_name="context")
21
+ @app.durable_openai_agent_orchestrator(model_retry_options=None)
22
+ def openai_agent_hello_world(context):
23
+ agent = Agent(
24
+ name="Assistant",
25
+ instructions="You only respond in haikus."
26
+ )
27
+
28
+ result = Runner.run_sync(agent, "Tell me about recursion in programming.")
29
+
30
+ return result.final_output;
31
+
32
+ #
33
+ # Run an agent that uses various tools.
34
+ #
35
+ class Weather(BaseModel):
36
+ city: str
37
+ temperature_range: str
38
+ conditions: str
39
+
40
+ @staticmethod
41
+ def from_json(data: str) -> "Weather":
42
+ return Weather(**json.loads(data))
43
+
44
+ @app.activity_trigger(input_name="city")
45
+ def get_weather(city: str) -> Weather:
46
+ print("[debug] get_weather called")
47
+ return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")
48
+
49
+ @app.function_name("openai_agent_use_tool")
50
+ @app.orchestration_trigger(context_name="context")
51
+ @app.durable_openai_agent_orchestrator(model_retry_options=None)
52
+ def openai_agent_use_tool(context):
53
+ agent = Agent(
54
+ name="Assistant",
55
+ instructions="You only respond in haikus.",
56
+ tools=[context.create_activity_tool(get_weather, retry_options=None)]
57
+ )
58
+
59
+ result = Runner.run_sync(agent, "Tell me the weather in Seattle.", )
60
+
61
+ return result.final_output;
62
+
63
+ @app.activity_trigger(input_name="city", activity="get_weather_with_explicit_name")
64
+ def get_named_weather(city: str) -> Weather:
65
+ print("[debug] get_weather called")
66
+ return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")
67
+
68
+ @app.function_name("openai_agent_use_tool_with_explicit_name")
69
+ @app.orchestration_trigger(context_name="context")
70
+ @app.durable_openai_agent_orchestrator(model_retry_options=None)
71
+ def openai_agent_use_tool_with_explicit_name(context):
72
+ agent = Agent(
73
+ name="Assistant",
74
+ instructions="You only respond in haikus.",
75
+ tools=[context.create_activity_tool(get_named_weather, retry_options=None)]
76
+ )
77
+
78
+ result = Runner.run_sync(agent, "Tell me the weather in Seattle.", )
79
+
80
+ return result.final_output;
81
+
82
+ @app.function_name("openai_agent_return_string_type")
83
+ @app.orchestration_trigger(context_name="context")
84
+ @app.durable_openai_agent_orchestrator(model_retry_options=None)
85
+ def openai_agent_return_string_type(context):
86
+ return "Hello World"
87
+
88
+ class DurableModel:
89
+ def __init__(self, property: str) -> None:
90
+ self._property = property
91
+
92
+ def to_json(self) -> str:
93
+ return json.dumps({"property": self._property})
94
+
95
+ @app.function_name("openai_agent_return_durable_model_type")
96
+ @app.orchestration_trigger(context_name="context")
97
+ @app.durable_openai_agent_orchestrator(model_retry_options=None)
98
+ def openai_agent_return_durable_model_type(context):
99
+ model = DurableModel(property="value")
100
+
101
+ return model
102
+
103
+ class TypedDictionaryModel(TypedDict):
104
+ property: str
105
+
106
+ @app.function_name("openai_agent_return_typed_dictionary_model_type")
107
+ @app.orchestration_trigger(context_name="context")
108
+ @app.durable_openai_agent_orchestrator(model_retry_options=None)
109
+ def openai_agent_return_typed_dictionary_model_type(context):
110
+ model = TypedDictionaryModel(property="value")
111
+
112
+ return model
113
+
114
+ class OpenAIPydanticModel(BaseModel):
115
+ property: str
116
+
117
+ @app.function_name("openai_agent_return_openai_pydantic_model_type")
118
+ @app.orchestration_trigger(context_name="context")
119
+ @app.durable_openai_agent_orchestrator(model_retry_options=None)
120
+ def openai_agent_return_openai_pydantic_model_type(context):
121
+ model = OpenAIPydanticModel(property="value")
122
+
123
+ return model
124
+
125
+ class PydanticModel(pydantic.BaseModel):
126
+ property: str
127
+
128
+ @app.function_name("openai_agent_return_pydantic_model_type")
129
+ @app.orchestration_trigger(context_name="context")
130
+ @app.durable_openai_agent_orchestrator(model_retry_options=None)
131
+ def openai_agent_return_pydantic_model_type(context):
132
+ model = PydanticModel(property="value")
133
+
134
+ return model
135
+
136
+ model_activity_name = "run_model"
137
+
138
+ def base_expected_state(output=None, replay_schema: ReplaySchema = ReplaySchema.V1) -> OrchestratorState:
139
+ return OrchestratorState(is_done=False, actions=[], output=output, replay_schema=replay_schema)
140
+
141
+ def add_activity_action(state: OrchestratorState, input_: str, activity_name=model_activity_name):
142
+ action = CallActivityAction(function_name=activity_name, input_=input_)
143
+ state.actions.append([action])
144
+
145
+ def add_activity_completed_events(
146
+ context_builder: ContextBuilder, id_: int, result: str, is_played=False, activity_name=model_activity_name):
147
+ context_builder.add_task_scheduled_event(name=activity_name, id_=id_)
148
+ context_builder.add_orchestrator_completed_event()
149
+ context_builder.add_orchestrator_started_event()
150
+ context_builder.add_task_completed_event(id_=id_, result=json.dumps(result), is_played=is_played)
151
+
152
+ def test_openai_agent_hello_world_start():
153
+ context_builder = ContextBuilder('test_openai_agent_hello_world_start')
154
+
155
+ result = get_orchestration_state_result(
156
+ context_builder, openai_agent_hello_world, uses_pystein=True)
157
+
158
+ expected_state = base_expected_state()
159
+ add_activity_action(expected_state, "{\"input\":[{\"content\":\"Tell me about recursion in programming.\",\"role\":\"user\"}],\"model_settings\":{\"temperature\":null,\"top_p\":null,\"frequency_penalty\":null,\"presence_penalty\":null,\"tool_choice\":null,\"parallel_tool_calls\":null,\"truncation\":null,\"max_tokens\":null,\"reasoning\":null,\"metadata\":null,\"store\":null,\"include_usage\":null,\"response_include\":null,\"extra_query\":null,\"extra_body\":null,\"extra_headers\":null,\"extra_args\":null},\"tracing\":0,\"model_name\":null,\"system_instructions\":\"You only respond in haikus.\",\"tools\":[],\"output_schema\":null,\"handoffs\":[],\"previous_response_id\":null,\"prompt\":null}")
160
+ expected = expected_state.to_json()
161
+
162
+ assert_valid_schema(result)
163
+ assert_orchestration_state_equals(expected, result)
164
+
165
+ def test_openai_agent_hello_world_completed():
166
+ context_builder = ContextBuilder('test_openai_agent_hello_world_completed')
167
+ add_activity_completed_events(context_builder, 0, '{"output":[{"id":"msg_68b9b2a9c67c81a38559c20c18fe86040a86c28ba39b53e8","content":[{"annotations":[],"text":"Skyscrapers whisper— \\nTaxis hum beneath the lights, \\nCity dreams don’t sleep.","type":"output_text","logprobs":null}],"role":"assistant","status":"completed","type":"message"}],"usage":{"requests":1,"input_tokens":27,"input_tokens_details":{"cached_tokens":0},"output_tokens":21,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":48},"response_id":"resp_68b9b2a9461481a3984d0f790dd33f7b0a86c28ba39b53e8"}')
168
+
169
+ result = get_orchestration_state_result(
170
+ context_builder, openai_agent_hello_world, uses_pystein=True)
171
+
172
+ expected_state = base_expected_state()
173
+ add_activity_action(expected_state, "{\"input\":[{\"content\":\"Tell me about recursion in programming.\",\"role\":\"user\"}],\"model_settings\":{\"temperature\":null,\"top_p\":null,\"frequency_penalty\":null,\"presence_penalty\":null,\"tool_choice\":null,\"parallel_tool_calls\":null,\"truncation\":null,\"max_tokens\":null,\"reasoning\":null,\"metadata\":null,\"store\":null,\"include_usage\":null,\"response_include\":null,\"extra_query\":null,\"extra_body\":null,\"extra_headers\":null,\"extra_args\":null},\"tracing\":0,\"model_name\":null,\"system_instructions\":\"You only respond in haikus.\",\"tools\":[],\"output_schema\":null,\"handoffs\":[],\"previous_response_id\":null,\"prompt\":null}")
174
+ expected_state._is_done = True
175
+ expected_state._output = 'Skyscrapers whisper— \nTaxis hum beneath the lights, \nCity dreams don’t sleep.'
176
+ expected = expected_state.to_json()
177
+
178
+ assert_valid_schema(result)
179
+ assert_orchestration_state_equals(expected, result)
180
+
181
+ def test_openai_agent_use_tool_activity_start():
182
+ context_builder = ContextBuilder('test_openai_agent_use_tool_start')
183
+ add_activity_completed_events(context_builder, 0, '{"output":[{"arguments":"{\\"args\\":\\"Seattle, WA\\"}","call_id":"call_mEdywElQTNpxAdivuEFjO0cT","name":"get_weather","type":"function_call","id":"fc_68b9ecc0ff9c819f863d6cf9e0a1b4e101011fd6f5f8c0a6","status":"completed"}],"usage":{"requests":1,"input_tokens":57,"input_tokens_details":{"cached_tokens":0},"output_tokens":17,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":74},"response_id":"resp_68b9ecc092e0819fb79b97c11aacef2001011fd6f5f8c0a6"}')
184
+
185
+ result = get_orchestration_state_result(
186
+ context_builder, openai_agent_use_tool, uses_pystein=True)
187
+
188
+ expected_state = base_expected_state()
189
+ add_activity_action(expected_state, "{\"input\":[{\"content\":\"Tell me the weather in Seattle.\",\"role\":\"user\"}],\"model_settings\":{\"temperature\":null,\"top_p\":null,\"frequency_penalty\":null,\"presence_penalty\":null,\"tool_choice\":null,\"parallel_tool_calls\":null,\"truncation\":null,\"max_tokens\":null,\"reasoning\":null,\"metadata\":null,\"store\":null,\"include_usage\":null,\"response_include\":null,\"extra_query\":null,\"extra_body\":null,\"extra_headers\":null,\"extra_args\":null},\"tracing\":0,\"model_name\":null,\"system_instructions\":\"You only respond in haikus.\",\"tools\":[{\"name\":\"get_weather\",\"description\":\"\",\"params_json_schema\":{\"properties\":{\"city\":{\"title\":\"City\",\"type\":\"string\"}},\"required\":[\"city\"],\"title\":\"get_weather_args\",\"type\":\"object\",\"additionalProperties\":false},\"strict_json_schema\":true}],\"output_schema\":null,\"handoffs\":[],\"previous_response_id\":null,\"prompt\":null}")
190
+ add_activity_action(expected_state, "{\"args\":\"Seattle, WA\"}", activity_name="get_weather")
191
+ expected = expected_state.to_json()
192
+
193
+ assert_valid_schema(result)
194
+ assert_orchestration_state_equals(expected, result)
195
+
196
+ def test_openai_agent_use_explicitly_named_tool_activity_start():
197
+ context_builder = ContextBuilder('test_openai_agent_use_tool_start')
198
+ add_activity_completed_events(context_builder, 0, '{"output":[{"arguments":"{\\"args\\":\\"Seattle, WA\\"}","call_id":"call_mEdywElQTNpxAdivuEFjO0cT","name":"get_named_weather","type":"function_call","id":"fc_68b9ecc0ff9c819f863d6cf9e0a1b4e101011fd6f5f8c0a6","status":"completed"}],"usage":{"requests":1,"input_tokens":57,"input_tokens_details":{"cached_tokens":0},"output_tokens":17,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":74},"response_id":"resp_68b9ecc092e0819fb79b97c11aacef2001011fd6f5f8c0a6"}')
199
+
200
+ result = get_orchestration_state_result(
201
+ context_builder, openai_agent_use_tool_with_explicit_name, uses_pystein=True)
202
+
203
+ expected_state = base_expected_state()
204
+ add_activity_action(expected_state, "{\"input\":[{\"content\":\"Tell me the weather in Seattle.\",\"role\":\"user\"}],\"model_settings\":{\"temperature\":null,\"top_p\":null,\"frequency_penalty\":null,\"presence_penalty\":null,\"tool_choice\":null,\"parallel_tool_calls\":null,\"truncation\":null,\"max_tokens\":null,\"reasoning\":null,\"metadata\":null,\"store\":null,\"include_usage\":null,\"response_include\":null,\"extra_query\":null,\"extra_body\":null,\"extra_headers\":null,\"extra_args\":null},\"tracing\":0,\"model_name\":null,\"system_instructions\":\"You only respond in haikus.\",\"tools\":[{\"name\":\"get_named_weather\",\"description\":\"\",\"params_json_schema\":{\"properties\":{\"city\":{\"title\":\"City\",\"type\":\"string\"}},\"required\":[\"city\"],\"title\":\"get_named_weather_args\",\"type\":\"object\",\"additionalProperties\":false},\"strict_json_schema\":true}],\"output_schema\":null,\"handoffs\":[],\"previous_response_id\":null,\"prompt\":null}")
205
+ add_activity_action(expected_state, "{\"args\":\"Seattle, WA\"}", activity_name="get_weather_with_explicit_name")
206
+ expected = expected_state.to_json()
207
+
208
+ assert_valid_schema(result)
209
+ assert_orchestration_state_equals(expected, result)
210
+
211
+ def test_openai_agent_use_tool_activity_completed():
212
+ context_builder = ContextBuilder('test_openai_agent_use_tool_start')
213
+ add_activity_completed_events(context_builder, 0, '{"output":[{"arguments":"{\\"args\\":\\"Seattle, WA\\"}","call_id":"call_mEdywElQTNpxAdivuEFjO0cT","name":"get_weather","type":"function_call","id":"fc_68b9ecc0ff9c819f863d6cf9e0a1b4e101011fd6f5f8c0a6","status":"completed"}],"usage":{"requests":1,"input_tokens":57,"input_tokens_details":{"cached_tokens":0},"output_tokens":17,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":74},"response_id":"resp_68b9ecc092e0819fb79b97c11aacef2001011fd6f5f8c0a6"}')
214
+ add_activity_completed_events(context_builder, 1, '{"__class__":"Weather","__module__":"function_app","__data__":"{\n \"city\": \"{\\\"args\\\":\\\"Seattle, WA\\\"}\",\n \"temperature_range\": \"14-20C\",\n \"conditions\": \"Sunny with wind.\"\n}"}')
215
+
216
+ result = get_orchestration_state_result(
217
+ context_builder, openai_agent_use_tool, uses_pystein=True)
218
+
219
+ expected_state = base_expected_state()
220
+ add_activity_action(expected_state, "{\"input\":[{\"content\":\"Tell me the weather in Seattle.\",\"role\":\"user\"}],\"model_settings\":{\"temperature\":null,\"top_p\":null,\"frequency_penalty\":null,\"presence_penalty\":null,\"tool_choice\":null,\"parallel_tool_calls\":null,\"truncation\":null,\"max_tokens\":null,\"reasoning\":null,\"metadata\":null,\"store\":null,\"include_usage\":null,\"response_include\":null,\"extra_query\":null,\"extra_body\":null,\"extra_headers\":null,\"extra_args\":null},\"tracing\":0,\"model_name\":null,\"system_instructions\":\"You only respond in haikus.\",\"tools\":[{\"name\":\"get_weather\",\"description\":\"\",\"params_json_schema\":{\"properties\":{\"city\":{\"title\":\"City\",\"type\":\"string\"}},\"required\":[\"city\"],\"title\":\"get_weather_args\",\"type\":\"object\",\"additionalProperties\":false},\"strict_json_schema\":true}],\"output_schema\":null,\"handoffs\":[],\"previous_response_id\":null,\"prompt\":null}")
221
+ add_activity_action(expected_state, "{\"args\":\"Seattle, WA\"}", activity_name="get_weather")
222
+ add_activity_action(expected_state, "{\"input\":[{\"content\":\"Tell me the weather in Seattle.\",\"role\":\"user\"},{\"arguments\":\"{\\\"args\\\":\\\"Seattle, WA\\\"}\",\"call_id\":\"call_mEdywElQTNpxAdivuEFjO0cT\",\"name\":\"get_weather\",\"type\":\"function_call\",\"id\":\"fc_68b9ecc0ff9c819f863d6cf9e0a1b4e101011fd6f5f8c0a6\",\"status\":\"completed\"},{\"call_id\":\"call_mEdywElQTNpxAdivuEFjO0cT\",\"output\":\"{\\\"__class__\\\":\\\"Weather\\\",\\\"__module__\\\":\\\"function_app\\\",\\\"__data__\\\":\\\"{\\n \\\"city\\\": \\\"{\\\\\\\"args\\\\\\\":\\\\\\\"Seattle, WA\\\\\\\"}\\\",\\n \\\"temperature_range\\\": \\\"14-20C\\\",\\n \\\"conditions\\\": \\\"Sunny with wind.\\\"\\n}\\\"}\",\"type\":\"function_call_output\"}],\"model_settings\":{\"temperature\":null,\"top_p\":null,\"frequency_penalty\":null,\"presence_penalty\":null,\"tool_choice\":null,\"parallel_tool_calls\":null,\"truncation\":null,\"max_tokens\":null,\"reasoning\":null,\"metadata\":null,\"store\":null,\"include_usage\":null,\"response_include\":null,\"extra_query\":null,\"extra_body\":null,\"extra_headers\":null,\"extra_args\":null},\"tracing\":0,\"model_name\":null,\"system_instructions\":\"You only respond in haikus.\",\"tools\":[{\"name\":\"get_weather\",\"description\":\"\",\"params_json_schema\":{\"properties\":{\"city\":{\"title\":\"City\",\"type\":\"string\"}},\"required\":[\"city\"],\"title\":\"get_weather_args\",\"type\":\"object\",\"additionalProperties\":false},\"strict_json_schema\":true}],\"output_schema\":null,\"handoffs\":[],\"previous_response_id\":null,\"prompt\":null}")
223
+ expected = expected_state.to_json()
224
+
225
+ assert_valid_schema(result)
226
+ assert_orchestration_state_equals(expected, result)
227
+
228
+ def test_openai_agent_use_tool_analysis_completed():
229
+ context_builder = ContextBuilder('test_openai_agent_use_tool_start')
230
+ add_activity_completed_events(context_builder, 0, '{"output":[{"arguments":"{\\"args\\":\\"Seattle, WA\\"}","call_id":"call_mEdywElQTNpxAdivuEFjO0cT","name":"get_weather","type":"function_call","id":"fc_68b9ecc0ff9c819f863d6cf9e0a1b4e101011fd6f5f8c0a6","status":"completed"}],"usage":{"requests":1,"input_tokens":57,"input_tokens_details":{"cached_tokens":0},"output_tokens":17,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":74},"response_id":"resp_68b9ecc092e0819fb79b97c11aacef2001011fd6f5f8c0a6"}')
231
+ add_activity_completed_events(context_builder, 1, '{"__class__":"Weather","__module__":"function_app","__data__":"{\n \"city\": \"{\\\"args\\\":\\\"Seattle, WA\\\"}\",\n \"temperature_range\": \"14-20C\",\n \"conditions\": \"Sunny with wind.\"\n}"}')
232
+ add_activity_completed_events(context_builder, 2, '{"output":[{"id":"msg_68b9f4b09c14819faa62abfd69cb53e501011fd6f5f8c0a6","content":[{"annotations":[],"text":"The weather in Seattle, WA is currently sunny with some wind. Temperatures are ranging from 14°C to 20°C.","type":"output_text","logprobs":null}],"role":"assistant","status":"completed","type":"message"}],"usage":{"requests":1,"input_tokens":107,"input_tokens_details":{"cached_tokens":0},"output_tokens":28,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":135},"response_id":"resp_68b9f4b00804819f9fe99eac95bd198e01011fd6f5f8c0a6"}')
233
+
234
+ result = get_orchestration_state_result(
235
+ context_builder, openai_agent_use_tool, uses_pystein=True)
236
+
237
+ expected_state = base_expected_state()
238
+ add_activity_action(expected_state, "{\"input\":[{\"content\":\"Tell me the weather in Seattle.\",\"role\":\"user\"}],\"model_settings\":{\"temperature\":null,\"top_p\":null,\"frequency_penalty\":null,\"presence_penalty\":null,\"tool_choice\":null,\"parallel_tool_calls\":null,\"truncation\":null,\"max_tokens\":null,\"reasoning\":null,\"metadata\":null,\"store\":null,\"include_usage\":null,\"response_include\":null,\"extra_query\":null,\"extra_body\":null,\"extra_headers\":null,\"extra_args\":null},\"tracing\":0,\"model_name\":null,\"system_instructions\":\"You only respond in haikus.\",\"tools\":[{\"name\":\"get_weather\",\"description\":\"\",\"params_json_schema\":{\"properties\":{\"city\":{\"title\":\"City\",\"type\":\"string\"}},\"required\":[\"city\"],\"title\":\"get_weather_args\",\"type\":\"object\",\"additionalProperties\":false},\"strict_json_schema\":true}],\"output_schema\":null,\"handoffs\":[],\"previous_response_id\":null,\"prompt\":null}")
239
+ add_activity_action(expected_state, "{\"args\":\"Seattle, WA\"}", activity_name="get_weather")
240
+ add_activity_action(expected_state, "{\"input\":[{\"content\":\"Tell me the weather in Seattle.\",\"role\":\"user\"},{\"arguments\":\"{\\\"args\\\":\\\"Seattle, WA\\\"}\",\"call_id\":\"call_mEdywElQTNpxAdivuEFjO0cT\",\"name\":\"get_weather\",\"type\":\"function_call\",\"id\":\"fc_68b9ecc0ff9c819f863d6cf9e0a1b4e101011fd6f5f8c0a6\",\"status\":\"completed\"},{\"call_id\":\"call_mEdywElQTNpxAdivuEFjO0cT\",\"output\":\"{\\\"__class__\\\":\\\"Weather\\\",\\\"__module__\\\":\\\"function_app\\\",\\\"__data__\\\":\\\"{\\n \\\"city\\\": \\\"{\\\\\\\"args\\\\\\\":\\\\\\\"Seattle, WA\\\\\\\"}\\\",\\n \\\"temperature_range\\\": \\\"14-20C\\\",\\n \\\"conditions\\\": \\\"Sunny with wind.\\\"\\n}\\\"}\",\"type\":\"function_call_output\"}],\"model_settings\":{\"temperature\":null,\"top_p\":null,\"frequency_penalty\":null,\"presence_penalty\":null,\"tool_choice\":null,\"parallel_tool_calls\":null,\"truncation\":null,\"max_tokens\":null,\"reasoning\":null,\"metadata\":null,\"store\":null,\"include_usage\":null,\"response_include\":null,\"extra_query\":null,\"extra_body\":null,\"extra_headers\":null,\"extra_args\":null},\"tracing\":0,\"model_name\":null,\"system_instructions\":\"You only respond in haikus.\",\"tools\":[{\"name\":\"get_weather\",\"description\":\"\",\"params_json_schema\":{\"properties\":{\"city\":{\"title\":\"City\",\"type\":\"string\"}},\"required\":[\"city\"],\"title\":\"get_weather_args\",\"type\":\"object\",\"additionalProperties\":false},\"strict_json_schema\":true}],\"output_schema\":null,\"handoffs\":[],\"previous_response_id\":null,\"prompt\":null}")
241
+ expected_state._is_done = True
242
+ expected_state._output = 'The weather in Seattle, WA is currently sunny with some wind. Temperatures are ranging from 14°C to 20°C.'
243
+ expected = expected_state.to_json()
244
+
245
+ assert_valid_schema(result)
246
+ assert_orchestration_state_equals(expected, result)
247
+
248
+ def test_openai_agent_string_serialization():
249
+ context_builder = ContextBuilder('test_openai_agent_string_serialization')
250
+
251
+ result = get_orchestration_state_result(
252
+ context_builder, openai_agent_return_string_type, uses_pystein=True)
253
+
254
+ expected_state = base_expected_state()
255
+ expected_state._is_done = True
256
+ expected_state._output = "Hello World"
257
+ expected = expected_state.to_json()
258
+
259
+ assert_valid_schema(result)
260
+ assert_orchestration_state_equals(expected, result)
261
+
262
+ def test_openai_agent_durable_model_serialization():
263
+ context_builder = ContextBuilder('test_openai_agent_durable_model_serialization')
264
+
265
+ result = get_orchestration_state_result(
266
+ context_builder, openai_agent_return_durable_model_type, uses_pystein=True)
267
+
268
+ expected_state = base_expected_state()
269
+ expected_state._is_done = True
270
+ expected_state._output = DurableModel(property="value").to_json()
271
+ expected = expected_state.to_json()
272
+
273
+ assert_valid_schema(result)
274
+ assert_orchestration_state_equals(expected, result)
275
+
276
+ def test_openai_agent_typed_dictionary_model_serialization():
277
+ context_builder = ContextBuilder('test_openai_agent_typed_dictionary_model_serialization')
278
+
279
+ result = get_orchestration_state_result(
280
+ context_builder, openai_agent_return_typed_dictionary_model_type, uses_pystein=True)
281
+
282
+ expected_state = base_expected_state()
283
+ expected_state._is_done = True
284
+ expected_state._output = json.dumps(TypedDictionaryModel(property="value"))
285
+ expected = expected_state.to_json()
286
+
287
+ assert_valid_schema(result)
288
+ assert_orchestration_state_equals(expected, result)
289
+
290
+ def test_openai_agent_openai_pydantic_model_serialization():
291
+ context_builder = ContextBuilder('test_openai_agent_openai_pydantic_model_serialization')
292
+
293
+ result = get_orchestration_state_result(
294
+ context_builder, openai_agent_return_openai_pydantic_model_type, uses_pystein=True)
295
+
296
+ expected_state = base_expected_state()
297
+ expected_state._is_done = True
298
+ expected_state._output = OpenAIPydanticModel(property="value").to_json()
299
+ expected = expected_state.to_json()
300
+
301
+ assert_valid_schema(result)
302
+ assert_orchestration_state_equals(expected, result)
303
+
304
+ def test_openai_agent_pydantic_model_serialization():
305
+ context_builder = ContextBuilder('test_openai_agent_pydantic_model_serialization')
306
+
307
+ result = get_orchestration_state_result(
308
+ context_builder, openai_agent_return_pydantic_model_type, uses_pystein=True)
309
+
310
+ expected_state = base_expected_state()
311
+ expected_state._is_done = True
312
+ expected_state._output = PydanticModel(property="value").model_dump_json()
313
+ expected = expected_state.to_json()
314
+
315
+ assert_valid_schema(result)
316
+ assert_orchestration_state_equals(expected, result)