agentic-layer-sdk-msaf 0.13.0__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.
- agentic_layer_sdk_msaf-0.13.0/.gitignore +207 -0
- agentic_layer_sdk_msaf-0.13.0/Makefile +26 -0
- agentic_layer_sdk_msaf-0.13.0/PKG-INFO +49 -0
- agentic_layer_sdk_msaf-0.13.0/README.md +37 -0
- agentic_layer_sdk_msaf-0.13.0/agenticlayer/msaf/__init__.py +9 -0
- agentic_layer_sdk_msaf-0.13.0/agenticlayer/msaf/agent.py +145 -0
- agentic_layer_sdk_msaf-0.13.0/agenticlayer/msaf/agent_to_a2a.py +305 -0
- agentic_layer_sdk_msaf-0.13.0/agenticlayer/msaf/client.py +58 -0
- agentic_layer_sdk_msaf-0.13.0/agenticlayer/msaf/otel.py +10 -0
- agentic_layer_sdk_msaf-0.13.0/agenticlayer/msaf/py.typed +0 -0
- agentic_layer_sdk_msaf-0.13.0/pyproject.toml +83 -0
- agentic_layer_sdk_msaf-0.13.0/tests/__init__.py +0 -0
- agentic_layer_sdk_msaf-0.13.0/tests/conftest.py +9 -0
- agentic_layer_sdk_msaf-0.13.0/tests/fixtures/__init__.py +0 -0
- agentic_layer_sdk_msaf-0.13.0/tests/fixtures/app_factory.py +48 -0
- agentic_layer_sdk_msaf-0.13.0/tests/fixtures/mock_client.py +45 -0
- agentic_layer_sdk_msaf-0.13.0/tests/py.typed +0 -0
- agentic_layer_sdk_msaf-0.13.0/tests/test_msaf_integration.py +208 -0
- agentic_layer_sdk_msaf-0.13.0/tests/utils/__init__.py +0 -0
- agentic_layer_sdk_msaf-0.13.0/tests/utils/helpers.py +53 -0
- agentic_layer_sdk_msaf-0.13.0/uv.lock +1985 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
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
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
# .vscode/
|
|
190
|
+
|
|
191
|
+
# Ruff stuff:
|
|
192
|
+
.ruff_cache/
|
|
193
|
+
|
|
194
|
+
# PyPI configuration file
|
|
195
|
+
.pypirc
|
|
196
|
+
|
|
197
|
+
# Cursor
|
|
198
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
199
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
200
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
201
|
+
.cursorignore
|
|
202
|
+
.cursorindexingignore
|
|
203
|
+
|
|
204
|
+
# Marimo
|
|
205
|
+
marimo/_static/
|
|
206
|
+
marimo/_lsp/
|
|
207
|
+
__marimo__/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
.PHONY: all
|
|
2
|
+
all: build
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
.PHONY: build
|
|
6
|
+
build:
|
|
7
|
+
uv sync
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
.PHONY: test
|
|
11
|
+
test: build
|
|
12
|
+
uv run pytest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
.PHONY: check
|
|
16
|
+
check: build
|
|
17
|
+
uv run ruff check
|
|
18
|
+
uv run mypy --no-incremental .
|
|
19
|
+
uv run bandit -c pyproject.toml -r .
|
|
20
|
+
make test
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
.PHONY: check-fix
|
|
24
|
+
check-fix: build
|
|
25
|
+
uv run ruff format
|
|
26
|
+
uv run ruff check --fix
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentic-layer-sdk-msaf
|
|
3
|
+
Version: 0.13.0
|
|
4
|
+
Requires-Python: <3.15,>=3.14
|
|
5
|
+
Requires-Dist: a2a-sdk
|
|
6
|
+
Requires-Dist: agent-framework-core>=1.0.0rc2
|
|
7
|
+
Requires-Dist: agentic-layer-sdk-shared
|
|
8
|
+
Requires-Dist: httpx
|
|
9
|
+
Requires-Dist: httpx-retries>=0.4.5
|
|
10
|
+
Requires-Dist: starlette
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# agentic-layer-sdk-msaf
|
|
14
|
+
|
|
15
|
+
Microsoft Agent Framework adapter for the Agentic Layer SDK.
|
|
16
|
+
|
|
17
|
+
This package provides utilities to convert a [Microsoft Agent Framework](https://github.com/microsoft/agent-framework) agent into an instrumented A2A Starlette web application.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from agent_framework import Agent
|
|
23
|
+
from agenticlayer.msaf.agent_to_a2a import to_a2a
|
|
24
|
+
from agenticlayer.msaf.client import create_openai_client
|
|
25
|
+
|
|
26
|
+
agent = Agent(
|
|
27
|
+
client=create_openai_client(),
|
|
28
|
+
name="MyAgent",
|
|
29
|
+
instructions="You are a helpful assistant.",
|
|
30
|
+
)
|
|
31
|
+
app = to_a2a(agent, name="MyAgent", rpc_url="http://localhost:8000/")
|
|
32
|
+
# Then run with: uvicorn module:app
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
### OpenAI-compatible gateway (LiteLLM proxy)
|
|
38
|
+
|
|
39
|
+
Set the following environment variables to point the agent at an OpenAI-compatible gateway
|
|
40
|
+
such as [LiteLLM proxy](https://docs.litellm.ai/docs/proxy/quick_start):
|
|
41
|
+
|
|
42
|
+
| Variable | Description |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `LITELLM_PROXY_API_BASE` | Base URL of the gateway, e.g. `http://litellm-proxy:4000` |
|
|
45
|
+
| `LITELLM_PROXY_API_KEY` | API key for the gateway |
|
|
46
|
+
| `OPENAI_CHAT_MODEL_ID` | Model name to use, e.g. `gpt-4o` |
|
|
47
|
+
|
|
48
|
+
`create_openai_client()` reads these variables automatically and passes them to
|
|
49
|
+
`OpenAIChatClient` as `base_url` and `api_key`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# agentic-layer-sdk-msaf
|
|
2
|
+
|
|
3
|
+
Microsoft Agent Framework adapter for the Agentic Layer SDK.
|
|
4
|
+
|
|
5
|
+
This package provides utilities to convert a [Microsoft Agent Framework](https://github.com/microsoft/agent-framework) agent into an instrumented A2A Starlette web application.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from agent_framework import Agent
|
|
11
|
+
from agenticlayer.msaf.agent_to_a2a import to_a2a
|
|
12
|
+
from agenticlayer.msaf.client import create_openai_client
|
|
13
|
+
|
|
14
|
+
agent = Agent(
|
|
15
|
+
client=create_openai_client(),
|
|
16
|
+
name="MyAgent",
|
|
17
|
+
instructions="You are a helpful assistant.",
|
|
18
|
+
)
|
|
19
|
+
app = to_a2a(agent, name="MyAgent", rpc_url="http://localhost:8000/")
|
|
20
|
+
# Then run with: uvicorn module:app
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
### OpenAI-compatible gateway (LiteLLM proxy)
|
|
26
|
+
|
|
27
|
+
Set the following environment variables to point the agent at an OpenAI-compatible gateway
|
|
28
|
+
such as [LiteLLM proxy](https://docs.litellm.ai/docs/proxy/quick_start):
|
|
29
|
+
|
|
30
|
+
| Variable | Description |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `LITELLM_PROXY_API_BASE` | Base URL of the gateway, e.g. `http://litellm-proxy:4000` |
|
|
33
|
+
| `LITELLM_PROXY_API_KEY` | API key for the gateway |
|
|
34
|
+
| `OPENAI_CHAT_MODEL_ID` | Model name to use, e.g. `gpt-4o` |
|
|
35
|
+
|
|
36
|
+
`create_openai_client()` reads these variables automatically and passes them to
|
|
37
|
+
`OpenAIChatClient` as `base_url` and `api_key`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Microsoft Agent Framework adapter for the Agentic Layer SDK.
|
|
3
|
+
Provides utilities to convert a Microsoft agent-framework Agent into
|
|
4
|
+
an instrumented A2A Starlette web application.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from agenticlayer.msaf.client import create_openai_client
|
|
8
|
+
|
|
9
|
+
__all__ = ["create_openai_client"]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Convert Sub Agents and Tools into agent-framework FunctionTools and MCPStreamableHTTPTools.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
|
|
10
|
+
from a2a.client.helpers import create_text_message_object
|
|
11
|
+
from a2a.types import AgentCapabilities, TaskState
|
|
12
|
+
from a2a.types import AgentCard as A2AAgentCard
|
|
13
|
+
from a2a.utils.constants import AGENT_CARD_WELL_KNOWN_PATH
|
|
14
|
+
from agent_framework._mcp import MCPStreamableHTTPTool
|
|
15
|
+
from agent_framework._tools import FunctionTool
|
|
16
|
+
from agenticlayer.config import McpTool, SubAgent
|
|
17
|
+
from httpx_retries import Retry, RetryTransport
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _make_a2a_tool(
|
|
23
|
+
name: str,
|
|
24
|
+
description: str,
|
|
25
|
+
url: str,
|
|
26
|
+
timeout: httpx.Timeout,
|
|
27
|
+
) -> FunctionTool:
|
|
28
|
+
"""Create a FunctionTool that calls a remote A2A agent via the A2A protocol.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
name: The name to expose to the LLM.
|
|
32
|
+
description: Description of what the sub-agent does.
|
|
33
|
+
url: The A2A RPC URL of the sub-agent.
|
|
34
|
+
timeout: HTTP timeout to use when calling the sub-agent.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
A FunctionTool whose invocation sends an A2A message to the remote agent.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
async def call_agent(request: str) -> str:
|
|
41
|
+
"""Call the remote A2A sub-agent with a text request."""
|
|
42
|
+
async with httpx.AsyncClient(timeout=timeout) as http_client:
|
|
43
|
+
minimal_card = A2AAgentCard(
|
|
44
|
+
name=name,
|
|
45
|
+
description=description,
|
|
46
|
+
url=url,
|
|
47
|
+
version="0.1.0",
|
|
48
|
+
capabilities=AgentCapabilities(),
|
|
49
|
+
skills=[],
|
|
50
|
+
default_input_modes=["text/plain"],
|
|
51
|
+
default_output_modes=["text/plain"],
|
|
52
|
+
supports_authenticated_extended_card=False,
|
|
53
|
+
)
|
|
54
|
+
factory = ClientFactory(ClientConfig(httpx_client=http_client))
|
|
55
|
+
client = factory.create(minimal_card)
|
|
56
|
+
|
|
57
|
+
message = create_text_message_object(content=request)
|
|
58
|
+
response_text = f"No response from agent {name}"
|
|
59
|
+
async for event in client.send_message(message):
|
|
60
|
+
# ClientEvent is tuple[Task, update] or a Message; use isinstance to distinguish
|
|
61
|
+
if not isinstance(event, tuple):
|
|
62
|
+
continue
|
|
63
|
+
task: Any = event[0]
|
|
64
|
+
if task.status.state == TaskState.completed and task.status.message:
|
|
65
|
+
texts = [p.root.text for p in task.status.message.parts if hasattr(p.root, "text")]
|
|
66
|
+
if texts:
|
|
67
|
+
response_text = "\n".join(texts)
|
|
68
|
+
break
|
|
69
|
+
return response_text
|
|
70
|
+
|
|
71
|
+
return FunctionTool(
|
|
72
|
+
name=name,
|
|
73
|
+
description=description or f"Call the {name} agent",
|
|
74
|
+
func=call_agent,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class MsafAgentFactory:
|
|
79
|
+
"""Factory for loading sub-agents and MCP tools into a Microsoft Agent Framework agent.
|
|
80
|
+
|
|
81
|
+
Fetches A2A agent cards at startup (failing fast if unreachable) and wraps each
|
|
82
|
+
sub-agent as a :class:`~agent_framework._tools.FunctionTool`. MCP servers are
|
|
83
|
+
wrapped as :class:`~agent_framework._mcp.MCPStreamableHTTPTool` instances which
|
|
84
|
+
must be entered as async context managers before use.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
timeout: httpx.Timeout = httpx.Timeout(timeout=10),
|
|
90
|
+
retry: Retry = Retry(total=10, backoff_factor=0.5, max_backoff_wait=15),
|
|
91
|
+
) -> None:
|
|
92
|
+
self.timeout = timeout
|
|
93
|
+
self.transport = RetryTransport(retry=retry)
|
|
94
|
+
|
|
95
|
+
async def load_sub_agents(self, sub_agents: list[SubAgent]) -> list[FunctionTool]:
|
|
96
|
+
"""Fetch agent cards and create FunctionTools for sub-agents.
|
|
97
|
+
|
|
98
|
+
Raises :class:`~a2a.client.errors.A2AClientHTTPError` if any agent card
|
|
99
|
+
is unreachable (after retries), causing app startup to fail early.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
sub_agents: List of sub-agent configurations.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
A list of FunctionTool instances, one per sub-agent.
|
|
106
|
+
"""
|
|
107
|
+
tools: list[FunctionTool] = []
|
|
108
|
+
for sub_agent in sub_agents:
|
|
109
|
+
base_url = str(sub_agent.url).replace(AGENT_CARD_WELL_KNOWN_PATH, "")
|
|
110
|
+
async with httpx.AsyncClient(transport=self.transport, timeout=self.timeout) as client:
|
|
111
|
+
resolver = A2ACardResolver(httpx_client=client, base_url=base_url)
|
|
112
|
+
agent_card = await resolver.get_agent_card()
|
|
113
|
+
tool = _make_a2a_tool(
|
|
114
|
+
name=sub_agent.name,
|
|
115
|
+
description=agent_card.description,
|
|
116
|
+
url=agent_card.url,
|
|
117
|
+
timeout=self.timeout,
|
|
118
|
+
)
|
|
119
|
+
logger.info("Loaded sub-agent %s from %s", sub_agent.name, agent_card.url)
|
|
120
|
+
tools.append(tool)
|
|
121
|
+
return tools
|
|
122
|
+
|
|
123
|
+
def create_mcp_tools(self, mcp_tools: list[McpTool]) -> list[MCPStreamableHTTPTool]:
|
|
124
|
+
"""Create MCPStreamableHTTPTool instances (not yet connected).
|
|
125
|
+
|
|
126
|
+
The returned tools must be entered as async context managers (i.e. ``async with tool``)
|
|
127
|
+
before they can be used with an agent.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
mcp_tools: List of MCP tool configurations.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
A list of unconnected MCPStreamableHTTPTool instances.
|
|
134
|
+
"""
|
|
135
|
+
tools: list[MCPStreamableHTTPTool] = []
|
|
136
|
+
for mcp_tool in mcp_tools:
|
|
137
|
+
logger.info("Creating MCP tool %s at %s", mcp_tool.name, mcp_tool.url)
|
|
138
|
+
tools.append(
|
|
139
|
+
MCPStreamableHTTPTool(
|
|
140
|
+
name=mcp_tool.name,
|
|
141
|
+
url=str(mcp_tool.url),
|
|
142
|
+
request_timeout=mcp_tool.timeout,
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
return tools
|