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.
@@ -16,18 +16,20 @@ from ._utils import (
16
16
  lru_cache,
17
17
  is_mapping,
18
18
  is_iterable,
19
+ is_sequence,
19
20
  )
20
21
  from .._files import is_base64_file_input
22
+ from ._compat import get_origin, is_typeddict
21
23
  from ._typing import (
22
24
  is_list_type,
23
25
  is_union_type,
24
26
  extract_type_arg,
25
27
  is_iterable_type,
26
28
  is_required_type,
29
+ is_sequence_type,
27
30
  is_annotated_type,
28
31
  strip_annotated_type,
29
32
  )
30
- from .._compat import get_origin, model_dump, is_typeddict
31
33
 
32
34
  _T = TypeVar("_T")
33
35
 
@@ -167,6 +169,8 @@ def _transform_recursive(
167
169
 
168
170
  Defaults to the same value as the `annotation` argument.
169
171
  """
172
+ from .._compat import model_dump
173
+
170
174
  if inner_type is None:
171
175
  inner_type = annotation
172
176
 
@@ -184,6 +188,8 @@ def _transform_recursive(
184
188
  (is_list_type(stripped_type) and is_list(data))
185
189
  # Iterable[T]
186
190
  or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
191
+ # Sequence[T]
192
+ or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
187
193
  ):
188
194
  # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
189
195
  # intended as an iterable, so we don't transform it.
@@ -329,6 +335,8 @@ async def _async_transform_recursive(
329
335
 
330
336
  Defaults to the same value as the `annotation` argument.
331
337
  """
338
+ from .._compat import model_dump
339
+
332
340
  if inner_type is None:
333
341
  inner_type = annotation
334
342
 
@@ -346,6 +354,8 @@ async def _async_transform_recursive(
346
354
  (is_list_type(stripped_type) and is_list(data))
347
355
  # Iterable[T]
348
356
  or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
357
+ # Sequence[T]
358
+ or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
349
359
  ):
350
360
  # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
351
361
  # intended as an iterable, so we don't transform it.
agentex/_utils/_typing.py CHANGED
@@ -15,7 +15,7 @@ from typing_extensions import (
15
15
 
16
16
  from ._utils import lru_cache
17
17
  from .._types import InheritsGeneric
18
- from .._compat import is_union as _is_union
18
+ from ._compat import is_union as _is_union
19
19
 
20
20
 
21
21
  def is_annotated_type(typ: type) -> bool:
@@ -26,6 +26,11 @@ def is_list_type(typ: type) -> bool:
26
26
  return (get_origin(typ) or typ) == list
27
27
 
28
28
 
29
+ def is_sequence_type(typ: type) -> bool:
30
+ origin = get_origin(typ) or typ
31
+ return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence
32
+
33
+
29
34
  def is_iterable_type(typ: type) -> bool:
30
35
  """If the given type is `typing.Iterable[T]`"""
31
36
  origin = get_origin(typ) or typ
agentex/_utils/_utils.py CHANGED
@@ -22,7 +22,6 @@ from typing_extensions import TypeGuard
22
22
  import sniffio
23
23
 
24
24
  from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike
25
- from .._compat import parse_date as parse_date, parse_datetime as parse_datetime
26
25
 
27
26
  _T = TypeVar("_T")
28
27
  _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
agentex/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "agentex"
4
- __version__ = "0.4.10" # x-release-please-version
4
+ __version__ = "0.4.12" # x-release-please-version
@@ -205,6 +205,8 @@ class ACPModule:
205
205
  self,
206
206
  task_id: str | None = None,
207
207
  task_name: str | None = None,
208
+ agent_id: str | None = None,
209
+ agent_name: str | None = None,
208
210
  trace_id: str | None = None,
209
211
  parent_span_id: str | None = None,
210
212
  start_to_close_timeout: timedelta = timedelta(seconds=5),
@@ -212,11 +214,13 @@ class ACPModule:
212
214
  retry_policy: RetryPolicy = DEFAULT_RETRY_POLICY,
213
215
  ) -> Task:
214
216
  """
215
- Cancel a task.
217
+ Cancel a task by sending cancel request to the agent that owns the task.
216
218
 
217
219
  Args:
218
- task_id: The ID of the task to cancel.
219
- task_name: The name of the task to cancel.
220
+ task_id: ID of the task to cancel.
221
+ task_name: Name of the task to cancel.
222
+ agent_id: ID of the agent that owns the task.
223
+ agent_name: Name of the agent that owns the task.
220
224
  trace_id: The trace ID for the task.
221
225
  parent_span_id: The parent span ID for the task.
222
226
  start_to_close_timeout: The start to close timeout for the task.
@@ -225,6 +229,10 @@ class ACPModule:
225
229
 
226
230
  Returns:
227
231
  The task entry.
232
+
233
+ Raises:
234
+ ValueError: If neither agent_name nor agent_id is provided,
235
+ or if neither task_name nor task_id is provided
228
236
  """
229
237
  if in_temporal_workflow():
230
238
  return await ActivityHelpers.execute_activity(
@@ -232,6 +240,8 @@ class ACPModule:
232
240
  request=TaskCancelParams(
233
241
  task_id=task_id,
234
242
  task_name=task_name,
243
+ agent_id=agent_id,
244
+ agent_name=agent_name,
235
245
  trace_id=trace_id,
236
246
  parent_span_id=parent_span_id,
237
247
  ),
@@ -244,6 +254,8 @@ class ACPModule:
244
254
  return await self._acp_service.task_cancel(
245
255
  task_id=task_id,
246
256
  task_name=task_name,
257
+ agent_id=agent_id,
258
+ agent_name=agent_name,
247
259
  trace_id=trace_id,
248
260
  parent_span_id=parent_span_id,
249
261
  )
@@ -4,6 +4,7 @@ from typing import Any, Literal
4
4
  from agentex.lib.adk.utils._modules.client import create_async_agentex_client
5
5
  from agents import Agent, RunResult, RunResultStreaming
6
6
  from agents.agent import StopAtTools, ToolsToFinalOutputFunction
7
+ from agents.guardrail import InputGuardrail, OutputGuardrail
7
8
  from agents.agent_output import AgentOutputSchemaBase
8
9
  from agents.model_settings import ModelSettings
9
10
  from agents.tool import Tool
@@ -84,6 +85,10 @@ class OpenAIModule:
84
85
  | StopAtTools
85
86
  | ToolsToFinalOutputFunction
86
87
  ) = "run_llm_again",
88
+ mcp_timeout_seconds: int | None = None,
89
+ input_guardrails: list[InputGuardrail] | None = None,
90
+ output_guardrails: list[OutputGuardrail] | None = None,
91
+ max_turns: int | None = None,
87
92
  ) -> SerializableRunResult | RunResult:
88
93
  """
89
94
  Run an agent without streaming or TaskMessage creation.
@@ -107,6 +112,10 @@ class OpenAIModule:
107
112
  tools: Optional list of tools.
108
113
  output_type: Optional output type.
109
114
  tool_use_behavior: Optional tool use behavior.
115
+ mcp_timeout_seconds: Optional param to set the timeout threshold for the MCP servers. Defaults to 5 seconds.
116
+ input_guardrails: Optional list of input guardrails to run on initial user input.
117
+ output_guardrails: Optional list of output guardrails to run on final agent output.
118
+ max_turns: Maximum number of turns the agent can take. Uses Runner's default if None.
110
119
 
111
120
  Returns:
112
121
  Union[SerializableRunResult, RunResult]: SerializableRunResult when in Temporal, RunResult otherwise.
@@ -126,6 +135,10 @@ class OpenAIModule:
126
135
  tools=tools,
127
136
  output_type=output_type,
128
137
  tool_use_behavior=tool_use_behavior,
138
+ mcp_timeout_seconds=mcp_timeout_seconds,
139
+ input_guardrails=input_guardrails,
140
+ output_guardrails=output_guardrails,
141
+ max_turns=max_turns,
129
142
  )
130
143
  return await ActivityHelpers.execute_activity(
131
144
  activity_name=OpenAIActivityName.RUN_AGENT,
@@ -150,6 +163,10 @@ class OpenAIModule:
150
163
  tools=tools,
151
164
  output_type=output_type,
152
165
  tool_use_behavior=tool_use_behavior,
166
+ mcp_timeout_seconds=mcp_timeout_seconds,
167
+ input_guardrails=input_guardrails,
168
+ output_guardrails=output_guardrails,
169
+ max_turns=max_turns,
153
170
  )
154
171
 
155
172
  async def run_agent_auto_send(
@@ -175,6 +192,10 @@ class OpenAIModule:
175
192
  | StopAtTools
176
193
  | ToolsToFinalOutputFunction
177
194
  ) = "run_llm_again",
195
+ mcp_timeout_seconds: int | None = None,
196
+ input_guardrails: list[InputGuardrail] | None = None,
197
+ output_guardrails: list[OutputGuardrail] | None = None,
198
+ max_turns: int | None = None,
178
199
  ) -> SerializableRunResult | RunResult:
179
200
  """
180
201
  Run an agent with automatic TaskMessage creation.
@@ -197,6 +218,10 @@ class OpenAIModule:
197
218
  tools: Optional list of tools.
198
219
  output_type: Optional output type.
199
220
  tool_use_behavior: Optional tool use behavior.
221
+ mcp_timeout_seconds: Optional param to set the timeout threshold for the MCP servers. Defaults to 5 seconds.
222
+ input_guardrails: Optional list of input guardrails to run on initial user input.
223
+ output_guardrails: Optional list of output guardrails to run on final agent output.
224
+ max_turns: Maximum number of turns the agent can take. Uses Runner's default if None.
200
225
 
201
226
  Returns:
202
227
  Union[SerializableRunResult, RunResult]: SerializableRunResult when in Temporal, RunResult otherwise.
@@ -217,6 +242,10 @@ class OpenAIModule:
217
242
  tools=tools,
218
243
  output_type=output_type,
219
244
  tool_use_behavior=tool_use_behavior,
245
+ mcp_timeout_seconds=mcp_timeout_seconds,
246
+ input_guardrails=input_guardrails,
247
+ output_guardrails=output_guardrails,
248
+ max_turns=max_turns,
220
249
  )
221
250
  return await ActivityHelpers.execute_activity(
222
251
  activity_name=OpenAIActivityName.RUN_AGENT_AUTO_SEND,
@@ -242,6 +271,10 @@ class OpenAIModule:
242
271
  tools=tools,
243
272
  output_type=output_type,
244
273
  tool_use_behavior=tool_use_behavior,
274
+ mcp_timeout_seconds=mcp_timeout_seconds,
275
+ input_guardrails=input_guardrails,
276
+ output_guardrails=output_guardrails,
277
+ max_turns=max_turns,
245
278
  )
246
279
 
247
280
  async def run_agent_streamed(
@@ -263,6 +296,10 @@ class OpenAIModule:
263
296
  | StopAtTools
264
297
  | ToolsToFinalOutputFunction
265
298
  ) = "run_llm_again",
299
+ mcp_timeout_seconds: int | None = None,
300
+ input_guardrails: list[InputGuardrail] | None = None,
301
+ output_guardrails: list[OutputGuardrail] | None = None,
302
+ max_turns: int | None = None,
266
303
  ) -> RunResultStreaming:
267
304
  """
268
305
  Run an agent with streaming enabled but no TaskMessage creation.
@@ -289,6 +326,10 @@ class OpenAIModule:
289
326
  tools: Optional list of tools.
290
327
  output_type: Optional output type.
291
328
  tool_use_behavior: Optional tool use behavior.
329
+ mcp_timeout_seconds: Optional param to set the timeout threshold for the MCP servers. Defaults to 5 seconds.
330
+ input_guardrails: Optional list of input guardrails to run on initial user input.
331
+ output_guardrails: Optional list of output guardrails to run on final agent output.
332
+ max_turns: Maximum number of turns the agent can take. Uses Runner's default if None.
292
333
 
293
334
  Returns:
294
335
  RunResultStreaming: The result of the agent run with streaming.
@@ -318,6 +359,10 @@ class OpenAIModule:
318
359
  tools=tools,
319
360
  output_type=output_type,
320
361
  tool_use_behavior=tool_use_behavior,
362
+ mcp_timeout_seconds=mcp_timeout_seconds,
363
+ input_guardrails=input_guardrails,
364
+ output_guardrails=output_guardrails,
365
+ max_turns=max_turns,
321
366
  )
322
367
 
323
368
  async def run_agent_streamed_auto_send(
@@ -344,6 +389,9 @@ class OpenAIModule:
344
389
  | ToolsToFinalOutputFunction
345
390
  ) = "run_llm_again",
346
391
  mcp_timeout_seconds: int | None = None,
392
+ input_guardrails: list[InputGuardrail] | None = None,
393
+ output_guardrails: list[OutputGuardrail] | None = None,
394
+ max_turns: int | None = None,
347
395
  ) -> SerializableRunResultStreaming | RunResultStreaming:
348
396
  """
349
397
  Run an agent with streaming enabled and automatic TaskMessage creation.
@@ -364,9 +412,12 @@ class OpenAIModule:
364
412
  model: Optional model to use.
365
413
  model_settings: Optional model settings.
366
414
  tools: Optional list of tools.
415
+ input_guardrails: Optional list of input guardrails to run on initial user input.
416
+ output_guardrails: Optional list of output guardrails to run on final agent output.
367
417
  output_type: Optional output type.
368
418
  tool_use_behavior: Optional tool use behavior.
369
419
  mcp_timeout_seconds: Optional param to set the timeout threshold for the MCP servers. Defaults to 5 seconds.
420
+ max_turns: Maximum number of turns the agent can take. Uses Runner's default if None.
370
421
 
371
422
  Returns:
372
423
  Union[SerializableRunResultStreaming, RunResultStreaming]: SerializableRunResultStreaming when in Temporal, RunResultStreaming otherwise.
@@ -388,6 +439,9 @@ class OpenAIModule:
388
439
  output_type=output_type,
389
440
  tool_use_behavior=tool_use_behavior,
390
441
  mcp_timeout_seconds=mcp_timeout_seconds,
442
+ input_guardrails=input_guardrails,
443
+ output_guardrails=output_guardrails,
444
+ max_turns=max_turns
391
445
  )
392
446
  return await ActivityHelpers.execute_activity(
393
447
  activity_name=OpenAIActivityName.RUN_AGENT_STREAMED_AUTO_SEND,
@@ -414,4 +468,7 @@ class OpenAIModule:
414
468
  output_type=output_type,
415
469
  tool_use_behavior=tool_use_behavior,
416
470
  mcp_timeout_seconds=mcp_timeout_seconds,
471
+ input_guardrails=input_guardrails,
472
+ output_guardrails=output_guardrails,
473
+ max_turns=max_turns,
417
474
  )
@@ -229,9 +229,12 @@ def merge_deployment_configs(
229
229
  all_env_vars[EnvVarKeys.AUTH_PRINCIPAL_B64.value] = encoded_principal
230
230
  else:
231
231
  raise DeploymentError(f"Auth principal unable to be encoded for agent_env_config: {agent_env_config}")
232
-
232
+
233
+ logger.info(f"Defined agent helm overrides: {agent_env_config.helm_overrides}")
234
+ logger.info(f"Before-merge helm values: {helm_values}")
233
235
  if agent_env_config.helm_overrides:
234
236
  _deep_merge(helm_values, agent_env_config.helm_overrides)
237
+ logger.info(f"After-merge helm values: {helm_values}")
235
238
 
236
239
  # Set final environment variables
237
240
  # Environment variable precedence: manifest -> environments.yaml -> secrets (highest)
@@ -262,14 +262,30 @@ class MyWorkflow(BaseWorkflow):
262
262
  ```
263
263
 
264
264
  ### Custom Activities
265
- Add custom activities for external operations:
265
+ Add custom activities for external operations. **Important**: Always specify appropriate timeouts (recommended: 10 minutes):
266
266
 
267
267
  ```python
268
268
  # In project/activities.py
269
- @activity.defn
269
+ from datetime import timedelta
270
+ from temporalio import activity
271
+ from temporalio.common import RetryPolicy
272
+
273
+ @activity.defn(name="call_external_api")
270
274
  async def call_external_api(data):
271
275
  # HTTP requests, database operations, etc.
272
276
  pass
277
+
278
+ # In your workflow, call it with a timeout:
279
+ result = await workflow.execute_activity(
280
+ "call_external_api",
281
+ data,
282
+ start_to_close_timeout=timedelta(minutes=10), # Recommended: 10 minute timeout
283
+ heartbeat_timeout=timedelta(minutes=1), # Optional: heartbeat monitoring
284
+ retry_policy=RetryPolicy(maximum_attempts=3) # Optional: retry policy
285
+ )
286
+
287
+ # Don't forget to register your custom activities in run_worker.py:
288
+ # all_activities = get_all_activities() + [your_custom_activity_function]
273
289
  ```
274
290
 
275
291
  ### Integration with External Services
@@ -52,7 +52,7 @@ environments:
52
52
  limits:
53
53
  cpu: "1000m"
54
54
  memory: "2Gi"
55
- temporal:
55
+ temporal-worker:
56
56
  enabled: true
57
57
  replicaCount: 2
58
58
  resources:
@@ -0,0 +1,77 @@
1
+ """
2
+ Custom Temporal Activities Template
3
+ ====================================
4
+ This file is for defining custom Temporal activities that can be executed
5
+ by your workflow. Activities are used for:
6
+ - External API calls
7
+ - Database operations
8
+ - File I/O operations
9
+ - Heavy computations
10
+ - Any non-deterministic operations
11
+
12
+ IMPORTANT: All activities should have appropriate timeouts!
13
+ Default recommendation: start_to_close_timeout=timedelta(minutes=10)
14
+ """
15
+
16
+ from datetime import timedelta
17
+ from typing import Any, Dict
18
+
19
+ from pydantic import BaseModel
20
+ from temporalio import activity
21
+ from temporalio.common import RetryPolicy
22
+
23
+ from agentex.lib.utils.logging import make_logger
24
+
25
+ logger = make_logger(__name__)
26
+
27
+
28
+ # Example activity parameter models
29
+ class ExampleActivityParams(BaseModel):
30
+ """Parameters for the example activity"""
31
+ data: Dict[str, Any]
32
+ task_id: str
33
+
34
+
35
+ # Example custom activity
36
+ @activity.defn(name="example_custom_activity")
37
+ async def example_custom_activity(params: ExampleActivityParams) -> Dict[str, Any]:
38
+ """
39
+ Example custom activity that demonstrates best practices.
40
+
41
+ When calling this activity from your workflow, use:
42
+ ```python
43
+ result = await workflow.execute_activity(
44
+ "example_custom_activity",
45
+ ExampleActivityParams(data={"key": "value"}, task_id=task_id),
46
+ start_to_close_timeout=timedelta(minutes=10), # Recommended: 10 minute timeout
47
+ heartbeat_timeout=timedelta(minutes=1), # Optional: heartbeat every minute
48
+ retry_policy=RetryPolicy(maximum_attempts=3) # Optional: retry up to 3 times
49
+ )
50
+ ```
51
+ """
52
+ logger.info(f"Processing activity for task {params.task_id} with data: {params.data}")
53
+
54
+ # Your activity logic here
55
+ # This could be:
56
+ # - API calls
57
+ # - Database operations
58
+ # - File processing
59
+ # - ML model inference
60
+ # - etc.
61
+
62
+ result = {
63
+ "status": "success",
64
+ "processed_data": params.data,
65
+ "task_id": params.task_id
66
+ }
67
+
68
+ return result
69
+
70
+
71
+ # Add more custom activities below as needed
72
+ # Remember to:
73
+ # 1. Use appropriate timeouts (default: 10 minutes)
74
+ # 2. Define clear parameter models with Pydantic
75
+ # 3. Handle errors appropriately
76
+ # 4. Use logging for debugging
77
+ # 5. Keep activities focused on a single responsibility
@@ -22,13 +22,15 @@ async def main():
22
22
  if task_queue_name is None:
23
23
  raise ValueError("WORKFLOW_TASK_QUEUE is not set")
24
24
 
25
+ all_activities = get_all_activities() + [] # add your own activities here
26
+
25
27
  # Create a worker with automatic tracing
26
28
  worker = AgentexWorker(
27
29
  task_queue=task_queue_name,
28
30
  )
29
31
 
30
32
  await worker.run(
31
- activities=get_all_activities(),
33
+ activities=all_activities,
32
34
  workflow={{ workflow_class }},
33
35
  )
34
36
 
@@ -180,9 +180,19 @@ class ACPService:
180
180
  self,
181
181
  task_id: str | None = None,
182
182
  task_name: str | None = None,
183
+ agent_id: str | None = None,
184
+ agent_name: str | None = None,
183
185
  trace_id: str | None = None,
184
186
  parent_span_id: str | None = None,
185
- ) -> Task:
187
+ ) -> Task:
188
+ # Require agent identification
189
+ if not agent_name and not agent_id:
190
+ raise ValueError("Either agent_name or agent_id must be provided to identify the agent that owns the task")
191
+
192
+ # Require task identification
193
+ if not task_name and not task_id:
194
+ raise ValueError("Either task_name or task_id must be provided to identify the task to cancel")
195
+
186
196
  trace = self._tracer.trace(trace_id=trace_id)
187
197
  async with trace.span(
188
198
  parent_id=parent_span_id,
@@ -190,27 +200,32 @@ class ACPService:
190
200
  input={
191
201
  "task_id": task_id,
192
202
  "task_name": task_name,
203
+ "agent_id": agent_id,
204
+ "agent_name": agent_name,
193
205
  },
194
206
  ) as span:
195
207
  heartbeat_if_in_workflow("task cancel")
208
+
209
+ # Build params for the agent (task identification)
210
+ params = {}
211
+ if task_id:
212
+ params["task_id"] = task_id
196
213
  if task_name:
214
+ params["task_name"] = task_name
215
+
216
+ # Send cancel request to the correct agent
217
+ if agent_name:
197
218
  json_rpc_response = await self._agentex_client.agents.rpc_by_name(
198
- agent_name=task_name,
219
+ agent_name=agent_name,
199
220
  method="task/cancel",
200
- params={
201
- "task_name": task_name,
202
- },
221
+ params=params,
203
222
  )
204
- elif task_id:
223
+ else: # agent_id is provided (validated above)
205
224
  json_rpc_response = await self._agentex_client.agents.rpc(
206
- agent_id=task_id,
225
+ agent_id=agent_id,
207
226
  method="task/cancel",
208
- params={
209
- "task_id": task_id,
210
- },
227
+ params=params,
211
228
  )
212
- else:
213
- raise ValueError("Either task_name or task_id must be provided")
214
229
 
215
230
  task_entry = Task.model_validate(json_rpc_response.result)
216
231
  if span: