agentex-sdk 0.4.10__py3-none-any.whl → 0.4.12__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.
@@ -1,33 +1,42 @@
1
1
  # Standard library imports
2
2
  import base64
3
- from collections.abc import Callable
4
- from contextlib import AsyncExitStack, asynccontextmanager
5
3
  from enum import Enum
6
- from typing import Any, Literal, Optional, override
7
-
8
- from pydantic import Field, PrivateAttr
4
+ from typing import Any, Literal, Optional
5
+ from contextlib import AsyncExitStack, asynccontextmanager
6
+ from collections.abc import Callable
9
7
 
10
8
  import cloudpickle
11
- from agents import RunContextWrapper, RunResult, RunResultStreaming
9
+ from mcp import StdioServerParameters
10
+ from agents import RunResult, RunContextWrapper, RunResultStreaming
11
+ from pydantic import Field, PrivateAttr
12
12
  from agents.mcp import MCPServerStdio, MCPServerStdioParams
13
+ from temporalio import activity
14
+ from agents.tool import (
15
+ ComputerTool as OAIComputerTool,
16
+ FunctionTool as OAIFunctionTool,
17
+ WebSearchTool as OAIWebSearchTool,
18
+ FileSearchTool as OAIFileSearchTool,
19
+ LocalShellTool as OAILocalShellTool,
20
+ CodeInterpreterTool as OAICodeInterpreterTool,
21
+ ImageGenerationTool as OAIImageGenerationTool,
22
+ )
23
+ from agents.guardrail import InputGuardrail, OutputGuardrail
24
+ from agents.exceptions import InputGuardrailTripwireTriggered, OutputGuardrailTripwireTriggered
13
25
  from agents.model_settings import ModelSettings as OAIModelSettings
14
- from agents.tool import FunctionTool as OAIFunctionTool
15
- from mcp import StdioServerParameters
16
- from openai.types.responses.response_includable import ResponseIncludable
17
26
  from openai.types.shared.reasoning import Reasoning
18
- from temporalio import activity
27
+ from openai.types.responses.response_includable import ResponseIncludable
19
28
 
20
- from agentex.lib.core.services.adk.providers.openai import OpenAIService
29
+ from agentex.lib.utils import logging
30
+
31
+ # Third-party imports
32
+ from agentex.lib.types.tracing import BaseModelWithTraceParams
21
33
 
22
34
  # Local imports
23
35
  from agentex.lib.types.agent_results import (
24
36
  SerializableRunResult,
25
37
  SerializableRunResultStreaming,
26
38
  )
27
-
28
- # Third-party imports
29
- from agentex.lib.types.tracing import BaseModelWithTraceParams
30
- from agentex.lib.utils import logging
39
+ from agentex.lib.core.services.adk.providers.openai import OpenAIService
31
40
 
32
41
  logger = logging.make_logger(__name__)
33
42
 
@@ -41,6 +50,147 @@ class OpenAIActivityName(str, Enum):
41
50
  RUN_AGENT_STREAMED_AUTO_SEND = "run_agent_streamed_auto_send"
42
51
 
43
52
 
53
+ class WebSearchTool(BaseModelWithTraceParams):
54
+ """Temporal-compatible wrapper for WebSearchTool."""
55
+
56
+ user_location: Optional[dict[str, Any]] = None # UserLocation object
57
+ search_context_size: Optional[Literal["low", "medium", "high"]] = "medium"
58
+
59
+ def to_oai_function_tool(self) -> OAIWebSearchTool:
60
+ kwargs = {}
61
+ if self.user_location is not None:
62
+ kwargs["user_location"] = self.user_location
63
+ if self.search_context_size is not None:
64
+ kwargs["search_context_size"] = self.search_context_size
65
+ return OAIWebSearchTool(**kwargs)
66
+
67
+
68
+ class FileSearchTool(BaseModelWithTraceParams):
69
+ """Temporal-compatible wrapper for FileSearchTool."""
70
+
71
+ vector_store_ids: list[str]
72
+ max_num_results: Optional[int] = None
73
+ include_search_results: bool = False
74
+ ranking_options: Optional[dict[str, Any]] = None
75
+ filters: Optional[dict[str, Any]] = None
76
+
77
+ def to_oai_function_tool(self):
78
+ return OAIFileSearchTool(
79
+ vector_store_ids=self.vector_store_ids,
80
+ max_num_results=self.max_num_results,
81
+ include_search_results=self.include_search_results,
82
+ ranking_options=self.ranking_options,
83
+ filters=self.filters,
84
+ )
85
+
86
+
87
+ class ComputerTool(BaseModelWithTraceParams):
88
+ """Temporal-compatible wrapper for ComputerTool."""
89
+
90
+ # We need to serialize the computer object and safety check function
91
+ computer_serialized: str = Field(default="", description="Serialized computer object")
92
+ on_safety_check_serialized: str = Field(default="", description="Serialized safety check function")
93
+
94
+ _computer: Any = PrivateAttr()
95
+ _on_safety_check: Optional[Callable] = PrivateAttr()
96
+
97
+ def __init__(
98
+ self,
99
+ *,
100
+ computer: Any = None,
101
+ on_safety_check: Optional[Callable] = None,
102
+ **data,
103
+ ):
104
+ super().__init__(**data)
105
+ if computer is not None:
106
+ self.computer_serialized = self._serialize_callable(computer)
107
+ self._computer = computer
108
+ elif self.computer_serialized:
109
+ self._computer = self._deserialize_callable(self.computer_serialized)
110
+
111
+ if on_safety_check is not None:
112
+ self.on_safety_check_serialized = self._serialize_callable(on_safety_check)
113
+ self._on_safety_check = on_safety_check
114
+ elif self.on_safety_check_serialized:
115
+ self._on_safety_check = self._deserialize_callable(self.on_safety_check_serialized)
116
+
117
+ @classmethod
118
+ def _deserialize_callable(cls, serialized: str) -> Any:
119
+ encoded = serialized.encode()
120
+ serialized_bytes = base64.b64decode(encoded)
121
+ return cloudpickle.loads(serialized_bytes)
122
+
123
+ @classmethod
124
+ def _serialize_callable(cls, func: Any) -> str:
125
+ serialized_bytes = cloudpickle.dumps(func)
126
+ encoded = base64.b64encode(serialized_bytes)
127
+ return encoded.decode()
128
+
129
+ def to_oai_function_tool(self):
130
+ return OAIComputerTool(
131
+ computer=self._computer,
132
+ on_safety_check=self._on_safety_check,
133
+ )
134
+
135
+
136
+ class CodeInterpreterTool(BaseModelWithTraceParams):
137
+ """Temporal-compatible wrapper for CodeInterpreterTool."""
138
+
139
+ tool_config: dict[str, Any] = Field(
140
+ default_factory=lambda: {"type": "code_interpreter"}, description="Tool configuration dict"
141
+ )
142
+
143
+ def to_oai_function_tool(self):
144
+ return OAICodeInterpreterTool(tool_config=self.tool_config)
145
+
146
+
147
+ class ImageGenerationTool(BaseModelWithTraceParams):
148
+ """Temporal-compatible wrapper for ImageGenerationTool."""
149
+
150
+ tool_config: dict[str, Any] = Field(
151
+ default_factory=lambda: {"type": "image_generation"}, description="Tool configuration dict"
152
+ )
153
+
154
+ def to_oai_function_tool(self):
155
+ return OAIImageGenerationTool(tool_config=self.tool_config)
156
+
157
+
158
+ class LocalShellTool(BaseModelWithTraceParams):
159
+ """Temporal-compatible wrapper for LocalShellTool."""
160
+
161
+ executor_serialized: str = Field(default="", description="Serialized LocalShellExecutor object")
162
+
163
+ _executor: Any = PrivateAttr()
164
+
165
+ def __init__(
166
+ self,
167
+ *,
168
+ executor: Any = None,
169
+ **data,
170
+ ):
171
+ super().__init__(**data)
172
+ if executor is not None:
173
+ self.executor_serialized = self._serialize_callable(executor)
174
+ self._executor = executor
175
+ elif self.executor_serialized:
176
+ self._executor = self._deserialize_callable(self.executor_serialized)
177
+
178
+ @classmethod
179
+ def _deserialize_callable(cls, serialized: str) -> Any:
180
+ encoded = serialized.encode()
181
+ serialized_bytes = base64.b64decode(encoded)
182
+ return cloudpickle.loads(serialized_bytes)
183
+
184
+ @classmethod
185
+ def _serialize_callable(cls, func: Any) -> str:
186
+ serialized_bytes = cloudpickle.dumps(func)
187
+ encoded = base64.b64encode(serialized_bytes)
188
+ return encoded.decode()
189
+
190
+ def to_oai_function_tool(self):
191
+ return OAILocalShellTool(executor=self._executor)
192
+
193
+
44
194
  class FunctionTool(BaseModelWithTraceParams):
45
195
  name: str
46
196
  description: str
@@ -78,22 +228,16 @@ class FunctionTool(BaseModelWithTraceParams):
78
228
  super().__init__(**data)
79
229
  if not on_invoke_tool:
80
230
  if not self.on_invoke_tool_serialized:
81
- raise ValueError(
82
- "One of `on_invoke_tool` or `on_invoke_tool_serialized` should be set"
83
- )
231
+ raise ValueError("One of `on_invoke_tool` or `on_invoke_tool_serialized` should be set")
84
232
  else:
85
- on_invoke_tool = self._deserialize_callable(
86
- self.on_invoke_tool_serialized
87
- )
233
+ on_invoke_tool = self._deserialize_callable(self.on_invoke_tool_serialized)
88
234
  else:
89
235
  self.on_invoke_tool_serialized = self._serialize_callable(on_invoke_tool)
90
236
 
91
237
  self._on_invoke_tool = on_invoke_tool
92
238
 
93
239
  @classmethod
94
- def _deserialize_callable(
95
- cls, serialized: str
96
- ) -> Callable[[RunContextWrapper, str], Any]:
240
+ def _deserialize_callable(cls, serialized: str) -> Callable[[RunContextWrapper, str], Any]:
97
241
  encoded = serialized.encode()
98
242
  serialized_bytes = base64.b64decode(encoded)
99
243
  return cloudpickle.loads(serialized_bytes)
@@ -107,11 +251,9 @@ class FunctionTool(BaseModelWithTraceParams):
107
251
  @property
108
252
  def on_invoke_tool(self) -> Callable[[RunContextWrapper, str], Any]:
109
253
  if self._on_invoke_tool is None and self.on_invoke_tool_serialized:
110
- self._on_invoke_tool = self._deserialize_callable(
111
- self.on_invoke_tool_serialized
112
- )
254
+ self._on_invoke_tool = self._deserialize_callable(self.on_invoke_tool_serialized)
113
255
  return self._on_invoke_tool
114
-
256
+
115
257
  @on_invoke_tool.setter
116
258
  def on_invoke_tool(self, value: Callable[[RunContextWrapper, str], Any]):
117
259
  self.on_invoke_tool_serialized = self._serialize_callable(value)
@@ -133,6 +275,126 @@ class FunctionTool(BaseModelWithTraceParams):
133
275
  return OAIFunctionTool(**data)
134
276
 
135
277
 
278
+ class TemporalInputGuardrail(BaseModelWithTraceParams):
279
+ """Temporal-compatible wrapper for InputGuardrail with function
280
+ serialization."""
281
+
282
+ name: str
283
+ _guardrail_function: Callable = PrivateAttr()
284
+ guardrail_function_serialized: str = Field(
285
+ default="",
286
+ description=(
287
+ "Serialized guardrail function. Set automatically during initialization. "
288
+ "Pass `guardrail_function` to the constructor instead."
289
+ ),
290
+ )
291
+
292
+ def __init__(
293
+ self,
294
+ *,
295
+ guardrail_function: Optional[Callable] = None,
296
+ **data,
297
+ ):
298
+ """Initialize with function serialization support for Temporal."""
299
+ super().__init__(**data)
300
+ if not guardrail_function:
301
+ if not self.guardrail_function_serialized:
302
+ raise ValueError("One of `guardrail_function` or `guardrail_function_serialized` should be set")
303
+ else:
304
+ guardrail_function = self._deserialize_callable(self.guardrail_function_serialized)
305
+ else:
306
+ self.guardrail_function_serialized = self._serialize_callable(guardrail_function)
307
+
308
+ self._guardrail_function = guardrail_function
309
+
310
+ @classmethod
311
+ def _deserialize_callable(cls, serialized: str) -> Callable:
312
+ encoded = serialized.encode()
313
+ serialized_bytes = base64.b64decode(encoded)
314
+ return cloudpickle.loads(serialized_bytes)
315
+
316
+ @classmethod
317
+ def _serialize_callable(cls, func: Callable) -> str:
318
+ serialized_bytes = cloudpickle.dumps(func)
319
+ encoded = base64.b64encode(serialized_bytes)
320
+ return encoded.decode()
321
+
322
+ @property
323
+ def guardrail_function(self) -> Callable:
324
+ if self._guardrail_function is None and self.guardrail_function_serialized:
325
+ self._guardrail_function = self._deserialize_callable(self.guardrail_function_serialized)
326
+ return self._guardrail_function
327
+
328
+ @guardrail_function.setter
329
+ def guardrail_function(self, value: Callable):
330
+ self.guardrail_function_serialized = self._serialize_callable(value)
331
+ self._guardrail_function = value
332
+
333
+ def to_oai_input_guardrail(self) -> InputGuardrail:
334
+ """Convert to OpenAI InputGuardrail."""
335
+ return InputGuardrail(guardrail_function=self.guardrail_function, name=self.name)
336
+
337
+
338
+ class TemporalOutputGuardrail(BaseModelWithTraceParams):
339
+ """Temporal-compatible wrapper for OutputGuardrail with function
340
+ serialization."""
341
+
342
+ name: str
343
+ _guardrail_function: Callable = PrivateAttr()
344
+ guardrail_function_serialized: str = Field(
345
+ default="",
346
+ description=(
347
+ "Serialized guardrail function. Set automatically during initialization. "
348
+ "Pass `guardrail_function` to the constructor instead."
349
+ ),
350
+ )
351
+
352
+ def __init__(
353
+ self,
354
+ *,
355
+ guardrail_function: Optional[Callable] = None,
356
+ **data,
357
+ ):
358
+ """Initialize with function serialization support for Temporal."""
359
+ super().__init__(**data)
360
+ if not guardrail_function:
361
+ if not self.guardrail_function_serialized:
362
+ raise ValueError("One of `guardrail_function` or `guardrail_function_serialized` should be set")
363
+ else:
364
+ guardrail_function = self._deserialize_callable(self.guardrail_function_serialized)
365
+ else:
366
+ self.guardrail_function_serialized = self._serialize_callable(guardrail_function)
367
+
368
+ self._guardrail_function = guardrail_function
369
+
370
+ @classmethod
371
+ def _deserialize_callable(cls, serialized: str) -> Callable:
372
+ encoded = serialized.encode()
373
+ serialized_bytes = base64.b64decode(encoded)
374
+ return cloudpickle.loads(serialized_bytes)
375
+
376
+ @classmethod
377
+ def _serialize_callable(cls, func: Callable) -> str:
378
+ serialized_bytes = cloudpickle.dumps(func)
379
+ encoded = base64.b64encode(serialized_bytes)
380
+ return encoded.decode()
381
+
382
+ @property
383
+ def guardrail_function(self) -> Callable:
384
+ if self._guardrail_function is None and self.guardrail_function_serialized:
385
+ self._guardrail_function = self._deserialize_callable(self.guardrail_function_serialized)
386
+ return self._guardrail_function
387
+
388
+ @guardrail_function.setter
389
+ def guardrail_function(self, value: Callable):
390
+ self.guardrail_function_serialized = self._serialize_callable(value)
391
+ self._guardrail_function = value
392
+
393
+ def to_oai_output_guardrail(self) -> OutputGuardrail:
394
+ """Convert to OpenAI OutputGuardrail."""
395
+ return OutputGuardrail(guardrail_function=self.guardrail_function, name=self.name)
396
+
397
+
136
398
  class ModelSettings(BaseModelWithTraceParams):
137
399
  temperature: float | None = None
138
400
  top_p: float | None = None
@@ -152,9 +414,7 @@ class ModelSettings(BaseModelWithTraceParams):
152
414
  extra_args: dict[str, Any] | None = None
153
415
 
154
416
  def to_oai_model_settings(self) -> OAIModelSettings:
155
- return OAIModelSettings(
156
- **self.model_dump(exclude=["trace_id", "parent_span_id"])
157
- )
417
+ return OAIModelSettings(**self.model_dump(exclude=["trace_id", "parent_span_id"]))
158
418
 
159
419
 
160
420
  class RunAgentParams(BaseModelWithTraceParams):
@@ -168,10 +428,24 @@ class RunAgentParams(BaseModelWithTraceParams):
168
428
  handoffs: list["RunAgentParams"] | None = None
169
429
  model: str | None = None
170
430
  model_settings: ModelSettings | None = None
171
- tools: list[FunctionTool] | None = None
431
+ tools: (
432
+ list[
433
+ FunctionTool
434
+ | WebSearchTool
435
+ | FileSearchTool
436
+ | ComputerTool
437
+ | CodeInterpreterTool
438
+ | ImageGenerationTool
439
+ | LocalShellTool
440
+ ]
441
+ | None
442
+ ) = None
172
443
  output_type: Any = None
173
444
  tool_use_behavior: Literal["run_llm_again", "stop_on_first_tool"] = "run_llm_again"
174
445
  mcp_timeout_seconds: int | None = None
446
+ input_guardrails: list[TemporalInputGuardrail] | None = None
447
+ output_guardrails: list[TemporalOutputGuardrail] | None = None
448
+ max_turns: int | None = None
175
449
 
176
450
 
177
451
  class RunAgentAutoSendParams(RunAgentParams):
@@ -214,6 +488,15 @@ class OpenAIActivities:
214
488
  @activity.defn(name=OpenAIActivityName.RUN_AGENT)
215
489
  async def run_agent(self, params: RunAgentParams) -> SerializableRunResult:
216
490
  """Run an agent without streaming or TaskMessage creation."""
491
+ # Convert Temporal guardrails to OpenAI guardrails
492
+ input_guardrails = None
493
+ if params.input_guardrails:
494
+ input_guardrails = [g.to_oai_input_guardrail() for g in params.input_guardrails]
495
+
496
+ output_guardrails = None
497
+ if params.output_guardrails:
498
+ output_guardrails = [g.to_oai_output_guardrail() for g in params.output_guardrails]
499
+
217
500
  result = await self._openai_service.run_agent(
218
501
  input_list=params.input_list,
219
502
  mcp_server_params=params.mcp_server_params,
@@ -228,54 +511,153 @@ class OpenAIActivities:
228
511
  tools=params.tools,
229
512
  output_type=params.output_type,
230
513
  tool_use_behavior=params.tool_use_behavior,
514
+ input_guardrails=input_guardrails,
515
+ output_guardrails=output_guardrails,
516
+ mcp_timeout_seconds=params.mcp_timeout_seconds,
517
+ max_turns=params.max_turns,
231
518
  )
232
519
  return self._to_serializable_run_result(result)
233
520
 
234
521
  @activity.defn(name=OpenAIActivityName.RUN_AGENT_AUTO_SEND)
235
- async def run_agent_auto_send(
236
- self, params: RunAgentAutoSendParams
237
- ) -> SerializableRunResult:
522
+ async def run_agent_auto_send(self, params: RunAgentAutoSendParams) -> SerializableRunResult:
238
523
  """Run an agent with automatic TaskMessage creation."""
239
- result = await self._openai_service.run_agent_auto_send(
240
- task_id=params.task_id,
241
- input_list=params.input_list,
242
- mcp_server_params=params.mcp_server_params,
243
- agent_name=params.agent_name,
244
- agent_instructions=params.agent_instructions,
245
- trace_id=params.trace_id,
246
- parent_span_id=params.parent_span_id,
247
- handoff_description=params.handoff_description,
248
- handoffs=params.handoffs,
249
- model=params.model,
250
- model_settings=params.model_settings,
251
- tools=params.tools,
252
- output_type=params.output_type,
253
- tool_use_behavior=params.tool_use_behavior,
254
- )
255
- return self._to_serializable_run_result(result)
524
+ # Convert Temporal guardrails to OpenAI guardrails
525
+ input_guardrails = None
526
+ if params.input_guardrails:
527
+ input_guardrails = [g.to_oai_input_guardrail() for g in params.input_guardrails]
528
+
529
+ output_guardrails = None
530
+ if params.output_guardrails:
531
+ output_guardrails = [g.to_oai_output_guardrail() for g in params.output_guardrails]
532
+
533
+ try:
534
+ result = await self._openai_service.run_agent_auto_send(
535
+ task_id=params.task_id,
536
+ input_list=params.input_list,
537
+ mcp_server_params=params.mcp_server_params,
538
+ agent_name=params.agent_name,
539
+ agent_instructions=params.agent_instructions,
540
+ trace_id=params.trace_id,
541
+ parent_span_id=params.parent_span_id,
542
+ handoff_description=params.handoff_description,
543
+ handoffs=params.handoffs,
544
+ model=params.model,
545
+ model_settings=params.model_settings,
546
+ tools=params.tools,
547
+ output_type=params.output_type,
548
+ tool_use_behavior=params.tool_use_behavior,
549
+ input_guardrails=input_guardrails,
550
+ output_guardrails=output_guardrails,
551
+ mcp_timeout_seconds=params.mcp_timeout_seconds,
552
+ max_turns=params.max_turns,
553
+ )
554
+ return self._to_serializable_run_result(result)
555
+ except InputGuardrailTripwireTriggered as e:
556
+ # Handle guardrail trigger gracefully
557
+ rejection_message = (
558
+ "I'm sorry, but I cannot process this request due to a guardrail. Please try a different question."
559
+ )
560
+
561
+ # Try to extract rejection message from the guardrail result
562
+ if hasattr(e, "guardrail_result") and hasattr(e.guardrail_result, "output"):
563
+ output_info = getattr(e.guardrail_result.output, "output_info", {})
564
+ if isinstance(output_info, dict) and "rejection_message" in output_info:
565
+ rejection_message = output_info["rejection_message"]
566
+
567
+ # Build the final input list with the rejection message
568
+ final_input_list = list(params.input_list or [])
569
+ final_input_list.append({"role": "assistant", "content": rejection_message})
570
+
571
+ return SerializableRunResult(final_output=rejection_message, final_input_list=final_input_list)
572
+ except OutputGuardrailTripwireTriggered as e:
573
+ # Handle output guardrail trigger gracefully
574
+ rejection_message = (
575
+ "I'm sorry, but I cannot provide this response due to a guardrail. Please try a different question."
576
+ )
577
+
578
+ # Try to extract rejection message from the guardrail result
579
+ if hasattr(e, "guardrail_result") and hasattr(e.guardrail_result, "output"):
580
+ output_info = getattr(e.guardrail_result.output, "output_info", {})
581
+ if isinstance(output_info, dict) and "rejection_message" in output_info:
582
+ rejection_message = output_info["rejection_message"]
583
+
584
+ # Build the final input list with the rejection message
585
+ final_input_list = list(params.input_list or [])
586
+ final_input_list.append({"role": "assistant", "content": rejection_message})
587
+
588
+ return SerializableRunResult(final_output=rejection_message, final_input_list=final_input_list)
256
589
 
257
590
  @activity.defn(name=OpenAIActivityName.RUN_AGENT_STREAMED_AUTO_SEND)
258
591
  async def run_agent_streamed_auto_send(
259
592
  self, params: RunAgentStreamedAutoSendParams
260
593
  ) -> SerializableRunResultStreaming:
261
594
  """Run an agent with streaming and automatic TaskMessage creation."""
262
- result = await self._openai_service.run_agent_streamed_auto_send(
263
- task_id=params.task_id,
264
- input_list=params.input_list,
265
- mcp_server_params=params.mcp_server_params,
266
- agent_name=params.agent_name,
267
- agent_instructions=params.agent_instructions,
268
- trace_id=params.trace_id,
269
- parent_span_id=params.parent_span_id,
270
- handoff_description=params.handoff_description,
271
- handoffs=params.handoffs,
272
- model=params.model,
273
- model_settings=params.model_settings,
274
- tools=params.tools,
275
- output_type=params.output_type,
276
- tool_use_behavior=params.tool_use_behavior,
277
- )
278
- return self._to_serializable_run_result_streaming(result)
595
+
596
+ # Convert Temporal guardrails to OpenAI guardrails
597
+ input_guardrails = None
598
+ if params.input_guardrails:
599
+ input_guardrails = [g.to_oai_input_guardrail() for g in params.input_guardrails]
600
+
601
+ output_guardrails = None
602
+ if params.output_guardrails:
603
+ output_guardrails = [g.to_oai_output_guardrail() for g in params.output_guardrails]
604
+
605
+ try:
606
+ result = await self._openai_service.run_agent_streamed_auto_send(
607
+ task_id=params.task_id,
608
+ input_list=params.input_list,
609
+ mcp_server_params=params.mcp_server_params,
610
+ agent_name=params.agent_name,
611
+ agent_instructions=params.agent_instructions,
612
+ trace_id=params.trace_id,
613
+ parent_span_id=params.parent_span_id,
614
+ handoff_description=params.handoff_description,
615
+ handoffs=params.handoffs,
616
+ model=params.model,
617
+ model_settings=params.model_settings,
618
+ tools=params.tools,
619
+ output_type=params.output_type,
620
+ tool_use_behavior=params.tool_use_behavior,
621
+ input_guardrails=input_guardrails,
622
+ output_guardrails=output_guardrails,
623
+ mcp_timeout_seconds=params.mcp_timeout_seconds,
624
+ max_turns=params.max_turns,
625
+ )
626
+ return self._to_serializable_run_result_streaming(result)
627
+ except InputGuardrailTripwireTriggered as e:
628
+ # Handle guardrail trigger gracefully
629
+ rejection_message = (
630
+ "I'm sorry, but I cannot process this request due to a guardrail. Please try a different question."
631
+ )
632
+
633
+ # Try to extract rejection message from the guardrail result
634
+ if hasattr(e, "guardrail_result") and hasattr(e.guardrail_result, "output"):
635
+ output_info = getattr(e.guardrail_result.output, "output_info", {})
636
+ if isinstance(output_info, dict) and "rejection_message" in output_info:
637
+ rejection_message = output_info["rejection_message"]
638
+
639
+ # Build the final input list with the rejection message
640
+ final_input_list = list(params.input_list or [])
641
+ final_input_list.append({"role": "assistant", "content": rejection_message})
642
+
643
+ return SerializableRunResultStreaming(final_output=rejection_message, final_input_list=final_input_list)
644
+ except OutputGuardrailTripwireTriggered as e:
645
+ # Handle output guardrail trigger gracefully
646
+ rejection_message = (
647
+ "I'm sorry, but I cannot provide this response due to a guardrail. Please try a different question."
648
+ )
649
+
650
+ # Try to extract rejection message from the guardrail result
651
+ if hasattr(e, "guardrail_result") and hasattr(e.guardrail_result, "output"):
652
+ output_info = getattr(e.guardrail_result.output, "output_info", {})
653
+ if isinstance(output_info, dict) and "rejection_message" in output_info:
654
+ rejection_message = output_info["rejection_message"]
655
+
656
+ # Build the final input list with the rejection message
657
+ final_input_list = list(params.input_list or [])
658
+ final_input_list.append({"role": "assistant", "content": rejection_message})
659
+
660
+ return SerializableRunResultStreaming(final_output=rejection_message, final_input_list=final_input_list)
279
661
 
280
662
  @staticmethod
281
663
  def _to_serializable_run_result(result: RunResult) -> SerializableRunResult:
@@ -2,9 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import List, Optional
5
+ from typing import Optional
6
6
  from typing_extensions import Literal, Required, TypedDict
7
7
 
8
+ from .._types import SequenceNotStr
8
9
  from .message_style import MessageStyle
9
10
  from .message_author import MessageAuthor
10
11
 
@@ -18,10 +19,10 @@ class ReasoningContentParam(TypedDict, total=False):
18
19
  `tool`.
19
20
  """
20
21
 
21
- summary: Required[List[str]]
22
+ summary: Required[SequenceNotStr[str]]
22
23
  """A list of short reasoning summaries"""
23
24
 
24
- content: Optional[List[str]]
25
+ content: Optional[SequenceNotStr[str]]
25
26
  """The reasoning content or chain-of-thought text"""
26
27
 
27
28
  style: MessageStyle
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: agentex-sdk
3
- Version: 0.4.10
3
+ Version: 0.4.12
4
4
  Summary: The official Python library for the agentex API
5
5
  Project-URL: Homepage, https://github.com/scaleapi/agentex-python
6
6
  Project-URL: Repository, https://github.com/scaleapi/agentex-python