agentex-sdk 0.1.0a6__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/__init__.py +103 -0
- agentex/_base_client.py +1992 -0
- agentex/_client.py +506 -0
- agentex/_compat.py +219 -0
- agentex/_constants.py +14 -0
- agentex/_exceptions.py +108 -0
- agentex/_files.py +123 -0
- agentex/_models.py +829 -0
- agentex/_qs.py +150 -0
- agentex/_resource.py +43 -0
- agentex/_response.py +830 -0
- agentex/_streaming.py +333 -0
- agentex/_types.py +219 -0
- agentex/_utils/__init__.py +57 -0
- agentex/_utils/_logs.py +25 -0
- agentex/_utils/_proxy.py +65 -0
- agentex/_utils/_reflection.py +42 -0
- agentex/_utils/_resources_proxy.py +24 -0
- agentex/_utils/_streams.py +12 -0
- agentex/_utils/_sync.py +86 -0
- agentex/_utils/_transform.py +447 -0
- agentex/_utils/_typing.py +151 -0
- agentex/_utils/_utils.py +422 -0
- agentex/_version.py +4 -0
- agentex/lib/.keep +4 -0
- agentex/lib/__init__.py +0 -0
- agentex/lib/adk/__init__.py +41 -0
- agentex/lib/adk/_modules/__init__.py +0 -0
- agentex/lib/adk/_modules/acp.py +247 -0
- agentex/lib/adk/_modules/agent_task_tracker.py +176 -0
- agentex/lib/adk/_modules/agents.py +77 -0
- agentex/lib/adk/_modules/events.py +141 -0
- agentex/lib/adk/_modules/messages.py +285 -0
- agentex/lib/adk/_modules/state.py +291 -0
- agentex/lib/adk/_modules/streaming.py +75 -0
- agentex/lib/adk/_modules/tasks.py +124 -0
- agentex/lib/adk/_modules/tracing.py +194 -0
- agentex/lib/adk/providers/__init__.py +9 -0
- agentex/lib/adk/providers/_modules/__init__.py +0 -0
- agentex/lib/adk/providers/_modules/litellm.py +232 -0
- agentex/lib/adk/providers/_modules/openai.py +416 -0
- agentex/lib/adk/providers/_modules/sgp.py +85 -0
- agentex/lib/adk/utils/__init__.py +5 -0
- agentex/lib/adk/utils/_modules/__init__.py +0 -0
- agentex/lib/adk/utils/_modules/templating.py +94 -0
- agentex/lib/cli/__init__.py +0 -0
- agentex/lib/cli/commands/__init__.py +0 -0
- agentex/lib/cli/commands/agents.py +328 -0
- agentex/lib/cli/commands/init.py +227 -0
- agentex/lib/cli/commands/main.py +33 -0
- agentex/lib/cli/commands/secrets.py +169 -0
- agentex/lib/cli/commands/tasks.py +118 -0
- agentex/lib/cli/commands/uv.py +133 -0
- agentex/lib/cli/handlers/__init__.py +0 -0
- agentex/lib/cli/handlers/agent_handlers.py +160 -0
- agentex/lib/cli/handlers/cleanup_handlers.py +186 -0
- agentex/lib/cli/handlers/deploy_handlers.py +351 -0
- agentex/lib/cli/handlers/run_handlers.py +452 -0
- agentex/lib/cli/handlers/secret_handlers.py +670 -0
- agentex/lib/cli/templates/default/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/default/Dockerfile-uv.j2 +42 -0
- agentex/lib/cli/templates/default/Dockerfile.j2 +42 -0
- agentex/lib/cli/templates/default/README.md.j2 +193 -0
- agentex/lib/cli/templates/default/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/default/manifest.yaml.j2 +116 -0
- agentex/lib/cli/templates/default/project/acp.py.j2 +29 -0
- agentex/lib/cli/templates/default/pyproject.toml.j2 +33 -0
- agentex/lib/cli/templates/default/requirements.txt.j2 +5 -0
- agentex/lib/cli/templates/deploy/Screenshot 2025-03-19 at 10.36.57/342/200/257AM.png +0 -0
- agentex/lib/cli/templates/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/sync/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/sync/Dockerfile-uv.j2 +42 -0
- agentex/lib/cli/templates/sync/Dockerfile.j2 +42 -0
- agentex/lib/cli/templates/sync/README.md.j2 +293 -0
- agentex/lib/cli/templates/sync/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/sync/manifest.yaml.j2 +116 -0
- agentex/lib/cli/templates/sync/project/acp.py.j2 +26 -0
- agentex/lib/cli/templates/sync/pyproject.toml.j2 +33 -0
- agentex/lib/cli/templates/sync/requirements.txt.j2 +5 -0
- agentex/lib/cli/templates/temporal/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 +48 -0
- agentex/lib/cli/templates/temporal/Dockerfile.j2 +48 -0
- agentex/lib/cli/templates/temporal/README.md.j2 +316 -0
- agentex/lib/cli/templates/temporal/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/temporal/manifest.yaml.j2 +137 -0
- agentex/lib/cli/templates/temporal/project/acp.py.j2 +30 -0
- agentex/lib/cli/templates/temporal/project/run_worker.py.j2 +33 -0
- agentex/lib/cli/templates/temporal/project/workflow.py.j2 +66 -0
- agentex/lib/cli/templates/temporal/pyproject.toml.j2 +34 -0
- agentex/lib/cli/templates/temporal/requirements.txt.j2 +5 -0
- agentex/lib/cli/utils/cli_utils.py +14 -0
- agentex/lib/cli/utils/credential_utils.py +103 -0
- agentex/lib/cli/utils/exceptions.py +6 -0
- agentex/lib/cli/utils/kubectl_utils.py +135 -0
- agentex/lib/cli/utils/kubernetes_secrets_utils.py +185 -0
- agentex/lib/core/__init__.py +0 -0
- agentex/lib/core/adapters/__init__.py +0 -0
- agentex/lib/core/adapters/llm/__init__.py +1 -0
- agentex/lib/core/adapters/llm/adapter_litellm.py +46 -0
- agentex/lib/core/adapters/llm/adapter_sgp.py +55 -0
- agentex/lib/core/adapters/llm/port.py +24 -0
- agentex/lib/core/adapters/streams/adapter_redis.py +128 -0
- agentex/lib/core/adapters/streams/port.py +50 -0
- agentex/lib/core/clients/__init__.py +1 -0
- agentex/lib/core/clients/temporal/__init__.py +0 -0
- agentex/lib/core/clients/temporal/temporal_client.py +181 -0
- agentex/lib/core/clients/temporal/types.py +47 -0
- agentex/lib/core/clients/temporal/utils.py +56 -0
- agentex/lib/core/services/__init__.py +0 -0
- agentex/lib/core/services/adk/__init__.py +0 -0
- agentex/lib/core/services/adk/acp/__init__.py +0 -0
- agentex/lib/core/services/adk/acp/acp.py +210 -0
- agentex/lib/core/services/adk/agent_task_tracker.py +85 -0
- agentex/lib/core/services/adk/agents.py +43 -0
- agentex/lib/core/services/adk/events.py +61 -0
- agentex/lib/core/services/adk/messages.py +164 -0
- agentex/lib/core/services/adk/providers/__init__.py +0 -0
- agentex/lib/core/services/adk/providers/litellm.py +256 -0
- agentex/lib/core/services/adk/providers/openai.py +723 -0
- agentex/lib/core/services/adk/providers/sgp.py +99 -0
- agentex/lib/core/services/adk/state.py +120 -0
- agentex/lib/core/services/adk/streaming.py +262 -0
- agentex/lib/core/services/adk/tasks.py +69 -0
- agentex/lib/core/services/adk/tracing.py +36 -0
- agentex/lib/core/services/adk/utils/__init__.py +0 -0
- agentex/lib/core/services/adk/utils/templating.py +58 -0
- agentex/lib/core/temporal/__init__.py +0 -0
- agentex/lib/core/temporal/activities/__init__.py +207 -0
- agentex/lib/core/temporal/activities/activity_helpers.py +37 -0
- agentex/lib/core/temporal/activities/adk/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/acp/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/acp/acp_activities.py +86 -0
- agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py +76 -0
- agentex/lib/core/temporal/activities/adk/agents_activities.py +35 -0
- agentex/lib/core/temporal/activities/adk/events_activities.py +50 -0
- agentex/lib/core/temporal/activities/adk/messages_activities.py +94 -0
- agentex/lib/core/temporal/activities/adk/providers/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py +71 -0
- agentex/lib/core/temporal/activities/adk/providers/openai_activities.py +210 -0
- agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py +42 -0
- agentex/lib/core/temporal/activities/adk/state_activities.py +85 -0
- agentex/lib/core/temporal/activities/adk/streaming_activities.py +33 -0
- agentex/lib/core/temporal/activities/adk/tasks_activities.py +48 -0
- agentex/lib/core/temporal/activities/adk/tracing_activities.py +55 -0
- agentex/lib/core/temporal/activities/adk/utils/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/utils/templating_activities.py +41 -0
- agentex/lib/core/temporal/services/__init__.py +0 -0
- agentex/lib/core/temporal/services/temporal_task_service.py +69 -0
- agentex/lib/core/temporal/types/__init__.py +0 -0
- agentex/lib/core/temporal/types/workflow.py +5 -0
- agentex/lib/core/temporal/workers/__init__.py +0 -0
- agentex/lib/core/temporal/workers/worker.py +162 -0
- agentex/lib/core/temporal/workflows/workflow.py +26 -0
- agentex/lib/core/tracing/__init__.py +5 -0
- agentex/lib/core/tracing/processors/agentex_tracing_processor.py +117 -0
- agentex/lib/core/tracing/processors/sgp_tracing_processor.py +119 -0
- agentex/lib/core/tracing/processors/tracing_processor_interface.py +40 -0
- agentex/lib/core/tracing/trace.py +311 -0
- agentex/lib/core/tracing/tracer.py +70 -0
- agentex/lib/core/tracing/tracing_processor_manager.py +62 -0
- agentex/lib/environment_variables.py +87 -0
- agentex/lib/py.typed +0 -0
- agentex/lib/sdk/__init__.py +0 -0
- agentex/lib/sdk/config/__init__.py +0 -0
- agentex/lib/sdk/config/agent_config.py +61 -0
- agentex/lib/sdk/config/agent_manifest.py +219 -0
- agentex/lib/sdk/config/build_config.py +35 -0
- agentex/lib/sdk/config/deployment_config.py +117 -0
- agentex/lib/sdk/config/local_development_config.py +56 -0
- agentex/lib/sdk/config/project_config.py +103 -0
- agentex/lib/sdk/fastacp/__init__.py +3 -0
- agentex/lib/sdk/fastacp/base/base_acp_server.py +406 -0
- agentex/lib/sdk/fastacp/fastacp.py +74 -0
- agentex/lib/sdk/fastacp/impl/agentic_base_acp.py +72 -0
- agentex/lib/sdk/fastacp/impl/sync_acp.py +109 -0
- agentex/lib/sdk/fastacp/impl/temporal_acp.py +97 -0
- agentex/lib/sdk/fastacp/tests/README.md +297 -0
- agentex/lib/sdk/fastacp/tests/conftest.py +307 -0
- agentex/lib/sdk/fastacp/tests/pytest.ini +10 -0
- agentex/lib/sdk/fastacp/tests/run_tests.py +227 -0
- agentex/lib/sdk/fastacp/tests/test_base_acp_server.py +450 -0
- agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py +344 -0
- agentex/lib/sdk/fastacp/tests/test_integration.py +477 -0
- agentex/lib/sdk/state_machine/__init__.py +6 -0
- agentex/lib/sdk/state_machine/noop_workflow.py +21 -0
- agentex/lib/sdk/state_machine/state.py +10 -0
- agentex/lib/sdk/state_machine/state_machine.py +189 -0
- agentex/lib/sdk/state_machine/state_workflow.py +16 -0
- agentex/lib/sdk/utils/__init__.py +0 -0
- agentex/lib/sdk/utils/messages.py +223 -0
- agentex/lib/types/__init__.py +0 -0
- agentex/lib/types/acp.py +94 -0
- agentex/lib/types/agent_configs.py +79 -0
- agentex/lib/types/agent_results.py +29 -0
- agentex/lib/types/credentials.py +34 -0
- agentex/lib/types/fastacp.py +61 -0
- agentex/lib/types/files.py +13 -0
- agentex/lib/types/json_rpc.py +49 -0
- agentex/lib/types/llm_messages.py +354 -0
- agentex/lib/types/task_message_updates.py +171 -0
- agentex/lib/types/tracing.py +34 -0
- agentex/lib/utils/__init__.py +0 -0
- agentex/lib/utils/completions.py +131 -0
- agentex/lib/utils/console.py +14 -0
- agentex/lib/utils/io.py +29 -0
- agentex/lib/utils/iterables.py +14 -0
- agentex/lib/utils/json_schema.py +23 -0
- agentex/lib/utils/logging.py +31 -0
- agentex/lib/utils/mcp.py +17 -0
- agentex/lib/utils/model_utils.py +46 -0
- agentex/lib/utils/parsing.py +15 -0
- agentex/lib/utils/regex.py +6 -0
- agentex/lib/utils/temporal.py +13 -0
- agentex/py.typed +0 -0
- agentex/resources/__init__.py +103 -0
- agentex/resources/agents.py +707 -0
- agentex/resources/events.py +294 -0
- agentex/resources/messages/__init__.py +33 -0
- agentex/resources/messages/batch.py +271 -0
- agentex/resources/messages/messages.py +492 -0
- agentex/resources/spans.py +557 -0
- agentex/resources/states.py +544 -0
- agentex/resources/tasks.py +615 -0
- agentex/resources/tracker.py +384 -0
- agentex/types/__init__.py +56 -0
- agentex/types/acp_type.py +7 -0
- agentex/types/agent.py +29 -0
- agentex/types/agent_list_params.py +13 -0
- agentex/types/agent_list_response.py +10 -0
- agentex/types/agent_rpc_by_name_params.py +21 -0
- agentex/types/agent_rpc_params.py +51 -0
- agentex/types/agent_rpc_params1.py +21 -0
- agentex/types/agent_rpc_response.py +20 -0
- agentex/types/agent_rpc_result.py +90 -0
- agentex/types/agent_task_tracker.py +34 -0
- agentex/types/data_content.py +30 -0
- agentex/types/data_content_param.py +31 -0
- agentex/types/data_delta.py +14 -0
- agentex/types/event.py +29 -0
- agentex/types/event_list_params.py +22 -0
- agentex/types/event_list_response.py +10 -0
- agentex/types/message_author.py +7 -0
- agentex/types/message_create_params.py +18 -0
- agentex/types/message_list_params.py +14 -0
- agentex/types/message_list_response.py +10 -0
- agentex/types/message_style.py +7 -0
- agentex/types/message_update_params.py +18 -0
- agentex/types/messages/__init__.py +8 -0
- agentex/types/messages/batch_create_params.py +16 -0
- agentex/types/messages/batch_create_response.py +10 -0
- agentex/types/messages/batch_update_params.py +16 -0
- agentex/types/messages/batch_update_response.py +10 -0
- agentex/types/shared/__init__.py +3 -0
- agentex/types/shared/task_message_update.py +83 -0
- agentex/types/span.py +36 -0
- agentex/types/span_create_params.py +40 -0
- agentex/types/span_list_params.py +12 -0
- agentex/types/span_list_response.py +10 -0
- agentex/types/span_update_params.py +37 -0
- agentex/types/state.py +25 -0
- agentex/types/state_create_params.py +16 -0
- agentex/types/state_list_params.py +16 -0
- agentex/types/state_list_response.py +10 -0
- agentex/types/state_update_params.py +16 -0
- agentex/types/task.py +23 -0
- agentex/types/task_delete_by_name_response.py +8 -0
- agentex/types/task_delete_response.py +8 -0
- agentex/types/task_list_response.py +10 -0
- agentex/types/task_message.py +33 -0
- agentex/types/task_message_content.py +16 -0
- agentex/types/task_message_content_param.py +17 -0
- agentex/types/task_message_delta.py +16 -0
- agentex/types/text_content.py +53 -0
- agentex/types/text_content_param.py +54 -0
- agentex/types/text_delta.py +14 -0
- agentex/types/tool_request_content.py +36 -0
- agentex/types/tool_request_content_param.py +37 -0
- agentex/types/tool_request_delta.py +18 -0
- agentex/types/tool_response_content.py +36 -0
- agentex/types/tool_response_content_param.py +36 -0
- agentex/types/tool_response_delta.py +18 -0
- agentex/types/tracker_list_params.py +16 -0
- agentex/types/tracker_list_response.py +10 -0
- agentex/types/tracker_update_params.py +19 -0
- agentex_sdk-0.1.0a6.dist-info/METADATA +426 -0
- agentex_sdk-0.1.0a6.dist-info/RECORD +289 -0
- agentex_sdk-0.1.0a6.dist-info/WHEEL +4 -0
- agentex_sdk-0.1.0a6.dist-info/entry_points.txt +2 -0
- agentex_sdk-0.1.0a6.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
import base64
|
2
|
+
import os
|
3
|
+
import tempfile
|
4
|
+
|
5
|
+
from scale_gp import SGPClient
|
6
|
+
|
7
|
+
from agentex.lib.core.tracing.tracer import AsyncTracer
|
8
|
+
from agentex.lib.types.files import FileContentResponse
|
9
|
+
from agentex.lib.utils.logging import make_logger
|
10
|
+
from agentex.lib.utils.temporal import heartbeat_if_in_workflow
|
11
|
+
|
12
|
+
logger = make_logger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class SGPService:
|
16
|
+
def __init__(self, sgp_client: SGPClient, tracer: AsyncTracer):
|
17
|
+
self.sgp_client = sgp_client
|
18
|
+
self.tracer = tracer
|
19
|
+
|
20
|
+
async def download_file_content(
|
21
|
+
self,
|
22
|
+
file_id: str,
|
23
|
+
filename: str,
|
24
|
+
trace_id: str | None = None,
|
25
|
+
parent_span_id: str | None = None,
|
26
|
+
) -> FileContentResponse:
|
27
|
+
"""
|
28
|
+
Download file content from SGP.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
file_id: The ID of the file to download.
|
32
|
+
filename: The filename of the file to download.
|
33
|
+
trace_id: The trace ID for tracing.
|
34
|
+
parent_span_id: The parent span ID for tracing.
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
FileContentResponse with mime_type and base64_content for constructing LLM input.
|
38
|
+
"""
|
39
|
+
trace = self.tracer.trace(trace_id)
|
40
|
+
async with trace.span(
|
41
|
+
parent_id=parent_span_id,
|
42
|
+
name="download_file_content",
|
43
|
+
input={"file_id": file_id, "filename": filename},
|
44
|
+
) as span:
|
45
|
+
logger.info(f"Downloading file content for file_id: {file_id}")
|
46
|
+
heartbeat_if_in_workflow("downloading file content")
|
47
|
+
|
48
|
+
# Get the SGP response
|
49
|
+
response = self.sgp_client.beta.files.content(file_id)
|
50
|
+
heartbeat_if_in_workflow("file content downloaded")
|
51
|
+
|
52
|
+
# Determine mime type based on file extension
|
53
|
+
mime_type = "application/pdf" # Default
|
54
|
+
file_extension = os.path.splitext(filename)[1].lower()
|
55
|
+
if file_extension:
|
56
|
+
if file_extension == ".pdf":
|
57
|
+
mime_type = "application/pdf"
|
58
|
+
elif file_extension in [".doc", ".docx"]:
|
59
|
+
mime_type = "application/msword"
|
60
|
+
elif file_extension in [".txt", ".text"]:
|
61
|
+
mime_type = "text/plain"
|
62
|
+
elif file_extension in [".png"]:
|
63
|
+
mime_type = "image/png"
|
64
|
+
elif file_extension in [".jpg", ".jpeg"]:
|
65
|
+
mime_type = "image/jpeg"
|
66
|
+
|
67
|
+
# Use a named temporary file - simpler approach
|
68
|
+
with tempfile.NamedTemporaryFile(suffix=file_extension) as temp_file:
|
69
|
+
heartbeat_if_in_workflow(f"saving to temp file: {temp_file.name}")
|
70
|
+
|
71
|
+
# Use write_to_file method if available
|
72
|
+
if hasattr(response, "write_to_file"):
|
73
|
+
response.write_to_file(temp_file.name)
|
74
|
+
else:
|
75
|
+
# Fallback to direct writing
|
76
|
+
content_bytes = response.read()
|
77
|
+
temp_file.write(content_bytes)
|
78
|
+
temp_file.flush()
|
79
|
+
|
80
|
+
# Seek to beginning of file for reading
|
81
|
+
temp_file.seek(0)
|
82
|
+
|
83
|
+
# Read the file in binary mode - exactly like the example
|
84
|
+
data = temp_file.read()
|
85
|
+
|
86
|
+
# Encode to base64
|
87
|
+
base64_content = base64.b64encode(data).decode("utf-8")
|
88
|
+
|
89
|
+
result = FileContentResponse(
|
90
|
+
mime_type=mime_type, base64_content=base64_content
|
91
|
+
)
|
92
|
+
|
93
|
+
# Record metadata for tracing
|
94
|
+
span.output = {
|
95
|
+
"file_id": file_id,
|
96
|
+
"mime_type": result.mime_type,
|
97
|
+
"content_size": len(result.base64_content),
|
98
|
+
}
|
99
|
+
return result
|
@@ -0,0 +1,120 @@
|
|
1
|
+
from typing import Any, Dict
|
2
|
+
|
3
|
+
from agentex import AsyncAgentex
|
4
|
+
from agentex.lib.core.tracing.tracer import AsyncTracer
|
5
|
+
from agentex.types.state import State
|
6
|
+
from agentex.lib.utils.logging import make_logger
|
7
|
+
|
8
|
+
logger = make_logger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
class StateService:
|
12
|
+
def __init__(
|
13
|
+
self, agentex_client: AsyncAgentex, tracer: AsyncTracer
|
14
|
+
):
|
15
|
+
self._agentex_client = agentex_client
|
16
|
+
self._tracer = tracer
|
17
|
+
|
18
|
+
async def create_state(
|
19
|
+
self,
|
20
|
+
task_id: str,
|
21
|
+
agent_id: str,
|
22
|
+
state: dict[str, Any],
|
23
|
+
trace_id: str | None = None,
|
24
|
+
parent_span_id: str | None = None,
|
25
|
+
) -> State:
|
26
|
+
trace = self._tracer.trace(trace_id)
|
27
|
+
async with trace.span(
|
28
|
+
parent_id=parent_span_id,
|
29
|
+
name="create_state",
|
30
|
+
input={"task_id": task_id, "agent_id": agent_id, "state": state},
|
31
|
+
) as span:
|
32
|
+
state_model = await self._agentex_client.states.create(
|
33
|
+
task_id=task_id,
|
34
|
+
agent_id=agent_id,
|
35
|
+
state=state,
|
36
|
+
)
|
37
|
+
if span:
|
38
|
+
span.output = state_model.model_dump()
|
39
|
+
return state_model
|
40
|
+
|
41
|
+
async def get_state(
|
42
|
+
self,
|
43
|
+
state_id: str | None = None,
|
44
|
+
task_id: str | None = None,
|
45
|
+
agent_id: str | None = None,
|
46
|
+
trace_id: str | None = None,
|
47
|
+
parent_span_id: str | None = None,
|
48
|
+
) -> State | None:
|
49
|
+
trace = self._tracer.trace(trace_id) if self._tracer else None
|
50
|
+
async with trace.span(
|
51
|
+
parent_id=parent_span_id,
|
52
|
+
name="get_state",
|
53
|
+
input={
|
54
|
+
"state_id": state_id,
|
55
|
+
"task_id": task_id,
|
56
|
+
"agent_id": agent_id,
|
57
|
+
},
|
58
|
+
) as span:
|
59
|
+
if state_id:
|
60
|
+
state = await self._agentex_client.states.retrieve(state_id=state_id)
|
61
|
+
elif task_id and agent_id:
|
62
|
+
states = await self._agentex_client.states.list(
|
63
|
+
task_id=task_id,
|
64
|
+
agent_id=agent_id,
|
65
|
+
)
|
66
|
+
state = states[0] if states else None
|
67
|
+
else:
|
68
|
+
raise ValueError(
|
69
|
+
"Must provide either state_id or both task_id and agent_id"
|
70
|
+
)
|
71
|
+
if span:
|
72
|
+
span.output = state.model_dump() if state else None
|
73
|
+
return state
|
74
|
+
|
75
|
+
async def update_state(
|
76
|
+
self,
|
77
|
+
state_id: str,
|
78
|
+
task_id: str,
|
79
|
+
agent_id: str,
|
80
|
+
state: Dict[str, object],
|
81
|
+
trace_id: str | None = None,
|
82
|
+
parent_span_id: str | None = None,
|
83
|
+
) -> State:
|
84
|
+
trace = self._tracer.trace(trace_id)
|
85
|
+
async with trace.span(
|
86
|
+
parent_id=parent_span_id,
|
87
|
+
name="update_state",
|
88
|
+
input={
|
89
|
+
"state_id": state_id,
|
90
|
+
"task_id": task_id,
|
91
|
+
"agent_id": agent_id,
|
92
|
+
"state": state,
|
93
|
+
},
|
94
|
+
) as span:
|
95
|
+
state_model = await self._agentex_client.states.update(
|
96
|
+
state_id=state_id,
|
97
|
+
task_id=task_id,
|
98
|
+
agent_id=agent_id,
|
99
|
+
state=state,
|
100
|
+
)
|
101
|
+
if span:
|
102
|
+
span.output = state_model.model_dump()
|
103
|
+
return state_model
|
104
|
+
|
105
|
+
async def delete_state(
|
106
|
+
self,
|
107
|
+
state_id: str,
|
108
|
+
trace_id: str | None = None,
|
109
|
+
parent_span_id: str | None = None,
|
110
|
+
) -> State:
|
111
|
+
trace = self._tracer.trace(trace_id)
|
112
|
+
async with trace.span(
|
113
|
+
parent_id=parent_span_id,
|
114
|
+
name="delete_state",
|
115
|
+
input={"state_id": state_id},
|
116
|
+
) as span:
|
117
|
+
state = await self._agentex_client.states.delete(state_id)
|
118
|
+
if span:
|
119
|
+
span.output = state.model_dump()
|
120
|
+
return state
|
@@ -0,0 +1,262 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Literal, cast
|
3
|
+
|
4
|
+
from agentex import AsyncAgentex
|
5
|
+
from agentex.lib.core.adapters.streams.port import EventStreamRepository
|
6
|
+
from agentex.lib.types.task_message_updates import (
|
7
|
+
TaskMessageDelta,
|
8
|
+
TaskMessageUpdate,
|
9
|
+
TextDelta,
|
10
|
+
DataDelta,
|
11
|
+
ToolRequestDelta,
|
12
|
+
ToolResponseDelta,
|
13
|
+
StreamTaskMessage,
|
14
|
+
StreamTaskMessageStart,
|
15
|
+
StreamTaskMessageDelta,
|
16
|
+
StreamTaskMessageFull,
|
17
|
+
StreamTaskMessageDone,
|
18
|
+
)
|
19
|
+
from agentex.lib.utils.logging import make_logger
|
20
|
+
from agentex.types.data_content import DataContent
|
21
|
+
from agentex.types.task_message import (
|
22
|
+
TaskMessage,
|
23
|
+
TaskMessageContent,
|
24
|
+
)
|
25
|
+
from agentex.types.task_message_content_param import TaskMessageContentParam
|
26
|
+
from agentex.types.text_content import TextContent
|
27
|
+
from agentex.types.tool_request_content import ToolRequestContent
|
28
|
+
from agentex.types.tool_response_content import ToolResponseContent
|
29
|
+
|
30
|
+
logger = make_logger(__name__)
|
31
|
+
|
32
|
+
|
33
|
+
def _get_stream_topic(task_id: str) -> str:
|
34
|
+
return f"task:{task_id}"
|
35
|
+
|
36
|
+
|
37
|
+
class DeltaAccumulator:
|
38
|
+
def __init__(self):
|
39
|
+
self._accumulated_deltas: list[TaskMessageDelta] = []
|
40
|
+
self._delta_type: Literal["text", "data", "tool_request", "tool_response"] | None = None
|
41
|
+
|
42
|
+
def add_delta(self, delta: TaskMessageDelta):
|
43
|
+
if self._delta_type is None:
|
44
|
+
if delta.type == "text":
|
45
|
+
self._delta_type = "text"
|
46
|
+
elif delta.type == "data":
|
47
|
+
self._delta_type = "data"
|
48
|
+
elif delta.type == "tool_request":
|
49
|
+
self._delta_type = "tool_request"
|
50
|
+
elif delta.type == "tool_response":
|
51
|
+
self._delta_type = "tool_response"
|
52
|
+
else:
|
53
|
+
raise ValueError(f"Unknown delta type: {delta.type}")
|
54
|
+
else:
|
55
|
+
if self._delta_type != delta.type:
|
56
|
+
raise ValueError(
|
57
|
+
f"Delta type mismatch: {self._delta_type} != {delta.type}"
|
58
|
+
)
|
59
|
+
|
60
|
+
self._accumulated_deltas.append(delta)
|
61
|
+
|
62
|
+
def convert_to_content(self) -> TaskMessageContent:
|
63
|
+
if self._delta_type == "text":
|
64
|
+
# Type assertion: we know all deltas are TextDelta when _delta_type is TEXT
|
65
|
+
text_deltas = [delta for delta in self._accumulated_deltas if isinstance(delta, TextDelta)]
|
66
|
+
text_content_str = "".join(
|
67
|
+
[delta.text_delta or "" for delta in text_deltas]
|
68
|
+
)
|
69
|
+
return TextContent(
|
70
|
+
author="agent",
|
71
|
+
content=text_content_str,
|
72
|
+
)
|
73
|
+
elif self._delta_type == "data":
|
74
|
+
# Type assertion: we know all deltas are DataDelta when _delta_type is DATA
|
75
|
+
data_deltas = [delta for delta in self._accumulated_deltas if isinstance(delta, DataDelta)]
|
76
|
+
data_content_str = "".join(
|
77
|
+
[delta.data_delta or "" for delta in data_deltas]
|
78
|
+
)
|
79
|
+
try:
|
80
|
+
data = json.loads(data_content_str)
|
81
|
+
except json.JSONDecodeError as e:
|
82
|
+
raise ValueError(
|
83
|
+
f"Accumulated data content is not valid JSON: {data_content_str}"
|
84
|
+
) from e
|
85
|
+
return DataContent(
|
86
|
+
author="agent",
|
87
|
+
data=data,
|
88
|
+
)
|
89
|
+
elif self._delta_type == "tool_request":
|
90
|
+
# Type assertion: we know all deltas are ToolRequestDelta when _delta_type is TOOL_REQUEST
|
91
|
+
tool_request_deltas = [delta for delta in self._accumulated_deltas if isinstance(delta, ToolRequestDelta)]
|
92
|
+
arguments_content_str = "".join(
|
93
|
+
[delta.arguments_delta or "" for delta in tool_request_deltas]
|
94
|
+
)
|
95
|
+
try:
|
96
|
+
arguments = json.loads(arguments_content_str)
|
97
|
+
except json.JSONDecodeError as e:
|
98
|
+
raise ValueError(
|
99
|
+
f"Accumulated tool request arguments is not valid JSON: {arguments_content_str}"
|
100
|
+
) from e
|
101
|
+
return ToolRequestContent(
|
102
|
+
author="agent",
|
103
|
+
tool_call_id=tool_request_deltas[0].tool_call_id,
|
104
|
+
name=tool_request_deltas[0].name,
|
105
|
+
arguments=arguments,
|
106
|
+
)
|
107
|
+
elif self._delta_type == "tool_response":
|
108
|
+
# Type assertion: we know all deltas are ToolResponseDelta when _delta_type is TOOL_RESPONSE
|
109
|
+
tool_response_deltas = [delta for delta in self._accumulated_deltas if isinstance(delta, ToolResponseDelta)]
|
110
|
+
tool_response_content_str = "".join(
|
111
|
+
[delta.tool_response_delta or "" for delta in tool_response_deltas]
|
112
|
+
)
|
113
|
+
return ToolResponseContent(
|
114
|
+
author="agent",
|
115
|
+
tool_call_id=tool_response_deltas[0].tool_call_id,
|
116
|
+
name=tool_response_deltas[0].name,
|
117
|
+
content=tool_response_content_str,
|
118
|
+
)
|
119
|
+
else:
|
120
|
+
raise ValueError(f"Unknown delta type: {self._delta_type}")
|
121
|
+
|
122
|
+
|
123
|
+
class StreamingTaskMessageContext:
|
124
|
+
def __init__(
|
125
|
+
self,
|
126
|
+
task_id: str,
|
127
|
+
initial_content: TaskMessageContent,
|
128
|
+
agentex_client: AsyncAgentex,
|
129
|
+
streaming_service: "StreamingService",
|
130
|
+
):
|
131
|
+
self.task_id = task_id
|
132
|
+
self.initial_content = initial_content
|
133
|
+
self.task_message: TaskMessage | None = None
|
134
|
+
self._agentex_client = agentex_client
|
135
|
+
self._streaming_service = streaming_service
|
136
|
+
self._is_closed = False
|
137
|
+
self._delta_accumulator = DeltaAccumulator()
|
138
|
+
|
139
|
+
async def __aenter__(self) -> "StreamingTaskMessageContext":
|
140
|
+
return await self.open()
|
141
|
+
|
142
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
143
|
+
return await self.close()
|
144
|
+
|
145
|
+
async def open(self) -> "StreamingTaskMessageContext":
|
146
|
+
self._is_closed = False
|
147
|
+
|
148
|
+
self.task_message = await self._agentex_client.messages.create(
|
149
|
+
task_id=self.task_id,
|
150
|
+
content=self.initial_content.model_dump(),
|
151
|
+
streaming_status="IN_PROGRESS",
|
152
|
+
)
|
153
|
+
|
154
|
+
# Send the START event
|
155
|
+
start_event = StreamTaskMessageStart(
|
156
|
+
parent_task_message=self.task_message,
|
157
|
+
content=self.initial_content,
|
158
|
+
)
|
159
|
+
await self._streaming_service.stream_update(start_event)
|
160
|
+
|
161
|
+
return self
|
162
|
+
|
163
|
+
async def close(self) -> TaskMessage:
|
164
|
+
"""Close the streaming context."""
|
165
|
+
if not self.task_message:
|
166
|
+
raise ValueError("Context not properly initialized - no task message")
|
167
|
+
|
168
|
+
if self._is_closed:
|
169
|
+
return self.task_message # Already done
|
170
|
+
|
171
|
+
# Send the DONE event
|
172
|
+
done_event = StreamTaskMessageDone(parent_task_message=self.task_message)
|
173
|
+
await self._streaming_service.stream_update(done_event)
|
174
|
+
|
175
|
+
# Update the task message with the final content
|
176
|
+
if self._delta_accumulator._accumulated_deltas:
|
177
|
+
self.task_message.content = self._delta_accumulator.convert_to_content()
|
178
|
+
|
179
|
+
await self._agentex_client.messages.update(
|
180
|
+
task_id=self.task_id,
|
181
|
+
message_id=self.task_message.id,
|
182
|
+
content=self.task_message.content.model_dump(),
|
183
|
+
streaming_status="DONE",
|
184
|
+
)
|
185
|
+
|
186
|
+
# Mark the context as done
|
187
|
+
self._is_closed = True
|
188
|
+
return self.task_message
|
189
|
+
|
190
|
+
async def stream_update(
|
191
|
+
self, update: StreamTaskMessage
|
192
|
+
) -> StreamTaskMessage | None:
|
193
|
+
"""Stream an update to the repository."""
|
194
|
+
if self._is_closed:
|
195
|
+
raise ValueError("Context is already done")
|
196
|
+
|
197
|
+
if not self.task_message:
|
198
|
+
raise ValueError("Context not properly initialized - no task message")
|
199
|
+
|
200
|
+
if isinstance(update, StreamTaskMessageDelta):
|
201
|
+
if update.delta is not None:
|
202
|
+
self._delta_accumulator.add_delta(update.delta)
|
203
|
+
|
204
|
+
result = await self._streaming_service.stream_update(update)
|
205
|
+
|
206
|
+
if isinstance(update, StreamTaskMessageDone):
|
207
|
+
await self.close()
|
208
|
+
return update
|
209
|
+
elif isinstance(update, StreamTaskMessageFull):
|
210
|
+
await self._agentex_client.messages.update(
|
211
|
+
task_id=self.task_id,
|
212
|
+
message_id=update.parent_task_message.id,
|
213
|
+
content=update.content.model_dump(),
|
214
|
+
streaming_status="DONE",
|
215
|
+
)
|
216
|
+
self._is_closed = True
|
217
|
+
return result
|
218
|
+
|
219
|
+
|
220
|
+
class StreamingService:
|
221
|
+
def __init__(
|
222
|
+
self,
|
223
|
+
agentex_client: AsyncAgentex,
|
224
|
+
stream_repository: EventStreamRepository,
|
225
|
+
):
|
226
|
+
self._agentex_client = agentex_client
|
227
|
+
self._stream_repository = stream_repository
|
228
|
+
|
229
|
+
def streaming_task_message_context(
|
230
|
+
self,
|
231
|
+
task_id: str,
|
232
|
+
initial_content: TaskMessageContent,
|
233
|
+
) -> StreamingTaskMessageContext:
|
234
|
+
return StreamingTaskMessageContext(
|
235
|
+
task_id=task_id,
|
236
|
+
initial_content=initial_content,
|
237
|
+
agentex_client=self._agentex_client,
|
238
|
+
streaming_service=self,
|
239
|
+
)
|
240
|
+
|
241
|
+
async def stream_update(
|
242
|
+
self, update: TaskMessageUpdate
|
243
|
+
) -> TaskMessageUpdate | None:
|
244
|
+
"""
|
245
|
+
Stream an update to the repository.
|
246
|
+
|
247
|
+
Args:
|
248
|
+
update: The update to stream
|
249
|
+
|
250
|
+
Returns:
|
251
|
+
True if event was streamed successfully, False otherwise
|
252
|
+
"""
|
253
|
+
stream_topic = _get_stream_topic(update.parent_task_message.task_id)
|
254
|
+
|
255
|
+
try:
|
256
|
+
await self._stream_repository.send_event(
|
257
|
+
topic=stream_topic, event=update.model_dump(mode="json") # type: ignore
|
258
|
+
)
|
259
|
+
return update
|
260
|
+
except Exception as e:
|
261
|
+
logger.exception(f"Failed to stream event: {e}")
|
262
|
+
return None
|
@@ -0,0 +1,69 @@
|
|
1
|
+
from agentex import AsyncAgentex
|
2
|
+
from agentex.lib.core.tracing.tracer import AsyncTracer
|
3
|
+
from agentex.types.task import Task
|
4
|
+
from agentex.lib.utils.logging import make_logger
|
5
|
+
from agentex.lib.utils.temporal import heartbeat_if_in_workflow
|
6
|
+
|
7
|
+
logger = make_logger(__name__)
|
8
|
+
|
9
|
+
|
10
|
+
class TasksService:
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
agentex_client: AsyncAgentex,
|
14
|
+
tracer: AsyncTracer,
|
15
|
+
):
|
16
|
+
self._agentex_client = agentex_client
|
17
|
+
self._tracer = tracer
|
18
|
+
|
19
|
+
async def get_task(
|
20
|
+
self,
|
21
|
+
task_id: str | None = None,
|
22
|
+
task_name: str | None = None,
|
23
|
+
trace_id: str | None = None,
|
24
|
+
parent_span_id: str | None = None,
|
25
|
+
) -> Task:
|
26
|
+
trace = self._tracer.trace(trace_id)
|
27
|
+
async with trace.span(
|
28
|
+
parent_id=parent_span_id,
|
29
|
+
name="get_task",
|
30
|
+
input={"task_id": task_id, "task_name": task_name},
|
31
|
+
) as span:
|
32
|
+
heartbeat_if_in_workflow("get task")
|
33
|
+
if not task_id and not task_name:
|
34
|
+
raise ValueError("Either task_id or task_name must be provided.")
|
35
|
+
if task_id:
|
36
|
+
task_model = await self._agentex_client.tasks.retrieve(task_id=task_id)
|
37
|
+
elif task_name:
|
38
|
+
task_model = await self._agentex_client.tasks.retrieve_by_name(task_name=task_name)
|
39
|
+
else:
|
40
|
+
raise ValueError("Either task_id or task_name must be provided.")
|
41
|
+
if span:
|
42
|
+
span.output = task_model.model_dump()
|
43
|
+
return task_model
|
44
|
+
|
45
|
+
async def delete_task(
|
46
|
+
self,
|
47
|
+
task_id: str | None = None,
|
48
|
+
task_name: str | None = None,
|
49
|
+
trace_id: str | None = None,
|
50
|
+
parent_span_id: str | None = None,
|
51
|
+
) -> Task:
|
52
|
+
trace = self._tracer.trace(trace_id) if self._tracer else None
|
53
|
+
async with trace.span(
|
54
|
+
parent_id=parent_span_id,
|
55
|
+
name="delete_task",
|
56
|
+
input={"task_id": task_id, "task_name": task_name},
|
57
|
+
) as span:
|
58
|
+
heartbeat_if_in_workflow("delete task")
|
59
|
+
if not task_id and not task_name:
|
60
|
+
raise ValueError("Either task_id or task_name must be provided.")
|
61
|
+
if task_id:
|
62
|
+
task_model = await self._agentex_client.tasks.delete(task_id=task_id)
|
63
|
+
elif task_name:
|
64
|
+
task_model = await self._agentex_client.tasks.delete_by_name(task_name=task_name)
|
65
|
+
else:
|
66
|
+
raise ValueError("Either task_id or task_name must be provided.")
|
67
|
+
if span:
|
68
|
+
span.output = task_model.model_dump()
|
69
|
+
return task_model
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from typing import Any
|
2
|
+
from agentex.lib.core.tracing.tracer import AsyncTracer
|
3
|
+
from agentex.types.span import Span
|
4
|
+
from agentex.lib.utils.logging import make_logger
|
5
|
+
from agentex.lib.utils.model_utils import BaseModel
|
6
|
+
from agentex.lib.utils.temporal import heartbeat_if_in_workflow
|
7
|
+
|
8
|
+
logger = make_logger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
class TracingService:
|
12
|
+
def __init__(self, tracer: AsyncTracer):
|
13
|
+
self._tracer = tracer
|
14
|
+
|
15
|
+
async def start_span(
|
16
|
+
self,
|
17
|
+
trace_id: str,
|
18
|
+
name: str,
|
19
|
+
parent_id: str | None = None,
|
20
|
+
input: list[Any] | dict[str, Any] | BaseModel | None = None,
|
21
|
+
data: list[Any] | dict[str, Any] | BaseModel | None = None,
|
22
|
+
) -> Span | None:
|
23
|
+
trace = self._tracer.trace(trace_id)
|
24
|
+
async with trace.span(
|
25
|
+
parent_id=parent_id,
|
26
|
+
name=name,
|
27
|
+
input=input or {},
|
28
|
+
data=data,
|
29
|
+
) as span:
|
30
|
+
heartbeat_if_in_workflow("start span")
|
31
|
+
return span if span else None
|
32
|
+
|
33
|
+
async def end_span(self, trace_id: str, span: Span) -> Span:
|
34
|
+
trace = self._tracer.trace(trace_id)
|
35
|
+
await trace.end_span(span)
|
36
|
+
return span
|
File without changes
|
@@ -0,0 +1,58 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from jinja2 import BaseLoader, Environment
|
5
|
+
|
6
|
+
from agentex.lib.core.tracing.tracer import AsyncTracer
|
7
|
+
from agentex.lib.utils.temporal import heartbeat_if_in_workflow
|
8
|
+
|
9
|
+
# Create a Jinja environment
|
10
|
+
JINJA_ENV = Environment(
|
11
|
+
loader=BaseLoader(),
|
12
|
+
trim_blocks=True,
|
13
|
+
lstrip_blocks=True,
|
14
|
+
extensions=["jinja2.ext.do"],
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
class TemplatingService:
|
19
|
+
def __init__(self, tracer: AsyncTracer | None = None):
|
20
|
+
self.tracer = tracer
|
21
|
+
|
22
|
+
async def render_jinja(
|
23
|
+
self,
|
24
|
+
template: str,
|
25
|
+
variables: dict[str, Any],
|
26
|
+
trace_id: str | None = None,
|
27
|
+
parent_span_id: str | None = None,
|
28
|
+
) -> str:
|
29
|
+
"""
|
30
|
+
Activity that renders a Jinja template with the provided data.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
template: The template string to render.
|
34
|
+
variables: The variables to render the template with.
|
35
|
+
trace_id: The trace ID for tracing.
|
36
|
+
parent_span_id: The parent span ID for tracing.
|
37
|
+
|
38
|
+
Returns:
|
39
|
+
The rendered template as a string
|
40
|
+
"""
|
41
|
+
trace = self.tracer.trace(trace_id)
|
42
|
+
async with trace.span(
|
43
|
+
parent_id=parent_span_id,
|
44
|
+
name="render_jinja",
|
45
|
+
input={"template": template, "variables": variables},
|
46
|
+
) as span:
|
47
|
+
heartbeat_if_in_workflow("render jinja")
|
48
|
+
global_variables = {
|
49
|
+
"datetime": datetime,
|
50
|
+
}
|
51
|
+
jinja_template = JINJA_ENV.from_string(template, globals=global_variables)
|
52
|
+
try:
|
53
|
+
rendered_template = jinja_template.render(variables)
|
54
|
+
if span:
|
55
|
+
span.output = {"jinja_output": rendered_template}
|
56
|
+
return rendered_template
|
57
|
+
except Exception as e:
|
58
|
+
raise ValueError(f"Error rendering Jinja template: {str(e)}") from e
|
File without changes
|