kagent-adk 0.0.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.

@@ -0,0 +1,173 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # PyPI configuration file
171
+ .pypirc
172
+
173
+ .DS_Store
@@ -0,0 +1 @@
1
+ 3.13.5
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: kagent-adk
3
+ Version: 0.0.1
4
+ Summary: kagent-adk is an sdk for integrating adk agents with kagent
5
+ Requires-Python: >=3.12.11
6
+ Requires-Dist: a2a-sdk>=0.2.16
7
+ Requires-Dist: anthropic[vertex]>=0.49.0
8
+ Requires-Dist: anyio>=4.9.0
9
+ Requires-Dist: fastapi>=0.115.1
10
+ Requires-Dist: google-adk>=1.8.0
11
+ Requires-Dist: google-auth>=2.40.2
12
+ Requires-Dist: google-genai>=1.21.1
13
+ Requires-Dist: httpx>=0.25.0
14
+ Requires-Dist: jsonref>=1.1.0
15
+ Requires-Dist: litellm>=1.74.3
16
+ Requires-Dist: mcp>=1.12.0
17
+ 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
20
+ 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
24
+ Requires-Dist: pydantic>=2.5.0
25
+ Requires-Dist: typing-extensions>=4.8.0
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest-asyncio>=0.25.3; extra == 'test'
28
+ Requires-Dist: pytest>=8.3.5; extra == 'test'
File without changes
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "kagent-adk"
7
+ version = "0.0.1"
8
+ description = "kagent-adk is an sdk for integrating adk agents with kagent"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12.11"
11
+ dependencies = [
12
+ "anyio>=4.9.0",
13
+ "openai>=1.72.0",
14
+ "mcp>=1.12.0",
15
+ "protobuf>=6.31.1",
16
+ "opentelemetry-api>=1.32.0",
17
+ "opentelemetry-sdk>=1.32.0",
18
+ "opentelemetry-exporter-otlp-proto-grpc>=1.32.0",
19
+ "opentelemetry-instrumentation-openai>= 0.39.0",
20
+ "opentelemetry-instrumentation-httpx >= 0.52.0",
21
+ "anthropic[vertex]>=0.49.0",
22
+ "fastapi>=0.115.1",
23
+ "litellm>=1.74.3",
24
+ "google-adk>=1.8.0",
25
+ "google-genai>=1.21.1",
26
+ "google-auth>=2.40.2",
27
+ "httpx>=0.25.0",
28
+ "pydantic>=2.5.0",
29
+ "typing-extensions>=4.8.0",
30
+ "jsonref>=1.1.0",
31
+ "a2a-sdk>=0.2.16",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ test = [
36
+ "pytest>=8.3.5",
37
+ "pytest-asyncio>=0.25.3",
38
+ ]
39
+
40
+ [tool.hatch.build.targets.wheel]
41
+ packages = ["src/kagent_adk"]
42
+
43
+ [tool.ruff]
44
+ extend = "../../pyproject.toml"
@@ -0,0 +1,10 @@
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"]
@@ -0,0 +1,202 @@
1
+ #! /usr/bin/env python3
2
+ import faulthandler
3
+ import logging
4
+ import os
5
+ import sys
6
+ from contextlib import asynccontextmanager
7
+ from typing import Callable
8
+
9
+ import httpx
10
+ from a2a.auth.user import User
11
+ from a2a.server.agent_execution import RequestContext, SimpleRequestContextBuilder
12
+ from a2a.server.apps import A2AStarletteApplication
13
+ from a2a.server.context import ServerCallContext
14
+ from a2a.server.request_handlers import DefaultRequestHandler
15
+ from a2a.server.tasks import TaskStore
16
+ from a2a.types import AgentCard, MessageSendParams, Task
17
+ from fastapi import FastAPI, Request
18
+ from fastapi.responses import PlainTextResponse
19
+ from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor
20
+ from google.adk.agents import BaseAgent
21
+ from google.adk.runners import Runner
22
+ from google.adk.sessions import InMemorySessionService
23
+ from google.genai import types
24
+
25
+ from .kagent_session_service import KAgentSessionService
26
+ from .kagent_task_store import KAgentTaskStore
27
+
28
+ # --- Constants ---
29
+ USER_ID = "admin@kagent.dev"
30
+
31
+ # --- Configure Logging ---
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ class KAgentUser(User):
36
+ def __init__(self, user_id: str):
37
+ self.user_id = user_id
38
+
39
+ @property
40
+ def is_authenticated(self) -> bool:
41
+ return False
42
+
43
+ @property
44
+ def user_name(self) -> str:
45
+ return self.user_id
46
+
47
+
48
+ class KAgentRequestContextBuilder(SimpleRequestContextBuilder):
49
+ """
50
+ A request context builder that will be used to hack in the user_id for now.
51
+ """
52
+
53
+ def __init__(self, user_id: str, task_store: TaskStore):
54
+ super().__init__(task_store=task_store)
55
+ self.user_id = user_id
56
+
57
+ async def build(
58
+ self,
59
+ params: MessageSendParams | None = None,
60
+ task_id: str | None = None,
61
+ context_id: str | None = None,
62
+ task: Task | None = None,
63
+ context: ServerCallContext | None = None,
64
+ ) -> RequestContext:
65
+ if not context:
66
+ context = ServerCallContext(user=KAgentUser(user_id=self.user_id))
67
+ else:
68
+ context.user = KAgentUser(user_id=self.user_id)
69
+ request_context = await super().build(params, task_id, context_id, task, context)
70
+ return request_context
71
+
72
+
73
+ def health_check(request: Request) -> PlainTextResponse:
74
+ return PlainTextResponse("OK")
75
+
76
+
77
+ def thread_dump(request: Request) -> PlainTextResponse:
78
+ import io
79
+
80
+ buf = io.StringIO()
81
+ faulthandler.dump_traceback(file=buf)
82
+ buf.seek(0)
83
+ return PlainTextResponse(buf.read())
84
+
85
+
86
+ kagent_url_override = os.getenv("KAGENT_URL")
87
+
88
+
89
+ class KAgentApp:
90
+ def __init__(
91
+ self,
92
+ root_agent: BaseAgent | Callable[[], BaseAgent],
93
+ agent_card: AgentCard,
94
+ kagent_url: str,
95
+ app_name: str,
96
+ ):
97
+ self.root_agent = root_agent
98
+ self.kagent_url = kagent_url
99
+ self.app_name = app_name
100
+ self.agent_card = agent_card
101
+
102
+ def build(self) -> FastAPI:
103
+ http_client = httpx.AsyncClient(base_url=kagent_url_override or self.kagent_url)
104
+ session_service = KAgentSessionService(http_client)
105
+
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}")
130
+
131
+ agent_executor = A2aAgentExecutor(
132
+ runner=runner,
133
+ )
134
+
135
+ kagent_task_store = KAgentTaskStore(http_client)
136
+
137
+ request_context_builder = KAgentRequestContextBuilder(user_id=USER_ID, task_store=kagent_task_store)
138
+ request_handler = DefaultRequestHandler(
139
+ agent_executor=agent_executor,
140
+ task_store=kagent_task_store,
141
+ request_context_builder=request_context_builder,
142
+ )
143
+
144
+ a2a_app = A2AStarletteApplication(
145
+ agent_card=self.agent_card,
146
+ http_handler=request_handler,
147
+ )
148
+
149
+ # @asynccontextmanager
150
+ # async def agent_lifespan(app: FastAPI):
151
+ # yield
152
+ # if isinstance(runner, Runner):
153
+ # await runner.close()
154
+
155
+ faulthandler.enable()
156
+ app = FastAPI()
157
+
158
+ # Health check/readiness probe
159
+ app.add_route("/health", methods=["GET"], route=health_check)
160
+ app.add_route("/thread_dump", methods=["GET"], route=thread_dump)
161
+ a2a_app.add_routes_to_app(app)
162
+
163
+ return app
164
+
165
+ async def test(self, task: str):
166
+ session_service = InMemorySessionService()
167
+ SESSION_ID = "12345"
168
+ USER_ID = "admin"
169
+ await session_service.create_session(
170
+ app_name=self.app_name,
171
+ session_id=SESSION_ID,
172
+ user_id=USER_ID,
173
+ )
174
+ if isinstance(self.root_agent, Callable):
175
+ agent_factory = self.root_agent
176
+ root_agent = agent_factory()
177
+ else:
178
+ root_agent = self.root_agent
179
+
180
+ runner = Runner(
181
+ agent=root_agent,
182
+ app_name=self.app_name,
183
+ session_service=session_service,
184
+ )
185
+
186
+ logger.info(f"\n>>> User Query: {task}")
187
+
188
+ # Prepare the user's message in ADK format
189
+ content = types.Content(role="user", parts=[types.Part(text=task)])
190
+ # Key Concept: run_async executes the agent logic and yields Events.
191
+ # We iterate through events to find the final answer.
192
+ async for event in runner.run_async(
193
+ user_id=USER_ID,
194
+ session_id=SESSION_ID,
195
+ new_message=content,
196
+ ):
197
+ # You can uncomment the line below to see *all* events during execution
198
+ # print(f" [Event] Author: {event.author}, Type: {type(event).__name__}, Final: {event.is_final_response()}, Content: {event.content}")
199
+
200
+ # Key Concept: is_final_response() marks the concluding message for the turn.
201
+ jsn = event.model_dump_json()
202
+ logger.info(f" [Event] {jsn}")
@@ -0,0 +1,175 @@
1
+ import logging
2
+ from typing import Any, Optional
3
+
4
+ import httpx
5
+ from google.adk.events.event import Event
6
+ from google.adk.sessions import Session
7
+ from google.adk.sessions.base_session_service import (
8
+ BaseSessionService,
9
+ GetSessionConfig,
10
+ ListSessionsResponse,
11
+ )
12
+ from typing_extensions import override
13
+
14
+ logger = logging.getLogger("kagent." + __name__)
15
+
16
+
17
+ class KAgentSessionService(BaseSessionService):
18
+ """A session service implementation that uses the Kagent API.
19
+ This service integrates with the Kagent server to manage session state
20
+ and persistence through HTTP API calls.
21
+ """
22
+
23
+ def __init__(self, client: httpx.AsyncClient):
24
+ super().__init__()
25
+ self.client = client
26
+
27
+ async def _get_user_id(self) -> str:
28
+ """Get the default user ID. Override this method to implement custom user ID logic."""
29
+ return "admin@kagent.dev"
30
+
31
+ @override
32
+ async def create_session(
33
+ self,
34
+ *,
35
+ app_name: str,
36
+ user_id: str,
37
+ state: Optional[dict[str, Any]] = None,
38
+ session_id: Optional[str] = None,
39
+ ) -> Session:
40
+ # Prepare request data
41
+ request_data = {
42
+ "user_id": user_id,
43
+ "agent_ref": app_name, # Use app_name as agent reference
44
+ }
45
+ if session_id:
46
+ request_data["id"] = session_id
47
+
48
+ # Make API call to create session
49
+ response = await self.client.post(
50
+ "/api/sessions",
51
+ json=request_data,
52
+ headers={"X-User-ID": user_id},
53
+ )
54
+ response.raise_for_status()
55
+
56
+ data = response.json()
57
+ if not data.get("data"):
58
+ raise RuntimeError(f"Failed to create session: {data.get('message', 'Unknown error')}")
59
+
60
+ session_data = data["data"]
61
+
62
+ # Convert to ADK Session format
63
+ return Session(id=session_data["id"], user_id=session_data["user_id"], state=state or {}, app_name=app_name)
64
+
65
+ @override
66
+ async def get_session(
67
+ self,
68
+ *,
69
+ app_name: str,
70
+ user_id: str,
71
+ session_id: str,
72
+ config: Optional[GetSessionConfig] = None,
73
+ ) -> Optional[Session]:
74
+ try:
75
+ url = f"/api/sessions/{session_id}?user_id={user_id}"
76
+ if config:
77
+ if config.after_timestamp:
78
+ # TODO: implement
79
+ # url += f"&after={config.after_timestamp}"
80
+ pass
81
+ if config.num_recent_events:
82
+ url += f"&limit={config.num_recent_events}"
83
+ else:
84
+ url += "&limit=-1"
85
+ else:
86
+ # return all
87
+ url += "&limit=-1"
88
+
89
+ # Make API call to get session
90
+ response: httpx.Response = await self.client.get(
91
+ url,
92
+ headers={"X-User-ID": user_id},
93
+ )
94
+ if response.status_code == 404:
95
+ return None
96
+ response.raise_for_status()
97
+
98
+ data = response.json()
99
+ if not data.get("data"):
100
+ return None
101
+
102
+ if not data.get("data").get("session"):
103
+ return None
104
+ session_data = data["data"]["session"]
105
+
106
+ events_data = data["data"]["events"]
107
+
108
+ events: list[Event] = []
109
+ for event_data in events_data:
110
+ events.append(Event.model_validate_json(event_data["data"]))
111
+
112
+ # Convert to ADK Session format
113
+ return Session(
114
+ id=session_data["id"],
115
+ user_id=session_data["user_id"],
116
+ events=events,
117
+ app_name=app_name,
118
+ state={}, # TODO: restore State
119
+ )
120
+ except httpx.HTTPStatusError as e:
121
+ if e.response.status_code == 404:
122
+ return None
123
+ raise
124
+
125
+ @override
126
+ async def list_sessions(self, *, app_name: str, user_id: str) -> ListSessionsResponse:
127
+ # Make API call to list sessions
128
+ response = await self.client.get(f"/api/sessions?user_id={user_id}", headers={"X-User-ID": user_id})
129
+ response.raise_for_status()
130
+
131
+ data = response.json()
132
+ sessions_data = data.get("data", [])
133
+
134
+ # Convert to ADK Session format
135
+ sessions = []
136
+ for session_data in sessions_data:
137
+ session = Session(id=session_data["id"], user_id=session_data["user_id"], state={}, app_name=app_name)
138
+ sessions.append(session)
139
+
140
+ return ListSessionsResponse(sessions=sessions)
141
+
142
+ def list_sessions_sync(self, *, app_name: str, user_id: str) -> ListSessionsResponse:
143
+ raise NotImplementedError("not supported. use async")
144
+
145
+ @override
146
+ async def delete_session(self, *, app_name: str, user_id: str, session_id: str) -> None:
147
+ # Make API call to delete session
148
+ response = await self.client.delete(
149
+ f"/api/sessions/{session_id}?user_id={user_id}",
150
+ headers={"X-User-ID": user_id},
151
+ )
152
+ response.raise_for_status()
153
+
154
+ @override
155
+ async def append_event(self, session: Session, event: Event) -> Event:
156
+ # Convert ADK Event to JSON format
157
+ event_data = {
158
+ "id": event.id,
159
+ "data": event.model_dump_json(),
160
+ }
161
+
162
+ # Make API call to append event to session
163
+ response = await self.client.post(
164
+ f"/api/sessions/{session.id}/events?user_id={session.user_id}",
165
+ json=event_data,
166
+ headers={"X-User-ID": session.user_id},
167
+ )
168
+ response.raise_for_status()
169
+
170
+ # TODO: potentially pull and update the session from the server
171
+ # Update the in-memory session.
172
+ session.last_update_time = event.timestamp
173
+ await super().append_event(session=session, event=event)
174
+
175
+ return event
@@ -0,0 +1,30 @@
1
+ from typing import override
2
+
3
+ import httpx
4
+ from a2a.server.tasks import TaskStore
5
+ from a2a.types import Task
6
+
7
+
8
+ class KAgentTaskStore(TaskStore):
9
+ client: httpx.AsyncClient
10
+
11
+ def __init__(self, client: httpx.AsyncClient):
12
+ self.client = client
13
+
14
+ @override
15
+ async def save(self, task: Task) -> None:
16
+ response = await self.client.post("/api/tasks", json=task.model_dump())
17
+ response.raise_for_status()
18
+
19
+ @override
20
+ async def get(self, task_id: str) -> Task | None:
21
+ response = await self.client.get(f"/api/tasks/{task_id}")
22
+ if response.status_code == 404:
23
+ return None
24
+ response.raise_for_status()
25
+ return Task.model_validate(response.json())
26
+
27
+ @override
28
+ async def delete(self, task_id: str) -> None:
29
+ response = await self.client.delete(f"/api/tasks/{task_id}")
30
+ response.raise_for_status()
@@ -0,0 +1,110 @@
1
+ import logging
2
+ from typing import Literal, Self, Union
3
+
4
+ from a2a.types import AgentCard
5
+ from google.adk.agents import Agent
6
+ from google.adk.agents.llm_agent import ToolUnion
7
+ from google.adk.agents.run_config import RunConfig, StreamingMode
8
+ from google.adk.models.anthropic_llm import Claude as ClaudeLLM
9
+ from google.adk.models.google_llm import Gemini as GeminiLLM
10
+ from google.adk.models.lite_llm import LiteLlm
11
+ from google.adk.tools.agent_tool import AgentTool
12
+ from google.adk.tools.mcp_tool import MCPToolset, SseConnectionParams, StreamableHTTPConnectionParams
13
+ from pydantic import BaseModel, Field
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class HttpMcpServerConfig(BaseModel):
19
+ params: StreamableHTTPConnectionParams
20
+ tools: list[str] = Field(default_factory=list)
21
+
22
+
23
+ class SseMcpServerConfig(BaseModel):
24
+ params: SseConnectionParams
25
+ tools: list[str] = Field(default_factory=list)
26
+
27
+
28
+ class BaseLLM(BaseModel):
29
+ model: str
30
+
31
+
32
+ class OpenAI(BaseLLM):
33
+ base_url: str | None = None
34
+
35
+ type: Literal["openai"]
36
+
37
+
38
+ class AzureOpenAI(BaseLLM):
39
+ type: Literal["azure_openai"]
40
+
41
+
42
+ class Anthropic(BaseLLM):
43
+ base_url: str | None = None
44
+
45
+ type: Literal["anthropic"]
46
+
47
+
48
+ class GeminiVertexAI(BaseLLM):
49
+ type: Literal["gemini_vertex_ai"]
50
+
51
+
52
+ class GeminiAnthropic(BaseLLM):
53
+ type: Literal["gemini_anthropic"]
54
+
55
+
56
+ class Ollama(BaseLLM):
57
+ type: Literal["ollama"]
58
+
59
+
60
+ class Gemini(BaseLLM):
61
+ type: Literal["gemini"]
62
+
63
+
64
+ class AgentConfig(BaseModel):
65
+ kagent_url: str # The URL of the KAgent server
66
+ agent_card: AgentCard
67
+ name: str
68
+ model: Union[OpenAI, Anthropic, GeminiVertexAI, GeminiAnthropic, Ollama, AzureOpenAI, Gemini] = Field(
69
+ discriminator="type"
70
+ )
71
+ description: str
72
+ instruction: str
73
+ http_tools: list[HttpMcpServerConfig] | None = None # tools, always MCP
74
+ sse_tools: list[SseMcpServerConfig] | None = None # tools, always MCP
75
+ agents: list[Self] | None = None # agent names
76
+
77
+ def to_agent(self) -> Agent:
78
+ mcp_toolsets: list[ToolUnion] = []
79
+ if self.http_tools:
80
+ for http_tool in self.http_tools: # add http tools
81
+ mcp_toolsets.append(MCPToolset(connection_params=http_tool.params, tool_filter=http_tool.tools))
82
+ if self.sse_tools:
83
+ for sse_tool in self.sse_tools: # add stdio tools
84
+ mcp_toolsets.append(MCPToolset(connection_params=sse_tool.params, tool_filter=sse_tool.tools))
85
+ if self.agents:
86
+ for agent in self.agents: # Add sub agents as tools
87
+ mcp_toolsets.append(AgentTool(agent.to_agent()))
88
+ if self.model.type == "openai":
89
+ model = LiteLlm(model=f"openai/{self.model.model}", base_url=self.model.base_url)
90
+ elif self.model.type == "anthropic":
91
+ model = LiteLlm(model=f"anthropic/{self.model.model}", base_url=self.model.base_url)
92
+ elif self.model.type == "gemini_vertex_ai":
93
+ model = GeminiLLM(model=self.model.model)
94
+ elif self.model.type == "gemini_anthropic":
95
+ model = ClaudeLLM(model=self.model.model)
96
+ elif self.model.type == "ollama":
97
+ model = LiteLlm(model=f"ollama_chat/{self.model.model}")
98
+ elif self.model.type == "azure_openai":
99
+ model = LiteLlm(model=f"azure/{self.model.model}")
100
+ elif self.model.type == "gemini":
101
+ model = self.model.model
102
+ else:
103
+ raise ValueError(f"Invalid model type: {self.model.type}")
104
+ return Agent(
105
+ name=self.name,
106
+ model=model,
107
+ description=self.description,
108
+ instruction=self.instruction,
109
+ tools=mcp_toolsets,
110
+ )