kagent-adk 0.0.1__tar.gz → 0.5.1__tar.gz
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-0.0.1 → kagent_adk-0.5.1}/PKG-INFO +3 -1
- {kagent_adk-0.0.1 → kagent_adk-0.5.1}/pyproject.toml +4 -1
- kagent_adk-0.5.1/src/kagent_adk/__init__.py +8 -0
- kagent_adk-0.5.1/src/kagent_adk/_agent_executor.py +259 -0
- {kagent_adk-0.0.1 → kagent_adk-0.5.1}/src/kagent_adk/a2a.py +13 -36
- kagent_adk-0.0.1/src/kagent_adk/__init__.py +0 -10
- {kagent_adk-0.0.1 → kagent_adk-0.5.1}/.gitignore +0 -0
- {kagent_adk-0.0.1 → kagent_adk-0.5.1}/.python-version +0 -0
- {kagent_adk-0.0.1 → kagent_adk-0.5.1}/README.md +0 -0
- /kagent_adk-0.0.1/src/kagent_adk/kagent_session_service.py → /kagent_adk-0.5.1/src/kagent_adk/_session_service.py +0 -0
- /kagent_adk-0.0.1/src/kagent_adk/kagent_task_store.py → /kagent_adk-0.5.1/src/kagent_adk/_task_store.py +0 -0
- {kagent_adk-0.0.1 → kagent_adk-0.5.1}/src/kagent_adk/models.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kagent-adk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
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'
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kagent-adk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.1"
|
|
8
8
|
description = "kagent-adk is an sdk for integrating adk agents with kagent"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12.11"
|
|
@@ -36,6 +36,9 @@ test = [
|
|
|
36
36
|
"pytest>=8.3.5",
|
|
37
37
|
"pytest-asyncio>=0.25.3",
|
|
38
38
|
]
|
|
39
|
+
memory = [
|
|
40
|
+
"psutil>=6.1.0", # For memory monitoring
|
|
41
|
+
]
|
|
39
42
|
|
|
40
43
|
[tool.hatch.build.targets.wheel]
|
|
41
44
|
packages = ["src/kagent_adk"]
|
|
@@ -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
|
|
@@ -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 .
|
|
26
|
-
from .
|
|
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
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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=
|
|
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,10 +0,0 @@
|
|
|
1
|
-
import importlib.metadata
|
|
2
|
-
|
|
3
|
-
from .a2a import KAgentApp
|
|
4
|
-
from .kagent_session_service import KAgentSessionService
|
|
5
|
-
from .kagent_task_store import KAgentTaskStore
|
|
6
|
-
from .models import AgentConfig
|
|
7
|
-
|
|
8
|
-
__version__ = importlib.metadata.version("kagent_adk")
|
|
9
|
-
|
|
10
|
-
__all__ = ["KAgentSessionService", "KAgentTaskStore", "KAgentApp", "AgentConfig"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|