agentex-sdk 0.6.0__py3-none-any.whl → 0.6.2__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.
- agentex/_client.py +15 -1
- agentex/_version.py +1 -1
- agentex/lib/adk/providers/_modules/litellm.py +1 -1
- agentex/lib/adk/providers/_modules/openai.py +16 -1
- agentex/lib/adk/providers/_modules/sgp.py +1 -1
- agentex/lib/adk/providers/_modules/sync_provider.py +32 -24
- agentex/lib/cli/commands/init.py +7 -18
- agentex/lib/cli/templates/default/README.md.j2 +1 -1
- agentex/lib/cli/templates/default/dev.ipynb.j2 +1 -1
- agentex/lib/cli/templates/default/manifest.yaml.j2 +1 -4
- agentex/lib/cli/templates/default/project/acp.py.j2 +3 -3
- agentex/lib/cli/templates/default/test_agent.py.j2 +1 -1
- agentex/lib/cli/templates/sync/manifest.yaml.j2 +0 -3
- agentex/lib/cli/templates/temporal/README.md.j2 +1 -1
- agentex/lib/cli/templates/temporal/dev.ipynb.j2 +1 -1
- agentex/lib/cli/templates/temporal/manifest.yaml.j2 +2 -5
- agentex/lib/cli/templates/temporal/project/acp.py.j2 +1 -1
- agentex/lib/cli/templates/temporal/test_agent.py.j2 +1 -1
- agentex/lib/core/temporal/plugins/openai_agents/models/temporal_streaming_model.py +66 -0
- agentex/lib/core/temporal/plugins/openai_agents/models/temporal_tracing_model.py +94 -17
- agentex/lib/environment_variables.py +1 -1
- agentex/lib/sdk/config/agent_config.py +1 -1
- agentex/lib/sdk/fastacp/base/base_acp_server.py +4 -4
- agentex/lib/sdk/fastacp/fastacp.py +30 -16
- agentex/lib/sdk/fastacp/impl/{agentic_base_acp.py → async_base_acp.py} +10 -8
- agentex/lib/sdk/fastacp/tests/README.md +3 -3
- agentex/lib/sdk/fastacp/tests/conftest.py +4 -4
- agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py +99 -72
- agentex/lib/sdk/fastacp/tests/test_integration.py +24 -24
- agentex/lib/types/fastacp.py +8 -5
- agentex/lib/utils/dev_tools/async_messages.py +1 -1
- agentex/resources/__init__.py +14 -0
- agentex/resources/deployment_history.py +272 -0
- agentex/types/__init__.py +3 -0
- agentex/types/agent.py +4 -1
- agentex/types/deployment_history.py +33 -0
- agentex/types/deployment_history_list_params.py +18 -0
- agentex/types/deployment_history_list_response.py +10 -0
- {agentex_sdk-0.6.0.dist-info → agentex_sdk-0.6.2.dist-info}/METADATA +1 -1
- {agentex_sdk-0.6.0.dist-info → agentex_sdk-0.6.2.dist-info}/RECORD +43 -39
- {agentex_sdk-0.6.0.dist-info → agentex_sdk-0.6.2.dist-info}/WHEEL +0 -0
- {agentex_sdk-0.6.0.dist-info → agentex_sdk-0.6.2.dist-info}/entry_points.txt +0 -0
- {agentex_sdk-0.6.0.dist-info → agentex_sdk-0.6.2.dist-info}/licenses/LICENSE +0 -0
agentex/_client.py
CHANGED
|
@@ -21,7 +21,7 @@ from ._types import (
|
|
|
21
21
|
)
|
|
22
22
|
from ._utils import is_given, get_async_library
|
|
23
23
|
from ._version import __version__
|
|
24
|
-
from .resources import spans, tasks, agents, events, states, tracker
|
|
24
|
+
from .resources import spans, tasks, agents, events, states, tracker, deployment_history
|
|
25
25
|
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
|
|
26
26
|
from ._exceptions import APIStatusError
|
|
27
27
|
from ._base_client import (
|
|
@@ -57,6 +57,7 @@ class Agentex(SyncAPIClient):
|
|
|
57
57
|
states: states.StatesResource
|
|
58
58
|
events: events.EventsResource
|
|
59
59
|
tracker: tracker.TrackerResource
|
|
60
|
+
deployment_history: deployment_history.DeploymentHistoryResource
|
|
60
61
|
with_raw_response: AgentexWithRawResponse
|
|
61
62
|
with_streaming_response: AgentexWithStreamedResponse
|
|
62
63
|
|
|
@@ -141,6 +142,7 @@ class Agentex(SyncAPIClient):
|
|
|
141
142
|
self.states = states.StatesResource(self)
|
|
142
143
|
self.events = events.EventsResource(self)
|
|
143
144
|
self.tracker = tracker.TrackerResource(self)
|
|
145
|
+
self.deployment_history = deployment_history.DeploymentHistoryResource(self)
|
|
144
146
|
self.with_raw_response = AgentexWithRawResponse(self)
|
|
145
147
|
self.with_streaming_response = AgentexWithStreamedResponse(self)
|
|
146
148
|
|
|
@@ -261,6 +263,7 @@ class AsyncAgentex(AsyncAPIClient):
|
|
|
261
263
|
states: states.AsyncStatesResource
|
|
262
264
|
events: events.AsyncEventsResource
|
|
263
265
|
tracker: tracker.AsyncTrackerResource
|
|
266
|
+
deployment_history: deployment_history.AsyncDeploymentHistoryResource
|
|
264
267
|
with_raw_response: AsyncAgentexWithRawResponse
|
|
265
268
|
with_streaming_response: AsyncAgentexWithStreamedResponse
|
|
266
269
|
|
|
@@ -345,6 +348,7 @@ class AsyncAgentex(AsyncAPIClient):
|
|
|
345
348
|
self.states = states.AsyncStatesResource(self)
|
|
346
349
|
self.events = events.AsyncEventsResource(self)
|
|
347
350
|
self.tracker = tracker.AsyncTrackerResource(self)
|
|
351
|
+
self.deployment_history = deployment_history.AsyncDeploymentHistoryResource(self)
|
|
348
352
|
self.with_raw_response = AsyncAgentexWithRawResponse(self)
|
|
349
353
|
self.with_streaming_response = AsyncAgentexWithStreamedResponse(self)
|
|
350
354
|
|
|
@@ -466,6 +470,7 @@ class AgentexWithRawResponse:
|
|
|
466
470
|
self.states = states.StatesResourceWithRawResponse(client.states)
|
|
467
471
|
self.events = events.EventsResourceWithRawResponse(client.events)
|
|
468
472
|
self.tracker = tracker.TrackerResourceWithRawResponse(client.tracker)
|
|
473
|
+
self.deployment_history = deployment_history.DeploymentHistoryResourceWithRawResponse(client.deployment_history)
|
|
469
474
|
|
|
470
475
|
|
|
471
476
|
class AsyncAgentexWithRawResponse:
|
|
@@ -477,6 +482,9 @@ class AsyncAgentexWithRawResponse:
|
|
|
477
482
|
self.states = states.AsyncStatesResourceWithRawResponse(client.states)
|
|
478
483
|
self.events = events.AsyncEventsResourceWithRawResponse(client.events)
|
|
479
484
|
self.tracker = tracker.AsyncTrackerResourceWithRawResponse(client.tracker)
|
|
485
|
+
self.deployment_history = deployment_history.AsyncDeploymentHistoryResourceWithRawResponse(
|
|
486
|
+
client.deployment_history
|
|
487
|
+
)
|
|
480
488
|
|
|
481
489
|
|
|
482
490
|
class AgentexWithStreamedResponse:
|
|
@@ -488,6 +496,9 @@ class AgentexWithStreamedResponse:
|
|
|
488
496
|
self.states = states.StatesResourceWithStreamingResponse(client.states)
|
|
489
497
|
self.events = events.EventsResourceWithStreamingResponse(client.events)
|
|
490
498
|
self.tracker = tracker.TrackerResourceWithStreamingResponse(client.tracker)
|
|
499
|
+
self.deployment_history = deployment_history.DeploymentHistoryResourceWithStreamingResponse(
|
|
500
|
+
client.deployment_history
|
|
501
|
+
)
|
|
491
502
|
|
|
492
503
|
|
|
493
504
|
class AsyncAgentexWithStreamedResponse:
|
|
@@ -499,6 +510,9 @@ class AsyncAgentexWithStreamedResponse:
|
|
|
499
510
|
self.states = states.AsyncStatesResourceWithStreamingResponse(client.states)
|
|
500
511
|
self.events = events.AsyncEventsResourceWithStreamingResponse(client.events)
|
|
501
512
|
self.tracker = tracker.AsyncTrackerResourceWithStreamingResponse(client.tracker)
|
|
513
|
+
self.deployment_history = deployment_history.AsyncDeploymentHistoryResourceWithStreamingResponse(
|
|
514
|
+
client.deployment_history
|
|
515
|
+
)
|
|
502
516
|
|
|
503
517
|
|
|
504
518
|
Client = Agentex
|
agentex/_version.py
CHANGED
|
@@ -32,7 +32,7 @@ DEFAULT_RETRY_POLICY = RetryPolicy(maximum_attempts=1)
|
|
|
32
32
|
class LiteLLMModule:
|
|
33
33
|
"""
|
|
34
34
|
Module for managing LiteLLM agent operations in Agentex.
|
|
35
|
-
Provides high-level methods for chat completion, streaming
|
|
35
|
+
Provides high-level methods for chat completion, streaming.
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
def __init__(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import sys
|
|
3
4
|
from typing import Any, Literal
|
|
4
5
|
from datetime import timedelta
|
|
5
6
|
|
|
@@ -12,6 +13,12 @@ from temporalio.common import RetryPolicy
|
|
|
12
13
|
from agents.agent_output import AgentOutputSchemaBase
|
|
13
14
|
from agents.model_settings import ModelSettings
|
|
14
15
|
|
|
16
|
+
# Use warnings.deprecated in Python 3.13+, typing_extensions.deprecated for older versions
|
|
17
|
+
if sys.version_info >= (3, 13):
|
|
18
|
+
from warnings import deprecated
|
|
19
|
+
else:
|
|
20
|
+
from typing_extensions import deprecated
|
|
21
|
+
|
|
15
22
|
from agentex.lib.utils.logging import make_logger
|
|
16
23
|
from agentex.lib.utils.temporal import in_temporal_workflow
|
|
17
24
|
from agentex.lib.core.tracing.tracer import AsyncTracer
|
|
@@ -383,6 +390,10 @@ class OpenAIModule:
|
|
|
383
390
|
previous_response_id=previous_response_id,
|
|
384
391
|
)
|
|
385
392
|
|
|
393
|
+
@deprecated(
|
|
394
|
+
"Use the OpenAI Agents SDK integration with Temporal instead. "
|
|
395
|
+
"See examples in tutorials/10_agentic/10_temporal/ for migration guidance."
|
|
396
|
+
)
|
|
386
397
|
async def run_agent_streamed_auto_send(
|
|
387
398
|
self,
|
|
388
399
|
task_id: str,
|
|
@@ -413,6 +424,10 @@ class OpenAIModule:
|
|
|
413
424
|
"""
|
|
414
425
|
Run an agent with streaming enabled and automatic TaskMessage creation.
|
|
415
426
|
|
|
427
|
+
.. deprecated::
|
|
428
|
+
Use the OpenAI Agents SDK integration with Temporal instead.
|
|
429
|
+
See examples in tutorials/10_agentic/10_temporal/ for migration guidance.
|
|
430
|
+
|
|
416
431
|
Args:
|
|
417
432
|
task_id: The ID of the task to run the agent for.
|
|
418
433
|
input_list: List of input data for the agent.
|
|
@@ -494,4 +509,4 @@ class OpenAIModule:
|
|
|
494
509
|
output_guardrails=output_guardrails,
|
|
495
510
|
max_turns=max_turns,
|
|
496
511
|
previous_response_id=previous_response_id,
|
|
497
|
-
)
|
|
512
|
+
)
|
|
@@ -25,7 +25,7 @@ DEFAULT_RETRY_POLICY = RetryPolicy(maximum_attempts=1)
|
|
|
25
25
|
class SGPModule:
|
|
26
26
|
"""
|
|
27
27
|
Module for managing SGP agent operations in Agentex.
|
|
28
|
-
Provides high-level methods for chat completion, streaming,
|
|
28
|
+
Provides high-level methods for chat completion, streaming, and message classification.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
31
|
def __init__(
|
|
@@ -164,18 +164,22 @@ class SyncStreamingModel(Model):
|
|
|
164
164
|
output_items = response_output if isinstance(response_output, list) else [response_output]
|
|
165
165
|
|
|
166
166
|
for item in output_items:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
167
|
+
try:
|
|
168
|
+
item_dict = _serialize_item(item)
|
|
169
|
+
if item_dict:
|
|
170
|
+
new_items.append(item_dict)
|
|
171
|
+
|
|
172
|
+
# Extract final_output from message type if available
|
|
173
|
+
if item_dict.get('type') == 'message' and not final_output:
|
|
174
|
+
content = item_dict.get('content', [])
|
|
175
|
+
if content and isinstance(content, list):
|
|
176
|
+
for content_part in content:
|
|
177
|
+
if isinstance(content_part, dict) and 'text' in content_part:
|
|
178
|
+
final_output = content_part['text']
|
|
179
|
+
break
|
|
180
|
+
except Exception as e:
|
|
181
|
+
logger.warning(f"Failed to serialize item in get_response: {e}")
|
|
182
|
+
continue
|
|
179
183
|
|
|
180
184
|
span.output = {
|
|
181
185
|
"new_items": new_items,
|
|
@@ -275,18 +279,22 @@ class SyncStreamingModel(Model):
|
|
|
275
279
|
if event_type == 'response.output_item.done':
|
|
276
280
|
item = getattr(event, 'item', None)
|
|
277
281
|
if item is not None:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
282
|
+
try:
|
|
283
|
+
item_dict = _serialize_item(item)
|
|
284
|
+
if item_dict:
|
|
285
|
+
new_items.append(item_dict)
|
|
286
|
+
|
|
287
|
+
# Update final_response_text from message type if available
|
|
288
|
+
if item_dict.get('type') == 'message':
|
|
289
|
+
content = item_dict.get('content', [])
|
|
290
|
+
if content and isinstance(content, list):
|
|
291
|
+
for content_part in content:
|
|
292
|
+
if isinstance(content_part, dict) and 'text' in content_part:
|
|
293
|
+
final_response_text = content_part['text']
|
|
294
|
+
break
|
|
295
|
+
except Exception as e:
|
|
296
|
+
logger.warning(f"Failed to serialize item in stream_response: {e}")
|
|
297
|
+
continue
|
|
290
298
|
|
|
291
299
|
yield event
|
|
292
300
|
|
agentex/lib/cli/commands/init.py
CHANGED
|
@@ -125,16 +125,16 @@ def init():
|
|
|
125
125
|
table.add_column("Template", style="cyan", no_wrap=True)
|
|
126
126
|
table.add_column("Description", style="white")
|
|
127
127
|
table.add_row(
|
|
128
|
-
"[bold cyan]
|
|
129
|
-
"
|
|
128
|
+
"[bold cyan]Async - ACP Only[/bold cyan]",
|
|
129
|
+
"Asynchronous, non-blocking agent that can process multiple concurrent requests. Best for straightforward asynchronous agents that don't need durable execution. Good for asynchronous workflows, stateful applications, and multi-step analysis.",
|
|
130
130
|
)
|
|
131
131
|
table.add_row(
|
|
132
|
-
"[bold cyan]
|
|
133
|
-
"
|
|
132
|
+
"[bold cyan]Async - Temporal[/bold cyan]",
|
|
133
|
+
"Asynchronous, non-blocking agent with durable execution for all steps. Best for production-grade agents that require complex multi-step tool calls, human-in-the-loop approvals, and long-running processes that require transactional reliability.",
|
|
134
134
|
)
|
|
135
135
|
table.add_row(
|
|
136
136
|
"[bold cyan]Sync ACP[/bold cyan]",
|
|
137
|
-
"
|
|
137
|
+
"Synchronous agent that processes one request per task with a simple request-response pattern. Best for low-latency use cases, FAQ bots, translation services, and data lookups.",
|
|
138
138
|
)
|
|
139
139
|
console.print()
|
|
140
140
|
console.print(table)
|
|
@@ -151,8 +151,8 @@ def init():
|
|
|
151
151
|
template_type = questionary.select(
|
|
152
152
|
"What type of template would you like to create?",
|
|
153
153
|
choices=[
|
|
154
|
-
{"name": "
|
|
155
|
-
{"name": "
|
|
154
|
+
{"name": "Async - ACP Only", "value": TemplateType.DEFAULT},
|
|
155
|
+
{"name": "Async - Temporal", "value": TemplateType.TEMPORAL},
|
|
156
156
|
{"name": "Sync ACP", "value": TemplateType.SYNC},
|
|
157
157
|
],
|
|
158
158
|
).ask()
|
|
@@ -184,16 +184,6 @@ def init():
|
|
|
184
184
|
).ask()
|
|
185
185
|
if not description:
|
|
186
186
|
return
|
|
187
|
-
|
|
188
|
-
agent_input_type = questionary.select(
|
|
189
|
-
"What type of input will your agent handle?",
|
|
190
|
-
choices=[
|
|
191
|
-
{"name": "Text Input", "value": "text"},
|
|
192
|
-
{"name": "Structured Input", "value": "json"},
|
|
193
|
-
],
|
|
194
|
-
).ask()
|
|
195
|
-
if not agent_input_type:
|
|
196
|
-
return
|
|
197
187
|
|
|
198
188
|
use_uv = questionary.select(
|
|
199
189
|
"Would you like to use uv for package management?",
|
|
@@ -206,7 +196,6 @@ def init():
|
|
|
206
196
|
answers = {
|
|
207
197
|
"template_type": template_type,
|
|
208
198
|
"project_path": project_path,
|
|
209
|
-
"agent_input_type": agent_input_type,
|
|
210
199
|
"agent_name": agent_name,
|
|
211
200
|
"agent_directory_name": agent_directory_name,
|
|
212
201
|
"description": description,
|
|
@@ -95,7 +95,7 @@ The notebook includes:
|
|
|
95
95
|
- **Async message subscription**: Subscribe to server-side events to receive agent responses
|
|
96
96
|
- **Rich message display**: Beautiful formatting with timestamps and author information
|
|
97
97
|
|
|
98
|
-
The notebook automatically uses your agent name (`{{ agent_name }}`) and demonstrates the
|
|
98
|
+
The notebook automatically uses your agent name (`{{ agent_name }}`) and demonstrates the async ACP workflow: create task → send event → subscribe to responses.
|
|
99
99
|
|
|
100
100
|
### 3. Manage Dependencies
|
|
101
101
|
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"metadata": {},
|
|
30
30
|
"outputs": [],
|
|
31
31
|
"source": [
|
|
32
|
-
"# (REQUIRED) Create a new task. For
|
|
32
|
+
"# (REQUIRED) Create a new task. For Async agents, you must create a task for messages to be associated with.\n",
|
|
33
33
|
"import uuid\n",
|
|
34
34
|
"\n",
|
|
35
35
|
"rpc_response = client.agents.create_task(\n",
|
|
@@ -57,7 +57,7 @@ local_development:
|
|
|
57
57
|
# Agent Configuration
|
|
58
58
|
# -----------------
|
|
59
59
|
agent:
|
|
60
|
-
acp_type:
|
|
60
|
+
acp_type: async
|
|
61
61
|
|
|
62
62
|
# Unique name for your agent
|
|
63
63
|
# Used for task routing and monitoring
|
|
@@ -67,9 +67,6 @@ agent:
|
|
|
67
67
|
# Helps with documentation and discovery
|
|
68
68
|
description: {{ description }}
|
|
69
69
|
|
|
70
|
-
# Type of input the agent will handle: text or json
|
|
71
|
-
agent_input_type: {{ agent_input_type }}
|
|
72
|
-
|
|
73
70
|
# Temporal workflow configuration
|
|
74
71
|
# Set enabled: true to use Temporal workflows for long-running tasks
|
|
75
72
|
temporal:
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from agentex.lib.sdk.fastacp.fastacp import FastACP
|
|
2
|
-
from agentex.lib.types.fastacp import
|
|
2
|
+
from agentex.lib.types.fastacp import AsyncACPConfig
|
|
3
3
|
from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# Create an ACP server
|
|
7
7
|
acp = FastACP.create(
|
|
8
|
-
acp_type="
|
|
9
|
-
config=
|
|
8
|
+
acp_type="async",
|
|
9
|
+
config=AsyncACPConfig(type="base")
|
|
10
10
|
)
|
|
11
11
|
|
|
12
12
|
|
|
@@ -24,7 +24,7 @@ from agentex import AsyncAgentex
|
|
|
24
24
|
from agentex.types import TaskMessage
|
|
25
25
|
from agentex.types.agent_rpc_params import ParamsCreateTaskRequest
|
|
26
26
|
from agentex.types.text_content_param import TextContentParam
|
|
27
|
-
from test_utils.
|
|
27
|
+
from test_utils.async_utils import (
|
|
28
28
|
poll_for_agent_response,
|
|
29
29
|
send_event_and_poll_yielding,
|
|
30
30
|
stream_agent_response,
|
|
@@ -66,9 +66,6 @@ agent:
|
|
|
66
66
|
# Helps with documentation and discovery
|
|
67
67
|
description: {{ description }}
|
|
68
68
|
|
|
69
|
-
# Type of input the agent will handle: text or json
|
|
70
|
-
agent_input_type: {{ agent_input_type }}
|
|
71
|
-
|
|
72
69
|
# Temporal workflow configuration
|
|
73
70
|
# Set enabled: true to use Temporal workflows for long-running tasks
|
|
74
71
|
temporal:
|
|
@@ -101,7 +101,7 @@ The notebook includes:
|
|
|
101
101
|
- **Async message subscription**: Subscribe to server-side events to receive agent responses
|
|
102
102
|
- **Rich message display**: Beautiful formatting with timestamps and author information
|
|
103
103
|
|
|
104
|
-
The notebook automatically uses your agent name (`{{ agent_name }}`) and demonstrates the
|
|
104
|
+
The notebook automatically uses your agent name (`{{ agent_name }}`) and demonstrates the async ACP workflow: create task → send event → subscribe to responses.
|
|
105
105
|
|
|
106
106
|
### 3. Develop Temporal Workflows
|
|
107
107
|
- Edit `workflow.py` to define your agent's async workflow logic
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"metadata": {},
|
|
30
30
|
"outputs": [],
|
|
31
31
|
"source": [
|
|
32
|
-
"# (REQUIRED) Create a new task. For
|
|
32
|
+
"# (REQUIRED) Create a new task. For Async agents, you must create a task for messages to be associated with.\n",
|
|
33
33
|
"import uuid\n",
|
|
34
34
|
"\n",
|
|
35
35
|
"rpc_response = client.agents.create_task(\n",
|
|
@@ -64,8 +64,8 @@ local_development:
|
|
|
64
64
|
# Agent Configuration
|
|
65
65
|
# -----------------
|
|
66
66
|
agent:
|
|
67
|
-
# Type of agent - either sync or
|
|
68
|
-
acp_type:
|
|
67
|
+
# Type of agent - either sync or async
|
|
68
|
+
acp_type: async
|
|
69
69
|
|
|
70
70
|
# Unique name for your agent
|
|
71
71
|
# Used for task routing and monitoring
|
|
@@ -75,9 +75,6 @@ agent:
|
|
|
75
75
|
# Helps with documentation and discovery
|
|
76
76
|
description: {{ description }}
|
|
77
77
|
|
|
78
|
-
# Type of input the agent will handle: text or json
|
|
79
|
-
agent_input_type: {{ agent_input_type }}
|
|
80
|
-
|
|
81
78
|
# Temporal workflow configuration
|
|
82
79
|
# This enables your agent to run as a Temporal workflow for long-running tasks
|
|
83
80
|
temporal:
|
|
@@ -39,7 +39,7 @@ from agentex.lib.types.fastacp import TemporalACPConfig
|
|
|
39
39
|
|
|
40
40
|
# Create the ACP server
|
|
41
41
|
acp = FastACP.create(
|
|
42
|
-
acp_type="
|
|
42
|
+
acp_type="async",
|
|
43
43
|
config=TemporalACPConfig(
|
|
44
44
|
# When deployed to the cluster, the Temporal address will automatically be set to the cluster address
|
|
45
45
|
# For local development, we set the address manually to talk to the local Temporal service set up via docker compose
|
|
@@ -24,7 +24,7 @@ from agentex import AsyncAgentex
|
|
|
24
24
|
from agentex.types import TaskMessage
|
|
25
25
|
from agentex.types.agent_rpc_params import ParamsCreateTaskRequest
|
|
26
26
|
from agentex.types.text_content_param import TextContentParam
|
|
27
|
-
from test_utils.
|
|
27
|
+
from test_utils.async_utils import (
|
|
28
28
|
poll_for_agent_response,
|
|
29
29
|
send_event_and_poll_yielding,
|
|
30
30
|
stream_agent_response,
|
|
@@ -62,6 +62,43 @@ from agentex.lib.core.temporal.plugins.openai_agents.interceptors.context_interc
|
|
|
62
62
|
# Create logger for this module
|
|
63
63
|
logger = logging.getLogger("agentex.temporal.streaming")
|
|
64
64
|
|
|
65
|
+
|
|
66
|
+
def _serialize_item(item: Any) -> dict[str, Any]:
|
|
67
|
+
"""
|
|
68
|
+
Universal serializer for any item type from OpenAI Agents SDK.
|
|
69
|
+
|
|
70
|
+
Uses model_dump() for Pydantic models, otherwise extracts attributes manually.
|
|
71
|
+
Filters out internal Pydantic fields that can't be serialized.
|
|
72
|
+
"""
|
|
73
|
+
if hasattr(item, 'model_dump'):
|
|
74
|
+
# Pydantic model - use model_dump for proper serialization
|
|
75
|
+
try:
|
|
76
|
+
return item.model_dump(mode='json', exclude_unset=True)
|
|
77
|
+
except Exception:
|
|
78
|
+
# Fallback to dict conversion
|
|
79
|
+
return dict(item) if hasattr(item, '__iter__') else {}
|
|
80
|
+
else:
|
|
81
|
+
# Not a Pydantic model - extract attributes manually
|
|
82
|
+
item_dict = {}
|
|
83
|
+
for attr_name in dir(item):
|
|
84
|
+
if not attr_name.startswith('_') and attr_name not in ('model_fields', 'model_config', 'model_computed_fields'):
|
|
85
|
+
try:
|
|
86
|
+
attr_value = getattr(item, attr_name, None)
|
|
87
|
+
# Skip methods and None values
|
|
88
|
+
if attr_value is not None and not callable(attr_value):
|
|
89
|
+
# Convert to JSON-serializable format
|
|
90
|
+
if hasattr(attr_value, 'model_dump'):
|
|
91
|
+
item_dict[attr_name] = attr_value.model_dump()
|
|
92
|
+
elif isinstance(attr_value, (str, int, float, bool, list, dict)):
|
|
93
|
+
item_dict[attr_name] = attr_value
|
|
94
|
+
else:
|
|
95
|
+
item_dict[attr_name] = str(attr_value)
|
|
96
|
+
except Exception:
|
|
97
|
+
# Skip attributes that can't be accessed
|
|
98
|
+
pass
|
|
99
|
+
return item_dict
|
|
100
|
+
|
|
101
|
+
|
|
65
102
|
class TemporalStreamingModel(Model):
|
|
66
103
|
"""Custom model implementation with streaming support."""
|
|
67
104
|
|
|
@@ -739,6 +776,35 @@ class TemporalStreamingModel(Model):
|
|
|
739
776
|
output_tokens_details=OutputTokensDetails(reasoning_tokens=len(''.join(reasoning_contents)) // 4), # Approximate
|
|
740
777
|
)
|
|
741
778
|
|
|
779
|
+
# Serialize response output items for span tracing
|
|
780
|
+
new_items = []
|
|
781
|
+
final_output = None
|
|
782
|
+
|
|
783
|
+
for item in response_output:
|
|
784
|
+
try:
|
|
785
|
+
item_dict = _serialize_item(item)
|
|
786
|
+
if item_dict:
|
|
787
|
+
new_items.append(item_dict)
|
|
788
|
+
|
|
789
|
+
# Extract final_output from message type if available
|
|
790
|
+
if item_dict.get('type') == 'message' and not final_output:
|
|
791
|
+
content = item_dict.get('content', [])
|
|
792
|
+
if content and isinstance(content, list):
|
|
793
|
+
for content_part in content:
|
|
794
|
+
if isinstance(content_part, dict) and 'text' in content_part:
|
|
795
|
+
final_output = content_part['text']
|
|
796
|
+
break
|
|
797
|
+
except Exception as e:
|
|
798
|
+
logger.warning(f"Failed to serialize item in temporal_streaming_model: {e}")
|
|
799
|
+
continue
|
|
800
|
+
|
|
801
|
+
# Set span output with structured data
|
|
802
|
+
if span:
|
|
803
|
+
span.output = {
|
|
804
|
+
"new_items": new_items,
|
|
805
|
+
"final_output": final_output,
|
|
806
|
+
}
|
|
807
|
+
|
|
742
808
|
# Return the response
|
|
743
809
|
return ModelResponse(
|
|
744
810
|
output=response_output,
|
|
@@ -7,9 +7,10 @@ context interceptor to access task_id, trace_id, and parent_span_id.
|
|
|
7
7
|
The key innovation is that these are thin wrappers around the standard OpenAI models,
|
|
8
8
|
avoiding code duplication while adding tracing capabilities.
|
|
9
9
|
"""
|
|
10
|
+
from __future__ import annotations
|
|
10
11
|
|
|
11
12
|
import logging
|
|
12
|
-
from typing import List, Union, Optional, override
|
|
13
|
+
from typing import Any, List, Union, Optional, override
|
|
13
14
|
|
|
14
15
|
from agents import (
|
|
15
16
|
Tool,
|
|
@@ -41,6 +42,42 @@ from agentex.lib.core.temporal.plugins.openai_agents.interceptors.context_interc
|
|
|
41
42
|
logger = logging.getLogger("agentex.temporal.tracing")
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
def _serialize_item(item: Any) -> dict[str, Any]:
|
|
46
|
+
"""
|
|
47
|
+
Universal serializer for any item type from OpenAI Agents SDK.
|
|
48
|
+
|
|
49
|
+
Uses model_dump() for Pydantic models, otherwise extracts attributes manually.
|
|
50
|
+
Filters out internal Pydantic fields that can't be serialized.
|
|
51
|
+
"""
|
|
52
|
+
if hasattr(item, 'model_dump'):
|
|
53
|
+
# Pydantic model - use model_dump for proper serialization
|
|
54
|
+
try:
|
|
55
|
+
return item.model_dump(mode='json', exclude_unset=True)
|
|
56
|
+
except Exception:
|
|
57
|
+
# Fallback to dict conversion
|
|
58
|
+
return dict(item) if hasattr(item, '__iter__') else {}
|
|
59
|
+
else:
|
|
60
|
+
# Not a Pydantic model - extract attributes manually
|
|
61
|
+
item_dict = {}
|
|
62
|
+
for attr_name in dir(item):
|
|
63
|
+
if not attr_name.startswith('_') and attr_name not in ('model_fields', 'model_config', 'model_computed_fields'):
|
|
64
|
+
try:
|
|
65
|
+
attr_value = getattr(item, attr_name, None)
|
|
66
|
+
# Skip methods and None values
|
|
67
|
+
if attr_value is not None and not callable(attr_value):
|
|
68
|
+
# Convert to JSON-serializable format
|
|
69
|
+
if hasattr(attr_value, 'model_dump'):
|
|
70
|
+
item_dict[attr_name] = attr_value.model_dump()
|
|
71
|
+
elif isinstance(attr_value, (str, int, float, bool, list, dict)):
|
|
72
|
+
item_dict[attr_name] = attr_value
|
|
73
|
+
else:
|
|
74
|
+
item_dict[attr_name] = str(attr_value)
|
|
75
|
+
except Exception:
|
|
76
|
+
# Skip attributes that can't be accessed
|
|
77
|
+
pass
|
|
78
|
+
return item_dict
|
|
79
|
+
|
|
80
|
+
|
|
44
81
|
class TemporalTracingModelProvider(OpenAIProvider):
|
|
45
82
|
"""Model provider that returns OpenAI models wrapped with AgentEx tracing.
|
|
46
83
|
|
|
@@ -171,15 +208,35 @@ class TemporalTracingResponsesModel(Model):
|
|
|
171
208
|
**kwargs,
|
|
172
209
|
)
|
|
173
210
|
|
|
174
|
-
#
|
|
211
|
+
# Serialize response output items for span tracing
|
|
212
|
+
new_items = []
|
|
213
|
+
final_output = None
|
|
214
|
+
|
|
215
|
+
if hasattr(response, 'output') and response.output:
|
|
216
|
+
response_output = response.output if isinstance(response.output, list) else [response.output]
|
|
217
|
+
|
|
218
|
+
for item in response_output:
|
|
219
|
+
try:
|
|
220
|
+
item_dict = _serialize_item(item)
|
|
221
|
+
if item_dict:
|
|
222
|
+
new_items.append(item_dict)
|
|
223
|
+
|
|
224
|
+
# Extract final_output from message type if available
|
|
225
|
+
if item_dict.get('type') == 'message' and not final_output:
|
|
226
|
+
content = item_dict.get('content', [])
|
|
227
|
+
if content and isinstance(content, list):
|
|
228
|
+
for content_part in content:
|
|
229
|
+
if isinstance(content_part, dict) and 'text' in content_part:
|
|
230
|
+
final_output = content_part['text']
|
|
231
|
+
break
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.warning(f"Failed to serialize item in temporal tracing model: {e}")
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
# Set span output with structured data
|
|
175
237
|
span.output = { # type: ignore[attr-defined]
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"usage": {
|
|
179
|
-
"input_tokens": response.usage.input_tokens if response.usage else None,
|
|
180
|
-
"output_tokens": response.usage.output_tokens if response.usage else None,
|
|
181
|
-
"total_tokens": response.usage.total_tokens if response.usage else None,
|
|
182
|
-
} if response.usage else None,
|
|
238
|
+
"new_items": new_items,
|
|
239
|
+
"final_output": final_output,
|
|
183
240
|
}
|
|
184
241
|
|
|
185
242
|
return response
|
|
@@ -284,15 +341,35 @@ class TemporalTracingChatCompletionsModel(Model):
|
|
|
284
341
|
**kwargs,
|
|
285
342
|
)
|
|
286
343
|
|
|
287
|
-
#
|
|
344
|
+
# Serialize response output items for span tracing
|
|
345
|
+
new_items = []
|
|
346
|
+
final_output = None
|
|
347
|
+
|
|
348
|
+
if hasattr(response, 'output') and response.output:
|
|
349
|
+
response_output = response.output if isinstance(response.output, list) else [response.output]
|
|
350
|
+
|
|
351
|
+
for item in response_output:
|
|
352
|
+
try:
|
|
353
|
+
item_dict = _serialize_item(item)
|
|
354
|
+
if item_dict:
|
|
355
|
+
new_items.append(item_dict)
|
|
356
|
+
|
|
357
|
+
# Extract final_output from message type if available
|
|
358
|
+
if item_dict.get('type') == 'message' and not final_output:
|
|
359
|
+
content = item_dict.get('content', [])
|
|
360
|
+
if content and isinstance(content, list):
|
|
361
|
+
for content_part in content:
|
|
362
|
+
if isinstance(content_part, dict) and 'text' in content_part:
|
|
363
|
+
final_output = content_part['text']
|
|
364
|
+
break
|
|
365
|
+
except Exception as e:
|
|
366
|
+
logger.warning(f"Failed to serialize item in temporal tracing model: {e}")
|
|
367
|
+
continue
|
|
368
|
+
|
|
369
|
+
# Set span output with structured data
|
|
288
370
|
span.output = { # type: ignore[attr-defined]
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"usage": {
|
|
292
|
-
"input_tokens": response.usage.input_tokens if response.usage else None,
|
|
293
|
-
"output_tokens": response.usage.output_tokens if response.usage else None,
|
|
294
|
-
"total_tokens": response.usage.total_tokens if response.usage else None,
|
|
295
|
-
} if response.usage else None,
|
|
371
|
+
"new_items": new_items,
|
|
372
|
+
"final_output": final_output,
|
|
296
373
|
}
|
|
297
374
|
|
|
298
375
|
return response
|