agentex-sdk 0.2.0__py3-none-any.whl → 0.2.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.
Files changed (36) hide show
  1. agentex/_version.py +1 -1
  2. agentex/lib/adk/_modules/acp.py +2 -1
  3. agentex/lib/adk/_modules/agent_task_tracker.py +2 -1
  4. agentex/lib/adk/_modules/agents.py +2 -1
  5. agentex/lib/adk/_modules/events.py +2 -1
  6. agentex/lib/adk/_modules/messages.py +4 -3
  7. agentex/lib/adk/_modules/state.py +2 -1
  8. agentex/lib/adk/_modules/streaming.py +4 -3
  9. agentex/lib/adk/_modules/tasks.py +2 -1
  10. agentex/lib/adk/_modules/tracing.py +2 -1
  11. agentex/lib/adk/providers/_modules/litellm.py +2 -2
  12. agentex/lib/adk/providers/_modules/openai.py +2 -2
  13. agentex/lib/adk/utils/_modules/client.py +12 -0
  14. agentex/lib/cli/commands/init.py +8 -4
  15. agentex/lib/cli/templates/default/README.md.j2 +23 -2
  16. agentex/lib/cli/templates/default/dev.ipynb.j2 +126 -0
  17. agentex/lib/cli/templates/sync/README.md.j2 +22 -2
  18. agentex/lib/cli/templates/sync/dev.ipynb.j2 +167 -0
  19. agentex/lib/cli/templates/sync/project/acp.py.j2 +63 -14
  20. agentex/lib/cli/templates/temporal/README.md.j2 +24 -3
  21. agentex/lib/cli/templates/temporal/dev.ipynb.j2 +126 -0
  22. agentex/lib/core/adapters/streams/adapter_redis.py +4 -4
  23. agentex/lib/core/adapters/streams/port.py +1 -1
  24. agentex/lib/core/services/adk/streaming.py +2 -3
  25. agentex/lib/core/temporal/activities/__init__.py +2 -2
  26. agentex/lib/sdk/fastacp/base/base_acp_server.py +11 -2
  27. agentex/lib/utils/dev_tools/__init__.py +9 -0
  28. agentex/lib/utils/dev_tools/async_messages.py +386 -0
  29. agentex/resources/agents.py +511 -3
  30. agentex/resources/tasks.py +4 -4
  31. agentex/types/agent_rpc_response.py +32 -4
  32. {agentex_sdk-0.2.0.dist-info → agentex_sdk-0.2.2.dist-info}/METADATA +1 -1
  33. {agentex_sdk-0.2.0.dist-info → agentex_sdk-0.2.2.dist-info}/RECORD +36 -30
  34. {agentex_sdk-0.2.0.dist-info → agentex_sdk-0.2.2.dist-info}/WHEEL +0 -0
  35. {agentex_sdk-0.2.0.dist-info → agentex_sdk-0.2.2.dist-info}/entry_points.txt +0 -0
  36. {agentex_sdk-0.2.0.dist-info → agentex_sdk-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,26 +1,75 @@
1
- from typing import AsyncGenerator, Union
1
+ import json
2
+ from agentex.lib import adk
2
3
  from agentex.lib.sdk.fastacp.fastacp import FastACP
3
- from agentex.lib.types.acp import SendMessageParams
4
+ from agentex.lib.types.fastacp import AgenticACPConfig
5
+ from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams
4
6
 
5
- from agentex.lib.types.task_message_updates import TaskMessageUpdate
6
- from agentex.types.task_message_content import TaskMessageContent
7
7
  from agentex.types.text_content import TextContent
8
8
  from agentex.lib.utils.logging import make_logger
9
9
 
10
10
  logger = make_logger(__name__)
11
11
 
12
12
 
13
- # Create an ACP server
13
+ # Create an ACP server with base configuration
14
+ # This sets up the core server that will handle task creation, events, and cancellation
14
15
  acp = FastACP.create(
15
- acp_type="sync",
16
+ acp_type="agentic",
17
+ config=AgenticACPConfig(
18
+ type="base",
19
+ ),
16
20
  )
17
21
 
18
- @acp.on_message_send
19
- async def handle_message_send(
20
- params: SendMessageParams
21
- ) -> TaskMessageContent | list[TaskMessageContent] | AsyncGenerator[TaskMessageUpdate, None]:
22
- """Default message handler with streaming support"""
23
- return TextContent(
24
- author="agent",
25
- content=f"Hello! I've received your message. Here's a generic response, but in future tutorials we'll see how you can get me to intelligently respond to your message. This is what I heard you say: {params.content.content}",
22
+ @acp.on_task_create
23
+ async def handle_task_create(params: CreateTaskParams):
24
+ # This handler is called first whenever a new task is created.
25
+ # It's a good place to initialize any state or resources needed for the task.
26
+
27
+ #########################################################
28
+ # 1. (👋) Do task initialization here.
29
+ #########################################################
30
+
31
+ # Acknowledge that the task has been created.
32
+ await adk.messages.create(
33
+ task_id=params.task.id,
34
+ content=TextContent(
35
+ author="agent",
36
+ content=f"Hello! I've received your task. Normally you can do some state initialization here, or just pass and do nothing until you get your first event. For now I'm just acknowledging that I've received a task with the following params:\n\n{json.dumps(params.params, indent=2)}.\n\nYou should only see this message once, when the task is created. All subsequent events will be handled by the `on_task_event_send` handler.",
37
+ ),
26
38
  )
39
+
40
+ @acp.on_task_event_send
41
+ async def handle_event_send(params: SendEventParams):
42
+ # This handler is called whenever a new event (like a message) is sent to the task
43
+
44
+ #########################################################
45
+ # 2. (👋) Echo back the client's message to show it in the UI.
46
+ #########################################################
47
+
48
+ # This is not done by default so the agent developer has full control over what is shown to the user.
49
+ if params.event.content:
50
+ await adk.messages.create(task_id=params.task.id, content=params.event.content)
51
+
52
+ #########################################################
53
+ # 3. (👋) Send a simple response message.
54
+ #########################################################
55
+
56
+ # In future tutorials, this is where we'll add more sophisticated response logic.
57
+ await adk.messages.create(
58
+ task_id=params.task.id,
59
+ content=TextContent(
60
+ author="agent",
61
+ content=f"Hello! I've received your message. I can't respond right now, but in future tutorials we'll see how you can get me to intelligently respond to your message.",
62
+ ),
63
+ )
64
+
65
+ @acp.on_task_cancel
66
+ async def handle_task_cancel(params: CancelTaskParams):
67
+ # This handler is called when a task is cancelled.
68
+ # It's useful for cleaning up any resources or state associated with the task.
69
+
70
+ #########################################################
71
+ # 4. (👋) Do task cleanup here.
72
+ #########################################################
73
+
74
+ # This is mostly for durable workflows that are cancellable like Temporal, but we will leave it here for demonstration purposes.
75
+ logger.info(f"Hello! I've received task cancel for task {params.task.id}: {params.task}. This isn't necessary for this example, but it's good to know that it's available.")
@@ -68,6 +68,7 @@ This file is essential for both local development and deployment of your agent.
68
68
  │ └── run_worker.py # Temporal worker setup
69
69
  ├── Dockerfile # Container definition
70
70
  ├── manifest.yaml # Deployment config
71
+ ├── dev.ipynb # Development notebook for testing
71
72
  {% if use_uv %}
72
73
  └── pyproject.toml # Dependencies (uv)
73
74
  {% else %}
@@ -82,12 +83,32 @@ This file is essential for both local development and deployment of your agent.
82
83
  - Add your own tools and capabilities
83
84
  - Implement custom state management
84
85
 
85
- ### 2. Develop Temporal Workflows
86
+ ### 2. Test Your Agent with the Development Notebook
87
+ Use the included `dev.ipynb` Jupyter notebook to test your agent interactively:
88
+
89
+ ```bash
90
+ # Start Jupyter notebook (make sure you have jupyter installed)
91
+ jupyter notebook dev.ipynb
92
+
93
+ # Or use VS Code to open the notebook directly
94
+ code dev.ipynb
95
+ ```
96
+
97
+ The notebook includes:
98
+ - **Setup**: Connect to your local AgentEx backend
99
+ - **Task creation**: Create a new task for the conversation
100
+ - **Event sending**: Send events to the agent and get responses
101
+ - **Async message subscription**: Subscribe to server-side events to receive agent responses
102
+ - **Rich message display**: Beautiful formatting with timestamps and author information
103
+
104
+ The notebook automatically uses your agent name (`{{ agent_name }}`) and demonstrates the agentic ACP workflow: create task → send event → subscribe to responses.
105
+
106
+ ### 3. Develop Temporal Workflows
86
107
  - Edit `workflow.py` to define your agent's async workflow logic
87
108
  - Modify `activities.py` to add custom activities
88
109
  - Use `run_worker.py` to configure the Temporal worker
89
110
 
90
- ### 3. Manage Dependencies
111
+ ### 4. Manage Dependencies
91
112
 
92
113
  {% if use_uv %}
93
114
  You chose **uv** for package management. Here's how to work with dependencies:
@@ -135,7 +156,7 @@ pip install -r requirements.txt
135
156
  - Wide compatibility
136
157
  {% endif %}
137
158
 
138
- ### 4. Configure Credentials
159
+ ### 5. Configure Credentials
139
160
  - Add any required credentials to your manifest.yaml
140
161
  - For local development, create a `.env` file in the project directory
141
162
  - Use `load_dotenv()` only in development mode:
@@ -0,0 +1,126 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "36834357",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "from agentex import Agentex\n",
11
+ "\n",
12
+ "client = Agentex(base_url=\"http://localhost:5003\")"
13
+ ]
14
+ },
15
+ {
16
+ "cell_type": "code",
17
+ "execution_count": null,
18
+ "id": "d1c309d6",
19
+ "metadata": {},
20
+ "outputs": [],
21
+ "source": [
22
+ "AGENT_NAME = \"{{ agent_name }}\""
23
+ ]
24
+ },
25
+ {
26
+ "cell_type": "code",
27
+ "execution_count": null,
28
+ "id": "9f6e6ef0",
29
+ "metadata": {},
30
+ "outputs": [],
31
+ "source": [
32
+ "# (REQUIRED) Create a new task. For Agentic agents, you must create a task for messages to be associated with.\n",
33
+ "import uuid\n",
34
+ "\n",
35
+ "rpc_response = client.agents.create_task(\n",
36
+ " agent_name=AGENT_NAME,\n",
37
+ " params={\n",
38
+ " \"name\": f\"{str(uuid.uuid4())[:8]}-task\",\n",
39
+ " \"params\": {}\n",
40
+ " }\n",
41
+ ")\n",
42
+ "\n",
43
+ "task = rpc_response.result\n",
44
+ "print(task)"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "code",
49
+ "execution_count": null,
50
+ "id": "b03b0d37",
51
+ "metadata": {},
52
+ "outputs": [],
53
+ "source": [
54
+ "# Send an event to the agent\n",
55
+ "\n",
56
+ "# The response is expected to be a list of TaskMessage objects, which is a union of the following types:\n",
57
+ "# - TextContent: A message with just text content \n",
58
+ "# - DataContent: A message with JSON-serializable data content\n",
59
+ "# - ToolRequestContent: A message with a tool request, which contains a JSON-serializable request to call a tool\n",
60
+ "# - ToolResponseContent: A message with a tool response, which contains response object from a tool call in its content\n",
61
+ "\n",
62
+ "# When processing the message/send response, if you are expecting more than TextContent, such as DataContent, ToolRequestContent, or ToolResponseContent, you can process them as well\n",
63
+ "\n",
64
+ "rpc_response = client.agents.send_event(\n",
65
+ " agent_name=AGENT_NAME,\n",
66
+ " params={\n",
67
+ " \"content\": {\"type\": \"text\", \"author\": \"user\", \"content\": \"Hello what can you do?\"},\n",
68
+ " \"task_id\": task.id,\n",
69
+ " }\n",
70
+ ")\n",
71
+ "\n",
72
+ "event = rpc_response.result\n",
73
+ "print(event)"
74
+ ]
75
+ },
76
+ {
77
+ "cell_type": "code",
78
+ "execution_count": null,
79
+ "id": "a6927cc0",
80
+ "metadata": {},
81
+ "outputs": [],
82
+ "source": [
83
+ "# Subscribe to the async task messages produced by the agent\n",
84
+ "from agentex.lib.utils.dev_tools import subscribe_to_async_task_messages\n",
85
+ "\n",
86
+ "task_messages = subscribe_to_async_task_messages(\n",
87
+ " client=client,\n",
88
+ " task=task, \n",
89
+ " only_after_timestamp=event.created_at, \n",
90
+ " print_messages=True,\n",
91
+ " rich_print=True,\n",
92
+ " timeout=5,\n",
93
+ ")"
94
+ ]
95
+ },
96
+ {
97
+ "cell_type": "code",
98
+ "execution_count": null,
99
+ "id": "4864e354",
100
+ "metadata": {},
101
+ "outputs": [],
102
+ "source": []
103
+ }
104
+ ],
105
+ "metadata": {
106
+ "kernelspec": {
107
+ "display_name": ".venv",
108
+ "language": "python",
109
+ "name": "python3"
110
+ },
111
+ "language_info": {
112
+ "codemirror_mode": {
113
+ "name": "ipython",
114
+ "version": 3
115
+ },
116
+ "file_extension": ".py",
117
+ "mimetype": "text/x-python",
118
+ "name": "python",
119
+ "nbconvert_exporter": "python",
120
+ "pygments_lexer": "ipython3",
121
+ "version": "3.12.9"
122
+ }
123
+ },
124
+ "nbformat": 4,
125
+ "nbformat_minor": 5
126
+ }
@@ -7,13 +7,13 @@ from typing import Annotated, Any
7
7
  import redis.asyncio as redis
8
8
  from fastapi import Depends
9
9
 
10
- from agentex.lib.core.adapters.streams.port import EventStreamRepository
10
+ from agentex.lib.core.adapters.streams.port import StreamRepository
11
11
  from agentex.lib.utils.logging import make_logger
12
12
 
13
13
  logger = make_logger(__name__)
14
14
 
15
15
 
16
- class RedisEventStreamRepository(EventStreamRepository):
16
+ class RedisStreamRepository(StreamRepository):
17
17
  """
18
18
  A simplified Redis implementation of the EventStreamRepository interface.
19
19
  Optimized for text/JSON streaming with SSE.
@@ -123,6 +123,6 @@ class RedisEventStreamRepository(EventStreamRepository):
123
123
  raise
124
124
 
125
125
 
126
- DRedisEventStreamRepository = Annotated[
127
- RedisEventStreamRepository | None, Depends(RedisEventStreamRepository)
126
+ DRedisStreamRepository = Annotated[
127
+ RedisStreamRepository | None, Depends(RedisStreamRepository)
128
128
  ]
@@ -3,7 +3,7 @@ from collections.abc import AsyncIterator
3
3
  from typing import Any
4
4
 
5
5
 
6
- class EventStreamRepository(ABC):
6
+ class StreamRepository(ABC):
7
7
  """
8
8
  Interface for event streaming repositories.
9
9
  Used to publish and subscribe to event streams.
@@ -2,7 +2,7 @@ import json
2
2
  from typing import Literal, cast
3
3
 
4
4
  from agentex import AsyncAgentex
5
- from agentex.lib.core.adapters.streams.port import EventStreamRepository
5
+ from agentex.lib.core.adapters.streams.port import StreamRepository
6
6
  from agentex.lib.types.task_message_updates import (
7
7
  TaskMessageDelta,
8
8
  TaskMessageUpdate,
@@ -22,7 +22,6 @@ from agentex.types.task_message import (
22
22
  TaskMessage,
23
23
  TaskMessageContent,
24
24
  )
25
- from agentex.types.task_message_content_param import TaskMessageContentParam
26
25
  from agentex.types.text_content import TextContent
27
26
  from agentex.types.tool_request_content import ToolRequestContent
28
27
  from agentex.types.tool_response_content import ToolResponseContent
@@ -221,7 +220,7 @@ class StreamingService:
221
220
  def __init__(
222
221
  self,
223
222
  agentex_client: AsyncAgentex,
224
- stream_repository: EventStreamRepository,
223
+ stream_repository: StreamRepository,
225
224
  ):
226
225
  self._agentex_client = agentex_client
227
226
  self._stream_repository = stream_repository
@@ -2,7 +2,7 @@ from scale_gp import SGPClient, SGPClientError
2
2
 
3
3
  from agentex import AsyncAgentex
4
4
  from agentex.lib.core.adapters.llm.adapter_litellm import LiteLLMGateway
5
- from agentex.lib.core.adapters.streams.adapter_redis import RedisEventStreamRepository
5
+ from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository
6
6
  from agentex.lib.core.services.adk.acp.acp import ACPService
7
7
  from agentex.lib.core.services.adk.agent_task_tracker import AgentTaskTrackerService
8
8
  from agentex.lib.core.services.adk.events import EventsService
@@ -57,7 +57,7 @@ def get_all_activities(sgp_client=None):
57
57
  sgp_client = None
58
58
 
59
59
  llm_gateway = LiteLLMGateway()
60
- stream_repository = RedisEventStreamRepository()
60
+ stream_repository = RedisStreamRepository()
61
61
  agentex_client = AsyncAgentex()
62
62
  tracer = AsyncTracer(agentex_client)
63
63
 
@@ -2,6 +2,7 @@ import asyncio
2
2
  import base64
3
3
  import inspect
4
4
  import json
5
+ import os
5
6
  from collections.abc import AsyncGenerator, Awaitable, Callable
6
7
  from contextlib import asynccontextmanager
7
8
  from typing import Any
@@ -13,7 +14,7 @@ from fastapi.responses import StreamingResponse
13
14
  from pydantic import TypeAdapter, ValidationError
14
15
 
15
16
  # from agentex.lib.sdk.fastacp.types import BaseACPConfig
16
- from agentex.lib.environment_variables import EnvironmentVariables
17
+ from agentex.lib.environment_variables import EnvironmentVariables, refreshed_environment_variables
17
18
  from agentex.lib.types.acp import (
18
19
  PARAMS_MODEL_BY_METHOD,
19
20
  RPC_SYNC_METHODS,
@@ -390,8 +391,16 @@ class BaseACPServer(FastAPI):
390
391
  registration_url, json=registration_data, timeout=30.0
391
392
  )
392
393
  if response.status_code == 200:
394
+ agent = response.json()
395
+ agent_id, agent_name = agent["id"], agent["name"]
396
+
397
+ os.environ["AGENT_ID"] = agent_id
398
+ os.environ["AGENT_NAME"] = agent_name
399
+ refreshed_environment_variables.AGENT_ID = agent_id
400
+ refreshed_environment_variables.AGENT_NAME = agent_name
401
+
393
402
  logger.info(
394
- f"Successfully registered agent '{env_vars.AGENT_NAME}' with Agentex server with acp_url: {full_acp_url}. Registration data: {registration_data}"
403
+ f"Successfully registered agent '{agent_name}' with Agentex server with acp_url: {full_acp_url}. Registration data: {registration_data}"
395
404
  )
396
405
  return # Success, exit the retry loop
397
406
  else:
@@ -0,0 +1,9 @@
1
+ """Development tools for AgentEx."""
2
+
3
+ from .async_messages import print_task_message, print_task_message_update, subscribe_to_async_task_messages
4
+
5
+ __all__ = [
6
+ "print_task_message",
7
+ "print_task_message_update",
8
+ "subscribe_to_async_task_messages",
9
+ ]