capability-runtime 0.1.0__tar.gz → 0.1.1__tar.gz
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-0.1.0 → capability_runtime-0.1.1}/PKG-INFO +2 -2
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/pyproject.toml +2 -2
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/__init__.py +3 -2
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/adapters/agent_adapter.py +294 -5
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/__init__.py +2 -1
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/agent.py +8 -1
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/runtime.py +16 -3
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/sdk_lifecycle.py +100 -4
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/services.py +10 -2
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime.egg-info/PKG-INFO +2 -2
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime.egg-info/SOURCES.txt +1 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime.egg-info/requires.txt +1 -1
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_dependency_pins.py +2 -1
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_per_capability_llm_config_model_routing.py +131 -1
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_public_api_exports.py +1 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_release_tag_version_guardrail.py +2 -2
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_initial_history_and_meta.py +47 -0
- capability_runtime-0.1.1/tests/test_upstream_prompt_profile_contract.py +101 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/README.md +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/setup.cfg +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/adapters/__init__.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/adapters/agently_backend.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/adapters/triggerflow_workflow_engine.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/adapters/workflow_engine.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/config.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/errors.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/guards.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_protocol.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/__init__.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/approvals_profiles.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/evidence_hooks.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/history.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/invoke_capability.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/resume.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/system_prompt.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/host_toolkit/turn_delta.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/logging_utils.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/manifest.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/output_validator.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/capability.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/chat_backend.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/context.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/workflow.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/registry.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/reporting/__init__.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/reporting/node_report.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/runtime_ui_events_mixin.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/service_facade.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/structured_output.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/structured_stream.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/types.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/__init__.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/projector.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/session.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/store.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/transport.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/ui_events/v1.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/upstream_compat.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/utils/__init__.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/utils/usage.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/workflow_runtime.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime.egg-info/dependency_links.txt +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime.egg-info/top_level.txt +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_agently_backend.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_agently_backend_replay.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_backend_mode.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_bilingual_docs_surface.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_bridge_artifacts_passthrough.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_bridge_register_tool_public_api.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_coding_agent_examples_atomic.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_coding_agent_examples_recipes.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_config_glue.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_docs_pinned_dependency_versions.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_docs_scheme2_no_skilladapter_residue.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_error_observability.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_alignment_fixes_l1.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_no_agent_sdk_imports.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_real_evidence_strict_integration.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_real_integration.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_smoke.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_ui_events_showcase_offline.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_ui_events_showcase_real_fallback.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_ui_events_showcase_real_integration.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_ui_events_showcase_ui_contract.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_examples_workflow_skills_first_smoke.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_guards.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_approvals_profiles.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_history_assembler.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_invoke_capability.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_invoke_capability_shared_runtime.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_resume_helper.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_system_prompt_evidence.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_host_toolkit_turn_delta.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_integration_agently_requester_smoke.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_integration_approval_event_shape.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_integration_sources_redis_pgsql_smoke.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_loop.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_node_report_builder.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_node_report_contract_v1.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_node_report_engine_identity_contract.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_offline_backend_injection_evidence.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_preflight_gate.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_project_identity_naming_matrix.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_public_repo_hygiene.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_qa_agent.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_registry.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_replay_tool_calls_alignment.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_repo_hygiene_no_tracked_env_or_pyc.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_repo_no_deep_imports_in_user_facing_docs.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_review_followups_module_contracts.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_bridge_fake_backend.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_concurrency.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_engine.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_hitl_host_protocol.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_hooks_and_schema_gate.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_manifest.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_run_stream_semantics_v1.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_service_facade_rpc_mapping.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_service_session_bridge.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_status_mapping.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_structured_output_bridge.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_runtime_structured_stream.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_sandbox_permissions_passthrough_contract.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_services_call_callback.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_services_map_node_status.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_skills_conformance_smoke.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_adapter_tool_registration.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_chat_backend_protocol_contract.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_chat_sse_usage_contract.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_compat_spaces_schema.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_runtime_client_server_contract.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_sandbox_profile_precedence_contract.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_skills_bundles_contract.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_tool_descriptor_compat.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_upstream_verification.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_versioning_strategy_guard.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_wal_backend_injection_contract.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_wal_locator_resolution_contract.py +0 -0
- {capability_runtime-0.1.0 → capability_runtime-0.1.1}/tests/test_workflow_host_runtime_surface.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: capability-runtime
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Bridge/glue layer that composes Agently (LLM/TriggerFlow) with skills-runtime-sdk (skills/tools/WAL/events).
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -8,7 +8,7 @@ Description-Content-Type: text/markdown
|
|
|
8
8
|
Requires-Dist: PyYAML>=6
|
|
9
9
|
Requires-Dist: pydantic<3,>=2
|
|
10
10
|
Requires-Dist: agently==4.0.8
|
|
11
|
-
Requires-Dist: skills-runtime-sdk==0.1.
|
|
11
|
+
Requires-Dist: skills-runtime-sdk==0.1.12
|
|
12
12
|
Provides-Extra: dev
|
|
13
13
|
Requires-Dist: pytest>=7; extra == "dev"
|
|
14
14
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "capability-runtime"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.1"
|
|
4
4
|
description = "Bridge/glue layer that composes Agently (LLM/TriggerFlow) with skills-runtime-sdk (skills/tools/WAL/events)."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -9,7 +9,7 @@ dependencies = [
|
|
|
9
9
|
"PyYAML>=6",
|
|
10
10
|
"pydantic>=2,<3",
|
|
11
11
|
"agently==4.0.8",
|
|
12
|
-
"skills-runtime-sdk==0.1.
|
|
12
|
+
"skills-runtime-sdk==0.1.12",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
[project.optional-dependencies]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""capability-runtime:统一 Runtime 入口(能力协议 + 执行 + 报告)。"""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
__version__ = "0.1.
|
|
4
|
+
__version__ = "0.1.1"
|
|
5
5
|
|
|
6
6
|
# === 统一入口 ===
|
|
7
7
|
from .config import CustomTool, RuntimeConfig
|
|
@@ -18,7 +18,7 @@ from .host_protocol import ApprovalTicket, HostRunSnapshot, HostRunStatus, Resum
|
|
|
18
18
|
from .manifest import CapabilityDescriptor, CapabilityManifestEntry, CapabilityVisibility
|
|
19
19
|
|
|
20
20
|
# === Protocol 导出 ===
|
|
21
|
-
from .protocol.agent import AgentIOSchema, AgentSpec
|
|
21
|
+
from .protocol.agent import AgentIOSchema, AgentSpec, PromptRenderMode
|
|
22
22
|
from .protocol.capability import (
|
|
23
23
|
CapabilityKind,
|
|
24
24
|
CapabilityRef,
|
|
@@ -76,6 +76,7 @@ __all__ = [
|
|
|
76
76
|
"RuntimeServiceFacade",
|
|
77
77
|
"AgentSpec",
|
|
78
78
|
"AgentIOSchema",
|
|
79
|
+
"PromptRenderMode",
|
|
79
80
|
"WorkflowSpec",
|
|
80
81
|
"Step",
|
|
81
82
|
"LoopStep",
|
|
@@ -10,8 +10,11 @@ AgentAdapter:AgentSpec 的执行适配器(统一 mock/bridge/sdk_native)
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import asyncio
|
|
13
|
+
import hashlib
|
|
13
14
|
import inspect
|
|
14
15
|
import json
|
|
16
|
+
import re
|
|
17
|
+
from dataclasses import dataclass
|
|
15
18
|
from typing import Any, AsyncIterator, Dict, List, Optional, Union
|
|
16
19
|
|
|
17
20
|
from skills_runtime.core.contracts import AgentEvent
|
|
@@ -30,6 +33,34 @@ _SECTION_TASK = "## 任务"
|
|
|
30
33
|
_SECTION_INPUT = "## 输入"
|
|
31
34
|
_SECTION_OUTPUT_PREFIX = "## 输出要求"
|
|
32
35
|
_SECTION_SKILLS = "## 使用以下 Skills"
|
|
36
|
+
_RUNTIME_PROMPT_KEY = "_runtime_prompt"
|
|
37
|
+
_PROMPT_HASH_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
|
|
38
|
+
_ALLOWED_PROMPT_ROLES = {"system", "developer", "user", "assistant", "tool"}
|
|
39
|
+
_ALLOWED_PROMPT_PROFILES = {"default_agent", "generation_direct", "structured_transform"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _InvalidPromptMessages(ValueError):
|
|
43
|
+
"""prompt control envelope 非法。"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class _PromptRenderPlan:
|
|
48
|
+
"""
|
|
49
|
+
AgentAdapter 内部 prompt 渲染计划。
|
|
50
|
+
|
|
51
|
+
字段:
|
|
52
|
+
- mode:三种 prompt rendering strategy 之一;
|
|
53
|
+
- task:传给 SDK Agent.run_stream_async 的 task 文本;
|
|
54
|
+
- prompt_profile:透传给上游 SDK 的 prompt profile;
|
|
55
|
+
- precomposed_messages:最终 provider messages 覆写;
|
|
56
|
+
- evidence:写入 NodeReport.meta 的最小披露摘要。
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
mode: str
|
|
60
|
+
task: str
|
|
61
|
+
prompt_profile: Optional[str]
|
|
62
|
+
precomposed_messages: Optional[List[Dict[str, Any]]]
|
|
63
|
+
evidence: Dict[str, Any]
|
|
33
64
|
|
|
34
65
|
|
|
35
66
|
class AgentAdapter:
|
|
@@ -168,6 +199,29 @@ class AgentAdapter:
|
|
|
168
199
|
真实执行(bridge/sdk_native):驱动 SDK Agent.run_stream_async 并聚合 NodeReport。
|
|
169
200
|
"""
|
|
170
201
|
|
|
202
|
+
try:
|
|
203
|
+
prompt_plan = self._resolve_prompt_render_plan(spec=spec, input=input)
|
|
204
|
+
except _InvalidPromptMessages as exc:
|
|
205
|
+
report = self._services.build_fail_closed_report(
|
|
206
|
+
run_id=context.run_id,
|
|
207
|
+
status="failed",
|
|
208
|
+
reason="invalid_prompt_messages",
|
|
209
|
+
completion_reason="invalid_prompt_messages",
|
|
210
|
+
meta={
|
|
211
|
+
"capability_id": spec.base.id,
|
|
212
|
+
"source": "prompt_rendering",
|
|
213
|
+
"prompt_error": str(exc),
|
|
214
|
+
},
|
|
215
|
+
)
|
|
216
|
+
yield CapabilityResult(
|
|
217
|
+
status=CapabilityStatus.FAILED,
|
|
218
|
+
error=str(exc),
|
|
219
|
+
error_code="INVALID_PROMPT_MESSAGES",
|
|
220
|
+
report=report,
|
|
221
|
+
node_report=report,
|
|
222
|
+
)
|
|
223
|
+
return
|
|
224
|
+
|
|
171
225
|
# preflight gate(生产默认 fail-closed)
|
|
172
226
|
issues: List[FrameworkIssue] = []
|
|
173
227
|
if getattr(self._services.config, "preflight_mode", "error") != "off":
|
|
@@ -184,6 +238,7 @@ class AgentAdapter:
|
|
|
184
238
|
"code": "SKILL_PREFLIGHT_FAILED",
|
|
185
239
|
"details": {"issues": [self._services.redact_issue(i) for i in issues]},
|
|
186
240
|
},
|
|
241
|
+
**prompt_plan.evidence,
|
|
187
242
|
},
|
|
188
243
|
)
|
|
189
244
|
yield CapabilityResult(
|
|
@@ -196,8 +251,34 @@ class AgentAdapter:
|
|
|
196
251
|
)
|
|
197
252
|
return
|
|
198
253
|
|
|
199
|
-
task =
|
|
200
|
-
|
|
254
|
+
task = prompt_plan.task
|
|
255
|
+
create_agent_kwargs: Dict[str, Any] = {"llm_config": spec.llm_config}
|
|
256
|
+
if prompt_plan.prompt_profile is not None:
|
|
257
|
+
create_agent_kwargs["prompt_profile"] = prompt_plan.prompt_profile
|
|
258
|
+
if prompt_plan.precomposed_messages is not None:
|
|
259
|
+
create_agent_kwargs["precomposed_messages"] = prompt_plan.precomposed_messages
|
|
260
|
+
try:
|
|
261
|
+
agent = self._services.create_sdk_agent(**create_agent_kwargs)
|
|
262
|
+
except Exception as exc:
|
|
263
|
+
report = self._services.build_fail_closed_report(
|
|
264
|
+
run_id=context.run_id,
|
|
265
|
+
status="failed",
|
|
266
|
+
reason="engine_error",
|
|
267
|
+
completion_reason="engine_exception",
|
|
268
|
+
meta={
|
|
269
|
+
"source": "sdk_agent_create",
|
|
270
|
+
"engine_exception": type(exc).__name__,
|
|
271
|
+
**prompt_plan.evidence,
|
|
272
|
+
},
|
|
273
|
+
)
|
|
274
|
+
yield CapabilityResult(
|
|
275
|
+
status=CapabilityStatus.FAILED,
|
|
276
|
+
error=str(exc),
|
|
277
|
+
error_code="ENGINE_ERROR",
|
|
278
|
+
report=report,
|
|
279
|
+
node_report=report,
|
|
280
|
+
)
|
|
281
|
+
return
|
|
201
282
|
|
|
202
283
|
events: List[AgentEvent] = []
|
|
203
284
|
host_meta = self._services.get_host_meta(context=context)
|
|
@@ -241,6 +322,7 @@ class AgentAdapter:
|
|
|
241
322
|
completion_reason="run_cancelled",
|
|
242
323
|
meta={"capability_id": spec.base.id, "source": "sdk_agent"},
|
|
243
324
|
)
|
|
325
|
+
self._apply_prompt_evidence(report=report, evidence=prompt_plan.evidence)
|
|
244
326
|
yield CapabilityResult(
|
|
245
327
|
status=CapabilityStatus.CANCELLED,
|
|
246
328
|
error="execution cancelled",
|
|
@@ -255,7 +337,7 @@ class AgentAdapter:
|
|
|
255
337
|
status="failed",
|
|
256
338
|
reason="engine_error",
|
|
257
339
|
completion_reason="engine_exception",
|
|
258
|
-
meta={"engine_exception": type(exc).__name__},
|
|
340
|
+
meta={"engine_exception": type(exc).__name__, **prompt_plan.evidence},
|
|
259
341
|
)
|
|
260
342
|
yield CapabilityResult(
|
|
261
343
|
status=CapabilityStatus.FAILED,
|
|
@@ -267,6 +349,7 @@ class AgentAdapter:
|
|
|
267
349
|
return
|
|
268
350
|
|
|
269
351
|
report = NodeReportBuilder().build(events=events)
|
|
352
|
+
self._apply_prompt_evidence(report=report, evidence=prompt_plan.evidence)
|
|
270
353
|
if issues and getattr(self._services.config, "preflight_mode", "error") == "warn":
|
|
271
354
|
report.meta["preflight_mode"] = "warn"
|
|
272
355
|
report.meta["preflight_issues"] = [self._services.redact_issue(i) for i in issues]
|
|
@@ -306,6 +389,166 @@ class AgentAdapter:
|
|
|
306
389
|
artifacts=list(report.artifacts),
|
|
307
390
|
)
|
|
308
391
|
|
|
392
|
+
def _resolve_prompt_render_plan(self, *, spec: AgentSpec, input: Dict[str, Any]) -> _PromptRenderPlan:
|
|
393
|
+
"""
|
|
394
|
+
解析 AgentSpec + run-level `_runtime_prompt`,生成 prompt 渲染计划。
|
|
395
|
+
|
|
396
|
+
参数:
|
|
397
|
+
- spec:Agent 声明
|
|
398
|
+
- input:run 输入,其中 `_runtime_prompt` 为保留控制面
|
|
399
|
+
|
|
400
|
+
返回:
|
|
401
|
+
- `_PromptRenderPlan`
|
|
402
|
+
|
|
403
|
+
异常:
|
|
404
|
+
- `_InvalidPromptMessages`:控制面非法,调用方应 fail-fast。
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
envelope_raw = input.get(_RUNTIME_PROMPT_KEY)
|
|
408
|
+
if envelope_raw is None:
|
|
409
|
+
envelope: Dict[str, Any] = {}
|
|
410
|
+
elif isinstance(envelope_raw, dict):
|
|
411
|
+
envelope = dict(envelope_raw)
|
|
412
|
+
else:
|
|
413
|
+
raise _InvalidPromptMessages("_runtime_prompt must be a dict")
|
|
414
|
+
|
|
415
|
+
raw_mode = envelope.get("mode") if "mode" in envelope else getattr(spec, "prompt_render_mode", "structured_task")
|
|
416
|
+
if raw_mode is None:
|
|
417
|
+
raw_mode = "structured_task"
|
|
418
|
+
if not isinstance(raw_mode, str) or not raw_mode.strip():
|
|
419
|
+
raise _InvalidPromptMessages("prompt render mode must be a non-empty string")
|
|
420
|
+
mode = raw_mode.strip()
|
|
421
|
+
if mode not in {"structured_task", "direct_task_text", "precomposed_messages"}:
|
|
422
|
+
raise _InvalidPromptMessages(
|
|
423
|
+
"unsupported prompt render mode; allowed: direct_task_text, precomposed_messages, structured_task"
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
prompt_profile = envelope.get("profile")
|
|
427
|
+
if prompt_profile is None:
|
|
428
|
+
prompt_profile = getattr(spec, "prompt_profile", None)
|
|
429
|
+
if prompt_profile is not None:
|
|
430
|
+
if not isinstance(prompt_profile, str) or not prompt_profile.strip():
|
|
431
|
+
raise _InvalidPromptMessages("prompt profile must be a non-empty string")
|
|
432
|
+
prompt_profile = prompt_profile.strip()
|
|
433
|
+
if prompt_profile not in _ALLOWED_PROMPT_PROFILES:
|
|
434
|
+
allowed = ", ".join(sorted(_ALLOWED_PROMPT_PROFILES))
|
|
435
|
+
raise _InvalidPromptMessages(f"unsupported prompt profile; allowed: {allowed}")
|
|
436
|
+
|
|
437
|
+
trace = envelope.get("trace") if "trace" in envelope else {}
|
|
438
|
+
if trace is None:
|
|
439
|
+
trace = {}
|
|
440
|
+
if not isinstance(trace, dict):
|
|
441
|
+
raise _InvalidPromptMessages("_runtime_prompt.trace must be a dict")
|
|
442
|
+
prompt_hash = trace.get("prompt_hash")
|
|
443
|
+
if prompt_hash is not None:
|
|
444
|
+
if not isinstance(prompt_hash, str) or _PROMPT_HASH_RE.fullmatch(prompt_hash.strip()) is None:
|
|
445
|
+
raise _InvalidPromptMessages("trace.prompt_hash must match sha256:<64 lowercase hex>")
|
|
446
|
+
prompt_hash = prompt_hash.strip()
|
|
447
|
+
composer_version = trace.get("composer_version")
|
|
448
|
+
if composer_version is not None and not isinstance(composer_version, str):
|
|
449
|
+
raise _InvalidPromptMessages("trace.composer_version must be a string")
|
|
450
|
+
|
|
451
|
+
if mode == "structured_task":
|
|
452
|
+
business_input = self._strip_runtime_prompt(input)
|
|
453
|
+
task = self._build_task(spec=spec, input=business_input)
|
|
454
|
+
evidence = self._build_prompt_evidence(
|
|
455
|
+
mode=mode,
|
|
456
|
+
prompt_profile=prompt_profile,
|
|
457
|
+
prompt_hash=prompt_hash or _hash_text(task),
|
|
458
|
+
composer_version=composer_version,
|
|
459
|
+
messages=None,
|
|
460
|
+
)
|
|
461
|
+
return _PromptRenderPlan(
|
|
462
|
+
mode=mode,
|
|
463
|
+
task=task,
|
|
464
|
+
prompt_profile=prompt_profile,
|
|
465
|
+
precomposed_messages=None,
|
|
466
|
+
evidence=evidence,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
if mode == "direct_task_text":
|
|
470
|
+
task_text = envelope.get("task_text")
|
|
471
|
+
if not isinstance(task_text, str) or not task_text.strip():
|
|
472
|
+
raise _InvalidPromptMessages("_runtime_prompt.task_text must be a non-empty string")
|
|
473
|
+
task = task_text
|
|
474
|
+
evidence = self._build_prompt_evidence(
|
|
475
|
+
mode=mode,
|
|
476
|
+
prompt_profile=prompt_profile,
|
|
477
|
+
prompt_hash=prompt_hash or _hash_text(task),
|
|
478
|
+
composer_version=composer_version,
|
|
479
|
+
messages=None,
|
|
480
|
+
)
|
|
481
|
+
return _PromptRenderPlan(
|
|
482
|
+
mode=mode,
|
|
483
|
+
task=task,
|
|
484
|
+
prompt_profile=prompt_profile,
|
|
485
|
+
precomposed_messages=None,
|
|
486
|
+
evidence=evidence,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
messages = _validate_precomposed_messages(envelope.get("messages"))
|
|
490
|
+
evidence = self._build_prompt_evidence(
|
|
491
|
+
mode=mode,
|
|
492
|
+
prompt_profile=prompt_profile,
|
|
493
|
+
prompt_hash=prompt_hash or _hash_messages(messages),
|
|
494
|
+
composer_version=composer_version,
|
|
495
|
+
messages=messages,
|
|
496
|
+
)
|
|
497
|
+
return _PromptRenderPlan(
|
|
498
|
+
mode=mode,
|
|
499
|
+
task="",
|
|
500
|
+
prompt_profile=prompt_profile,
|
|
501
|
+
precomposed_messages=messages,
|
|
502
|
+
evidence=evidence,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
def _build_prompt_evidence(
|
|
506
|
+
self,
|
|
507
|
+
*,
|
|
508
|
+
mode: str,
|
|
509
|
+
prompt_profile: Optional[str],
|
|
510
|
+
prompt_hash: str,
|
|
511
|
+
composer_version: Any,
|
|
512
|
+
messages: Optional[List[Dict[str, Any]]],
|
|
513
|
+
) -> Dict[str, Any]:
|
|
514
|
+
"""
|
|
515
|
+
构造 NodeReport.meta 的 prompt 最小披露摘要。
|
|
516
|
+
|
|
517
|
+
参数:
|
|
518
|
+
- mode:prompt render mode
|
|
519
|
+
- prompt_profile:可选 SDK prompt profile
|
|
520
|
+
- prompt_hash:`sha256:<hex>` 摘要
|
|
521
|
+
- composer_version:可选 composer 版本
|
|
522
|
+
- messages:precomposed messages;仅用于 count/roles 摘要
|
|
523
|
+
"""
|
|
524
|
+
|
|
525
|
+
evidence: Dict[str, Any] = {
|
|
526
|
+
"prompt_render_mode": mode,
|
|
527
|
+
"prompt_hash": prompt_hash,
|
|
528
|
+
}
|
|
529
|
+
if prompt_profile:
|
|
530
|
+
evidence["prompt_profile"] = prompt_profile
|
|
531
|
+
if isinstance(composer_version, str) and composer_version:
|
|
532
|
+
evidence["prompt_composer_version"] = composer_version
|
|
533
|
+
if messages is not None:
|
|
534
|
+
evidence["prompt_messages_count"] = len(messages)
|
|
535
|
+
evidence["prompt_message_roles"] = [str(item["role"]) for item in messages]
|
|
536
|
+
return evidence
|
|
537
|
+
|
|
538
|
+
def _strip_runtime_prompt(self, input: Dict[str, Any]) -> Dict[str, Any]:
|
|
539
|
+
"""从业务 input 中剥离 runtime prompt 控制面。"""
|
|
540
|
+
|
|
541
|
+
if _RUNTIME_PROMPT_KEY not in input:
|
|
542
|
+
return input
|
|
543
|
+
return {k: v for k, v in input.items() if k != _RUNTIME_PROMPT_KEY}
|
|
544
|
+
|
|
545
|
+
def _apply_prompt_evidence(self, *, report: Any, evidence: Dict[str, Any]) -> None:
|
|
546
|
+
"""把 prompt evidence 写入 NodeReport.meta;兼容测试中的 dict fake report。"""
|
|
547
|
+
|
|
548
|
+
meta = getattr(report, "meta", None)
|
|
549
|
+
if isinstance(meta, dict):
|
|
550
|
+
meta.update(evidence)
|
|
551
|
+
|
|
309
552
|
def _build_task(self, *, spec: AgentSpec, input: Dict[str, Any]) -> str:
|
|
310
553
|
"""
|
|
311
554
|
将 AgentSpec + input 转换为 SDK Agent 的 task 文本(结构化拼接)。
|
|
@@ -323,9 +566,10 @@ class AgentAdapter:
|
|
|
323
566
|
if spec.base.description:
|
|
324
567
|
parts.append(f"{_SECTION_TASK}\n{spec.base.description}")
|
|
325
568
|
|
|
326
|
-
|
|
569
|
+
business_input = self._strip_runtime_prompt(input)
|
|
570
|
+
if business_input:
|
|
327
571
|
lines: List[str] = []
|
|
328
|
-
for k, v in
|
|
572
|
+
for k, v in business_input.items():
|
|
329
573
|
if isinstance(v, str):
|
|
330
574
|
lines.append(f"- {k}: {v}")
|
|
331
575
|
else:
|
|
@@ -437,3 +681,48 @@ class AgentAdapter:
|
|
|
437
681
|
if account and domain:
|
|
438
682
|
return f"[{account}:{domain}]"
|
|
439
683
|
return None
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def _hash_text(text: str) -> str:
|
|
687
|
+
"""生成 prompt 文本的 sha256 摘要。"""
|
|
688
|
+
|
|
689
|
+
return "sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def _hash_messages(messages: List[Dict[str, Any]]) -> str:
|
|
693
|
+
"""对 provider messages 做稳定 JSON canonicalization 后生成摘要。"""
|
|
694
|
+
|
|
695
|
+
canonical = json.dumps(messages, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
|
696
|
+
return _hash_text(canonical)
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _validate_precomposed_messages(raw: Any) -> List[Dict[str, Any]]:
|
|
700
|
+
"""
|
|
701
|
+
校验 host 提供的最终 provider messages。
|
|
702
|
+
|
|
703
|
+
约束:
|
|
704
|
+
- 只接受非空 list[dict];
|
|
705
|
+
- 每条消息必须有合法 role 与字符串 content;
|
|
706
|
+
- 返回浅拷贝,避免后续执行链路修改调用方输入。
|
|
707
|
+
"""
|
|
708
|
+
|
|
709
|
+
if not isinstance(raw, list):
|
|
710
|
+
raise _InvalidPromptMessages("_runtime_prompt.messages must be a list")
|
|
711
|
+
if not raw:
|
|
712
|
+
raise _InvalidPromptMessages("_runtime_prompt.messages must not be empty")
|
|
713
|
+
|
|
714
|
+
out: List[Dict[str, Any]] = []
|
|
715
|
+
for idx, item in enumerate(raw):
|
|
716
|
+
if not isinstance(item, dict):
|
|
717
|
+
raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}] must be a dict")
|
|
718
|
+
role = item.get("role")
|
|
719
|
+
content = item.get("content")
|
|
720
|
+
if not isinstance(role, str) or role not in _ALLOWED_PROMPT_ROLES:
|
|
721
|
+
raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}].role is invalid")
|
|
722
|
+
if not isinstance(content, str):
|
|
723
|
+
raise _InvalidPromptMessages(f"_runtime_prompt.messages[{idx}].content must be a string")
|
|
724
|
+
copied = dict(item)
|
|
725
|
+
copied["role"] = role
|
|
726
|
+
copied["content"] = content
|
|
727
|
+
out.append(copied)
|
|
728
|
+
return out
|
{capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/__init__.py
RENAMED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
"""Protocol 层:纯能力类型定义,不依赖任何上游模块。"""
|
|
4
4
|
|
|
5
|
-
from .agent import AgentIOSchema, AgentSpec
|
|
5
|
+
from .agent import AgentIOSchema, AgentSpec, PromptRenderMode
|
|
6
6
|
from .capability import (
|
|
7
7
|
CapabilityKind,
|
|
8
8
|
CapabilityRef,
|
|
@@ -30,6 +30,7 @@ __all__ = [
|
|
|
30
30
|
"CapabilityResult",
|
|
31
31
|
"AgentSpec",
|
|
32
32
|
"AgentIOSchema",
|
|
33
|
+
"PromptRenderMode",
|
|
33
34
|
"ChatBackendProtocol",
|
|
34
35
|
"WorkflowSpec",
|
|
35
36
|
"Step",
|
{capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/protocol/agent.py
RENAMED
|
@@ -3,11 +3,14 @@ from __future__ import annotations
|
|
|
3
3
|
"""Agent 元能力声明。"""
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import Any, Dict, List, Optional
|
|
6
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
7
7
|
|
|
8
8
|
from .capability import CapabilityRef, CapabilitySpec
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
PromptRenderMode = Literal["structured_task", "direct_task_text", "precomposed_messages"]
|
|
12
|
+
|
|
13
|
+
|
|
11
14
|
@dataclass(frozen=True)
|
|
12
15
|
class AgentIOSchema:
|
|
13
16
|
"""
|
|
@@ -36,6 +39,8 @@ class AgentSpec:
|
|
|
36
39
|
- output_schema: 输出 schema(可选)
|
|
37
40
|
- loop_compatible: 是否可被 LoopStep 循环调用
|
|
38
41
|
- llm_config: LLM 覆盖配置
|
|
42
|
+
- prompt_render_mode: prompt 渲染模式(默认 structured_task,保持兼容)
|
|
43
|
+
- prompt_profile: 可选上游 SDK prompt profile(如 generation_direct)
|
|
39
44
|
- prompt_template: 可选的 prompt 模板(支持 {field} 占位符)
|
|
40
45
|
- system_prompt: 可选的“Agent 级 system message”(用于该 Agent 的提示词组织)
|
|
41
46
|
|
|
@@ -58,5 +63,7 @@ class AgentSpec:
|
|
|
58
63
|
output_schema: Optional[AgentIOSchema] = None
|
|
59
64
|
loop_compatible: bool = False
|
|
60
65
|
llm_config: Optional[Dict[str, Any]] = None
|
|
66
|
+
prompt_render_mode: PromptRenderMode = "structured_task"
|
|
67
|
+
prompt_profile: Optional[str] = None
|
|
61
68
|
prompt_template: Optional[str] = None
|
|
62
69
|
system_prompt: Optional[str] = None
|
|
@@ -914,17 +914,30 @@ class Runtime(RuntimeUIEventsMixin):
|
|
|
914
914
|
return []
|
|
915
915
|
return self._sdk.preflight()
|
|
916
916
|
|
|
917
|
-
def create_sdk_agent(
|
|
917
|
+
def create_sdk_agent(
|
|
918
|
+
self,
|
|
919
|
+
*,
|
|
920
|
+
llm_config: Optional[Dict[str, Any]] = None,
|
|
921
|
+
prompt_profile: Optional[str] = None,
|
|
922
|
+
precomposed_messages: Optional[List[Dict[str, Any]]] = None,
|
|
923
|
+
) -> Any:
|
|
918
924
|
"""
|
|
919
925
|
RuntimeServices 协议方法:创建 per-run SDK Agent。
|
|
920
926
|
|
|
921
927
|
参数:
|
|
922
|
-
- llm_config:可选 LLM
|
|
928
|
+
- llm_config:可选 LLM 覆写配置
|
|
929
|
+
- prompt_profile:可选 SDK prompt profile
|
|
930
|
+
- precomposed_messages:可选最终 provider messages 覆写
|
|
923
931
|
"""
|
|
924
932
|
|
|
925
933
|
if self._sdk is None:
|
|
926
934
|
raise RuntimeError("SDK lifecycle is not initialized")
|
|
927
|
-
return self._sdk.create_agent(
|
|
935
|
+
return self._sdk.create_agent(
|
|
936
|
+
custom_tools=list(self._config.custom_tools),
|
|
937
|
+
llm_config=llm_config,
|
|
938
|
+
prompt_profile=prompt_profile,
|
|
939
|
+
precomposed_messages=precomposed_messages,
|
|
940
|
+
)
|
|
928
941
|
|
|
929
942
|
|
|
930
943
|
__all__ = ["Runtime"]
|
{capability_runtime-0.1.0 → capability_runtime-0.1.1}/src/capability_runtime/sdk_lifecycle.py
RENAMED
|
@@ -2,9 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
"""SDK 生命周期组件:初始化、预检与 per-run Agent 创建。"""
|
|
4
4
|
|
|
5
|
+
import hashlib
|
|
5
6
|
import inspect
|
|
6
|
-
|
|
7
|
+
import uuid
|
|
7
8
|
from dataclasses import dataclass
|
|
9
|
+
from datetime import datetime, timezone
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from typing import Any, AsyncGenerator, AsyncIterator, Dict, List, Optional
|
|
10
12
|
|
|
@@ -83,13 +85,22 @@ class SdkLifecycle:
|
|
|
83
85
|
)
|
|
84
86
|
]
|
|
85
87
|
|
|
86
|
-
def create_agent(
|
|
88
|
+
def create_agent(
|
|
89
|
+
self,
|
|
90
|
+
*,
|
|
91
|
+
custom_tools: List[CustomTool],
|
|
92
|
+
llm_config: Optional[Dict[str, Any]] = None,
|
|
93
|
+
prompt_profile: Optional[str] = None,
|
|
94
|
+
precomposed_messages: Optional[List[Dict[str, Any]]] = None,
|
|
95
|
+
) -> Any:
|
|
87
96
|
"""
|
|
88
97
|
创建 per-run SDK Agent 实例(避免跨 run 共享可变状态)。
|
|
89
98
|
|
|
90
99
|
参数:
|
|
91
100
|
- custom_tools:每次创建 Agent 时要注册的自定义工具列表
|
|
92
101
|
- llm_config:可选 LLM 覆写配置(当前支持 `model`、`tool_choice` 与 `response_format` 字段覆写)
|
|
102
|
+
- prompt_profile:可选 SDK prompt profile,通过 per-run config overlay 透传给上游
|
|
103
|
+
- precomposed_messages:可选最终 provider messages 覆写
|
|
93
104
|
"""
|
|
94
105
|
|
|
95
106
|
from skills_runtime.core.agent import Agent
|
|
@@ -106,12 +117,23 @@ class SdkLifecycle:
|
|
|
106
117
|
override_response_format = _extract_response_format_override(llm_config)
|
|
107
118
|
if override_response_format is not None:
|
|
108
119
|
backend = _ResponseFormatOverrideBackend(backend=backend, response_format=override_response_format)
|
|
120
|
+
if precomposed_messages is not None:
|
|
121
|
+
backend = _PrecomposedMessagesBackend(backend=backend, messages=precomposed_messages)
|
|
109
122
|
usage_bridge_backend = _UsageTapBackend(backend=backend)
|
|
110
123
|
backend = usage_bridge_backend
|
|
111
124
|
|
|
125
|
+
config_paths = list(self._state.config_paths)
|
|
126
|
+
prompt_profile_overlay: Optional[Path] = None
|
|
127
|
+
if prompt_profile is not None:
|
|
128
|
+
prompt_profile_overlay = _write_prompt_profile_overlay(
|
|
129
|
+
workspace_root=self._state.workspace_root,
|
|
130
|
+
prompt_profile=prompt_profile,
|
|
131
|
+
)
|
|
132
|
+
config_paths.append(prompt_profile_overlay)
|
|
133
|
+
|
|
112
134
|
kwargs: Dict[str, Any] = {
|
|
113
135
|
"workspace_root": self._state.workspace_root,
|
|
114
|
-
"config_paths":
|
|
136
|
+
"config_paths": config_paths,
|
|
115
137
|
"env_vars": dict(self._config.env_vars),
|
|
116
138
|
"backend": backend,
|
|
117
139
|
"human_io": self._config.human_io,
|
|
@@ -124,7 +146,18 @@ class SdkLifecycle:
|
|
|
124
146
|
"wal_backend": self._config.wal_backend,
|
|
125
147
|
}
|
|
126
148
|
|
|
127
|
-
|
|
149
|
+
try:
|
|
150
|
+
agent = Agent(**kwargs)
|
|
151
|
+
finally:
|
|
152
|
+
if prompt_profile_overlay is not None:
|
|
153
|
+
try:
|
|
154
|
+
prompt_profile_overlay.unlink(missing_ok=True)
|
|
155
|
+
except Exception as exc:
|
|
156
|
+
log_suppressed_exception(
|
|
157
|
+
context="prompt_profile_overlay_cleanup",
|
|
158
|
+
exc=exc,
|
|
159
|
+
extra={"path": str(prompt_profile_overlay)},
|
|
160
|
+
)
|
|
128
161
|
diagnostics: Dict[str, Dict[str, bool]] = {}
|
|
129
162
|
for t in custom_tools:
|
|
130
163
|
diagnostics[t.spec.name] = _register_custom_tool_compat(agent, t)
|
|
@@ -558,6 +591,39 @@ class _ResponseFormatOverrideBackend:
|
|
|
558
591
|
yield ev
|
|
559
592
|
|
|
560
593
|
|
|
594
|
+
class _PrecomposedMessagesBackend:
|
|
595
|
+
"""
|
|
596
|
+
ChatBackend 薄代理:仅覆写 request.messages,然后委托给底层 backend。
|
|
597
|
+
|
|
598
|
+
说明:
|
|
599
|
+
- 该包装不绕过 SDK Agent / WAL / events,只在 provider request 出口替换 messages;
|
|
600
|
+
- messages 做浅拷贝转发,避免下游 backend 修改 host 输入。
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
def __init__(self, *, backend: ChatBackendProtocol, messages: List[Dict[str, Any]]) -> None:
|
|
604
|
+
self._backend: ChatBackendProtocol = backend
|
|
605
|
+
self._messages: List[Dict[str, Any]] = [dict(item) for item in messages]
|
|
606
|
+
|
|
607
|
+
async def stream_chat(self, request: ChatRequest) -> AsyncGenerator[ChatStreamEvent, None]:
|
|
608
|
+
"""
|
|
609
|
+
覆写 request.messages 并转发 `stream_chat`。
|
|
610
|
+
|
|
611
|
+
参数:
|
|
612
|
+
- request:上游 SDK 生成的 ChatRequest(或兼容对象)
|
|
613
|
+
"""
|
|
614
|
+
|
|
615
|
+
forwarded = _clone_request_with_field_update(
|
|
616
|
+
request,
|
|
617
|
+
field_name="messages",
|
|
618
|
+
value=[dict(item) for item in self._messages],
|
|
619
|
+
dataclasses_context="precomposed_messages_dataclasses_replace",
|
|
620
|
+
clone_context="precomposed_messages_override",
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
async for ev in self._backend.stream_chat(forwarded):
|
|
624
|
+
yield ev
|
|
625
|
+
|
|
626
|
+
|
|
561
627
|
class _UsageTapBackend:
|
|
562
628
|
"""
|
|
563
629
|
ChatBackend 薄代理:通过 request.extra 注入 usage sink,best-effort 收集 bridge usage。
|
|
@@ -737,6 +803,36 @@ def _clone_request_with_extra(request: Any, update_extra: Any) -> Any:
|
|
|
737
803
|
return forwarded
|
|
738
804
|
|
|
739
805
|
|
|
806
|
+
def _write_prompt_profile_overlay(*, workspace_root: Path, prompt_profile: str) -> Path:
|
|
807
|
+
"""
|
|
808
|
+
写入 per-run prompt profile SDK overlay,并返回 overlay 路径。
|
|
809
|
+
|
|
810
|
+
说明:
|
|
811
|
+
- 只写 profile 名称,不写 prompt 明文;
|
|
812
|
+
- 文件名使用 profile 摘要,避免 profile 字符串直接进入路径。
|
|
813
|
+
"""
|
|
814
|
+
|
|
815
|
+
import yaml
|
|
816
|
+
|
|
817
|
+
profile = str(prompt_profile).strip()
|
|
818
|
+
if not profile:
|
|
819
|
+
raise ValueError("prompt_profile must be a non-empty string")
|
|
820
|
+
|
|
821
|
+
digest = hashlib.sha256(profile.encode("utf-8")).hexdigest()[:16]
|
|
822
|
+
overlay_dir = Path(workspace_root) / ".skills_runtime_sdk"
|
|
823
|
+
overlay_dir.mkdir(parents=True, exist_ok=True)
|
|
824
|
+
overlay_path = overlay_dir / f"caprt_prompt_profile_{digest}_{uuid.uuid4().hex}.yaml"
|
|
825
|
+
overlay_path.write_text(
|
|
826
|
+
yaml.safe_dump(
|
|
827
|
+
{"prompt": {"profile": profile}},
|
|
828
|
+
allow_unicode=True,
|
|
829
|
+
sort_keys=True,
|
|
830
|
+
),
|
|
831
|
+
encoding="utf-8",
|
|
832
|
+
)
|
|
833
|
+
return overlay_path
|
|
834
|
+
|
|
835
|
+
|
|
740
836
|
def _load_yaml_dict(path: Path) -> Dict[str, Any]:
|
|
741
837
|
"""
|
|
742
838
|
读取 YAML 文件并返回 dict。
|