Skip to content

Commit 0bade43

Browse files
committed
Squashed 'astrbot-sdk/' changes from 8186cd3ee..44b67256b
44b67256b chore: refresh vendor snapshot [skip ci] 920d69ce1 Merge pull request #108 from united-pooh/dev e4d388ff2 feat: 添加多个装饰器错误处理函数,增强装饰器的错误上下文信息 fix: 修复 StdioTransport 中的异常处理逻辑,确保在读取过程中正确处理 ValueError fix: 更新 WorkerSession 初始化错误处理,提供更清晰的错误日志 REVERT: 8186cd3ee chore: refresh vendor snapshot [skip ci] git-subtree-dir: astrbot-sdk git-subtree-split: 44b67256b55da78053a3ff3160df8256189c7d5e
1 parent 24deea9 commit 0bade43

5 files changed

Lines changed: 200 additions & 81 deletions

File tree

src/astrbot_sdk/_internal/decorator_lifecycle.py

Lines changed: 165 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,58 @@ async def _await_if_needed(value: Any) -> Any:
102102
return value
103103

104104

105+
def _decorator_target_name(instance: Any, method_name: str | None = None) -> str:
106+
class_name = instance.__class__.__name__
107+
if method_name is None:
108+
return class_name
109+
return f"{class_name}.{method_name}"
110+
111+
112+
def _decorator_error(
113+
*,
114+
instance: Any,
115+
decorator_name: str,
116+
exc: Exception,
117+
method_name: str | None = None,
118+
details: str | None = None,
119+
) -> RuntimeError:
120+
message = f"{_decorator_target_name(instance, method_name)} {decorator_name} failed"
121+
if details:
122+
message += f" ({details})"
123+
message += f": {exc}"
124+
return RuntimeError(message)
125+
126+
127+
def _http_api_details(meta: HttpApiMeta) -> str:
128+
details = [f"route={meta.route!r}", f"methods={list(meta.methods)!r}"]
129+
if meta.capability_name:
130+
details.append(f"capability_name={meta.capability_name!r}")
131+
return ", ".join(details)
132+
133+
134+
def _provider_change_details(meta: Any) -> str:
135+
return f"provider_types={list(meta.provider_types)!r}"
136+
137+
138+
def _background_task_details(meta: BackgroundTaskMeta, method_name: str) -> str:
139+
description = meta.description or f"background_task:{method_name}"
140+
return (
141+
f"description={description!r}, auto_start={meta.auto_start!r}, "
142+
f"on_error={meta.on_error!r}"
143+
)
144+
145+
146+
def _mcp_server_details(meta: MCPServerMeta) -> str:
147+
return (
148+
f"name={meta.name!r}, scope={meta.scope!r}, timeout={meta.timeout!r}, "
149+
f"wait_until_ready={meta.wait_until_ready!r}"
150+
)
151+
152+
153+
def _skill_details(name: str, path: str) -> str:
154+
return f"name={name!r}, path={path!r}"
155+
156+
105157
def _normalize_provider_type(value: Any) -> str:
106158
enum_value = getattr(value, "value", None)
107159
if isinstance(enum_value, str):
@@ -130,9 +182,7 @@ async def _run_model_validation(
130182
try:
131183
validated = meta.model.model_validate(config)
132184
except ValidationError as exc:
133-
raise ValueError(
134-
f"{instance.__class__.__name__}.{method_name} validate_config failed: {exc}"
135-
) from exc
185+
raise ValueError(str(exc)) from exc
136186
_validated_config_store(instance)[method_name] = validated
137187
return
138188

@@ -207,21 +257,38 @@ async def _run_validate_config(instance: Any, context: RuntimeContext) -> None:
207257
meta = get_validate_config_meta(raw)
208258
if meta is None:
209259
continue
210-
await _run_model_validation(
211-
instance=instance,
212-
method_name=method_name,
213-
meta=meta,
214-
config=config,
215-
)
260+
try:
261+
await _run_model_validation(
262+
instance=instance,
263+
method_name=method_name,
264+
meta=meta,
265+
config=config,
266+
)
267+
except Exception as exc:
268+
raise _decorator_error(
269+
instance=instance,
270+
method_name=method_name,
271+
decorator_name="@validate_config",
272+
exc=exc,
273+
) from exc
216274

217275

218276
async def _register_http_apis(instance: Any, context: RuntimeContext) -> None:
219277
state = _runtime_state(instance)
220-
for _method_name, bound, raw in _iter_bound_methods(instance):
278+
for method_name, bound, raw in _iter_bound_methods(instance):
221279
meta = get_http_api_meta(raw)
222280
if meta is None:
223281
continue
224-
await _register_http_api(bound=bound, meta=meta, context=context)
282+
try:
283+
await _register_http_api(bound=bound, meta=meta, context=context)
284+
except Exception as exc:
285+
raise _decorator_error(
286+
instance=instance,
287+
method_name=method_name,
288+
decorator_name="@http_api",
289+
details=_http_api_details(meta),
290+
exc=exc,
291+
) from exc
225292
state.http_apis.append((meta.route, list(meta.methods)))
226293

227294

@@ -252,10 +319,11 @@ async def _register_provider_change_hooks(
252319
context: RuntimeContext,
253320
) -> None:
254321
state = _runtime_state(instance)
255-
for _method_name, bound, raw in _iter_bound_methods(instance):
322+
for method_name, bound, raw in _iter_bound_methods(instance):
256323
meta = get_provider_change_meta(raw)
257324
if meta is None:
258325
continue
326+
target_name = _decorator_target_name(instance, method_name)
259327

260328
async def callback(
261329
provider_id: str,
@@ -270,11 +338,29 @@ async def callback(
270338
if current_type not in _meta.provider_types:
271339
return
272340
owner = instance if isinstance(instance, Star) else None
273-
with bind_star_runtime(owner, context):
274-
result = _bound(provider_id, provider_type, umo)
275-
await _await_if_needed(result)
341+
try:
342+
with bind_star_runtime(owner, context):
343+
result = _bound(provider_id, provider_type, umo)
344+
await _await_if_needed(result)
345+
except Exception as exc:
346+
raise RuntimeError(
347+
f"{target_name} @on_provider_change callback failed "
348+
f"(provider_id={provider_id!r}, provider_type={provider_type!r}, "
349+
f"umo={umo!r}): {exc}"
350+
) from exc
276351

277-
task = await context.provider_manager.register_provider_change_hook(callback)
352+
try:
353+
task = await context.provider_manager.register_provider_change_hook(
354+
callback
355+
)
356+
except Exception as exc:
357+
raise _decorator_error(
358+
instance=instance,
359+
method_name=method_name,
360+
decorator_name="@on_provider_change",
361+
details=_provider_change_details(meta),
362+
exc=exc,
363+
) from exc
278364
# TODO: provider.manager.watch_changes is currently restricted to
279365
# reserved/system plugins. If this decorator should be public-facing,
280366
# the capability boundary needs to be widened or a dedicated event feed
@@ -288,17 +374,26 @@ async def _start_background_tasks(instance: Any, context: RuntimeContext) -> Non
288374
meta = get_background_task_meta(raw)
289375
if meta is None or not meta.auto_start:
290376
continue
291-
task = await context.register_task(
292-
_background_runner(
377+
try:
378+
task = await context.register_task(
379+
_background_runner(
380+
instance=instance,
381+
bound=bound,
382+
context=context,
383+
meta=meta,
384+
method_name=method_name,
385+
),
386+
meta.description
387+
or f"background_task:{instance.__class__.__name__}.{method_name}",
388+
)
389+
except Exception as exc:
390+
raise _decorator_error(
293391
instance=instance,
294-
bound=bound,
295-
context=context,
296-
meta=meta,
297392
method_name=method_name,
298-
),
299-
meta.description
300-
or f"background_task:{instance.__class__.__name__}.{method_name}",
301-
)
393+
decorator_name="@background_task",
394+
details=_background_task_details(meta, method_name),
395+
exc=exc,
396+
) from exc
302397
state.background_tasks.append(task)
303398

304399

@@ -319,41 +414,68 @@ async def _background_runner(
319414
return
320415
except asyncio.CancelledError:
321416
raise
322-
except Exception:
417+
except Exception as exc:
323418
if meta.on_error != "restart":
324-
raise
419+
raise _decorator_error(
420+
instance=instance,
421+
method_name=method_name,
422+
decorator_name="@background_task",
423+
details=_background_task_details(meta, method_name),
424+
exc=exc,
425+
) from exc
325426
context.logger.exception(
326-
"SDK decorator background_task restarting after failure: plugin_id={} task={}",
427+
"SDK decorator background_task restarting after failure: plugin_id={} task={} details={}",
327428
context.plugin_id,
328429
f"{instance.__class__.__name__}.{method_name}",
430+
_background_task_details(meta, method_name),
329431
)
330432

331433

332-
def _iter_class_and_method_meta(
434+
def _iter_class_and_method_meta_entries(
333435
instance: Any,
334436
getter,
335-
) -> list[Any]:
336-
values = list(getter(instance.__class__))
337-
for _method_name, _bound, raw in _iter_bound_methods(instance):
338-
values.extend(getter(raw))
437+
) -> list[tuple[str, Any]]:
438+
values = [
439+
(_decorator_target_name(instance), meta) for meta in getter(instance.__class__)
440+
]
441+
for method_name, _bound, raw in _iter_bound_methods(instance):
442+
values.extend(
443+
(_decorator_target_name(instance, method_name), meta)
444+
for meta in getter(raw)
445+
)
339446
return values
340447

341448

342449
async def _register_skills(instance: Any, context: RuntimeContext) -> None:
343450
state = _runtime_state(instance)
344-
for meta in _iter_class_and_method_meta(instance, get_skill_meta):
345-
await context.register_skill(
346-
name=meta.name,
347-
path=meta.path,
348-
description=meta.description,
349-
)
451+
for target_name, meta in _iter_class_and_method_meta_entries(
452+
instance, get_skill_meta
453+
):
454+
try:
455+
await context.register_skill(
456+
name=meta.name,
457+
path=meta.path,
458+
description=meta.description,
459+
)
460+
except Exception as exc:
461+
raise RuntimeError(
462+
f"{target_name} @register_skill failed "
463+
f"({_skill_details(meta.name, meta.path)}): {exc}"
464+
) from exc
350465
state.registered_skills.append(meta.name)
351466

352467

353468
async def _register_mcp_servers(instance: Any, context: RuntimeContext) -> None:
354469
state = _runtime_state(instance)
355-
for meta in _iter_class_and_method_meta(instance, get_mcp_server_meta):
356-
await _register_mcp_server(meta=meta, context=context)
470+
for target_name, meta in _iter_class_and_method_meta_entries(
471+
instance, get_mcp_server_meta
472+
):
473+
try:
474+
await _register_mcp_server(meta=meta, context=context)
475+
except Exception as exc:
476+
raise RuntimeError(
477+
f"{target_name} @mcp_server failed ({_mcp_server_details(meta)}): {exc}"
478+
) from exc
357479
if meta.scope == "global":
358480
state.global_mcp_servers.append(meta.name)
359481
else:
@@ -453,6 +575,8 @@ async def run_lifecycle_with_decorators(
453575
method_name: str,
454576
context: RuntimeContext,
455577
) -> None:
578+
# Wrap decorator-managed startup failures with decorator-specific context so
579+
# plugin authors do not only see a generic worker initialize timeout.
456580
# Keep the lifecycle wrapper centralized so decorator-managed resources still
457581
# work when plugins override on_start/on_stop without calling super().
458582
if method_name == "on_start":

src/astrbot_sdk/llm/agents.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def from_payload(cls, payload: dict[str, Any]) -> AgentSpec:
3030
class BaseAgentRunner(ABC):
3131
"""agent registration surface.
3232
33-
only supports agent registration metadata. Actual execution remains
33+
only supports agent registration metadata. Actual execution remains
3434
owned by the core tool loop and is not directly callable from SDK plugins.
3535
"""
3636

src/astrbot_sdk/runtime/supervisor.py

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -235,34 +235,18 @@ def _build_transport(self):
235235

236236
async def _wait_until_initialized(self) -> None:
237237
assert self.peer is not None
238-
init_task = asyncio.create_task(
239-
self.peer.wait_until_remote_initialized(
238+
try:
239+
await self.peer.wait_until_remote_initialized(
240240
timeout=WORKER_INITIALIZE_TIMEOUT_SECONDS
241241
)
242-
)
243-
closed_task = asyncio.create_task(self.peer.wait_closed())
244-
done, pending = await asyncio.wait(
245-
{init_task, closed_task},
246-
return_when=asyncio.FIRST_COMPLETED,
247-
)
248-
for task in pending:
249-
task.cancel()
250-
try:
251-
await task
252-
except asyncio.CancelledError:
253-
pass
254-
255-
if init_task in done:
256-
try:
257-
await init_task
258-
except TimeoutError as exc:
259-
raise RuntimeError(
260-
f"worker {self.worker_id} 初始化超时 "
261-
f"({WORKER_INITIALIZE_TIMEOUT_SECONDS:.0f}s)"
262-
) from exc
263-
264-
if closed_task in done:
265-
raise RuntimeError(f"worker {self.worker_id} 在初始化阶段退出")
242+
except TimeoutError as exc:
243+
raise RuntimeError(
244+
f"worker {self.worker_id} 初始化超时 "
245+
f"({WORKER_INITIALIZE_TIMEOUT_SECONDS:.0f}s);"
246+
"请检查 worker 日志中的 on_start / 装饰器初始化错误"
247+
) from exc
248+
except AstrBotError as exc:
249+
raise RuntimeError(f"worker {self.worker_id} 在初始化阶段退出") from exc
266250

267251
def _sync_remote_state(self) -> None:
268252
assert self.peer is not None

0 commit comments

Comments
 (0)