capability-runtime 0.1.0__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.
- capability_runtime/__init__.py +90 -0
- capability_runtime/adapters/__init__.py +13 -0
- capability_runtime/adapters/agent_adapter.py +439 -0
- capability_runtime/adapters/agently_backend.py +423 -0
- capability_runtime/adapters/triggerflow_workflow_engine.py +865 -0
- capability_runtime/adapters/workflow_engine.py +43 -0
- capability_runtime/config.py +172 -0
- capability_runtime/errors.py +20 -0
- capability_runtime/guards.py +150 -0
- capability_runtime/host_protocol.py +400 -0
- capability_runtime/host_toolkit/__init__.py +55 -0
- capability_runtime/host_toolkit/approvals_profiles.py +94 -0
- capability_runtime/host_toolkit/evidence_hooks.py +65 -0
- capability_runtime/host_toolkit/history.py +74 -0
- capability_runtime/host_toolkit/invoke_capability.py +409 -0
- capability_runtime/host_toolkit/resume.py +317 -0
- capability_runtime/host_toolkit/system_prompt.py +132 -0
- capability_runtime/host_toolkit/turn_delta.py +128 -0
- capability_runtime/logging_utils.py +94 -0
- capability_runtime/manifest.py +173 -0
- capability_runtime/output_validator.py +139 -0
- capability_runtime/protocol/__init__.py +43 -0
- capability_runtime/protocol/agent.py +62 -0
- capability_runtime/protocol/capability.py +98 -0
- capability_runtime/protocol/chat_backend.py +38 -0
- capability_runtime/protocol/context.py +244 -0
- capability_runtime/protocol/workflow.py +119 -0
- capability_runtime/registry.py +287 -0
- capability_runtime/reporting/__init__.py +2 -0
- capability_runtime/reporting/node_report.py +497 -0
- capability_runtime/runtime.py +930 -0
- capability_runtime/runtime_ui_events_mixin.py +310 -0
- capability_runtime/sdk_lifecycle.py +982 -0
- capability_runtime/service_facade.py +418 -0
- capability_runtime/services.py +181 -0
- capability_runtime/structured_output.py +208 -0
- capability_runtime/structured_stream.py +38 -0
- capability_runtime/types.py +103 -0
- capability_runtime/ui_events/__init__.py +19 -0
- capability_runtime/ui_events/projector.py +617 -0
- capability_runtime/ui_events/session.py +292 -0
- capability_runtime/ui_events/store.py +127 -0
- capability_runtime/ui_events/transport.py +33 -0
- capability_runtime/ui_events/v1.py +76 -0
- capability_runtime/upstream_compat.py +182 -0
- capability_runtime/utils/__init__.py +1 -0
- capability_runtime/utils/usage.py +65 -0
- capability_runtime/workflow_runtime.py +218 -0
- capability_runtime-0.1.0.dist-info/METADATA +232 -0
- capability_runtime-0.1.0.dist-info/RECORD +52 -0
- capability_runtime-0.1.0.dist-info/WHEEL +5 -0
- capability_runtime-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
统一运行时:声明 → 注册 → 校验 → 执行 → 报告。
|
|
5
|
+
|
|
6
|
+
定位:
|
|
7
|
+
- 对外只提供一个执行入口(Runtime),避免"双入口/双路径"导致的语义分叉;
|
|
8
|
+
- mock/bridge/sdk_native 通过 `RuntimeConfig.mode` 切换;
|
|
9
|
+
- 控制面证据链以 `NodeReport` 为主(事件聚合),数据面输出保持生态兼容。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import time
|
|
14
|
+
import uuid
|
|
15
|
+
from dataclasses import replace
|
|
16
|
+
from typing import Any, AsyncIterator, Dict, List, Optional, Union
|
|
17
|
+
|
|
18
|
+
from skills_runtime.core.contracts import AgentEvent
|
|
19
|
+
|
|
20
|
+
from .config import RuntimeConfig
|
|
21
|
+
from .guards import ExecutionGuards
|
|
22
|
+
from .host_protocol import (
|
|
23
|
+
ApprovalTicket,
|
|
24
|
+
HostRunSnapshot,
|
|
25
|
+
ResumeIntent,
|
|
26
|
+
build_approval_ticket_from_report,
|
|
27
|
+
build_resume_intent as build_host_resume_intent,
|
|
28
|
+
summarize_host_run_result,
|
|
29
|
+
)
|
|
30
|
+
from .manifest import CapabilityDescriptor, CapabilityManifestEntry, CapabilityVisibility, build_manifest_entry_from_spec
|
|
31
|
+
from .output_validator import OutputValidator
|
|
32
|
+
from .protocol.agent import AgentSpec
|
|
33
|
+
from .protocol.capability import CapabilityKind, CapabilityResult, CapabilityStatus
|
|
34
|
+
from .protocol.context import ExecutionContext, RecursionLimitError
|
|
35
|
+
from .protocol.workflow import WorkflowSpec
|
|
36
|
+
from .registry import AnySpec, CapabilityRegistry, _get_base
|
|
37
|
+
from .reporting.node_report import build_fail_closed_report
|
|
38
|
+
from .sdk_lifecycle import (
|
|
39
|
+
SdkLifecycle,
|
|
40
|
+
_normalize_skills_config_for_skills_runtime,
|
|
41
|
+
)
|
|
42
|
+
from .services import call_callback, get_host_meta, redact_issue
|
|
43
|
+
from .structured_output import (
|
|
44
|
+
finalize_structured_result,
|
|
45
|
+
parse_json_object_snapshot,
|
|
46
|
+
validate_structured_output,
|
|
47
|
+
)
|
|
48
|
+
from .structured_stream import StructuredStreamEvent, diff_top_level_fields
|
|
49
|
+
from .types import NodeReport
|
|
50
|
+
from .adapters.agent_adapter import AgentAdapter
|
|
51
|
+
from .adapters.workflow_engine import WorkflowStreamEvent
|
|
52
|
+
from .adapters.triggerflow_workflow_engine import TriggerFlowWorkflowEngine
|
|
53
|
+
from .runtime_ui_events_mixin import RuntimeUIEventsMixin
|
|
54
|
+
from .workflow_runtime import (
|
|
55
|
+
WorkflowReplayRequest,
|
|
56
|
+
WorkflowRunSnapshot,
|
|
57
|
+
summarize_workflow_items,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Runtime(RuntimeUIEventsMixin):
|
|
62
|
+
"""
|
|
63
|
+
统一运行时(唯一入口)。
|
|
64
|
+
|
|
65
|
+
关键语义:
|
|
66
|
+
- 注册与校验由 Registry 驱动;
|
|
67
|
+
- 执行入口只有 `run()` / `run_stream()`;
|
|
68
|
+
- `run()` 基于 `run_stream()` 实现;
|
|
69
|
+
- 并发安全:per-run guards、per-run SDK Agent(由实现保证不共享可变状态)。
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, config: RuntimeConfig) -> None:
|
|
73
|
+
"""
|
|
74
|
+
构造 Runtime。
|
|
75
|
+
|
|
76
|
+
参数:
|
|
77
|
+
- config:运行时配置(含 mode 与桥接注入点)
|
|
78
|
+
|
|
79
|
+
注意:
|
|
80
|
+
- RuntimeConfig 为 frozen dataclass;内部可能通过 `replace()` 回填有效配置;
|
|
81
|
+
- 通过 `Runtime.config` 读取的配置可能与初始化时传入的实例不同。
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
self._config = config
|
|
85
|
+
self._registry = CapabilityRegistry()
|
|
86
|
+
self._sdk: Optional[SdkLifecycle] = None
|
|
87
|
+
# 兼容保留:部分内部逻辑仍通过 `_sdk_state` 读取 skills_config(仅只读)。
|
|
88
|
+
self._sdk_state: Any = None
|
|
89
|
+
self._output_validator = OutputValidator(
|
|
90
|
+
mode=self._config.output_validation_mode,
|
|
91
|
+
validator=self._config.output_validator,
|
|
92
|
+
)
|
|
93
|
+
# UI events taps:用于把 SDK AgentEvent(含 workflow 内 nested agent 事件)
|
|
94
|
+
# 旁路投影为 RuntimeEvent v1,不影响 NodeReport/WAL 真相源。
|
|
95
|
+
self._agent_event_taps: List[Any] = []
|
|
96
|
+
self._agent_adapter = AgentAdapter(services=self)
|
|
97
|
+
injected_engine = getattr(config, "workflow_engine", None)
|
|
98
|
+
self._workflow_engine = injected_engine if injected_engine is not None else TriggerFlowWorkflowEngine()
|
|
99
|
+
|
|
100
|
+
if config.mode in ("bridge", "sdk_native"):
|
|
101
|
+
self._sdk = SdkLifecycle(config)
|
|
102
|
+
self._sdk_state = self._sdk.state
|
|
103
|
+
# 兼容:大部分调用方仅提供 sdk_config_paths(overlay),并不显式传 skills_config。
|
|
104
|
+
# 为了让 Adapter/Engine 只依赖 RuntimeServices.config(而不读取内部 _sdk_state),
|
|
105
|
+
# 这里把"有效 skills_config"回填到对外暴露的 config 视图中(只读替换)。
|
|
106
|
+
if self._config.skills_config is None:
|
|
107
|
+
self._config = replace(self._config, skills_config=self._sdk_state.skills_config)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def config(self) -> RuntimeConfig:
|
|
111
|
+
"""
|
|
112
|
+
运行时配置(只读)。
|
|
113
|
+
|
|
114
|
+
注意:
|
|
115
|
+
- 返回的是运行时有效配置,可能与初始化时传入的 RuntimeConfig 实例不同;
|
|
116
|
+
- 内部可能通过 `replace()` 回填 skills_config 等字段。
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
return self._config
|
|
120
|
+
|
|
121
|
+
def register(self, spec: AnySpec) -> None:
|
|
122
|
+
"""
|
|
123
|
+
注册一个能力。
|
|
124
|
+
|
|
125
|
+
参数:
|
|
126
|
+
- spec:AgentSpec 或 WorkflowSpec
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
self._registry.register_with_manifest(
|
|
130
|
+
spec,
|
|
131
|
+
entry=build_manifest_entry_from_spec(spec, source="runtime.register"),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def register_many(self, specs: List[AnySpec]) -> None:
|
|
135
|
+
"""批量注册能力。"""
|
|
136
|
+
|
|
137
|
+
for s in specs:
|
|
138
|
+
self._registry.register_with_manifest(
|
|
139
|
+
s,
|
|
140
|
+
entry=build_manifest_entry_from_spec(s, source="runtime.register_many"),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def register_manifest_entry(self, entry: CapabilityManifestEntry) -> CapabilityManifestEntry:
|
|
144
|
+
"""
|
|
145
|
+
仅注册 manifest entry(允许先于 spec)。
|
|
146
|
+
|
|
147
|
+
参数:
|
|
148
|
+
- entry:宿主定义的 manifest 元数据
|
|
149
|
+
|
|
150
|
+
返回:
|
|
151
|
+
- 已注册的 manifest entry
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
return self._registry.register_manifest_entry(entry)
|
|
155
|
+
|
|
156
|
+
def register_with_manifest(
|
|
157
|
+
self,
|
|
158
|
+
spec: AnySpec,
|
|
159
|
+
*,
|
|
160
|
+
entry: CapabilityManifestEntry | None = None,
|
|
161
|
+
) -> CapabilityManifestEntry:
|
|
162
|
+
"""
|
|
163
|
+
注册能力并同步 manifest entry。
|
|
164
|
+
|
|
165
|
+
参数:
|
|
166
|
+
- spec:AgentSpec 或 WorkflowSpec
|
|
167
|
+
- entry:可选显式 manifest entry
|
|
168
|
+
|
|
169
|
+
返回:
|
|
170
|
+
- 实际注册的 manifest entry
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
manifest_entry = entry or build_manifest_entry_from_spec(spec, source="runtime.register_with_manifest")
|
|
174
|
+
return self._registry.register_with_manifest(spec, entry=manifest_entry)
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def registry(self) -> CapabilityRegistry:
|
|
178
|
+
"""
|
|
179
|
+
能力注册表(只读视角)。
|
|
180
|
+
|
|
181
|
+
说明:
|
|
182
|
+
- 主要用于 workflow 引擎递归分发执行时查询 target spec;
|
|
183
|
+
- 调用方不应直接修改内部状态(注册应通过 Runtime.register* 完成)。
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
return self._registry
|
|
187
|
+
|
|
188
|
+
def validate(self) -> List[str]:
|
|
189
|
+
"""
|
|
190
|
+
校验所有依赖,返回缺失能力 ID 列表。
|
|
191
|
+
|
|
192
|
+
返回:
|
|
193
|
+
- 缺失 ID 列表;空列表表示全部满足
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
return self._registry.validate_dependencies()
|
|
197
|
+
|
|
198
|
+
def describe_capability(self, capability_id: str) -> CapabilityDescriptor | None:
|
|
199
|
+
"""
|
|
200
|
+
返回宿主可消费的 capability descriptor。
|
|
201
|
+
|
|
202
|
+
参数:
|
|
203
|
+
- capability_id:能力 ID
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
return self._registry.get_descriptor(capability_id)
|
|
207
|
+
|
|
208
|
+
def list_capabilities(
|
|
209
|
+
self,
|
|
210
|
+
*,
|
|
211
|
+
visibility: CapabilityVisibility | None = None,
|
|
212
|
+
exposed_only: bool = False,
|
|
213
|
+
) -> list[CapabilityDescriptor]:
|
|
214
|
+
"""
|
|
215
|
+
列出 capability descriptors。
|
|
216
|
+
|
|
217
|
+
参数:
|
|
218
|
+
- visibility:可选可见性过滤
|
|
219
|
+
- exposed_only:仅返回 `entry.expose=True` 的能力
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
return self._registry.list_descriptors(visibility=visibility, exposed_only=exposed_only)
|
|
223
|
+
|
|
224
|
+
def build_approval_ticket(
|
|
225
|
+
self,
|
|
226
|
+
result: CapabilityResult,
|
|
227
|
+
*,
|
|
228
|
+
capability_id: str,
|
|
229
|
+
) -> ApprovalTicket | None:
|
|
230
|
+
"""
|
|
231
|
+
从 terminal result 构造宿主 ApprovalTicket。
|
|
232
|
+
|
|
233
|
+
参数:
|
|
234
|
+
- result:终态 CapabilityResult
|
|
235
|
+
- capability_id:能力 ID
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
return build_approval_ticket_from_report(result.node_report, capability_id=capability_id)
|
|
239
|
+
|
|
240
|
+
def summarize_host_run(
|
|
241
|
+
self,
|
|
242
|
+
result: CapabilityResult,
|
|
243
|
+
*,
|
|
244
|
+
capability_id: str,
|
|
245
|
+
) -> HostRunSnapshot:
|
|
246
|
+
"""
|
|
247
|
+
把 terminal result 收敛为宿主运行摘要。
|
|
248
|
+
|
|
249
|
+
参数:
|
|
250
|
+
- result:终态 CapabilityResult
|
|
251
|
+
- capability_id:能力 ID
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
return summarize_host_run_result(result, capability_id=capability_id)
|
|
255
|
+
|
|
256
|
+
def build_resume_intent(
|
|
257
|
+
self,
|
|
258
|
+
*,
|
|
259
|
+
run_id: str,
|
|
260
|
+
approval_key: str | None = None,
|
|
261
|
+
decision: str | None = None,
|
|
262
|
+
session_id: str | None = None,
|
|
263
|
+
host_turn_id: str | None = None,
|
|
264
|
+
) -> ResumeIntent:
|
|
265
|
+
"""
|
|
266
|
+
构造宿主续跑意图。
|
|
267
|
+
|
|
268
|
+
参数:
|
|
269
|
+
- run_id:运行 ID
|
|
270
|
+
- approval_key:可选审批键
|
|
271
|
+
- decision:可选审批决定
|
|
272
|
+
- session_id:可选会话 ID
|
|
273
|
+
- host_turn_id:可选宿主 turn ID
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
return build_host_resume_intent(
|
|
277
|
+
run_id=run_id,
|
|
278
|
+
approval_key=approval_key,
|
|
279
|
+
decision=decision,
|
|
280
|
+
session_id=session_id,
|
|
281
|
+
host_turn_id=host_turn_id,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def _build_missing_terminal_result(
|
|
285
|
+
self,
|
|
286
|
+
*,
|
|
287
|
+
run_id: str,
|
|
288
|
+
capability_id: str,
|
|
289
|
+
source: str,
|
|
290
|
+
error: str,
|
|
291
|
+
error_code: str = "ENGINE_ERROR",
|
|
292
|
+
) -> CapabilityResult:
|
|
293
|
+
"""
|
|
294
|
+
为“内部未产出 terminal/result”这类不变量破坏合成 fail-closed 终态。
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
report = self.build_fail_closed_report(
|
|
298
|
+
run_id=run_id,
|
|
299
|
+
status="failed",
|
|
300
|
+
reason="engine_error",
|
|
301
|
+
completion_reason="missing_terminal_result",
|
|
302
|
+
meta={"source": source, "capability_id": capability_id},
|
|
303
|
+
)
|
|
304
|
+
return CapabilityResult(
|
|
305
|
+
status=CapabilityStatus.FAILED,
|
|
306
|
+
error=error,
|
|
307
|
+
error_code=error_code,
|
|
308
|
+
report=report,
|
|
309
|
+
node_report=report,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def _build_fail_closed_result(
|
|
313
|
+
self,
|
|
314
|
+
*,
|
|
315
|
+
run_id: str,
|
|
316
|
+
capability_id: str,
|
|
317
|
+
source: str,
|
|
318
|
+
error: str,
|
|
319
|
+
error_code: str,
|
|
320
|
+
reason: str,
|
|
321
|
+
completion_reason: str,
|
|
322
|
+
meta: Optional[Dict[str, Any]] = None,
|
|
323
|
+
) -> CapabilityResult:
|
|
324
|
+
"""为 public failure / contract failure 构造带 node_report 的 FAILED terminal。"""
|
|
325
|
+
|
|
326
|
+
report = self.build_fail_closed_report(
|
|
327
|
+
run_id=run_id,
|
|
328
|
+
status="failed",
|
|
329
|
+
reason=reason,
|
|
330
|
+
completion_reason=completion_reason,
|
|
331
|
+
meta={"source": source, "capability_id": capability_id, **(meta or {})},
|
|
332
|
+
)
|
|
333
|
+
return CapabilityResult(
|
|
334
|
+
status=CapabilityStatus.FAILED,
|
|
335
|
+
error=error,
|
|
336
|
+
error_code=error_code,
|
|
337
|
+
report=report,
|
|
338
|
+
node_report=report,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
async def run(
|
|
342
|
+
self,
|
|
343
|
+
capability_id: str,
|
|
344
|
+
*,
|
|
345
|
+
input: Optional[Dict[str, Any]] = None,
|
|
346
|
+
context: Optional[ExecutionContext] = None,
|
|
347
|
+
) -> CapabilityResult:
|
|
348
|
+
"""
|
|
349
|
+
非流式执行(等待完成后返回)。
|
|
350
|
+
|
|
351
|
+
参数:
|
|
352
|
+
- capability_id:能力 ID
|
|
353
|
+
- input:输入参数 dict
|
|
354
|
+
- context:可选执行上下文(宿主控制;若不传则由 Runtime 创建)
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
result: Optional[CapabilityResult] = None
|
|
358
|
+
async for item in self.run_stream(capability_id, input=input, context=context):
|
|
359
|
+
if isinstance(item, CapabilityResult):
|
|
360
|
+
result = item
|
|
361
|
+
if result is None:
|
|
362
|
+
return self._build_missing_terminal_result(
|
|
363
|
+
run_id=context.run_id if context is not None else uuid.uuid4().hex,
|
|
364
|
+
capability_id=capability_id,
|
|
365
|
+
source="runtime.run",
|
|
366
|
+
error="Runtime.run_stream produced no terminal CapabilityResult",
|
|
367
|
+
)
|
|
368
|
+
return result
|
|
369
|
+
|
|
370
|
+
async def run_structured(
|
|
371
|
+
self,
|
|
372
|
+
capability_id: str,
|
|
373
|
+
*,
|
|
374
|
+
input: Optional[Dict[str, Any]] = None,
|
|
375
|
+
context: Optional[ExecutionContext] = None,
|
|
376
|
+
) -> CapabilityResult:
|
|
377
|
+
"""
|
|
378
|
+
强结构输出入口:成功时返回 `dict` 输出。
|
|
379
|
+
|
|
380
|
+
约束:
|
|
381
|
+
- 仅支持带 `output_schema` 的 Agent capability;
|
|
382
|
+
- 不改变 `run()` 的既有语义,而是在其之上做强结构收口。
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
spec = self._registry.get(capability_id)
|
|
386
|
+
if spec is None:
|
|
387
|
+
return await self.run(capability_id, input=input, context=context)
|
|
388
|
+
if not isinstance(spec, AgentSpec):
|
|
389
|
+
run_id = context.run_id if context is not None else uuid.uuid4().hex
|
|
390
|
+
return self._build_fail_closed_result(
|
|
391
|
+
run_id=run_id,
|
|
392
|
+
capability_id=capability_id,
|
|
393
|
+
source="runtime.run_structured",
|
|
394
|
+
error=f"Structured output is only supported for Agent capability: {capability_id!r}",
|
|
395
|
+
error_code="STRUCTURED_OUTPUT_UNSUPPORTED_KIND",
|
|
396
|
+
reason="structured_output_error",
|
|
397
|
+
completion_reason="structured_output_unsupported_kind",
|
|
398
|
+
)
|
|
399
|
+
if spec.output_schema is None or not spec.output_schema.fields:
|
|
400
|
+
run_id = context.run_id if context is not None else uuid.uuid4().hex
|
|
401
|
+
return self._build_fail_closed_result(
|
|
402
|
+
run_id=run_id,
|
|
403
|
+
capability_id=capability_id,
|
|
404
|
+
source="runtime.run_structured",
|
|
405
|
+
error=f"Structured output schema is missing for capability: {capability_id!r}",
|
|
406
|
+
error_code="STRUCTURED_OUTPUT_SCHEMA_MISSING",
|
|
407
|
+
reason="structured_output_error",
|
|
408
|
+
completion_reason="structured_output_schema_missing",
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
result = await self.run(capability_id, input=input, context=context)
|
|
412
|
+
if result.status != CapabilityStatus.SUCCESS:
|
|
413
|
+
return result
|
|
414
|
+
|
|
415
|
+
validation = validate_structured_output(
|
|
416
|
+
final_output=result.output,
|
|
417
|
+
output_schema=spec.output_schema,
|
|
418
|
+
capability_id=capability_id,
|
|
419
|
+
mode="error",
|
|
420
|
+
)
|
|
421
|
+
return finalize_structured_result(result=result, validation=validation, fail_on_error=True)
|
|
422
|
+
|
|
423
|
+
async def run_structured_stream(
|
|
424
|
+
self,
|
|
425
|
+
capability_id: str,
|
|
426
|
+
*,
|
|
427
|
+
input: Optional[Dict[str, Any]] = None,
|
|
428
|
+
context: Optional[ExecutionContext] = None,
|
|
429
|
+
) -> AsyncIterator[StructuredStreamEvent]:
|
|
430
|
+
"""
|
|
431
|
+
结构化输出流式消费入口。
|
|
432
|
+
|
|
433
|
+
说明:
|
|
434
|
+
- 面向业务代码,不透传 mixed stream / UI events 的全部细节;
|
|
435
|
+
- 仅在观察到 `llm_response_delta(text)` 时产出中途快照与字段更新。
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
spec = self._registry.get(capability_id)
|
|
439
|
+
ctx = context or ExecutionContext(run_id=uuid.uuid4().hex, max_depth=self._config.max_depth)
|
|
440
|
+
yield StructuredStreamEvent(type="started", run_id=ctx.run_id, capability_id=capability_id)
|
|
441
|
+
|
|
442
|
+
if spec is None:
|
|
443
|
+
yield StructuredStreamEvent(
|
|
444
|
+
type="terminal",
|
|
445
|
+
run_id=ctx.run_id,
|
|
446
|
+
capability_id=capability_id,
|
|
447
|
+
status=CapabilityStatus.FAILED.value,
|
|
448
|
+
error=f"Capability not found: {capability_id!r}",
|
|
449
|
+
error_code="CAPABILITY_NOT_FOUND",
|
|
450
|
+
)
|
|
451
|
+
return
|
|
452
|
+
if not isinstance(spec, AgentSpec):
|
|
453
|
+
yield StructuredStreamEvent(
|
|
454
|
+
type="terminal",
|
|
455
|
+
run_id=ctx.run_id,
|
|
456
|
+
capability_id=capability_id,
|
|
457
|
+
status=CapabilityStatus.FAILED.value,
|
|
458
|
+
error=f"Structured output is only supported for Agent capability: {capability_id!r}",
|
|
459
|
+
error_code="STRUCTURED_OUTPUT_UNSUPPORTED_KIND",
|
|
460
|
+
)
|
|
461
|
+
return
|
|
462
|
+
if spec.output_schema is None or not spec.output_schema.fields:
|
|
463
|
+
yield StructuredStreamEvent(
|
|
464
|
+
type="terminal",
|
|
465
|
+
run_id=ctx.run_id,
|
|
466
|
+
capability_id=capability_id,
|
|
467
|
+
status=CapabilityStatus.FAILED.value,
|
|
468
|
+
error=f"Structured output schema is missing for capability: {capability_id!r}",
|
|
469
|
+
error_code="STRUCTURED_OUTPUT_SCHEMA_MISSING",
|
|
470
|
+
)
|
|
471
|
+
return
|
|
472
|
+
|
|
473
|
+
accumulated_text = ""
|
|
474
|
+
previous_snapshot: Optional[Dict[str, Any]] = None
|
|
475
|
+
terminal: Optional[CapabilityResult] = None
|
|
476
|
+
|
|
477
|
+
async for item in self.run_stream(capability_id, input=input, context=ctx):
|
|
478
|
+
if isinstance(item, CapabilityResult):
|
|
479
|
+
terminal = item
|
|
480
|
+
continue
|
|
481
|
+
if not isinstance(item, AgentEvent):
|
|
482
|
+
continue
|
|
483
|
+
if item.type != "llm_response_delta":
|
|
484
|
+
continue
|
|
485
|
+
if str(item.payload.get("delta_type") or "") != "text":
|
|
486
|
+
continue
|
|
487
|
+
|
|
488
|
+
text = str(item.payload.get("text") or "")
|
|
489
|
+
if not text:
|
|
490
|
+
continue
|
|
491
|
+
accumulated_text += text
|
|
492
|
+
yield StructuredStreamEvent(
|
|
493
|
+
type="text_delta",
|
|
494
|
+
run_id=ctx.run_id,
|
|
495
|
+
capability_id=capability_id,
|
|
496
|
+
text=text,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
snapshot = parse_json_object_snapshot(accumulated_text)
|
|
500
|
+
if snapshot is None or snapshot == previous_snapshot:
|
|
501
|
+
continue
|
|
502
|
+
|
|
503
|
+
yield StructuredStreamEvent(
|
|
504
|
+
type="object_snapshot",
|
|
505
|
+
run_id=ctx.run_id,
|
|
506
|
+
capability_id=capability_id,
|
|
507
|
+
snapshot=dict(snapshot),
|
|
508
|
+
)
|
|
509
|
+
for field, value in diff_top_level_fields(previous_snapshot, snapshot):
|
|
510
|
+
yield StructuredStreamEvent(
|
|
511
|
+
type="field_updated",
|
|
512
|
+
run_id=ctx.run_id,
|
|
513
|
+
capability_id=capability_id,
|
|
514
|
+
field=field,
|
|
515
|
+
value=value,
|
|
516
|
+
)
|
|
517
|
+
previous_snapshot = dict(snapshot)
|
|
518
|
+
|
|
519
|
+
if terminal is None:
|
|
520
|
+
fail_closed = self._build_missing_terminal_result(
|
|
521
|
+
run_id=ctx.run_id,
|
|
522
|
+
capability_id=capability_id,
|
|
523
|
+
source="runtime.run_structured_stream",
|
|
524
|
+
error="Runtime.run_stream produced no terminal CapabilityResult",
|
|
525
|
+
error_code="STRUCTURED_OUTPUT_MISSING_TERMINAL",
|
|
526
|
+
)
|
|
527
|
+
terminal = fail_closed
|
|
528
|
+
|
|
529
|
+
structured_terminal = terminal
|
|
530
|
+
if terminal.status == CapabilityStatus.SUCCESS:
|
|
531
|
+
validation = validate_structured_output(
|
|
532
|
+
final_output=terminal.output,
|
|
533
|
+
output_schema=spec.output_schema,
|
|
534
|
+
capability_id=capability_id,
|
|
535
|
+
mode="error",
|
|
536
|
+
)
|
|
537
|
+
structured_terminal = finalize_structured_result(
|
|
538
|
+
result=terminal,
|
|
539
|
+
validation=validation,
|
|
540
|
+
fail_on_error=True,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
raw_output = structured_terminal.metadata.get("raw_output")
|
|
544
|
+
if not isinstance(raw_output, str):
|
|
545
|
+
if isinstance(terminal.output, str):
|
|
546
|
+
raw_output = terminal.output
|
|
547
|
+
else:
|
|
548
|
+
raw_output = None
|
|
549
|
+
|
|
550
|
+
yield StructuredStreamEvent(
|
|
551
|
+
type="terminal",
|
|
552
|
+
run_id=ctx.run_id,
|
|
553
|
+
capability_id=capability_id,
|
|
554
|
+
status=structured_terminal.status.value,
|
|
555
|
+
output=structured_terminal.output if isinstance(structured_terminal.output, dict) else None,
|
|
556
|
+
raw_output=raw_output,
|
|
557
|
+
error=structured_terminal.error,
|
|
558
|
+
error_code=structured_terminal.error_code,
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
async def run_stream(
|
|
562
|
+
self,
|
|
563
|
+
capability_id: str,
|
|
564
|
+
*,
|
|
565
|
+
input: Optional[Dict[str, Any]] = None,
|
|
566
|
+
context: Optional[ExecutionContext] = None,
|
|
567
|
+
) -> AsyncIterator[Union[AgentEvent, WorkflowStreamEvent, CapabilityResult]]:
|
|
568
|
+
"""
|
|
569
|
+
流式执行(执行层混合流):过程中可能产出事件,最后产出终态 CapabilityResult。
|
|
570
|
+
|
|
571
|
+
事实定义(本方法可能产出三类 item;消费端需自行分流处理):
|
|
572
|
+
- `AgentEvent`:仅在执行 Agent 且为 bridge/sdk_native 时出现;来自上游 `skills_runtime` 的事实事件流。
|
|
573
|
+
- `dict`(workflow.* 轻量事件):仅在执行 Workflow 时出现;只表达编排进度(started/step.* /finished),不承诺深审计细节。
|
|
574
|
+
- `CapabilityResult`:终态结果(最后一条)。其中 `node_report/events_path` 为证据指针(真相源仍为 WAL/events + NodeReport)。
|
|
575
|
+
|
|
576
|
+
约束:
|
|
577
|
+
- mock 模式可能只产出终态 `CapabilityResult`(无中间事件);
|
|
578
|
+
- bridge/sdk_native 模式 MUST 转发上游 `AgentEvent`(如执行路径确实进入上游引擎);
|
|
579
|
+
- 如果你需要"单一稳定事件协议 + 续传游标 + 最小披露",请使用 `run_ui_events()` / `start_ui_events_session()`。
|
|
580
|
+
|
|
581
|
+
注意:
|
|
582
|
+
- 本方法为内部/进阶接口;消费方需自行 isinstance 分流三种类型;
|
|
583
|
+
- 推荐使用 `run_ui_events()` 或 `start_ui_events_session()` 获得统一事件协议。
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
spec = self._registry.get(capability_id)
|
|
587
|
+
if spec is None:
|
|
588
|
+
run_id = context.run_id if context is not None else uuid.uuid4().hex
|
|
589
|
+
yield self._build_fail_closed_result(
|
|
590
|
+
run_id=run_id,
|
|
591
|
+
capability_id=capability_id,
|
|
592
|
+
source="runtime.run_stream",
|
|
593
|
+
error=f"Capability not found: {capability_id!r}",
|
|
594
|
+
error_code="CAPABILITY_NOT_FOUND",
|
|
595
|
+
reason="capability_not_found",
|
|
596
|
+
completion_reason="capability_not_found",
|
|
597
|
+
)
|
|
598
|
+
return
|
|
599
|
+
|
|
600
|
+
guards = ExecutionGuards(max_total_loop_iterations=self._config.max_total_loop_iterations)
|
|
601
|
+
if context is not None:
|
|
602
|
+
# 使用 with_guards() 确保 per-run 隔离,复制可变容器避免共享引用
|
|
603
|
+
ctx = context.with_guards(guards)
|
|
604
|
+
else:
|
|
605
|
+
ctx = ExecutionContext(
|
|
606
|
+
run_id=uuid.uuid4().hex,
|
|
607
|
+
max_depth=self._config.max_depth,
|
|
608
|
+
guards=guards,
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
started = time.monotonic()
|
|
612
|
+
if _get_base(spec).kind == CapabilityKind.AGENT:
|
|
613
|
+
async for x in self._execute_agent_stream(spec=spec, input=input or {}, context=ctx):
|
|
614
|
+
if isinstance(x, CapabilityResult):
|
|
615
|
+
x.duration_ms = (time.monotonic() - started) * 1000
|
|
616
|
+
yield x
|
|
617
|
+
return
|
|
618
|
+
|
|
619
|
+
async for x in self._execute_workflow_stream(spec=spec, input=input or {}, context=ctx):
|
|
620
|
+
if isinstance(x, CapabilityResult):
|
|
621
|
+
x.duration_ms = (time.monotonic() - started) * 1000
|
|
622
|
+
yield x
|
|
623
|
+
return
|
|
624
|
+
|
|
625
|
+
async def run_workflow_observable(
|
|
626
|
+
self,
|
|
627
|
+
workflow_id: str,
|
|
628
|
+
*,
|
|
629
|
+
input: Optional[Dict[str, Any]] = None,
|
|
630
|
+
context: Optional[ExecutionContext] = None,
|
|
631
|
+
) -> AsyncIterator[Union[WorkflowStreamEvent, CapabilityResult]]:
|
|
632
|
+
"""
|
|
633
|
+
workflow host-facing observable surface。
|
|
634
|
+
|
|
635
|
+
参数:
|
|
636
|
+
- workflow_id:workflow ID
|
|
637
|
+
- input:可选输入
|
|
638
|
+
- context:可选执行上下文
|
|
639
|
+
"""
|
|
640
|
+
|
|
641
|
+
async for item in self.run_stream(workflow_id, input=input, context=context):
|
|
642
|
+
if isinstance(item, AgentEvent):
|
|
643
|
+
continue
|
|
644
|
+
yield item
|
|
645
|
+
|
|
646
|
+
def summarize_workflow_run(
|
|
647
|
+
self,
|
|
648
|
+
*,
|
|
649
|
+
workflow_id: str,
|
|
650
|
+
items: list[Any],
|
|
651
|
+
terminal: CapabilityResult | None = None,
|
|
652
|
+
) -> WorkflowRunSnapshot:
|
|
653
|
+
"""
|
|
654
|
+
收敛 workflow 运行摘要。
|
|
655
|
+
|
|
656
|
+
参数:
|
|
657
|
+
- workflow_id:workflow ID
|
|
658
|
+
- items:workflow 轻量事件列表
|
|
659
|
+
- terminal:可选终态结果
|
|
660
|
+
"""
|
|
661
|
+
|
|
662
|
+
return summarize_workflow_items(workflow_id=workflow_id, items=items, terminal=terminal)
|
|
663
|
+
|
|
664
|
+
async def replay_workflow(
|
|
665
|
+
self,
|
|
666
|
+
request: WorkflowReplayRequest,
|
|
667
|
+
*,
|
|
668
|
+
context: Optional[ExecutionContext] = None,
|
|
669
|
+
) -> CapabilityResult:
|
|
670
|
+
"""
|
|
671
|
+
基于 host request 重放 workflow。
|
|
672
|
+
|
|
673
|
+
参数:
|
|
674
|
+
- request:workflow replay 请求
|
|
675
|
+
- context:可选执行上下文;为空时使用 request.run_id 新建
|
|
676
|
+
"""
|
|
677
|
+
|
|
678
|
+
ctx = context or ExecutionContext(run_id=request.run_id, max_depth=self._config.max_depth)
|
|
679
|
+
return await self.run(request.workflow_id, input=request.current_input or {}, context=ctx)
|
|
680
|
+
|
|
681
|
+
async def replay(
|
|
682
|
+
self,
|
|
683
|
+
*,
|
|
684
|
+
workflow_id: str,
|
|
685
|
+
run_id: str,
|
|
686
|
+
current_input: dict[str, Any],
|
|
687
|
+
) -> CapabilityResult:
|
|
688
|
+
"""
|
|
689
|
+
workflow replay 的最小公共桥接入口。
|
|
690
|
+
|
|
691
|
+
参数:
|
|
692
|
+
- workflow_id:目标 workflow ID
|
|
693
|
+
- run_id:沿用的运行 ID
|
|
694
|
+
- current_input:当前输入
|
|
695
|
+
"""
|
|
696
|
+
|
|
697
|
+
return await self.replay_workflow(
|
|
698
|
+
WorkflowReplayRequest(
|
|
699
|
+
workflow_id=workflow_id,
|
|
700
|
+
run_id=run_id,
|
|
701
|
+
current_input=current_input,
|
|
702
|
+
)
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
def bind_runtime_server(self) -> None:
|
|
706
|
+
"""
|
|
707
|
+
若配置了 runtime_server,则显式把本地 Runtime 绑定给它。
|
|
708
|
+
"""
|
|
709
|
+
|
|
710
|
+
runtime_server = getattr(self._config, "runtime_server", None)
|
|
711
|
+
if runtime_server is None:
|
|
712
|
+
return
|
|
713
|
+
bind_runtime = getattr(runtime_server, "bind_runtime", None)
|
|
714
|
+
if callable(bind_runtime):
|
|
715
|
+
bind_runtime(self)
|
|
716
|
+
return
|
|
717
|
+
set_runtime = getattr(runtime_server, "set_runtime", None)
|
|
718
|
+
if callable(set_runtime):
|
|
719
|
+
set_runtime(self)
|
|
720
|
+
return
|
|
721
|
+
setattr(runtime_server, "runtime", self)
|
|
722
|
+
|
|
723
|
+
async def _execute(self, *, spec: AnySpec, input: Dict[str, Any], context: ExecutionContext) -> CapabilityResult:
|
|
724
|
+
"""
|
|
725
|
+
内部执行: 创建子 context 并分发到 Agent/Workflow 执行器。
|
|
726
|
+
|
|
727
|
+
参数:
|
|
728
|
+
- spec: 能力声明
|
|
729
|
+
- input: 输入参数
|
|
730
|
+
- context: 执行上下文
|
|
731
|
+
"""
|
|
732
|
+
|
|
733
|
+
base = _get_base(spec)
|
|
734
|
+
try:
|
|
735
|
+
child_ctx = context.child(base.id)
|
|
736
|
+
except RecursionLimitError as exc:
|
|
737
|
+
return self._build_fail_closed_result(
|
|
738
|
+
run_id=context.run_id,
|
|
739
|
+
capability_id=base.id,
|
|
740
|
+
source="runtime.execute",
|
|
741
|
+
error=str(exc),
|
|
742
|
+
error_code="RECURSION_LIMIT",
|
|
743
|
+
reason="recursion_limit",
|
|
744
|
+
completion_reason="recursion_limit",
|
|
745
|
+
meta={"error_type": "recursion_limit"},
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
if base.kind == CapabilityKind.AGENT:
|
|
749
|
+
# 非流式入口内部执行时,仍走流式实现并收敛为最终结果。
|
|
750
|
+
last: Optional[CapabilityResult] = None
|
|
751
|
+
async for item in self._execute_agent_stream(spec=spec, input=input, context=child_ctx):
|
|
752
|
+
if isinstance(item, CapabilityResult):
|
|
753
|
+
last = item
|
|
754
|
+
return last or self._build_missing_terminal_result(
|
|
755
|
+
run_id=child_ctx.run_id,
|
|
756
|
+
capability_id=base.id,
|
|
757
|
+
source="runtime.execute_agent",
|
|
758
|
+
error="Agent execution produced no result",
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
if not isinstance(spec, WorkflowSpec):
|
|
762
|
+
return self._build_fail_closed_result(
|
|
763
|
+
run_id=child_ctx.run_id,
|
|
764
|
+
capability_id=base.id,
|
|
765
|
+
source="runtime.execute",
|
|
766
|
+
error=f"Invalid workflow spec type: {type(spec).__name__}",
|
|
767
|
+
error_code="INVALID_WORKFLOW_SPEC",
|
|
768
|
+
reason="invalid_spec",
|
|
769
|
+
completion_reason="invalid_workflow_spec",
|
|
770
|
+
meta={"actual_type": type(spec).__name__},
|
|
771
|
+
)
|
|
772
|
+
return await self._workflow_engine.execute(spec=spec, input=input, context=child_ctx, services=self)
|
|
773
|
+
|
|
774
|
+
async def execute_capability(
|
|
775
|
+
self,
|
|
776
|
+
*,
|
|
777
|
+
spec: AnySpec,
|
|
778
|
+
input: Dict[str, Any],
|
|
779
|
+
context: ExecutionContext,
|
|
780
|
+
) -> CapabilityResult:
|
|
781
|
+
"""RuntimeServices 协议方法:执行能力并返回终态结果。"""
|
|
782
|
+
|
|
783
|
+
return await self._execute(spec=spec, input=input, context=context)
|
|
784
|
+
|
|
785
|
+
async def _execute_workflow_stream(
|
|
786
|
+
self,
|
|
787
|
+
*,
|
|
788
|
+
spec: AnySpec,
|
|
789
|
+
input: Dict[str, Any],
|
|
790
|
+
context: ExecutionContext,
|
|
791
|
+
) -> AsyncIterator[Union[WorkflowStreamEvent, CapabilityResult]]:
|
|
792
|
+
"""执行 WorkflowSpec(流式):轻量 workflow 事件 + 终态 CapabilityResult。"""
|
|
793
|
+
|
|
794
|
+
base = _get_base(spec)
|
|
795
|
+
try:
|
|
796
|
+
child_ctx = context.child(base.id)
|
|
797
|
+
except RecursionLimitError as exc:
|
|
798
|
+
yield self._build_fail_closed_result(
|
|
799
|
+
run_id=context.run_id,
|
|
800
|
+
capability_id=base.id,
|
|
801
|
+
source="runtime.execute_workflow_stream",
|
|
802
|
+
error=str(exc),
|
|
803
|
+
error_code="RECURSION_LIMIT",
|
|
804
|
+
reason="recursion_limit",
|
|
805
|
+
completion_reason="recursion_limit",
|
|
806
|
+
meta={"error_type": "recursion_limit"},
|
|
807
|
+
)
|
|
808
|
+
return
|
|
809
|
+
|
|
810
|
+
if not isinstance(spec, WorkflowSpec):
|
|
811
|
+
yield self._build_fail_closed_result(
|
|
812
|
+
run_id=child_ctx.run_id,
|
|
813
|
+
capability_id=base.id,
|
|
814
|
+
source="runtime.execute_workflow_stream",
|
|
815
|
+
error=f"Invalid workflow spec type: {type(spec).__name__}",
|
|
816
|
+
error_code="INVALID_WORKFLOW_SPEC",
|
|
817
|
+
reason="invalid_spec",
|
|
818
|
+
completion_reason="invalid_workflow_spec",
|
|
819
|
+
meta={"actual_type": type(spec).__name__},
|
|
820
|
+
)
|
|
821
|
+
return
|
|
822
|
+
|
|
823
|
+
async for item in self._workflow_engine.execute_stream(
|
|
824
|
+
spec=spec, input=input, context=child_ctx, services=self
|
|
825
|
+
):
|
|
826
|
+
yield item
|
|
827
|
+
|
|
828
|
+
async def _execute_agent_stream(
|
|
829
|
+
self, *, spec: AnySpec, input: Dict[str, Any], context: ExecutionContext
|
|
830
|
+
) -> AsyncIterator[Union[AgentEvent, CapabilityResult]]:
|
|
831
|
+
"""
|
|
832
|
+
执行 AgentSpec(流式)。
|
|
833
|
+
|
|
834
|
+
说明:
|
|
835
|
+
- mock 模式:直接调用 mock_handler,产出 CapabilityResult;
|
|
836
|
+
- bridge/sdk_native:使用上游 SDK Agent 执行并转发 AgentEvent,最终聚合 NodeReport。
|
|
837
|
+
"""
|
|
838
|
+
|
|
839
|
+
if not isinstance(spec, AgentSpec):
|
|
840
|
+
yield self._build_fail_closed_result(
|
|
841
|
+
run_id=context.run_id,
|
|
842
|
+
capability_id=_get_base(spec).id,
|
|
843
|
+
source="runtime.execute_agent_stream",
|
|
844
|
+
error=f"Invalid agent spec type: {type(spec).__name__}",
|
|
845
|
+
error_code="INVALID_AGENT_SPEC",
|
|
846
|
+
reason="invalid_spec",
|
|
847
|
+
completion_reason="invalid_agent_spec",
|
|
848
|
+
meta={"actual_type": type(spec).__name__},
|
|
849
|
+
)
|
|
850
|
+
return
|
|
851
|
+
|
|
852
|
+
async for item in self._agent_adapter.execute_stream(spec=spec, input=input, context=context):
|
|
853
|
+
yield item
|
|
854
|
+
|
|
855
|
+
def build_fail_closed_report(
|
|
856
|
+
self,
|
|
857
|
+
*,
|
|
858
|
+
run_id: str,
|
|
859
|
+
status: str,
|
|
860
|
+
reason: Optional[str],
|
|
861
|
+
completion_reason: str,
|
|
862
|
+
meta: Dict[str, Any],
|
|
863
|
+
) -> NodeReport:
|
|
864
|
+
"""
|
|
865
|
+
RuntimeServices 协议方法:构造 fail-closed NodeReport。
|
|
866
|
+
"""
|
|
867
|
+
|
|
868
|
+
return build_fail_closed_report(
|
|
869
|
+
run_id=run_id,
|
|
870
|
+
status=status,
|
|
871
|
+
reason=reason,
|
|
872
|
+
completion_reason=completion_reason,
|
|
873
|
+
meta=meta,
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
def apply_output_validation(
|
|
877
|
+
self,
|
|
878
|
+
*,
|
|
879
|
+
final_output: Any,
|
|
880
|
+
report: NodeReport,
|
|
881
|
+
context: Dict[str, Any],
|
|
882
|
+
output_schema: Optional[Any] = None,
|
|
883
|
+
) -> None:
|
|
884
|
+
"""
|
|
885
|
+
RuntimeServices 协议方法:执行输出校验并写入 NodeReport.meta。
|
|
886
|
+
"""
|
|
887
|
+
|
|
888
|
+
self._output_validator.validate(
|
|
889
|
+
final_output=final_output,
|
|
890
|
+
report=report,
|
|
891
|
+
context=context,
|
|
892
|
+
output_schema=output_schema,
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
def redact_issue(self, issue: Any) -> Dict[str, Any]:
|
|
896
|
+
"""RuntimeServices 协议方法:issue 最小披露归一。"""
|
|
897
|
+
|
|
898
|
+
return redact_issue(issue)
|
|
899
|
+
|
|
900
|
+
def get_host_meta(self, *, context: ExecutionContext) -> Dict[str, Any]:
|
|
901
|
+
"""RuntimeServices 协议方法:读取 host 保留元数据。"""
|
|
902
|
+
|
|
903
|
+
return get_host_meta(context=context)
|
|
904
|
+
|
|
905
|
+
def call_callback(self, cb: Any, *args: Any) -> None:
|
|
906
|
+
"""RuntimeServices 协议方法:兼容 callback 调用。"""
|
|
907
|
+
|
|
908
|
+
call_callback(cb, *args)
|
|
909
|
+
|
|
910
|
+
def preflight(self) -> list[Any]:
|
|
911
|
+
"""RuntimeServices 协议方法:执行 skills preflight。"""
|
|
912
|
+
|
|
913
|
+
if self._sdk is None:
|
|
914
|
+
return []
|
|
915
|
+
return self._sdk.preflight()
|
|
916
|
+
|
|
917
|
+
def create_sdk_agent(self, *, llm_config: Optional[Dict[str, Any]] = None) -> Any:
|
|
918
|
+
"""
|
|
919
|
+
RuntimeServices 协议方法:创建 per-run SDK Agent。
|
|
920
|
+
|
|
921
|
+
参数:
|
|
922
|
+
- llm_config:可选 LLM 覆写配置(当前仅支持 `model` 字段覆写)
|
|
923
|
+
"""
|
|
924
|
+
|
|
925
|
+
if self._sdk is None:
|
|
926
|
+
raise RuntimeError("SDK lifecycle is not initialized")
|
|
927
|
+
return self._sdk.create_agent(custom_tools=list(self._config.custom_tools), llm_config=llm_config)
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
__all__ = ["Runtime"]
|