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
@@ -79,3 +79,11 @@ try:
79
79
  __all__.append('Blueprint')
80
80
  except ModuleNotFoundError:
81
81
  pass
82
+
83
+ # Import OpenAI Agents integration (optional dependency)
84
+ try:
85
+ from . import openai_agents # noqa
86
+ __all__.append('openai_agents')
87
+ except ImportError:
88
+ # OpenAI agents integration requires additional dependencies
89
+ pass
@@ -1,6 +1,8 @@
1
1
  # Copyright (c) Microsoft Corporation. All rights reserved.
2
2
  # Licensed under the MIT License.
3
- from .metadata import OrchestrationTrigger, ActivityTrigger, EntityTrigger,\
3
+
4
+ from azure.durable_functions.models.RetryOptions import RetryOptions
5
+ from .metadata import OrchestrationTrigger, ActivityTrigger, EntityTrigger, \
4
6
  DurableClient
5
7
  from typing import Callable, Optional
6
8
  from azure.durable_functions.entity import Entity
@@ -45,6 +47,7 @@ class Blueprint(TriggerApi, BindingApi, SettingsApi):
45
47
  New instance of a Durable Functions app
46
48
  """
47
49
  super().__init__(auth_level=http_auth_level)
50
+ self._is_durable_openai_agent_setup = False
48
51
 
49
52
  def _configure_entity_callable(self, wrap) -> Callable:
50
53
  """Obtain decorator to construct an Entity class from a user-defined Function.
@@ -250,6 +253,66 @@ class Blueprint(TriggerApi, BindingApi, SettingsApi):
250
253
 
251
254
  return wrap
252
255
 
256
+ def _create_invoke_model_activity(self, model_provider, activity_name):
257
+ """Create and register the invoke_model_activity function with the provided FunctionApp."""
258
+
259
+ @self.activity_trigger(input_name="input", activity=activity_name)
260
+ async def run_model_activity(input: str):
261
+ from azure.durable_functions.openai_agents.orchestrator_generator\
262
+ import durable_openai_agent_activity
263
+
264
+ return await durable_openai_agent_activity(input, model_provider)
265
+
266
+ return run_model_activity
267
+
268
+ def _setup_durable_openai_agent(self, model_provider, activity_name):
269
+ if not self._is_durable_openai_agent_setup:
270
+ self._create_invoke_model_activity(model_provider, activity_name)
271
+ self._is_durable_openai_agent_setup = True
272
+
273
+ def durable_openai_agent_orchestrator(
274
+ self,
275
+ _func=None,
276
+ *,
277
+ model_provider=None,
278
+ model_retry_options: Optional[RetryOptions] = RetryOptions(
279
+ first_retry_interval_in_milliseconds=2000, max_number_of_attempts=5
280
+ ),
281
+ ):
282
+ """Decorate Azure Durable Functions orchestrators that use OpenAI Agents.
283
+
284
+ Parameters
285
+ ----------
286
+ model_provider: Optional[ModelProvider]
287
+ Use a non-default ModelProvider instead of the default OpenAIProvider,
288
+ such as when testing.
289
+ """
290
+ from agents import ModelProvider
291
+ from azure.durable_functions.openai_agents.orchestrator_generator\
292
+ import durable_openai_agent_orchestrator_generator
293
+
294
+ if model_provider is not None and type(model_provider) is not ModelProvider:
295
+ raise TypeError("Provided model provider must be of type ModelProvider")
296
+
297
+ activity_name = "run_model"
298
+
299
+ self._setup_durable_openai_agent(model_provider, activity_name)
300
+
301
+ def generator_wrapper_wrapper(func):
302
+
303
+ @wraps(func)
304
+ def generator_wrapper(context):
305
+ return durable_openai_agent_orchestrator_generator(
306
+ func, context, model_retry_options, activity_name
307
+ )
308
+
309
+ return generator_wrapper
310
+
311
+ if _func is None:
312
+ return generator_wrapper_wrapper
313
+ else:
314
+ return generator_wrapper_wrapper(_func)
315
+
253
316
 
254
317
  class DFApp(Blueprint, FunctionRegister):
255
318
  """Durable Functions (DF) app.
@@ -0,0 +1,13 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ """OpenAI Agents integration for Durable Functions.
4
+
5
+ This module provides decorators and utilities to integrate OpenAI Agents
6
+ with Durable Functions orchestration patterns.
7
+ """
8
+
9
+ from .context import DurableAIAgentContext
10
+
11
+ __all__ = [
12
+ 'DurableAIAgentContext',
13
+ ]
@@ -0,0 +1,194 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ import json
4
+ from typing import Any, Callable, Optional, TYPE_CHECKING, Union
5
+
6
+ from azure.durable_functions.models.DurableOrchestrationContext import (
7
+ DurableOrchestrationContext,
8
+ )
9
+ from azure.durable_functions.models.RetryOptions import RetryOptions
10
+
11
+ from agents import RunContextWrapper, Tool
12
+ from agents.function_schema import function_schema
13
+ from agents.tool import FunctionTool
14
+
15
+ from azure.durable_functions.models.Task import TaskBase
16
+ from .task_tracker import TaskTracker
17
+
18
+
19
+ if TYPE_CHECKING:
20
+ # At type-check time we want all members / signatures for IDE & linters.
21
+ _BaseDurableContext = DurableOrchestrationContext
22
+ else:
23
+ class _BaseDurableContext: # lightweight runtime stub
24
+ """Runtime stub base class for delegation; real context is wrapped.
25
+
26
+ At runtime we avoid inheriting from DurableOrchestrationContext so that
27
+ attribute lookups for its members are delegated via __getattr__ to the
28
+ wrapped ``_context`` instance.
29
+ """
30
+
31
+ __slots__ = ()
32
+
33
+
34
+ class DurableAIAgentContext(_BaseDurableContext):
35
+ """Context for AI agents running in Azure Durable Functions orchestration.
36
+
37
+ Design
38
+ ------
39
+ * Static analysis / IDEs: Appears to subclass ``DurableOrchestrationContext`` so
40
+ you get autocompletion and type hints (under TYPE_CHECKING branch).
41
+ * Runtime: Inherits from a trivial stub. All durable orchestration operations
42
+ are delegated to the real ``DurableOrchestrationContext`` instance provided
43
+ as ``context`` and stored in ``_context``.
44
+
45
+ Consequences
46
+ ------------
47
+ * ``isinstance(DurableAIAgentContext, DurableOrchestrationContext)`` is **False** at
48
+ runtime (expected).
49
+ * Delegation via ``__getattr__`` works for every member of the real context.
50
+ * No reliance on internal initialization side-effects of the durable SDK.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ context: DurableOrchestrationContext,
56
+ task_tracker: TaskTracker,
57
+ model_retry_options: Optional[RetryOptions],
58
+ ):
59
+ self._context = context
60
+ self._task_tracker = task_tracker
61
+ self._model_retry_options = model_retry_options
62
+
63
+ def call_activity(
64
+ self, name: Union[str, Callable], input_: Optional[Any] = None
65
+ ) -> TaskBase:
66
+ """Schedule an activity for execution.
67
+
68
+ Parameters
69
+ ----------
70
+ name: str | Callable
71
+ Either the name of the activity function to call, as a string or,
72
+ in the Python V2 programming model, the activity function itself.
73
+ input_: Optional[Any]
74
+ The JSON-serializable input to pass to the activity function.
75
+
76
+ Returns
77
+ -------
78
+ Task
79
+ A Durable Task that completes when the called activity function completes or fails.
80
+ """
81
+ task = self._context.call_activity(name, input_)
82
+ self._task_tracker.record_activity_call()
83
+ return task
84
+
85
+ def call_activity_with_retry(
86
+ self,
87
+ name: Union[str, Callable],
88
+ retry_options: RetryOptions,
89
+ input_: Optional[Any] = None,
90
+ ) -> TaskBase:
91
+ """Schedule an activity for execution with retry options.
92
+
93
+ Parameters
94
+ ----------
95
+ name: str | Callable
96
+ Either the name of the activity function to call, as a string or,
97
+ in the Python V2 programming model, the activity function itself.
98
+ retry_options: RetryOptions
99
+ The retry options for the activity function.
100
+ input_: Optional[Any]
101
+ The JSON-serializable input to pass to the activity function.
102
+
103
+ Returns
104
+ -------
105
+ Task
106
+ A Durable Task that completes when the called activity function completes or
107
+ fails completely.
108
+ """
109
+ task = self._context.call_activity_with_retry(name, retry_options, input_)
110
+ self._task_tracker.record_activity_call()
111
+ return task
112
+
113
+ def create_activity_tool(
114
+ self,
115
+ activity_func: Callable,
116
+ *,
117
+ description: Optional[str] = None,
118
+ retry_options: Optional[RetryOptions] = RetryOptions(
119
+ first_retry_interval_in_milliseconds=2000, max_number_of_attempts=5
120
+ ),
121
+ ) -> Tool:
122
+ """Convert an Azure Durable Functions activity to an OpenAI Agents SDK Tool.
123
+
124
+ Args
125
+ ----
126
+ activity_func: The Azure Functions activity function to convert
127
+ description: Optional description override for the tool
128
+ retry_options: The retry options for the activity function
129
+
130
+ Returns
131
+ -------
132
+ Tool: An OpenAI Agents SDK Tool object
133
+
134
+ """
135
+ if activity_func._function is None:
136
+ raise ValueError("The provided function is not a valid Azure Function.")
137
+
138
+ if (activity_func._function._trigger is not None
139
+ and activity_func._function._trigger.activity is not None):
140
+ activity_name = activity_func._function._trigger.activity
141
+ else:
142
+ activity_name = activity_func._function._name
143
+
144
+ input_name = None
145
+ if (activity_func._function._trigger is not None
146
+ and hasattr(activity_func._function._trigger, 'name')):
147
+ input_name = activity_func._function._trigger.name
148
+
149
+ async def run_activity(ctx: RunContextWrapper[Any], input: str) -> Any:
150
+ # Parse JSON input and extract the named value if input_name is specified
151
+ activity_input = input
152
+ if input_name:
153
+ try:
154
+ parsed_input = json.loads(input)
155
+ if isinstance(parsed_input, dict) and input_name in parsed_input:
156
+ activity_input = parsed_input[input_name]
157
+ # If parsing fails or the named parameter is not found, pass the original input
158
+ except (json.JSONDecodeError, TypeError):
159
+ pass
160
+
161
+ if retry_options:
162
+ result = self._task_tracker.get_activity_call_result_with_retry(
163
+ activity_name, retry_options, activity_input
164
+ )
165
+ else:
166
+ result = self._task_tracker.get_activity_call_result(activity_name, activity_input)
167
+ return result
168
+
169
+ schema = function_schema(
170
+ func=activity_func._function._func,
171
+ docstring_style=None,
172
+ description_override=description,
173
+ use_docstring_info=True,
174
+ strict_json_schema=True,
175
+ )
176
+
177
+ return FunctionTool(
178
+ name=schema.name,
179
+ description=schema.description or "",
180
+ params_json_schema=schema.params_json_schema,
181
+ on_invoke_tool=run_activity,
182
+ strict_json_schema=True,
183
+ )
184
+
185
+ def __getattr__(self, name):
186
+ """Delegate missing attributes to the underlying DurableOrchestrationContext."""
187
+ try:
188
+ return getattr(self._context, name)
189
+ except AttributeError:
190
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
191
+
192
+ def __dir__(self):
193
+ """Improve introspection and tab-completion by including delegated attributes."""
194
+ return sorted(set(dir(type(self)) + list(self.__dict__) + dir(self._context)))
@@ -0,0 +1,17 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ import asyncio
4
+
5
+
6
+ def ensure_event_loop():
7
+ """Ensure an event loop is available for sync execution context.
8
+
9
+ This is necessary when calling Runner.run_sync from Azure Functions
10
+ Durable orchestrators, which run in a synchronous context but need
11
+ an event loop for internal async operations.
12
+ """
13
+ try:
14
+ asyncio.get_running_loop()
15
+ except RuntimeError:
16
+ loop = asyncio.new_event_loop()
17
+ asyncio.set_event_loop(loop)
@@ -0,0 +1,11 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ from azure.durable_functions.models.Task import TaskBase
4
+
5
+
6
+ class YieldException(BaseException):
7
+ """Exception raised when an orchestrator should yield control."""
8
+
9
+ def __init__(self, task: TaskBase):
10
+ super().__init__("Orchestrator should yield.")
11
+ self.task = task
@@ -0,0 +1,67 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ """Handoff conversion utilities for Azure Durable Functions OpenAI agent operations."""
4
+
5
+ from typing import Any
6
+
7
+ from agents import Handoff
8
+ from pydantic import BaseModel
9
+
10
+
11
+ class DurableHandoff(BaseModel):
12
+ """Serializable representation of a Handoff.
13
+
14
+ Contains only the data needed by the model execution to
15
+ determine what to handoff to, not the actual handoff invocation.
16
+ """
17
+
18
+ tool_name: str
19
+ tool_description: str
20
+ input_json_schema: dict[str, Any]
21
+ agent_name: str
22
+ strict_json_schema: bool = True
23
+
24
+ @classmethod
25
+ def from_handoff(cls, handoff: Handoff) -> "DurableHandoff":
26
+ """Create a DurableHandoff from an OpenAI agent Handoff.
27
+
28
+ This method converts OpenAI agent Handoff instances into serializable
29
+ DurableHandoff objects for use within Azure Durable Functions.
30
+
31
+ Parameters
32
+ ----------
33
+ handoff : Handoff
34
+ The OpenAI agent Handoff to convert
35
+
36
+ Returns
37
+ -------
38
+ DurableHandoff
39
+ A serializable handoff representation
40
+ """
41
+ return cls(
42
+ tool_name=handoff.tool_name,
43
+ tool_description=handoff.tool_description,
44
+ input_json_schema=handoff.input_json_schema,
45
+ agent_name=handoff.agent_name,
46
+ strict_json_schema=handoff.strict_json_schema,
47
+ )
48
+
49
+ def to_handoff(self) -> Handoff[Any, Any]:
50
+ """Create an OpenAI agent Handoff instance from this DurableHandoff.
51
+
52
+ This method converts the serializable DurableHandoff back into an
53
+ OpenAI agent Handoff instance for execution.
54
+
55
+ Returns
56
+ -------
57
+ Handoff
58
+ OpenAI agent Handoff instance
59
+ """
60
+ return Handoff(
61
+ tool_name=self.tool_name,
62
+ tool_description=self.tool_description,
63
+ input_json_schema=self.input_json_schema,
64
+ agent_name=self.agent_name,
65
+ strict_json_schema=self.strict_json_schema,
66
+ on_invoke_handoff=lambda ctx, input: None,
67
+ )
@@ -0,0 +1,268 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ import enum
4
+ import json
5
+ from typing import Any, AsyncIterator, Optional, Union, cast
6
+
7
+ from azure.durable_functions.models.RetryOptions import RetryOptions
8
+ from pydantic import BaseModel, Field
9
+ from agents import (
10
+ AgentOutputSchema,
11
+ AgentOutputSchemaBase,
12
+ Handoff,
13
+ Model,
14
+ ModelProvider,
15
+ ModelResponse,
16
+ ModelSettings,
17
+ ModelTracing,
18
+ OpenAIProvider,
19
+ Tool,
20
+ TResponseInputItem,
21
+ UserError,
22
+ )
23
+ from agents.items import TResponseStreamEvent
24
+ from openai.types.responses.response_prompt_param import ResponsePromptParam
25
+
26
+ from .task_tracker import TaskTracker
27
+ from .tools import (
28
+ DurableTool,
29
+ create_tool_from_durable_tool,
30
+ convert_tool_to_durable_tool,
31
+ )
32
+ from .handoffs import DurableHandoff
33
+
34
+
35
+ class DurableAgentOutputSchema(AgentOutputSchemaBase, BaseModel):
36
+ """Serializable representation of agent output schema."""
37
+
38
+ output_type_name: Optional[str] = None
39
+ output_schema: Optional[dict[str, Any]] = None
40
+ strict_json_schema: bool
41
+
42
+ def is_plain_text(self) -> bool:
43
+ """Whether the output type is plain text (versus a JSON object)."""
44
+ return self.output_type_name in (None, "str")
45
+
46
+ def name(self) -> str:
47
+ """Get the name of the output type."""
48
+ if self.output_type_name is None:
49
+ raise ValueError("Output type name has not been specified")
50
+ return self.output_type_name
51
+
52
+ def json_schema(self) -> dict[str, Any]:
53
+ """Return the JSON schema of the output.
54
+
55
+ Will only be called if the output type is not plain text.
56
+ """
57
+ if self.is_plain_text():
58
+ raise UserError("Cannot provide JSON schema for plain text output types")
59
+ if self.output_schema is None:
60
+ raise UserError("Output schema definition is missing")
61
+ return self.output_schema
62
+
63
+ def is_strict_json_schema(self) -> bool:
64
+ """Check if the JSON schema is in strict mode.
65
+
66
+ Strict mode constrains the JSON schema features, but guarantees valid JSON.
67
+ See here for details:
68
+ https://platform.openai.com/docs/guides/structured-outputs#supported-schemas
69
+ """
70
+ return self.strict_json_schema
71
+
72
+ def validate_json(self, json_str: str) -> Any:
73
+ """Validate a JSON string against the output type.
74
+
75
+ You must return the validated object, or raise a `ModelBehaviorError` if
76
+ the JSON is invalid.
77
+ """
78
+ raise NotImplementedError()
79
+
80
+
81
+ class ModelTracingLevel(enum.IntEnum):
82
+ """Serializable IntEnum representation of ModelTracing for Azure Durable Functions.
83
+
84
+ Values must match ModelTracing from the OpenAI SDK. This separate enum is required
85
+ because ModelTracing is a standard Enum while Pydantic serialization requires IntEnum
86
+ for proper JSON serialization in activity inputs.
87
+ """
88
+
89
+ DISABLED = 0
90
+ ENABLED = 1
91
+ ENABLED_WITHOUT_DATA = 2
92
+
93
+
94
+ class DurableModelActivityInput(BaseModel):
95
+ """Serializable input for the durable model invocation activity."""
96
+
97
+ input: Union[str, list[TResponseInputItem]]
98
+ model_settings: ModelSettings
99
+ tracing: ModelTracingLevel
100
+ model_name: Optional[str] = None
101
+ system_instructions: Optional[str] = None
102
+ tools: list[DurableTool] = Field(default_factory=list)
103
+ output_schema: Optional[DurableAgentOutputSchema] = None
104
+ handoffs: list[DurableHandoff] = Field(default_factory=list)
105
+ previous_response_id: Optional[str] = None
106
+ prompt: Optional[Any] = None
107
+
108
+ def to_json(self) -> str:
109
+ """Convert to a JSON string."""
110
+ try:
111
+ return self.model_dump_json(warnings=False)
112
+ except Exception:
113
+ # Fallback to basic JSON serialization
114
+ try:
115
+ return json.dumps(self.model_dump(warnings=False), default=str)
116
+ except Exception as fallback_error:
117
+ raise ValueError(
118
+ f"Unable to serialize DurableModelActivityInput: {fallback_error}"
119
+ ) from fallback_error
120
+
121
+ @classmethod
122
+ def from_json(cls, json_str: str) -> 'DurableModelActivityInput':
123
+ """Create from a JSON string."""
124
+ return cls.model_validate_json(json_str)
125
+
126
+
127
+ class ModelInvoker:
128
+ """Handles OpenAI model invocations for Durable Functions activities."""
129
+
130
+ def __init__(self, model_provider: Optional[ModelProvider] = None):
131
+ """Initialize the activity with a model provider."""
132
+ self._model_provider = model_provider or OpenAIProvider()
133
+
134
+ async def invoke_model_activity(self, input: DurableModelActivityInput) -> ModelResponse:
135
+ """Activity that invokes a model with the given input."""
136
+ model = self._model_provider.get_model(input.model_name)
137
+
138
+ # Avoid https://github.com/pydantic/pydantic/issues/9541
139
+ normalized_input = json.loads(json.dumps(input.input, default=str))
140
+
141
+ # Convert durable tools to agent tools
142
+ tools = [
143
+ create_tool_from_durable_tool(durable_tool)
144
+ for durable_tool in input.tools
145
+ ]
146
+
147
+ # Convert handoff descriptors to agent handoffs
148
+ handoffs = [
149
+ durable_handoff.to_handoff()
150
+ for durable_handoff in input.handoffs
151
+ ]
152
+
153
+ return await model.get_response(
154
+ system_instructions=input.system_instructions,
155
+ input=normalized_input,
156
+ model_settings=input.model_settings,
157
+ tools=tools,
158
+ output_schema=input.output_schema,
159
+ handoffs=handoffs,
160
+ tracing=ModelTracing(input.tracing),
161
+ previous_response_id=input.previous_response_id,
162
+ prompt=input.prompt,
163
+ )
164
+
165
+
166
+ class DurableActivityModel(Model):
167
+ """A model implementation that uses durable activities for model invocations."""
168
+
169
+ def __init__(
170
+ self,
171
+ model_name: Optional[str],
172
+ task_tracker: TaskTracker,
173
+ retry_options: Optional[RetryOptions],
174
+ activity_name: str,
175
+ ) -> None:
176
+ self.model_name = model_name
177
+ self.task_tracker = task_tracker
178
+ self.retry_options = retry_options
179
+ self.activity_name = activity_name
180
+
181
+ async def get_response(
182
+ self,
183
+ system_instructions: Optional[str],
184
+ input: Union[str, list[TResponseInputItem]],
185
+ model_settings: ModelSettings,
186
+ tools: list[Tool],
187
+ output_schema: Optional[AgentOutputSchemaBase],
188
+ handoffs: list[Handoff],
189
+ tracing: ModelTracing,
190
+ *,
191
+ previous_response_id: Optional[str],
192
+ prompt: Optional[ResponsePromptParam],
193
+ conversation_id: Optional[str] = None,
194
+ ) -> ModelResponse:
195
+ """Get a response from the model."""
196
+ # Convert agent tools to Durable tools
197
+ durable_tools = [convert_tool_to_durable_tool(tool) for tool in tools]
198
+
199
+ # Convert agent handoffs to Durable handoff descriptors
200
+ durable_handoffs = [DurableHandoff.from_handoff(handoff) for handoff in handoffs]
201
+ if output_schema is not None and not isinstance(
202
+ output_schema, AgentOutputSchema
203
+ ):
204
+ raise TypeError(
205
+ f"Only AgentOutputSchema is supported by Durable Model, "
206
+ f"got {type(output_schema).__name__}"
207
+ )
208
+
209
+ output_schema_input = (
210
+ None
211
+ if output_schema is None
212
+ else DurableAgentOutputSchema(
213
+ output_type_name=output_schema.name(),
214
+ output_schema=(
215
+ output_schema.json_schema()
216
+ if not output_schema.is_plain_text()
217
+ else None
218
+ ),
219
+ strict_json_schema=output_schema.is_strict_json_schema(),
220
+ )
221
+ )
222
+
223
+ activity_input = DurableModelActivityInput(
224
+ model_name=self.model_name,
225
+ system_instructions=system_instructions,
226
+ input=cast(Union[str, list[TResponseInputItem]], input),
227
+ model_settings=model_settings,
228
+ tools=durable_tools,
229
+ output_schema=output_schema_input,
230
+ handoffs=durable_handoffs,
231
+ tracing=ModelTracingLevel.DISABLED, # ModelTracingLevel(tracing.value),
232
+ previous_response_id=previous_response_id,
233
+ prompt=prompt,
234
+ )
235
+
236
+ activity_input_json = activity_input.to_json()
237
+
238
+ if self.retry_options:
239
+ response = self.task_tracker.get_activity_call_result_with_retry(
240
+ self.activity_name,
241
+ self.retry_options,
242
+ activity_input_json,
243
+ )
244
+ else:
245
+ response = self.task_tracker.get_activity_call_result(
246
+ self.activity_name,
247
+ activity_input_json
248
+ )
249
+
250
+ json_response = json.loads(response)
251
+ model_response = ModelResponse(**json_response)
252
+ return model_response
253
+
254
+ def stream_response(
255
+ self,
256
+ system_instructions: Optional[str],
257
+ input: Union[str, list[TResponseInputItem]],
258
+ model_settings: ModelSettings,
259
+ tools: list[Tool],
260
+ output_schema: Optional[AgentOutputSchemaBase],
261
+ handoffs: list[Handoff],
262
+ tracing: ModelTracing,
263
+ *,
264
+ previous_response_id: Optional[str],
265
+ prompt: Optional[ResponsePromptParam],
266
+ ) -> AsyncIterator[TResponseStreamEvent]:
267
+ """Stream a response from the model."""
268
+ raise NotImplementedError("Durable model doesn't support streams yet")