kagent-adk 0.0.1__py3-none-any.whl → 0.5.0__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.

Potentially problematic release.


This version of kagent-adk might be problematic. Click here for more details.

kagent_adk/__init__.py CHANGED
@@ -1,10 +1,8 @@
1
1
  import importlib.metadata
2
2
 
3
3
  from .a2a import KAgentApp
4
- from .kagent_session_service import KAgentSessionService
5
- from .kagent_task_store import KAgentTaskStore
6
4
  from .models import AgentConfig
7
5
 
8
6
  __version__ = importlib.metadata.version("kagent_adk")
9
7
 
10
- __all__ = ["KAgentSessionService", "KAgentTaskStore", "KAgentApp", "AgentConfig"]
8
+ __all__ = ["KAgentApp", "AgentConfig"]
@@ -0,0 +1,259 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import logging
5
+ import uuid
6
+ from datetime import datetime, timezone
7
+ from typing import Any, Awaitable, Callable, Optional
8
+
9
+ from a2a.server.agent_execution import AgentExecutor
10
+ from a2a.server.agent_execution.context import RequestContext
11
+ from a2a.server.events.event_queue import EventQueue
12
+ from a2a.types import (
13
+ Artifact,
14
+ Message,
15
+ Role,
16
+ TaskArtifactUpdateEvent,
17
+ TaskState,
18
+ TaskStatus,
19
+ TaskStatusUpdateEvent,
20
+ TextPart,
21
+ )
22
+ from google.adk.a2a.converters.event_converter import convert_event_to_a2a_events
23
+ from google.adk.a2a.converters.request_converter import convert_a2a_request_to_adk_run_args
24
+ from google.adk.a2a.converters.utils import _get_adk_metadata_key
25
+ from google.adk.a2a.executor.task_result_aggregator import TaskResultAggregator
26
+ from google.adk.runners import Runner
27
+ from google.adk.utils.feature_decorator import experimental
28
+ from pydantic import BaseModel
29
+ from typing_extensions import override
30
+
31
+ logger = logging.getLogger("google_adk." + __name__)
32
+
33
+
34
+ @experimental
35
+ class A2aAgentExecutorConfig(BaseModel):
36
+ """Configuration for the A2aAgentExecutor."""
37
+
38
+ pass
39
+
40
+
41
+ # This class is a copy of the A2aAgentExecutor class in the ADK sdk,
42
+ # with the following changes:
43
+ # - The runner is ALWAYS a callable that returns a Runner instance
44
+ # - The runner is cleaned up at the end of the execution
45
+ @experimental
46
+ class A2aAgentExecutor(AgentExecutor):
47
+ """An AgentExecutor that runs an ADK Agent against an A2A request and
48
+ publishes updates to an event queue.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ *,
54
+ runner: Callable[..., Runner | Awaitable[Runner]],
55
+ config: Optional[A2aAgentExecutorConfig] = None,
56
+ ):
57
+ super().__init__()
58
+ self._runner = runner
59
+ self._config = config
60
+
61
+ async def _resolve_runner(self) -> Runner:
62
+ """Resolve the runner, handling cases where it's a callable that returns a Runner."""
63
+ if callable(self._runner):
64
+ # Call the function to get the runner
65
+ result = self._runner()
66
+
67
+ # Handle async callables
68
+ if inspect.iscoroutine(result):
69
+ resolved_runner = await result
70
+ else:
71
+ resolved_runner = result
72
+
73
+ # Ensure we got a Runner instance
74
+ if not isinstance(resolved_runner, Runner):
75
+ raise TypeError(f"Callable must return a Runner instance, got {type(resolved_runner)}")
76
+
77
+ return resolved_runner
78
+
79
+ raise TypeError(
80
+ f"Runner must be a Runner instance or a callable that returns a Runner, got {type(self._runner)}"
81
+ )
82
+
83
+ @override
84
+ async def cancel(self, context: RequestContext, event_queue: EventQueue):
85
+ """Cancel the execution."""
86
+ # TODO: Implement proper cancellation logic if needed
87
+ raise NotImplementedError("Cancellation is not supported")
88
+
89
+ @override
90
+ async def execute(
91
+ self,
92
+ context: RequestContext,
93
+ event_queue: EventQueue,
94
+ ):
95
+ """Executes an A2A request and publishes updates to the event queue
96
+ specified. It runs as following:
97
+ * Takes the input from the A2A request
98
+ * Convert the input to ADK input content, and runs the ADK agent
99
+ * Collects output events of the underlying ADK Agent
100
+ * Converts the ADK output events into A2A task updates
101
+ * Publishes the updates back to A2A server via event queue
102
+ """
103
+ if not context.message:
104
+ raise ValueError("A2A request must have a message")
105
+
106
+ # for new task, create a task submitted event
107
+ if not context.current_task:
108
+ await event_queue.enqueue_event(
109
+ TaskStatusUpdateEvent(
110
+ task_id=context.task_id,
111
+ status=TaskStatus(
112
+ state=TaskState.submitted,
113
+ message=context.message,
114
+ timestamp=datetime.now(timezone.utc).isoformat(),
115
+ ),
116
+ context_id=context.context_id,
117
+ final=False,
118
+ )
119
+ )
120
+
121
+ # Handle the request and publish updates to the event queue
122
+ runner = await self._resolve_runner()
123
+ try:
124
+ await self._handle_request(context, event_queue, runner)
125
+ except Exception as e:
126
+ logger.error("Error handling A2A request: %s", e, exc_info=True)
127
+ # Publish failure event
128
+ try:
129
+ await event_queue.enqueue_event(
130
+ TaskStatusUpdateEvent(
131
+ task_id=context.task_id,
132
+ status=TaskStatus(
133
+ state=TaskState.failed,
134
+ timestamp=datetime.now(timezone.utc).isoformat(),
135
+ message=Message(
136
+ message_id=str(uuid.uuid4()),
137
+ role=Role.agent,
138
+ parts=[TextPart(text=str(e))],
139
+ ),
140
+ ),
141
+ context_id=context.context_id,
142
+ final=True,
143
+ )
144
+ )
145
+ except Exception as enqueue_error:
146
+ logger.error("Failed to publish failure event: %s", enqueue_error, exc_info=True)
147
+ finally:
148
+ await runner.close()
149
+
150
+ async def _handle_request(
151
+ self,
152
+ context: RequestContext,
153
+ event_queue: EventQueue,
154
+ runner: Runner,
155
+ ):
156
+ # Convert the a2a request to ADK run args
157
+ run_args = convert_a2a_request_to_adk_run_args(context)
158
+
159
+ # ensure the session exists
160
+ session = await self._prepare_session(context, run_args, runner)
161
+
162
+ # create invocation context
163
+ invocation_context = runner._new_invocation_context(
164
+ session=session,
165
+ new_message=run_args["new_message"],
166
+ run_config=run_args["run_config"],
167
+ )
168
+
169
+ # publish the task working event
170
+ await event_queue.enqueue_event(
171
+ TaskStatusUpdateEvent(
172
+ task_id=context.task_id,
173
+ status=TaskStatus(
174
+ state=TaskState.working,
175
+ timestamp=datetime.now(timezone.utc).isoformat(),
176
+ ),
177
+ context_id=context.context_id,
178
+ final=False,
179
+ metadata={
180
+ _get_adk_metadata_key("app_name"): runner.app_name,
181
+ _get_adk_metadata_key("user_id"): run_args["user_id"],
182
+ _get_adk_metadata_key("session_id"): run_args["session_id"],
183
+ },
184
+ )
185
+ )
186
+
187
+ task_result_aggregator = TaskResultAggregator()
188
+ async for adk_event in runner.run_async(**run_args):
189
+ for a2a_event in convert_event_to_a2a_events(
190
+ adk_event, invocation_context, context.task_id, context.context_id
191
+ ):
192
+ task_result_aggregator.process_event(a2a_event)
193
+ await event_queue.enqueue_event(a2a_event)
194
+
195
+ # publish the task result event - this is final
196
+ if (
197
+ task_result_aggregator.task_state == TaskState.working
198
+ and task_result_aggregator.task_status_message is not None
199
+ and task_result_aggregator.task_status_message.parts
200
+ ):
201
+ # if task is still working properly, publish the artifact update event as
202
+ # the final result according to a2a protocol.
203
+ await event_queue.enqueue_event(
204
+ TaskArtifactUpdateEvent(
205
+ task_id=context.task_id,
206
+ last_chunk=True,
207
+ context_id=context.context_id,
208
+ artifact=Artifact(
209
+ artifact_id=str(uuid.uuid4()),
210
+ parts=task_result_aggregator.task_status_message.parts,
211
+ ),
212
+ )
213
+ )
214
+ # public the final status update event
215
+ await event_queue.enqueue_event(
216
+ TaskStatusUpdateEvent(
217
+ task_id=context.task_id,
218
+ status=TaskStatus(
219
+ state=TaskState.completed,
220
+ timestamp=datetime.now(timezone.utc).isoformat(),
221
+ ),
222
+ context_id=context.context_id,
223
+ final=True,
224
+ )
225
+ )
226
+ else:
227
+ await event_queue.enqueue_event(
228
+ TaskStatusUpdateEvent(
229
+ task_id=context.task_id,
230
+ status=TaskStatus(
231
+ state=task_result_aggregator.task_state,
232
+ timestamp=datetime.now(timezone.utc).isoformat(),
233
+ message=task_result_aggregator.task_status_message,
234
+ ),
235
+ context_id=context.context_id,
236
+ final=True,
237
+ )
238
+ )
239
+
240
+ async def _prepare_session(self, context: RequestContext, run_args: dict[str, Any], runner: Runner):
241
+ session_id = run_args["session_id"]
242
+ # create a new session if not exists
243
+ user_id = run_args["user_id"]
244
+ session = await runner.session_service.get_session(
245
+ app_name=runner.app_name,
246
+ user_id=user_id,
247
+ session_id=session_id,
248
+ )
249
+ if session is None:
250
+ session = await runner.session_service.create_session(
251
+ app_name=runner.app_name,
252
+ user_id=user_id,
253
+ state={},
254
+ session_id=session_id,
255
+ )
256
+ # Update run_args with the new session_id
257
+ run_args["session_id"] = session.id
258
+
259
+ return session
kagent_adk/a2a.py CHANGED
@@ -1,10 +1,11 @@
1
1
  #! /usr/bin/env python3
2
2
  import faulthandler
3
+ import inspect
3
4
  import logging
4
5
  import os
5
6
  import sys
6
7
  from contextlib import asynccontextmanager
7
- from typing import Callable
8
+ from typing import Awaitable, Callable, override
8
9
 
9
10
  import httpx
10
11
  from a2a.auth.user import User
@@ -16,14 +17,14 @@ from a2a.server.tasks import TaskStore
16
17
  from a2a.types import AgentCard, MessageSendParams, Task
17
18
  from fastapi import FastAPI, Request
18
19
  from fastapi.responses import PlainTextResponse
19
- from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor
20
20
  from google.adk.agents import BaseAgent
21
21
  from google.adk.runners import Runner
22
22
  from google.adk.sessions import InMemorySessionService
23
23
  from google.genai import types
24
24
 
25
- from .kagent_session_service import KAgentSessionService
26
- from .kagent_task_store import KAgentTaskStore
25
+ from ._agent_executor import A2aAgentExecutor
26
+ from ._session_service import KAgentSessionService
27
+ from ._task_store import KAgentTaskStore
27
28
 
28
29
  # --- Constants ---
29
30
  USER_ID = "admin@kagent.dev"
@@ -89,7 +90,7 @@ kagent_url_override = os.getenv("KAGENT_URL")
89
90
  class KAgentApp:
90
91
  def __init__(
91
92
  self,
92
- root_agent: BaseAgent | Callable[[], BaseAgent],
93
+ root_agent: BaseAgent,
93
94
  agent_card: AgentCard,
94
95
  kagent_url: str,
95
96
  app_name: str,
@@ -103,33 +104,15 @@ class KAgentApp:
103
104
  http_client = httpx.AsyncClient(base_url=kagent_url_override or self.kagent_url)
104
105
  session_service = KAgentSessionService(http_client)
105
106
 
106
- if isinstance(self.root_agent, Callable):
107
- agent_factory = self.root_agent
108
-
109
- def create_runner() -> Runner:
110
- return Runner(
111
- agent=agent_factory(),
112
- app_name=self.app_name,
113
- session_service=session_service,
114
- )
115
-
116
- runner = create_runner
117
- elif isinstance(self.root_agent, BaseAgent):
118
- agent_instance = self.root_agent
119
-
120
- def create_runner() -> Runner:
121
- return Runner(
122
- agent=agent_instance,
123
- app_name=self.app_name,
124
- session_service=session_service,
125
- )
126
-
127
- runner = create_runner
128
- else:
129
- raise ValueError(f"Invalid root agent: {self.root_agent}")
107
+ def create_runner() -> Runner:
108
+ return Runner(
109
+ agent=self.root_agent,
110
+ app_name=self.app_name,
111
+ session_service=session_service,
112
+ )
130
113
 
131
114
  agent_executor = A2aAgentExecutor(
132
- runner=runner,
115
+ runner=create_runner,
133
116
  )
134
117
 
135
118
  kagent_task_store = KAgentTaskStore(http_client)
@@ -146,12 +129,6 @@ class KAgentApp:
146
129
  http_handler=request_handler,
147
130
  )
148
131
 
149
- # @asynccontextmanager
150
- # async def agent_lifespan(app: FastAPI):
151
- # yield
152
- # if isinstance(runner, Runner):
153
- # await runner.close()
154
-
155
132
  faulthandler.enable()
156
133
  app = FastAPI()
157
134
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kagent-adk
3
- Version: 0.0.1
3
+ Version: 0.5.0
4
4
  Summary: kagent-adk is an sdk for integrating adk agents with kagent
5
5
  Requires-Python: >=3.12.11
6
6
  Requires-Dist: a2a-sdk>=0.2.16
@@ -23,6 +23,8 @@ Requires-Dist: opentelemetry-sdk>=1.32.0
23
23
  Requires-Dist: protobuf>=6.31.1
24
24
  Requires-Dist: pydantic>=2.5.0
25
25
  Requires-Dist: typing-extensions>=4.8.0
26
+ Provides-Extra: memory
27
+ Requires-Dist: psutil>=6.1.0; extra == 'memory'
26
28
  Provides-Extra: test
27
29
  Requires-Dist: pytest-asyncio>=0.25.3; extra == 'test'
28
30
  Requires-Dist: pytest>=8.3.5; extra == 'test'
@@ -0,0 +1,9 @@
1
+ kagent_adk/__init__.py,sha256=3oB8gbSzsvVmqV8w-BGKkRlH3JPfK6o27AfOD6wAd8o,182
2
+ kagent_adk/_agent_executor.py,sha256=L1ReZ8VPseMcy3-HGE-kbo97M9G8NqSWDDlMplE7Kgw,9798
3
+ kagent_adk/_session_service.py,sha256=A47gsfDVp8jITzeW987AHTJLEhcU_mU3ik_SFptFGIc,5815
4
+ kagent_adk/_task_store.py,sha256=3ApKbFfcDZmcEnwef6bCDhBhoGY9ZYwwyP671B1DHFo,889
5
+ kagent_adk/a2a.py,sha256=HBEdGq4gPr78AD88GhygsPhntnctY5dw5pt-BN7FpaI,5647
6
+ kagent_adk/models.py,sha256=dtwUQny2r5K1JtCtl_mGk3dNMW-XnPPjWJT8JbyWE0E,3654
7
+ kagent_adk-0.5.0.dist-info/METADATA,sha256=k5WvtF1jPEEuhBvZXVGbGqgqx7X9WiyoHtYR2CdVzYg,1092
8
+ kagent_adk-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ kagent_adk-0.5.0.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- kagent_adk/__init__.py,sha256=S0SQAy9KBIX8Vm55TO5d4Jj94AvmTDXtG8YurUKrSj4,329
2
- kagent_adk/a2a.py,sha256=d49IIoC2r__Zmjf5iuD_pFp8XKevrDn1Yh3sH0FRZKY,6460
3
- kagent_adk/kagent_session_service.py,sha256=A47gsfDVp8jITzeW987AHTJLEhcU_mU3ik_SFptFGIc,5815
4
- kagent_adk/kagent_task_store.py,sha256=3ApKbFfcDZmcEnwef6bCDhBhoGY9ZYwwyP671B1DHFo,889
5
- kagent_adk/models.py,sha256=dtwUQny2r5K1JtCtl_mGk3dNMW-XnPPjWJT8JbyWE0E,3654
6
- kagent_adk-0.0.1.dist-info/METADATA,sha256=FumdkqL9hAtChYuWbWkvMoZ31e5cJ1hdtZw5DbgaPjw,1021
7
- kagent_adk-0.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
- kagent_adk-0.0.1.dist-info/RECORD,,
File without changes