kagent-adk 0.5.5__py3-none-any.whl → 0.6.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.

@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import inspect
4
5
  import logging
5
6
  import uuid
@@ -24,14 +25,12 @@ from google.adk.a2a.converters.request_converter import convert_a2a_request_to_a
24
25
  from google.adk.a2a.converters.utils import _get_adk_metadata_key
25
26
  from google.adk.a2a.executor.task_result_aggregator import TaskResultAggregator
26
27
  from google.adk.runners import Runner
27
- from google.adk.utils.feature_decorator import experimental
28
28
  from pydantic import BaseModel
29
29
  from typing_extensions import override
30
30
 
31
31
  logger = logging.getLogger("google_adk." + __name__)
32
32
 
33
33
 
34
- @experimental
35
34
  class A2aAgentExecutorConfig(BaseModel):
36
35
  """Configuration for the A2aAgentExecutor."""
37
36
 
@@ -42,7 +41,6 @@ class A2aAgentExecutorConfig(BaseModel):
42
41
  # with the following changes:
43
42
  # - The runner is ALWAYS a callable that returns a Runner instance
44
43
  # - The runner is cleaned up at the end of the execution
45
- @experimental
46
44
  class A2aAgentExecutor(AgentExecutor):
47
45
  """An AgentExecutor that runs an ADK Agent against an A2A request and
48
46
  publishes updates to an event queue.
@@ -145,7 +143,16 @@ class A2aAgentExecutor(AgentExecutor):
145
143
  except Exception as enqueue_error:
146
144
  logger.error("Failed to publish failure event: %s", enqueue_error, exc_info=True)
147
145
  finally:
148
- await runner.close()
146
+ # Shield cleanup from external cancellation so toolsets (e.g., MCP) can
147
+ # gracefully close their sessions without being torn down mid-flight.
148
+ try:
149
+ await asyncio.wait_for(asyncio.shield(runner.close()), timeout=15.0)
150
+ except asyncio.CancelledError:
151
+ # Suppress cancellation during cleanup to avoid noisy tracebacks
152
+ # from libraries that assume non-cancelled close semantics.
153
+ logger.warning("Runner.close() was cancelled; suppressing during cleanup")
154
+ except Exception as close_error:
155
+ logger.error("Error during runner.close(): %s", close_error, exc_info=True)
149
156
 
150
157
  async def _handle_request(
151
158
  self,
kagent_adk/cli.py ADDED
@@ -0,0 +1,204 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import os
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ import uvicorn
9
+ from a2a.types import AgentCard
10
+ from google.adk.cli.utils.agent_loader import AgentLoader
11
+ from opentelemetry import _logs, trace
12
+ from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
13
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
14
+ from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
15
+ from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
16
+ from opentelemetry.instrumentation.openai import OpenAIInstrumentor
17
+ from opentelemetry.sdk._events import EventLoggerProvider
18
+ from opentelemetry.sdk._logs import LoggerProvider
19
+ from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
20
+ from opentelemetry.sdk.resources import Resource
21
+ from opentelemetry.sdk.trace import TracerProvider
22
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
23
+
24
+ from . import AgentConfig, KAgentApp
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ app = typer.Typer()
29
+
30
+ kagent_url = os.getenv("KAGENT_URL")
31
+ kagent_name = os.getenv("KAGENT_NAME")
32
+ kagent_namespace = os.getenv("KAGENT_NAMESPACE")
33
+
34
+
35
+ class Config:
36
+ _url: str
37
+ _name: str
38
+ _namespace: str
39
+
40
+ def __init__(self):
41
+ if not kagent_url:
42
+ raise ValueError("KAGENT_URL is not set")
43
+ if not kagent_name:
44
+ raise ValueError("KAGENT_NAME is not set")
45
+ if not kagent_namespace:
46
+ raise ValueError("KAGENT_NAMESPACE is not set")
47
+ self._url = kagent_url
48
+ self._name = kagent_name
49
+ self._namespace = kagent_namespace
50
+
51
+ @property
52
+ def name(self):
53
+ return self._name.replace("-", "_")
54
+
55
+ @property
56
+ def namespace(self):
57
+ return self._namespace.replace("-", "_")
58
+
59
+ @property
60
+ def app_name(self):
61
+ return self.namespace + "__NS__" + self.name
62
+
63
+ @property
64
+ def url(self):
65
+ return self._url
66
+
67
+
68
+ def configure_tracing():
69
+ tracing_enabled = os.getenv("OTEL_TRACING_ENABLED", "false").lower() == "true"
70
+ logging_enabled = os.getenv("OTEL_LOGGING_ENABLED", "false").lower() == "true"
71
+
72
+ resource = Resource({"service.name": "kagent"})
73
+
74
+ # Configure tracing if enabled
75
+ if tracing_enabled:
76
+ logging.info("Enabling tracing")
77
+ tracer_provider = TracerProvider(resource=resource)
78
+ # Check new env var first, fall back to old one for backward compatibility
79
+ trace_endpoint = os.getenv("OTEL_TRACING_EXPORTER_OTLP_ENDPOINT") or os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
80
+ if trace_endpoint:
81
+ processor = BatchSpanProcessor(OTLPSpanExporter(endpoint=trace_endpoint))
82
+ else:
83
+ processor = BatchSpanProcessor(OTLPSpanExporter())
84
+ tracer_provider.add_span_processor(processor)
85
+ trace.set_tracer_provider(tracer_provider)
86
+ HTTPXClientInstrumentor().instrument()
87
+
88
+ # Configure logging if enabled
89
+ if logging_enabled:
90
+ logging.info("Enabling logging for GenAI events")
91
+ logger_provider = LoggerProvider(resource=resource)
92
+ log_endpoint = os.getenv("OTEL_LOGGING_EXPORTER_OTLP_ENDPOINT")
93
+ logging.info(f"Log endpoint configured: {log_endpoint}")
94
+
95
+ # Add OTLP exporter
96
+ if log_endpoint:
97
+ log_processor = BatchLogRecordProcessor(OTLPLogExporter(endpoint=log_endpoint))
98
+ else:
99
+ log_processor = BatchLogRecordProcessor(OTLPLogExporter())
100
+ logger_provider.add_log_record_processor(log_processor)
101
+
102
+ _logs.set_logger_provider(logger_provider)
103
+ logging.info("Log provider configured with OTLP")
104
+ # When logging is enabled, use new event-based approach (input/output as log events in Body)
105
+ logging.info("OpenAI instrumentation configured with event logging capability")
106
+ # Create event logger provider using the configured logger provider
107
+ event_logger_provider = EventLoggerProvider(logger_provider)
108
+ OpenAIInstrumentor(use_legacy_attributes=False).instrument(event_logger_provider=event_logger_provider)
109
+ AnthropicInstrumentor(use_legacy_attributes=False).instrument(event_logger_provider=event_logger_provider)
110
+ else:
111
+ # Use legacy attributes (input/output as GenAI span attributes)
112
+ logging.info("OpenAI instrumentation configured with legacy GenAI span attributes")
113
+ OpenAIInstrumentor().instrument()
114
+ AnthropicInstrumentor().instrument()
115
+
116
+
117
+ @app.command()
118
+ def static(
119
+ host: str = "127.0.0.1",
120
+ port: int = 8080,
121
+ workers: int = 1,
122
+ filepath: str = "/config",
123
+ reload: Annotated[bool, typer.Option("--reload")] = False,
124
+ ):
125
+ configure_tracing()
126
+
127
+ app_cfg = Config()
128
+
129
+ with open(os.path.join(filepath, "config.json"), "r") as f:
130
+ config = json.load(f)
131
+ agent_config = AgentConfig.model_validate(config)
132
+ with open(os.path.join(filepath, "agent-card.json"), "r") as f:
133
+ agent_card = json.load(f)
134
+ agent_card = AgentCard.model_validate(agent_card)
135
+ root_agent = agent_config.to_agent(app_cfg.name)
136
+
137
+ kagent_app = KAgentApp(root_agent, agent_card, app_cfg.url, app_cfg.app_name)
138
+
139
+ uvicorn.run(
140
+ kagent_app.build,
141
+ host=host,
142
+ port=port,
143
+ workers=workers,
144
+ reload=reload,
145
+ )
146
+
147
+
148
+ @app.command()
149
+ def run(
150
+ name: Annotated[str, typer.Argument(help="The name of the agent to run")],
151
+ working_dir: str = ".",
152
+ host: str = "127.0.0.1",
153
+ port: int = 8080,
154
+ workers: int = 1,
155
+ ):
156
+ configure_tracing()
157
+ app_cfg = Config()
158
+
159
+ agent_loader = AgentLoader(agents_dir=working_dir)
160
+ root_agent = agent_loader.load_agent(name)
161
+
162
+ with open(os.path.join(working_dir, name, "agent-card.json"), "r") as f:
163
+ agent_card = json.load(f)
164
+ agent_card = AgentCard.model_validate(agent_card)
165
+ kagent_app = KAgentApp(root_agent, agent_card, app_cfg.url, app_cfg.app_name)
166
+ uvicorn.run(
167
+ kagent_app.build,
168
+ host=host,
169
+ port=port,
170
+ workers=workers,
171
+ )
172
+
173
+
174
+ async def test_agent(agent_config: AgentConfig, agent_card: AgentCard, task: str):
175
+ app_cfg = Config()
176
+ agent = agent_config.to_agent(app_cfg.name)
177
+ app = KAgentApp(agent, agent_card, app_cfg.url, app_cfg.app_name)
178
+ await app.test(task)
179
+
180
+
181
+ @app.command()
182
+ def test(
183
+ task: Annotated[str, typer.Option("--task", help="The task to test the agent with")],
184
+ filepath: Annotated[str, typer.Option("--filepath", help="The path to the agent config file")],
185
+ ):
186
+ with open(filepath, "r") as f:
187
+ content = f.read()
188
+ config = json.loads(content)
189
+
190
+ with open(os.path.join(filepath, "agent-card.json"), "r") as f:
191
+ agent_card = json.load(f)
192
+ agent_card = AgentCard.model_validate(agent_card)
193
+ agent_config = AgentConfig.model_validate(config)
194
+ asyncio.run(test_agent(agent_config, agent_card, task))
195
+
196
+
197
+ def run_cli():
198
+ logging.basicConfig(level=logging.INFO)
199
+ logging.info("Starting KAgent")
200
+ app()
201
+
202
+
203
+ if __name__ == "__main__":
204
+ run_cli()
kagent_adk/models.py CHANGED
@@ -1,9 +1,10 @@
1
1
  import logging
2
2
  from typing import Literal, Self, Union
3
3
 
4
- from a2a.types import AgentCard
5
4
  from google.adk.agents import Agent
5
+ from google.adk.agents.base_agent import BaseAgent
6
6
  from google.adk.agents.llm_agent import ToolUnion
7
+ from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
7
8
  from google.adk.agents.run_config import RunConfig, StreamingMode
8
9
  from google.adk.models.anthropic_llm import Claude as ClaudeLLM
9
10
  from google.adk.models.google_llm import Gemini as GeminiLLM
@@ -25,6 +26,12 @@ class SseMcpServerConfig(BaseModel):
25
26
  tools: list[str] = Field(default_factory=list)
26
27
 
27
28
 
29
+ class RemoteAgentConfig(BaseModel):
30
+ name: str
31
+ url: str
32
+ description: str = ""
33
+
34
+
28
35
  class BaseLLM(BaseModel):
29
36
  model: str
30
37
 
@@ -62,9 +69,6 @@ class Gemini(BaseLLM):
62
69
 
63
70
 
64
71
  class AgentConfig(BaseModel):
65
- kagent_url: str # The URL of the KAgent server
66
- agent_card: AgentCard
67
- name: str
68
72
  model: Union[OpenAI, Anthropic, GeminiVertexAI, GeminiAnthropic, Ollama, AzureOpenAI, Gemini] = Field(
69
73
  discriminator="type"
70
74
  )
@@ -73,8 +77,11 @@ class AgentConfig(BaseModel):
73
77
  http_tools: list[HttpMcpServerConfig] | None = None # tools, always MCP
74
78
  sse_tools: list[SseMcpServerConfig] | None = None # tools, always MCP
75
79
  agents: list[Self] | None = None # agent names
80
+ remote_agents: list[RemoteAgentConfig] | None = None # remote agents
76
81
 
77
- def to_agent(self) -> Agent:
82
+ def to_agent(self, name: str) -> Agent:
83
+ if name is None or not str(name).strip():
84
+ raise ValueError("Agent name must be a non-empty string.")
78
85
  mcp_toolsets: list[ToolUnion] = []
79
86
  if self.http_tools:
80
87
  for http_tool in self.http_tools: # add http tools
@@ -84,7 +91,17 @@ class AgentConfig(BaseModel):
84
91
  mcp_toolsets.append(MCPToolset(connection_params=sse_tool.params, tool_filter=sse_tool.tools))
85
92
  if self.agents:
86
93
  for agent in self.agents: # Add sub agents as tools
87
- mcp_toolsets.append(AgentTool(agent.to_agent()))
94
+ mcp_toolsets.append(AgentTool(agent.to_agent(name)))
95
+ remote_agents: list[BaseAgent] = []
96
+ if self.remote_agents:
97
+ for remote_agent in self.remote_agents: # Add remote agents as tools
98
+ remote_agents.append(
99
+ RemoteA2aAgent(
100
+ name=remote_agent.name,
101
+ agent_card=remote_agent.url,
102
+ description=remote_agent.description,
103
+ )
104
+ )
88
105
  if self.model.type == "openai":
89
106
  model = LiteLlm(model=f"openai/{self.model.model}", base_url=self.model.base_url)
90
107
  elif self.model.type == "anthropic":
@@ -102,9 +119,10 @@ class AgentConfig(BaseModel):
102
119
  else:
103
120
  raise ValueError(f"Invalid model type: {self.model.type}")
104
121
  return Agent(
105
- name=self.name,
122
+ name=name,
106
123
  model=model,
107
124
  description=self.description,
108
125
  instruction=self.instruction,
109
126
  tools=mcp_toolsets,
127
+ sub_agents=remote_agents,
110
128
  )
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kagent-adk
3
- Version: 0.5.5
3
+ Version: 0.6.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
7
+ Requires-Dist: aiofiles>=24.1.0
7
8
  Requires-Dist: anthropic[vertex]>=0.49.0
8
9
  Requires-Dist: anyio>=4.9.0
9
10
  Requires-Dist: fastapi>=0.115.1
@@ -15,14 +16,17 @@ Requires-Dist: jsonref>=1.1.0
15
16
  Requires-Dist: litellm>=1.74.3
16
17
  Requires-Dist: mcp>=1.12.0
17
18
  Requires-Dist: openai>=1.72.0
18
- Requires-Dist: opentelemetry-api>=1.32.0
19
- Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.32.0
19
+ Requires-Dist: opentelemetry-api>=1.36.0
20
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.36.0
21
+ Requires-Dist: opentelemetry-instrumentation-anthropic>=0.44.0
20
22
  Requires-Dist: opentelemetry-instrumentation-httpx>=0.52.0
21
- Requires-Dist: opentelemetry-instrumentation-openai>=0.39.0
22
- Requires-Dist: opentelemetry-sdk>=1.32.0
23
- Requires-Dist: protobuf>=6.31.1
23
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.44.3
24
+ Requires-Dist: opentelemetry-sdk>=1.36.0
25
+ Requires-Dist: protobuf>=6
24
26
  Requires-Dist: pydantic>=2.5.0
27
+ Requires-Dist: typer>=0.15.0
25
28
  Requires-Dist: typing-extensions>=4.8.0
29
+ Requires-Dist: uvicorn>=0.34.0
26
30
  Provides-Extra: memory
27
31
  Requires-Dist: psutil>=6.1.0; extra == 'memory'
28
32
  Provides-Extra: test
@@ -0,0 +1,11 @@
1
+ kagent_adk/__init__.py,sha256=3oB8gbSzsvVmqV8w-BGKkRlH3JPfK6o27AfOD6wAd8o,182
2
+ kagent_adk/_agent_executor.py,sha256=VJn3uQ3EITouzqiIzcN7MZgZ8ozannZIIqjVVyYO57k,10388
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/cli.py,sha256=MSdd38T1vsFEhpj3MEAg4IdQhlrcumnSbRi-8xUKXVE,6977
7
+ kagent_adk/models.py,sha256=ZR1Hbc6fjA82fGdgrfa5-HyWOkXQTrjX2FiS57_O8i4,4411
8
+ kagent_adk-0.6.0.dist-info/METADATA,sha256=sC4cbGZgfPghAy882KwBRHoTHKpMyCMkXi2bjtj2oc8,1242
9
+ kagent_adk-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ kagent_adk-0.6.0.dist-info/entry_points.txt,sha256=bmqHEc9zPYkbRoK57wF3wi5ohHTMB6yHTslKfvuVpc4,50
11
+ kagent_adk-0.6.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kagent-adk = kagent_adk.cli:app
@@ -1,9 +0,0 @@
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.5.dist-info/METADATA,sha256=jEYOAi56xs-S2f8DMblZja1iJHE1Pka2tUIv6RHIX0g,1092
8
- kagent_adk-0.5.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
- kagent_adk-0.5.5.dist-info/RECORD,,