polaris-python-ai 0.0.1.dev2__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.
- polaris_ai/__init__.py +34 -0
- polaris_ai/_async.py +39 -0
- polaris_ai/_version.py +18 -0
- polaris_ai/a2a/__init__.py +31 -0
- polaris_ai/a2a/_card.py +44 -0
- polaris_ai/a2a/registry.py +240 -0
- polaris_ai/a2a/resolver.py +198 -0
- polaris_ai/a2a/server.py +92 -0
- polaris_ai/agentscope/__init__.py +76 -0
- polaris_ai/agentscope/a2a.py +276 -0
- polaris_ai/agentscope/cards.py +106 -0
- polaris_ai/agentscope/config.py +221 -0
- polaris_ai/agentscope/context.py +195 -0
- polaris_ai/agentscope/mcp.py +598 -0
- polaris_ai/agentscope/middleware.py +103 -0
- polaris_ai/agentscope/runtime.py +291 -0
- polaris_ai/agentscope/skill.py +464 -0
- polaris_ai/langgraph/__init__.py +50 -0
- polaris_ai/langgraph/config.py +160 -0
- polaris_ai/langgraph/context.py +152 -0
- polaris_ai/langgraph/registry.py +258 -0
- polaris_ai/langgraph/remote.py +315 -0
- polaris_ai/langgraph/resolver.py +226 -0
- polaris_ai/log.py +408 -0
- polaris_ai/metrics.py +154 -0
- polaris_python_ai-0.0.1.dev2.dist-info/METADATA +351 -0
- polaris_python_ai-0.0.1.dev2.dist-info/RECORD +30 -0
- polaris_python_ai-0.0.1.dev2.dist-info/WHEEL +5 -0
- polaris_python_ai-0.0.1.dev2.dist-info/licenses/LICENSE +27 -0
- polaris_python_ai-0.0.1.dev2.dist-info/top_level.txt +1 -0
polaris_ai/a2a/server.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python-ai available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2026 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
"""Lifecycle glue between an already started A2A server and Polaris."""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import inspect
|
|
23
|
+
|
|
24
|
+
from polaris_ai._async import await_completion
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PolarisA2AServerRegistration:
|
|
28
|
+
"""Register an A2A endpoint after its server becomes ready.
|
|
29
|
+
|
|
30
|
+
``server_ready`` accepts an ``asyncio.Event``, an awaitable, or a callable
|
|
31
|
+
returning an awaitable. Closing the registration closes the registrar, so
|
|
32
|
+
normal exceptions and cancellation unwind through the context manager and
|
|
33
|
+
trigger deregistration.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, registrar, agent_card, transports, *, server_ready=None):
|
|
37
|
+
if registrar is None:
|
|
38
|
+
raise ValueError("A PolarisA2ARegistrar is required")
|
|
39
|
+
if agent_card is None:
|
|
40
|
+
raise ValueError("An AgentCard is required")
|
|
41
|
+
self._registrar = registrar
|
|
42
|
+
self._agent_card = agent_card
|
|
43
|
+
self._transports = tuple(transports)
|
|
44
|
+
self._server_ready = server_ready
|
|
45
|
+
self._lock = asyncio.Lock()
|
|
46
|
+
self._registered = False
|
|
47
|
+
self._closed = False
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def registered(self) -> bool:
|
|
51
|
+
return self._registered
|
|
52
|
+
|
|
53
|
+
async def _wait_for_server(self):
|
|
54
|
+
ready = self._server_ready
|
|
55
|
+
if ready is None:
|
|
56
|
+
return
|
|
57
|
+
if isinstance(ready, asyncio.Event):
|
|
58
|
+
await ready.wait()
|
|
59
|
+
return
|
|
60
|
+
if inspect.isawaitable(ready):
|
|
61
|
+
await ready
|
|
62
|
+
return
|
|
63
|
+
if callable(ready):
|
|
64
|
+
result = ready()
|
|
65
|
+
if inspect.isawaitable(result):
|
|
66
|
+
await result
|
|
67
|
+
return
|
|
68
|
+
raise TypeError("server_ready must be an asyncio.Event, awaitable, or callable")
|
|
69
|
+
|
|
70
|
+
async def async_start(self):
|
|
71
|
+
async with self._lock:
|
|
72
|
+
if self._closed:
|
|
73
|
+
raise RuntimeError("Polaris A2A server registration is closed")
|
|
74
|
+
if self._registered:
|
|
75
|
+
return self
|
|
76
|
+
await self._wait_for_server()
|
|
77
|
+
await self._registrar.async_register(self._agent_card, self._transports)
|
|
78
|
+
self._registered = True
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
async def async_close(self):
|
|
82
|
+
async with self._lock:
|
|
83
|
+
self._closed = True
|
|
84
|
+
await await_completion(self._registrar.async_close())
|
|
85
|
+
if not self._registrar.registered:
|
|
86
|
+
self._registered = False
|
|
87
|
+
|
|
88
|
+
async def __aenter__(self):
|
|
89
|
+
return await self.async_start()
|
|
90
|
+
|
|
91
|
+
async def __aexit__(self, *_):
|
|
92
|
+
await self.async_close()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python-ai available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2026 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""AgentScope integrations; optional dependencies load only when requested."""
|
|
17
|
+
|
|
18
|
+
from importlib import import_module
|
|
19
|
+
|
|
20
|
+
from polaris_ai._version import __version__
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"PolarisSkillLoader",
|
|
24
|
+
"PolarisAgentContext",
|
|
25
|
+
"PolarisA2AAgent",
|
|
26
|
+
"PolarisAgentConfig",
|
|
27
|
+
"PolarisAgentRuntime",
|
|
28
|
+
"PolarisDynamicSkillMiddleware",
|
|
29
|
+
"PolarisMCPResolver",
|
|
30
|
+
"PolarisMCPClientPool",
|
|
31
|
+
"PolarisAgentCardTemplate",
|
|
32
|
+
"build_agent_card",
|
|
33
|
+
"PolarisA2ARegistrar",
|
|
34
|
+
"PolarisA2AResolver",
|
|
35
|
+
"PolarisA2AServerRegistration",
|
|
36
|
+
"A2ATransport",
|
|
37
|
+
"AgentCardNotFoundError",
|
|
38
|
+
"RegisteredInstance",
|
|
39
|
+
"PolarisSkillNotFoundError",
|
|
40
|
+
"PolarisSkillRepositoryInfo",
|
|
41
|
+
"PolarisAILogOptions",
|
|
42
|
+
"PolarisAILogger",
|
|
43
|
+
"__version__",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def __getattr__(name: str):
|
|
48
|
+
if name not in __all__:
|
|
49
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
50
|
+
if name == "PolarisSkillLoader":
|
|
51
|
+
module = ".skill"
|
|
52
|
+
elif name == "PolarisAgentContext":
|
|
53
|
+
module = ".context"
|
|
54
|
+
elif name == "PolarisA2AAgent":
|
|
55
|
+
module = ".a2a"
|
|
56
|
+
elif name in ("PolarisAgentConfig",):
|
|
57
|
+
module = ".config"
|
|
58
|
+
elif name in ("PolarisAgentRuntime",):
|
|
59
|
+
module = ".runtime"
|
|
60
|
+
elif name in ("PolarisDynamicSkillMiddleware",):
|
|
61
|
+
module = ".middleware"
|
|
62
|
+
elif name in ("PolarisMCPResolver", "PolarisMCPClientPool"):
|
|
63
|
+
module = ".mcp"
|
|
64
|
+
elif name in ("PolarisAgentCardTemplate", "build_agent_card"):
|
|
65
|
+
module = ".cards"
|
|
66
|
+
elif name == "PolarisA2AServerRegistration":
|
|
67
|
+
module = "polaris_ai.a2a.server"
|
|
68
|
+
elif name in ("PolarisSkillNotFoundError", "PolarisSkillRepositoryInfo"):
|
|
69
|
+
module = ".skill"
|
|
70
|
+
elif name in ("PolarisAILogOptions", "PolarisAILogger"):
|
|
71
|
+
module = "polaris_ai.log"
|
|
72
|
+
else:
|
|
73
|
+
module = "polaris_ai.a2a"
|
|
74
|
+
value = getattr(import_module(module, __name__), name)
|
|
75
|
+
globals()[name] = value
|
|
76
|
+
return value
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python-ai available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2026 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""A resilient wrapper around AgentScope's native A2A client."""
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
from polaris.pkg.model.service import InstanceType
|
|
22
|
+
|
|
23
|
+
from polaris_ai._async import await_completion
|
|
24
|
+
from polaris_ai.log import get_ai_logger
|
|
25
|
+
from polaris_ai.metrics import PolarisCallReporter
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PolarisA2AAgent:
|
|
29
|
+
"""Resolve once, then rebuild the native A2A client only when needed.
|
|
30
|
+
|
|
31
|
+
A card is resolved before the first call. Later calls reuse the native
|
|
32
|
+
AgentScope ``A2AAgent`` unless the caller invalidates it or a network
|
|
33
|
+
failure occurs before any response event has been delivered. Business
|
|
34
|
+
errors are propagated without switching instances. The wrapper owns the
|
|
35
|
+
supplied resolver and closes it together with the current client.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, resolver, agent_name: str, *, agent_factory=None, reporter=None,
|
|
39
|
+
caller_service: str = "", labels: str = "", logger=None):
|
|
40
|
+
if resolver is None:
|
|
41
|
+
raise ValueError("A PolarisA2AResolver is required")
|
|
42
|
+
if not isinstance(agent_name, str) or not agent_name.strip():
|
|
43
|
+
raise ValueError("agent_name is required")
|
|
44
|
+
if agent_factory is None:
|
|
45
|
+
from agentscope.agent import A2AAgent
|
|
46
|
+
|
|
47
|
+
agent_factory = A2AAgent
|
|
48
|
+
self._resolver = resolver
|
|
49
|
+
self._agent_name = agent_name.strip()
|
|
50
|
+
self._agent_factory = agent_factory
|
|
51
|
+
self._reporter = reporter
|
|
52
|
+
self.logger = logger if logger is not None else get_ai_logger("a2a")
|
|
53
|
+
if self._reporter is None and getattr(resolver, "consumer", None) is not None:
|
|
54
|
+
self._reporter = PolarisCallReporter(
|
|
55
|
+
resolver.consumer,
|
|
56
|
+
getattr(resolver, "namespace", "default"),
|
|
57
|
+
caller_service=caller_service,
|
|
58
|
+
labels=labels,
|
|
59
|
+
)
|
|
60
|
+
self._agent = None
|
|
61
|
+
self._card = None
|
|
62
|
+
self._lock = asyncio.Lock()
|
|
63
|
+
self._closed = False
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def name(self) -> str:
|
|
67
|
+
return self._agent_name
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def card(self):
|
|
71
|
+
return self._card
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def agent(self):
|
|
75
|
+
return self._agent
|
|
76
|
+
|
|
77
|
+
def _check_open(self):
|
|
78
|
+
if self._closed:
|
|
79
|
+
raise RuntimeError("Polaris A2A agent is closed")
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _is_timeout(error: BaseException) -> bool:
|
|
83
|
+
if isinstance(error, (asyncio.TimeoutError, TimeoutError)):
|
|
84
|
+
return True
|
|
85
|
+
try:
|
|
86
|
+
from a2a.client.errors import A2AClientTimeoutError
|
|
87
|
+
except ImportError:
|
|
88
|
+
return False
|
|
89
|
+
return isinstance(error, A2AClientTimeoutError)
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _is_retryable(error: BaseException) -> bool:
|
|
93
|
+
if PolarisA2AAgent._is_timeout(error) or isinstance(error, OSError):
|
|
94
|
+
return True
|
|
95
|
+
try:
|
|
96
|
+
from a2a.client.errors import A2AClientTimeoutError
|
|
97
|
+
except ImportError:
|
|
98
|
+
return False
|
|
99
|
+
if isinstance(error, A2AClientTimeoutError):
|
|
100
|
+
return True
|
|
101
|
+
try:
|
|
102
|
+
from a2a.client.errors import A2AClientError
|
|
103
|
+
except ImportError:
|
|
104
|
+
return False
|
|
105
|
+
return isinstance(error, A2AClientError) and str(error).startswith("Network communication error")
|
|
106
|
+
|
|
107
|
+
async def _ensure_agent(self):
|
|
108
|
+
self._check_open()
|
|
109
|
+
if self._agent is None:
|
|
110
|
+
self.logger.debug("Resolving A2A agent client: name=%s", self._agent_name)
|
|
111
|
+
self._card = await self._resolver.async_resolve(self._agent_name)
|
|
112
|
+
self._agent = self._agent_factory(self._card)
|
|
113
|
+
await self._agent.__aenter__()
|
|
114
|
+
self.logger.debug("Created A2A agent client: name=%s", self._agent_name)
|
|
115
|
+
return self._agent
|
|
116
|
+
|
|
117
|
+
async def _close_agent(self):
|
|
118
|
+
agent = self._agent
|
|
119
|
+
if agent is None:
|
|
120
|
+
return
|
|
121
|
+
self.logger.debug("Closing A2A agent client: name=%s", self._agent_name)
|
|
122
|
+
await await_completion(agent.aclose())
|
|
123
|
+
if self._agent is agent:
|
|
124
|
+
self._agent = None
|
|
125
|
+
self._card = None
|
|
126
|
+
|
|
127
|
+
async def _replace_agent(self):
|
|
128
|
+
self.logger.warning_throttled(
|
|
129
|
+
("rebuild", self._agent_name),
|
|
130
|
+
"Rebuilding A2A agent client after network failure: name=%s",
|
|
131
|
+
self._agent_name,
|
|
132
|
+
)
|
|
133
|
+
await self._close_agent()
|
|
134
|
+
await self._ensure_agent()
|
|
135
|
+
|
|
136
|
+
async def reply(self, inputs):
|
|
137
|
+
async with self._lock:
|
|
138
|
+
self.logger.debug("Sending non-streaming A2A request: name=%s", self._agent_name)
|
|
139
|
+
agent = await self._ensure_agent()
|
|
140
|
+
started_at = time.monotonic()
|
|
141
|
+
final_message = None
|
|
142
|
+
saw_remote_event = False
|
|
143
|
+
try:
|
|
144
|
+
async for event in agent.reply_stream(inputs, yield_final_msg=True):
|
|
145
|
+
if not _is_local_reply_start_event(event):
|
|
146
|
+
saw_remote_event = True
|
|
147
|
+
if isinstance(event, _message_type()):
|
|
148
|
+
final_message = event
|
|
149
|
+
if final_message is None:
|
|
150
|
+
raise RuntimeError("A2A agent did not produce a final message")
|
|
151
|
+
await self._report_call(started_at, success=True)
|
|
152
|
+
self.logger.debug("Completed non-streaming A2A request: name=%s",
|
|
153
|
+
self._agent_name)
|
|
154
|
+
return final_message
|
|
155
|
+
except BaseException as error:
|
|
156
|
+
await self._report_call(
|
|
157
|
+
started_at, success=False,
|
|
158
|
+
timeout=self._is_timeout(error),
|
|
159
|
+
)
|
|
160
|
+
if saw_remote_event or not self._is_retryable(error):
|
|
161
|
+
self.logger.debug(
|
|
162
|
+
"A2A request failed without retry: name=%s error=%r",
|
|
163
|
+
self._agent_name, error,
|
|
164
|
+
)
|
|
165
|
+
raise
|
|
166
|
+
await self._replace_agent()
|
|
167
|
+
retry_started = time.monotonic()
|
|
168
|
+
try:
|
|
169
|
+
self.logger.debug("Retrying A2A request: name=%s", self._agent_name)
|
|
170
|
+
reply = await self._agent.reply(inputs)
|
|
171
|
+
except BaseException as retry_error:
|
|
172
|
+
await self._report_call(
|
|
173
|
+
retry_started, success=False,
|
|
174
|
+
timeout=self._is_timeout(retry_error),
|
|
175
|
+
)
|
|
176
|
+
raise
|
|
177
|
+
await self._report_call(retry_started, success=True)
|
|
178
|
+
self.logger.debug("Retried A2A request succeeded: name=%s", self._agent_name)
|
|
179
|
+
return reply
|
|
180
|
+
|
|
181
|
+
async def reply_stream(self, inputs, *, yield_final_msg: bool = False):
|
|
182
|
+
async with self._lock:
|
|
183
|
+
self.logger.debug("Sending streaming A2A request: name=%s", self._agent_name)
|
|
184
|
+
agent = await self._ensure_agent()
|
|
185
|
+
started_at = time.monotonic()
|
|
186
|
+
yielded = False
|
|
187
|
+
succeeded = False
|
|
188
|
+
try:
|
|
189
|
+
async for event in agent.reply_stream(inputs, yield_final_msg=yield_final_msg):
|
|
190
|
+
yielded = True
|
|
191
|
+
yield event
|
|
192
|
+
succeeded = True
|
|
193
|
+
except BaseException as error:
|
|
194
|
+
await self._report_call(
|
|
195
|
+
started_at, success=False,
|
|
196
|
+
timeout=self._is_timeout(error),
|
|
197
|
+
)
|
|
198
|
+
if yielded or not self._is_retryable(error):
|
|
199
|
+
self.logger.debug(
|
|
200
|
+
"A2A stream failed without retry: name=%s yielded=%s error=%r",
|
|
201
|
+
self._agent_name, yielded, error,
|
|
202
|
+
)
|
|
203
|
+
raise
|
|
204
|
+
await self._replace_agent()
|
|
205
|
+
async for event in self._agent.reply_stream(inputs, yield_final_msg=yield_final_msg):
|
|
206
|
+
yield event
|
|
207
|
+
succeeded = True
|
|
208
|
+
finally:
|
|
209
|
+
if succeeded:
|
|
210
|
+
await self._report_call(started_at, success=True)
|
|
211
|
+
self.logger.debug("Completed streaming A2A request: name=%s",
|
|
212
|
+
self._agent_name)
|
|
213
|
+
|
|
214
|
+
async def _report_call(self, started_at, *, success, timeout=False):
|
|
215
|
+
if self._reporter is None:
|
|
216
|
+
return
|
|
217
|
+
endpoint = self._resolver.selected_endpoint(self._agent_name)
|
|
218
|
+
if endpoint is None:
|
|
219
|
+
return
|
|
220
|
+
host, port = endpoint
|
|
221
|
+
await self._reporter.report(
|
|
222
|
+
service=self._agent_name,
|
|
223
|
+
host=host,
|
|
224
|
+
port=port,
|
|
225
|
+
method="message/send",
|
|
226
|
+
instance_type=InstanceType.A2A,
|
|
227
|
+
started_at=started_at,
|
|
228
|
+
success=success,
|
|
229
|
+
timeout=timeout,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
async def observe(self, msgs):
|
|
233
|
+
async with self._lock:
|
|
234
|
+
agent = await self._ensure_agent()
|
|
235
|
+
await agent.observe(msgs)
|
|
236
|
+
|
|
237
|
+
async def invalidate(self):
|
|
238
|
+
"""Close the current client and make the next call resolve again."""
|
|
239
|
+
async with self._lock:
|
|
240
|
+
self._check_open()
|
|
241
|
+
self.logger.debug("Invalidating A2A agent client: name=%s", self._agent_name)
|
|
242
|
+
await self._close_agent()
|
|
243
|
+
self._resolver.invalidate(self._agent_name)
|
|
244
|
+
|
|
245
|
+
async def aclose(self):
|
|
246
|
+
self._closed = True
|
|
247
|
+
self.logger.debug("Closing A2A agent wrapper: name=%s", self._agent_name)
|
|
248
|
+
await await_completion(self._close_all())
|
|
249
|
+
|
|
250
|
+
async def _close_all(self):
|
|
251
|
+
async with self._lock:
|
|
252
|
+
try:
|
|
253
|
+
await self._close_agent()
|
|
254
|
+
finally:
|
|
255
|
+
await self._resolver.async_close()
|
|
256
|
+
|
|
257
|
+
async def __aenter__(self):
|
|
258
|
+
self._check_open()
|
|
259
|
+
return self
|
|
260
|
+
|
|
261
|
+
async def __aexit__(self, *_):
|
|
262
|
+
await self.aclose()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _message_type():
|
|
266
|
+
from agentscope.message import Msg
|
|
267
|
+
|
|
268
|
+
return Msg
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _is_local_reply_start_event(event):
|
|
272
|
+
try:
|
|
273
|
+
from agentscope.event import ReplyStartEvent
|
|
274
|
+
except ImportError:
|
|
275
|
+
return False
|
|
276
|
+
return isinstance(event, ReplyStartEvent)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python-ai available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2026 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""Reusable native AgentCard construction for AgentScope applications."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from collections.abc import Sequence
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from a2a.types import (
|
|
25
|
+
AgentCapabilities,
|
|
26
|
+
AgentCard,
|
|
27
|
+
AgentInterface,
|
|
28
|
+
AgentSkill,
|
|
29
|
+
)
|
|
30
|
+
except ImportError as error:
|
|
31
|
+
raise ImportError(
|
|
32
|
+
"AgentCard construction requires pip install 'polaris-python-ai[a2a]'."
|
|
33
|
+
) from error
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _agent_skill(skill) -> AgentSkill:
|
|
37
|
+
if isinstance(skill, AgentSkill):
|
|
38
|
+
return skill
|
|
39
|
+
name = getattr(skill, "name", "")
|
|
40
|
+
description = getattr(skill, "description", "")
|
|
41
|
+
if not name:
|
|
42
|
+
raise ValueError("Agent Skill requires a name")
|
|
43
|
+
return AgentSkill(id=name, name=name, description=description, tags=["polaris"])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build_agent_card(
|
|
47
|
+
*,
|
|
48
|
+
name: str,
|
|
49
|
+
url: str,
|
|
50
|
+
description: str = "",
|
|
51
|
+
version: str = "1.0.0",
|
|
52
|
+
protocol_binding: str = "JSONRPC",
|
|
53
|
+
protocol_version: str = "1.0",
|
|
54
|
+
skills: Sequence = (),
|
|
55
|
+
input_modes: Sequence[str] = ("text/plain",),
|
|
56
|
+
output_modes: Sequence[str] = ("text/plain",),
|
|
57
|
+
streaming: bool = True,
|
|
58
|
+
) -> AgentCard:
|
|
59
|
+
"""Build a native A2A card while preserving framework-owned types."""
|
|
60
|
+
if not isinstance(name, str) or not name.strip():
|
|
61
|
+
raise ValueError("Agent name is required")
|
|
62
|
+
if not isinstance(url, str) or not url.strip():
|
|
63
|
+
raise ValueError("Agent URL is required")
|
|
64
|
+
return AgentCard(
|
|
65
|
+
name=name.strip(),
|
|
66
|
+
description=description,
|
|
67
|
+
version=version,
|
|
68
|
+
supported_interfaces=[AgentInterface(
|
|
69
|
+
url=url,
|
|
70
|
+
protocol_binding=protocol_binding,
|
|
71
|
+
protocol_version=protocol_version,
|
|
72
|
+
)],
|
|
73
|
+
capabilities=AgentCapabilities(streaming=streaming),
|
|
74
|
+
default_input_modes=list(input_modes),
|
|
75
|
+
default_output_modes=list(output_modes),
|
|
76
|
+
skills=[_agent_skill(skill) for skill in skills],
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True)
|
|
81
|
+
class PolarisAgentCardTemplate:
|
|
82
|
+
"""Reusable card defaults; only endpoint and runtime Skills vary per Agent."""
|
|
83
|
+
|
|
84
|
+
description: str = ""
|
|
85
|
+
version: str = "1.0.0"
|
|
86
|
+
protocol_binding: str = "JSONRPC"
|
|
87
|
+
protocol_version: str = "1.0"
|
|
88
|
+
input_modes: tuple[str, ...] = ("text/plain",)
|
|
89
|
+
output_modes: tuple[str, ...] = ("text/plain",)
|
|
90
|
+
streaming: bool = True
|
|
91
|
+
skills: tuple = field(default_factory=tuple)
|
|
92
|
+
|
|
93
|
+
def build(self, name: str, url: str, *, skills: Sequence = ()) -> AgentCard:
|
|
94
|
+
selected = tuple(skills) if skills else self.skills
|
|
95
|
+
return build_agent_card(
|
|
96
|
+
name=name,
|
|
97
|
+
url=url,
|
|
98
|
+
description=self.description,
|
|
99
|
+
version=self.version,
|
|
100
|
+
protocol_binding=self.protocol_binding,
|
|
101
|
+
protocol_version=self.protocol_version,
|
|
102
|
+
skills=selected,
|
|
103
|
+
input_modes=self.input_modes,
|
|
104
|
+
output_modes=self.output_modes,
|
|
105
|
+
streaming=self.streaming,
|
|
106
|
+
)
|