Skip to content

Commit cbc8fe9

Browse files
committed
refactor(super_agent): Apply Dara SDK patches lazily and improve type hints
This change refactors the application of monkey patches to the Dara SDK models and clients, applying them only when needed rather than at module load time. It also improves type hint accuracy for several parameters in both synchronous and asynchronous client methods. The key changes include: - Introducing `ensure_super_agent_patches_applied()` function that applies all necessary patches lazily - Moving placeholder image definition into its own constant - Updating docstrings for clarity - Improving parameter types across multiple client method signatures Co-developed-by: Aone Copilot <noreply@alibaba-inc.com> Signed-off-by: Sodawyx <sodawyx@126.com>
1 parent d796360 commit cbc8fe9

5 files changed

Lines changed: 110 additions & 74 deletions

File tree

agentrun/super_agent/__client_async_template.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from agentrun.agent_runtime.runtime import AgentRuntime
2323
from agentrun.super_agent.agent import SuperAgent
2424
from agentrun.super_agent.api.control import (
25+
ensure_super_agent_patches_applied,
2526
from_agent_runtime,
2627
is_super_agent,
2728
SUPER_AGENT_TAG,
@@ -86,6 +87,9 @@ class SuperAgentClient:
8687
"""Super Agent CRUDL 客户端."""
8788

8889
def __init__(self, config: Optional[Config] = None) -> None:
90+
# 按需打 Dara SDK 兼容补丁 (幂等)。放在本构造函数里, 让 "仅 import
91+
# agentrun.super_agent" 的调用方不被动承担全局 SDK 副作用。
92+
ensure_super_agent_patches_applied()
8993
self.config = config
9094
self._rt = AgentRuntimeClient(config=config)
9195
# create/update 绕过 AgentRuntimeClient 的 artifact_type 校验 (SUPER_AGENT 不需要 code/container),
@@ -302,19 +306,22 @@ def get(self, name: str, *, config: Optional[Config] = None) -> SuperAgent:
302306
return agent
303307

304308
# ─── Update (read-merge-write) ─────────────────────
309+
# 参数默认值 ``_UNSET`` 是内部哨兵 (object())。为保留 IDE 自动补全与 mypy
310+
# 类型检查, 签名保持精确类型标注, 对 ``= _UNSET`` 的赋值加 ``type: ignore``。
311+
# 未传 = 保持不变, 显式传 None = 清空字段。
305312
async def update_async(
306313
self,
307314
name: str,
308315
*,
309-
description: Any = _UNSET,
310-
prompt: Any = _UNSET,
311-
agents: Any = _UNSET,
312-
tools: Any = _UNSET,
313-
skills: Any = _UNSET,
314-
sandboxes: Any = _UNSET,
315-
workspaces: Any = _UNSET,
316-
model_service_name: Any = _UNSET,
317-
model_name: Any = _UNSET,
316+
description: Optional[str] = _UNSET, # type: ignore[assignment]
317+
prompt: Optional[str] = _UNSET, # type: ignore[assignment]
318+
agents: Optional[List[str]] = _UNSET, # type: ignore[assignment]
319+
tools: Optional[List[str]] = _UNSET, # type: ignore[assignment]
320+
skills: Optional[List[str]] = _UNSET, # type: ignore[assignment]
321+
sandboxes: Optional[List[str]] = _UNSET, # type: ignore[assignment]
322+
workspaces: Optional[List[str]] = _UNSET, # type: ignore[assignment]
323+
model_service_name: Optional[str] = _UNSET, # type: ignore[assignment]
324+
model_name: Optional[str] = _UNSET, # type: ignore[assignment]
318325
config: Optional[Config] = None,
319326
) -> SuperAgent:
320327
"""异步更新超级 Agent (read-merge-write)."""
@@ -356,15 +363,15 @@ def update(
356363
self,
357364
name: str,
358365
*,
359-
description: Any = _UNSET,
360-
prompt: Any = _UNSET,
361-
agents: Any = _UNSET,
362-
tools: Any = _UNSET,
363-
skills: Any = _UNSET,
364-
sandboxes: Any = _UNSET,
365-
workspaces: Any = _UNSET,
366-
model_service_name: Any = _UNSET,
367-
model_name: Any = _UNSET,
366+
description: Optional[str] = _UNSET, # type: ignore[assignment]
367+
prompt: Optional[str] = _UNSET, # type: ignore[assignment]
368+
agents: Optional[List[str]] = _UNSET, # type: ignore[assignment]
369+
tools: Optional[List[str]] = _UNSET, # type: ignore[assignment]
370+
skills: Optional[List[str]] = _UNSET, # type: ignore[assignment]
371+
sandboxes: Optional[List[str]] = _UNSET, # type: ignore[assignment]
372+
workspaces: Optional[List[str]] = _UNSET, # type: ignore[assignment]
373+
model_service_name: Optional[str] = _UNSET, # type: ignore[assignment]
374+
model_name: Optional[str] = _UNSET, # type: ignore[assignment]
368375
config: Optional[Config] = None,
369376
) -> SuperAgent:
370377
"""同步更新超级 Agent (read-merge-write)."""

agentrun/super_agent/agui.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
from __future__ import annotations
2525

26-
from typing import AsyncIterator, Dict, Literal, Optional, Type
26+
from typing import AsyncGenerator, Dict, Literal, Optional, Type
2727

2828
from ag_ui.core import (
2929
BaseEvent,
@@ -113,7 +113,7 @@ async def as_agui_events(
113113
stream: InvokeStream,
114114
*,
115115
on_unknown: UnknownMode = "raise",
116-
) -> AsyncIterator[BaseEvent]:
116+
) -> AsyncGenerator[BaseEvent, None]:
117117
"""把 :class:`InvokeStream` 中的原始 :class:`SSEEvent` 解码为强类型流.
118118
119119
无论正常消费结束、中途异常、解码异常, 都保证 ``await stream.aclose()`` 被调用

agentrun/super_agent/api/control.py

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
from __future__ import annotations
1515

1616
import json
17-
from typing import Any, Dict, List, Optional
17+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
1818
from urllib.parse import urlparse, urlunparse
1919

20+
if TYPE_CHECKING:
21+
from agentrun.super_agent.agent import SuperAgent
22+
2023
from alibabacloud_agentrun20250910.client import Client as _DaraClient
2124
from alibabacloud_agentrun20250910.models import (
2225
CreateAgentRuntimeInput as _DaraCreateAgentRuntimeInput,
@@ -60,6 +63,12 @@
6063

6164
_RAM_DATA_DOMAINS = ("agentrun-data", "funagent-data-pre")
6265

66+
# SUPER_AGENT 不跑用户 container/code, 但服务端强制要求 artifact/container_configuration 非空,
67+
# 这里给一个占位镜像地址即可。region 取杭州仅为了格式合法, 服务端不会实际 pull。
68+
_PLACEHOLDER_IMAGE = (
69+
"registry.cn-hangzhou.aliyuncs.com/agentrun/super-agent-placeholder:v1"
70+
)
71+
6372

6473
# ─── URL 工具 ──────────────────────────────────────────
6574

@@ -143,14 +152,23 @@ def model_dump(self, **kwargs: Any) -> Dict[str, Any]:
143152
return super().model_dump(**kwargs)
144153

145154

146-
# ─── Dara 模型猴补丁 ──────────────────────────────────────
147-
# Dara 的 ``ProtocolConfiguration`` 当前版本没有 ``externalEndpoint`` 字段;
148-
# ``AgentRuntimeClient.create_async/update_async`` 内部做
149-
# ``CreateAgentRuntimeInput().from_map(pydantic.model_dump())`` 的 roundtrip,
150-
# 会在 Dara 层丢失此字段。这里做一次加性 patch: 仅追加读写 ``externalEndpoint``,
151-
# 不改变任何现有字段行为, 用模块级哨兵属性保证幂等。
155+
# ─── Dara 模型/客户端猴补丁 ──────────────────────────────────────
156+
# 当前版 Dara SDK 缺 ``ProtocolConfiguration.externalEndpoint`` 和
157+
# ``CreateAgentRuntimeInput/UpdateAgentRuntimeInput/ListAgentRuntimesRequest.tags``
158+
# 字段, 会在 Pydantic ↔ Dara ``from_map / to_map`` roundtrip 中静默丢失; 且
159+
# ``Client.list_agent_runtimes_with_options{,_async}`` 不会把 ``tags`` 写到 query。
160+
#
161+
# 所有补丁都延迟到 ``SuperAgentClient`` 实例化时 (见
162+
# ``ensure_super_agent_patches_applied``) 才触发, 避免仅 import 本模块的调用方
163+
# 被动承担全局副作用。补丁本身用哨兵属性保证幂等, 重复调用安全。
164+
# TODO: 等 Dara SDK 原生支持后删除。
165+
166+
167+
def _patch_dara_protocol_configuration() -> None:
168+
"""补齐 ``ProtocolConfiguration.externalEndpoint`` 的 from_map/to_map 读写."""
169+
if getattr(_DaraProtocolConfiguration, "_super_agent_patched", False):
170+
return
152171

153-
if not getattr(_DaraProtocolConfiguration, "_super_agent_patched", False):
154172
_orig_to_map = _DaraProtocolConfiguration.to_map
155173
_orig_from_map = _DaraProtocolConfiguration.from_map
156174

@@ -174,10 +192,8 @@ def _patched_from_map(
174192
_DaraProtocolConfiguration._super_agent_patched = True # type: ignore[attr-defined]
175193

176194

177-
# Dara 的 ``CreateAgentRuntimeInput`` / ``UpdateAgentRuntimeInput`` 当前版本没有
178-
# ``tags`` 字段, 与 ``ProtocolConfiguration`` 同理会在 Pydantic → Dara 的 roundtrip
179-
# 中被静默丢弃. 这里沿用同款加性 patch, 只补齐 ``tags`` 字段的读写.
180195
def _patch_dara_tags(cls: Any) -> None:
196+
"""给 Dara model 补齐 ``tags`` 字段的 from_map/to_map 读写."""
181197
if getattr(cls, "_super_agent_tags_patched", False):
182198
return
183199
_orig_to_map = cls.to_map
@@ -201,22 +217,6 @@ def _patched_from_map(self: Any, m: Optional[Dict[str, Any]] = None) -> Any:
201217
cls._super_agent_tags_patched = True # type: ignore[attr-defined]
202218

203219

204-
_patch_dara_tags(_DaraCreateAgentRuntimeInput)
205-
_patch_dara_tags(_DaraUpdateAgentRuntimeInput)
206-
# ``ListAgentRuntimesRequest`` 同样没有 ``tags`` 字段: 补上 from_map/to_map 以保留
207-
# 属性; 真正让服务端生效的查询参数注入由下面的 client 级补丁完成。
208-
_patch_dara_tags(_DaraListAgentRuntimesRequest)
209-
210-
211-
# ─── Dara 客户端猴补丁: list 请求 query 注入 tags ───────────────
212-
# 现版 Dara ``Client.list_agent_runtimes_with_options`` 不读 ``request.tags``
213-
# 构造 query, 导致即便 Pydantic 侧把 tags 传下来, 服务端也收不到。这里一次性
214-
# 包裹同步 / 异步两个方法: 若 request 带有 ``tags`` 就在底层 ``call_api`` 调用
215-
# 前把 ``tags`` (列表 → 逗号分隔) 追加到 ``req.query``。
216-
# 每个 API 调用都会 ``_get_client()`` 新建 ``Client`` 实例, 实例属性级别的替换
217-
# 在并发下是安全的。
218-
219-
220220
def _tags_query_value(tags: Any) -> Optional[str]:
221221
if tags is None:
222222
return None
@@ -228,6 +228,13 @@ def _tags_query_value(tags: Any) -> Optional[str]:
228228

229229

230230
def _patch_dara_client_list_tags() -> None:
231+
"""包裹 ``Client.list_agent_runtimes_with_options{,_async}``: 若 request 带
232+
``tags`` 就在底层 ``call_api`` 调用前把 ``tags`` (列表 → 逗号分隔) 追加到
233+
``req.query``。
234+
235+
每次 API 调用由 ``_get_client()`` 新建 ``Client`` 实例, 实例属性级别的
236+
``self.call_api = _injecting`` 替换在并发下是安全的。
237+
"""
231238
if getattr(_DaraClient, "_super_agent_list_tags_patched", False):
232239
return
233240

@@ -285,7 +292,20 @@ async def _injecting(params: Any, req: Any, rt: Any) -> Any:
285292
_DaraClient._super_agent_list_tags_patched = True # type: ignore[attr-defined]
286293

287294

288-
_patch_dara_client_list_tags()
295+
def ensure_super_agent_patches_applied() -> None:
296+
"""按需应用全部 Dara SDK 兼容补丁 (幂等)。
297+
298+
由 ``SuperAgentClient.__init__`` 调用。如果调用方直接使用
299+
``to_create_input`` / ``to_update_input`` 并自己构造 ``CreateAgentRuntimeInput``
300+
/ ``ListAgentRuntimesRequest``, 也应在 Pydantic → Dara 转换前调用一次本函数。
301+
"""
302+
_patch_dara_protocol_configuration()
303+
_patch_dara_tags(_DaraCreateAgentRuntimeInput)
304+
_patch_dara_tags(_DaraUpdateAgentRuntimeInput)
305+
# ``ListAgentRuntimesRequest`` 补齐 from_map/to_map 保留属性; 真正让服务端
306+
# 生效的 query 注入由 ``_patch_dara_client_list_tags`` 完成。
307+
_patch_dara_tags(_DaraListAgentRuntimesRequest)
308+
_patch_dara_client_list_tags()
289309

290310

291311
# ─── AgentRuntime ↔ SuperAgent 转换 ────────────────────────
@@ -392,9 +412,7 @@ def to_create_input(
392412
external_agent_endpoint_url=build_super_agent_endpoint(cfg),
393413
# 占位 artifact: SUPER_AGENT 不跑用户 container/code, 但服务端要求非空。
394414
artifact_type=AgentRuntimeArtifact.CONTAINER,
395-
container_configuration=AgentRuntimeContainer(
396-
image="registry.cn-hangzhou.aliyuncs.com/agentrun/super-agent-placeholder:v1"
397-
),
415+
container_configuration=AgentRuntimeContainer(image=_PLACEHOLDER_IMAGE),
398416
network_configuration=NetworkConfig(network_mode=NetworkMode.PUBLIC),
399417
)
400418

@@ -425,9 +443,7 @@ def to_update_input(
425443
external_agent_endpoint_url=build_super_agent_endpoint(cfg),
426444
# 占位 artifact: SUPER_AGENT 不跑用户 container/code, 但服务端要求非空。
427445
artifact_type=AgentRuntimeArtifact.CONTAINER,
428-
container_configuration=AgentRuntimeContainer(
429-
image="registry.cn-hangzhou.aliyuncs.com/agentrun/super-agent-placeholder:v1"
430-
),
446+
container_configuration=AgentRuntimeContainer(image=_PLACEHOLDER_IMAGE),
431447
network_configuration=NetworkConfig(network_mode=NetworkMode.PUBLIC),
432448
)
433449

@@ -502,7 +518,7 @@ def _get_external_endpoint(rt: AgentRuntime) -> str:
502518
)
503519

504520

505-
def from_agent_runtime(rt: AgentRuntime) -> "SuperAgent": # noqa: F821
521+
def from_agent_runtime(rt: AgentRuntime) -> "SuperAgent":
506522
"""反解 AgentRuntime → SuperAgent 实例 (不注入 ``_client``)."""
507523
# 延迟导入避免循环
508524
from agentrun.super_agent.agent import SuperAgent
@@ -547,4 +563,5 @@ def from_agent_runtime(rt: AgentRuntime) -> "SuperAgent": # noqa: F821
547563
"from_agent_runtime",
548564
"is_super_agent",
549565
"parse_super_agent_config",
566+
"ensure_super_agent_patches_applied",
550567
]

agentrun/super_agent/client.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from agentrun.agent_runtime.runtime import AgentRuntime
3333
from agentrun.super_agent.agent import SuperAgent
3434
from agentrun.super_agent.api.control import (
35+
ensure_super_agent_patches_applied,
3536
from_agent_runtime,
3637
is_super_agent,
3738
SUPER_AGENT_TAG,
@@ -96,6 +97,9 @@ class SuperAgentClient:
9697
"""Super Agent CRUDL 客户端."""
9798

9899
def __init__(self, config: Optional[Config] = None) -> None:
100+
# 按需打 Dara SDK 兼容补丁 (幂等)。放在本构造函数里, 让 "仅 import
101+
# agentrun.super_agent" 的调用方不被动承担全局 SDK 副作用。
102+
ensure_super_agent_patches_applied()
99103
self.config = config
100104
self._rt = AgentRuntimeClient(config=config)
101105
# create/update 绕过 AgentRuntimeClient 的 artifact_type 校验 (SUPER_AGENT 不需要 code/container),
@@ -312,19 +316,22 @@ def get(self, name: str, *, config: Optional[Config] = None) -> SuperAgent:
312316
return agent
313317

314318
# ─── Update (read-merge-write) ─────────────────────
319+
# 参数默认值 ``_UNSET`` 是内部哨兵 (object())。为保留 IDE 自动补全与 mypy
320+
# 类型检查, 签名保持精确类型标注, 对 ``= _UNSET`` 的赋值加 ``type: ignore``。
321+
# 未传 = 保持不变, 显式传 None = 清空字段。
315322
async def update_async(
316323
self,
317324
name: str,
318325
*,
319-
description: Any = _UNSET,
320-
prompt: Any = _UNSET,
321-
agents: Any = _UNSET,
322-
tools: Any = _UNSET,
323-
skills: Any = _UNSET,
324-
sandboxes: Any = _UNSET,
325-
workspaces: Any = _UNSET,
326-
model_service_name: Any = _UNSET,
327-
model_name: Any = _UNSET,
326+
description: Optional[str] = _UNSET, # type: ignore[assignment]
327+
prompt: Optional[str] = _UNSET, # type: ignore[assignment]
328+
agents: Optional[List[str]] = _UNSET, # type: ignore[assignment]
329+
tools: Optional[List[str]] = _UNSET, # type: ignore[assignment]
330+
skills: Optional[List[str]] = _UNSET, # type: ignore[assignment]
331+
sandboxes: Optional[List[str]] = _UNSET, # type: ignore[assignment]
332+
workspaces: Optional[List[str]] = _UNSET, # type: ignore[assignment]
333+
model_service_name: Optional[str] = _UNSET, # type: ignore[assignment]
334+
model_name: Optional[str] = _UNSET, # type: ignore[assignment]
328335
config: Optional[Config] = None,
329336
) -> SuperAgent:
330337
"""异步更新超级 Agent (read-merge-write)."""
@@ -366,15 +373,15 @@ def update(
366373
self,
367374
name: str,
368375
*,
369-
description: Any = _UNSET,
370-
prompt: Any = _UNSET,
371-
agents: Any = _UNSET,
372-
tools: Any = _UNSET,
373-
skills: Any = _UNSET,
374-
sandboxes: Any = _UNSET,
375-
workspaces: Any = _UNSET,
376-
model_service_name: Any = _UNSET,
377-
model_name: Any = _UNSET,
376+
description: Optional[str] = _UNSET, # type: ignore[assignment]
377+
prompt: Optional[str] = _UNSET, # type: ignore[assignment]
378+
agents: Optional[List[str]] = _UNSET, # type: ignore[assignment]
379+
tools: Optional[List[str]] = _UNSET, # type: ignore[assignment]
380+
skills: Optional[List[str]] = _UNSET, # type: ignore[assignment]
381+
sandboxes: Optional[List[str]] = _UNSET, # type: ignore[assignment]
382+
workspaces: Optional[List[str]] = _UNSET, # type: ignore[assignment]
383+
model_service_name: Optional[str] = _UNSET, # type: ignore[assignment]
384+
model_name: Optional[str] = _UNSET, # type: ignore[assignment]
378385
config: Optional[Config] = None,
379386
) -> SuperAgent:
380387
"""同步更新超级 Agent (read-merge-write)."""

tests/unittests/super_agent/test_control.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
_add_ram_prefix_to_host,
1111
API_VERSION,
1212
build_super_agent_endpoint,
13+
ensure_super_agent_patches_applied,
1314
EXTERNAL_TAG,
1415
from_agent_runtime,
1516
is_super_agent,
@@ -23,6 +24,10 @@
2324
from agentrun.super_agent.api.data import SuperAgentDataAPI
2425
from agentrun.utils.config import Config
2526

27+
# 本文件部分测试 (list request tags 补丁) 依赖 Dara SDK 已被打过补丁,
28+
# 显式在模块加载时触发补丁 (幂等, 与 SuperAgentClient.__init__ 内触发点一致)。
29+
ensure_super_agent_patches_applied()
30+
2631
# ─── build_super_agent_endpoint ────────────────────────────────
2732

2833

@@ -282,7 +287,7 @@ def test_to_update_input_full_protocol_replace():
282287

283288

284289
# ─── Dara ListAgentRuntimesRequest tags 补丁 ──────────────────
285-
# 同时确保 import agentrun.super_agent.api.control 已应用补丁 (已在文件顶部导入)
290+
# 补丁已在模块顶部通过 ensure_super_agent_patches_applied() 显式触发
286291

287292

288293
def test_list_request_from_map_preserves_tags():

0 commit comments

Comments
 (0)