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.
@@ -0,0 +1,291 @@
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
+ """Compose the Polaris AgentScope adapters from application options."""
17
+
18
+ from polaris.api.log import LogOptions
19
+
20
+ from polaris_ai._async import await_completion
21
+ from polaris_ai.a2a import PolarisA2ARegistrar, PolarisA2AResolver
22
+ from polaris_ai.agentscope.config import PolarisAgentConfig
23
+ from polaris_ai.agentscope.context import PolarisAgentContext
24
+ from polaris_ai.agentscope.middleware import (
25
+ PolarisDynamicSkillMiddleware,
26
+ get_shared_snapshot_loader,
27
+ )
28
+ from polaris_ai.agentscope.skill import PolarisSkillLoader
29
+ from polaris_ai.log import PolarisAILogOptions, configure_ai_logging
30
+
31
+
32
+ class PolarisAgentRuntime:
33
+ """Own configured adapters while preserving borrowed-context semantics."""
34
+
35
+ def __init__(
36
+ self,
37
+ config: PolarisAgentConfig,
38
+ *,
39
+ sdk_context=None,
40
+ log_options: LogOptions | None = None,
41
+ ai_log_options: PolarisAILogOptions | None = None,
42
+ ):
43
+ if not isinstance(config, PolarisAgentConfig):
44
+ raise TypeError("config must be a PolarisAgentConfig")
45
+ if not config.enabled:
46
+ raise ValueError("Polaris AgentScope integration is disabled")
47
+ self.config = config
48
+ self.context = None
49
+ self.ai_logger = None
50
+ self.skill_loader: PolarisSkillLoader | None = None
51
+ self.resolver: PolarisA2AResolver | None = None
52
+ self.registrar: PolarisA2ARegistrar | None = None
53
+ self.mcp_resolver = None
54
+ self._skill_toolkit = None
55
+ self._closed = False
56
+ try:
57
+ self.context = PolarisAgentContext(
58
+ config.namespace,
59
+ address=config.addresses,
60
+ connector_token=config.connector_token,
61
+ skill_address=config.skill.addresses,
62
+ log_options=log_options,
63
+ sdk_context=sdk_context,
64
+ )
65
+ if ai_log_options is not None:
66
+ client_id_getter = getattr(self.context.context, "get_client_id", None)
67
+ client_id = (
68
+ client_id_getter() if callable(client_id_getter)
69
+ else f"runtime-{id(self.context.context)}"
70
+ )
71
+ self.ai_logger = configure_ai_logging(client_id, ai_log_options)
72
+ if config.skill.enabled:
73
+ self.skill_loader = PolarisSkillLoader(
74
+ self.context.context,
75
+ config.namespace,
76
+ service_name=(
77
+ config.skill.mounted_service_name if config.skill.mounted_enabled else ""
78
+ ),
79
+ names=config.skill.names,
80
+ version=config.skill.version,
81
+ list_limit=config.skill.list_limit,
82
+ max_skills=config.skill.max_skills,
83
+ refresh_interval=config.skill.refresh_interval,
84
+ logger=self._category_logger("skill"),
85
+ )
86
+ if config.a2a.enabled and config.a2a.discovery.enabled:
87
+ self.resolver = PolarisA2AResolver(
88
+ self.context.context, config.namespace,
89
+ logger=self._category_logger("a2a"),
90
+ )
91
+ if config.a2a.enabled and config.a2a.registry.enabled:
92
+ self.registrar = PolarisA2ARegistrar(
93
+ self.context.context,
94
+ config.namespace,
95
+ service_token=config.service_token,
96
+ ttl=config.a2a.registry.ttl,
97
+ logger=self._category_logger("a2a"),
98
+ )
99
+ if config.mcp.enabled:
100
+ from polaris_ai.agentscope.mcp import PolarisMCPResolver
101
+
102
+ self.mcp_resolver = PolarisMCPResolver(
103
+ self.context.context,
104
+ config.namespace,
105
+ caller_service=config.mcp.caller_service,
106
+ labels=config.mcp.labels,
107
+ protocol_version=config.mcp.protocol_version,
108
+ scheme=config.mcp.scheme,
109
+ timeout=config.mcp.timeout,
110
+ filter_contract=config.mcp.filter_contract,
111
+ headers=dict(config.mcp.headers),
112
+ logger=self._category_logger("mcp"),
113
+ metrics_logger=self._category_logger("metrics"),
114
+ )
115
+ if self.ai_logger is not None:
116
+ self.ai_logger.get_logger("base").info(
117
+ "Initialized Polaris AI runtime: namespace=%s skill=%s "
118
+ "a2aRegistry=%s a2aDiscovery=%s mcp=%s",
119
+ config.namespace, config.skill.enabled,
120
+ config.a2a.enabled and config.a2a.registry.enabled,
121
+ config.a2a.enabled and config.a2a.discovery.enabled,
122
+ config.mcp.enabled,
123
+ )
124
+ self.ai_logger.get_logger("base").debug(
125
+ "Polaris AI runtime settings: addresses=%s skillMounted=%s "
126
+ "skillNames=%d mcpScheme=%s",
127
+ config.addresses, config.skill.mounted_enabled,
128
+ len(config.skill.names), config.mcp.scheme or "<instance>",
129
+ )
130
+ except BaseException:
131
+ self.close()
132
+ raise
133
+
134
+ def _category_logger(self, category):
135
+ return self.ai_logger.get_logger(category) if self.ai_logger is not None else None
136
+
137
+ def _log_close_start(self):
138
+ ai_logger = getattr(self, "ai_logger", None)
139
+ if ai_logger is None:
140
+ return
141
+ ai_logger.get_logger("base").info(
142
+ "Closing Polaris AI runtime: namespace=%s", self.config.namespace,
143
+ )
144
+
145
+ @property
146
+ def provider(self):
147
+ return self.context.provider
148
+
149
+ @property
150
+ def consumer(self):
151
+ return self.context.consumer
152
+
153
+ @property
154
+ def skill(self):
155
+ return self.context.skill
156
+
157
+ @property
158
+ def skill_toolkit(self):
159
+ """Build the Toolkit selected by ``skill.attach_to_agent``."""
160
+ self._check_open()
161
+ if (not self.config.skill.enabled or not self.config.skill.attach_to_agent
162
+ or self.skill_loader is None):
163
+ return None
164
+ if self._skill_toolkit is None:
165
+ from agentscope.tool import Toolkit
166
+
167
+ self._skill_toolkit = Toolkit(skills_or_loaders=[self.skill_loader])
168
+ return self._skill_toolkit
169
+
170
+ def _attach_skill_loader(self, toolkit):
171
+ for group in toolkit.tool_groups:
172
+ if group.name == "basic":
173
+ snapshot = get_shared_snapshot_loader(self.skill_loader)
174
+ if snapshot is not None:
175
+ normalized = []
176
+ snapshot_present = False
177
+ for item in group.skills_or_loaders:
178
+ if item is self.skill_loader:
179
+ continue
180
+ if item is snapshot:
181
+ if snapshot_present:
182
+ continue
183
+ snapshot_present = True
184
+ normalized.append(item)
185
+ if not snapshot_present:
186
+ normalized.append(snapshot)
187
+ group.skills_or_loaders[:] = normalized
188
+ return
189
+ if self.skill_loader not in group.skills_or_loaders:
190
+ group.skills_or_loaders.append(self.skill_loader)
191
+ return
192
+ raise ValueError("Toolkit has no basic tool group")
193
+
194
+ def create_agent(self, *, name: str, system_prompt: str, model, toolkit=None,
195
+ middlewares=(), **kwargs):
196
+ """Create a native Agent with the configured Polaris Skill attachment."""
197
+ self._check_open()
198
+ from agentscope.agent import Agent
199
+
200
+ selected_toolkit = toolkit
201
+ selected_middlewares = list(middlewares)
202
+ if (self.config.skill.enabled and self.config.skill.attach_to_agent
203
+ and self.skill_loader is not None):
204
+ if selected_toolkit is None:
205
+ selected_toolkit = self.skill_toolkit
206
+ else:
207
+ self._attach_skill_loader(selected_toolkit)
208
+ selected_middlewares.append(PolarisDynamicSkillMiddleware(
209
+ self.skill_loader,
210
+ logger=self._category_logger("agentscope"),
211
+ ))
212
+ return Agent(
213
+ name=name,
214
+ system_prompt=system_prompt,
215
+ model=model,
216
+ toolkit=selected_toolkit,
217
+ middlewares=selected_middlewares,
218
+ **kwargs,
219
+ )
220
+
221
+ def _check_open(self):
222
+ if getattr(self, "_closed", True):
223
+ raise RuntimeError("PolarisAgentRuntime is closed")
224
+
225
+ async def _close_adapters(self):
226
+ if self.registrar is not None:
227
+ await self.registrar.async_close()
228
+ if self.resolver is not None:
229
+ await self.resolver.async_close()
230
+ if self.skill_loader is not None:
231
+ await self.skill_loader.async_close()
232
+
233
+ def _close_adapters_sync(self):
234
+ if self.registrar is not None:
235
+ self.registrar.close()
236
+ if self.resolver is not None:
237
+ self.resolver.close()
238
+ if self.skill_loader is not None:
239
+ self.skill_loader.close()
240
+
241
+ async def _close_all_async(self):
242
+ try:
243
+ await self._close_adapters()
244
+ finally:
245
+ try:
246
+ context = getattr(self, "context", None)
247
+ if context is not None:
248
+ await context.async_close()
249
+ finally:
250
+ ai_logger = getattr(self, "ai_logger", None)
251
+ if ai_logger is not None:
252
+ await ai_logger.async_close()
253
+
254
+ def close(self):
255
+ """Close adapters, then the shared context only if this runtime owns it."""
256
+ if getattr(self, "_closed", True):
257
+ return
258
+ self._log_close_start()
259
+ self._closed = True
260
+ try:
261
+ self._close_adapters_sync()
262
+ finally:
263
+ try:
264
+ context = getattr(self, "context", None)
265
+ if context is not None:
266
+ context.close()
267
+ finally:
268
+ ai_logger = getattr(self, "ai_logger", None)
269
+ if ai_logger is not None:
270
+ ai_logger.close()
271
+
272
+ async def async_close(self):
273
+ if getattr(self, "_closed", True):
274
+ return
275
+ self._log_close_start()
276
+ self._closed = True
277
+ await await_completion(self._close_all_async())
278
+
279
+ def __enter__(self):
280
+ self._check_open()
281
+ return self
282
+
283
+ def __exit__(self, *_):
284
+ self.close()
285
+
286
+ async def __aenter__(self):
287
+ self._check_open()
288
+ return self
289
+
290
+ async def __aexit__(self, *_):
291
+ await self.async_close()