Explorar el Código

feat: 补齐编排验证与证据依赖注入

SamLee hace 1 día
padre
commit
b62b17f4fc

+ 14 - 1
agent/agent/orchestration/wiring.py

@@ -6,8 +6,10 @@ from typing import Optional
 
 from .config import OrchestrationConfig
 from .coordinator import TaskCoordinator
+from .evidence import EvidenceProvider
 from .executor import LocalAgentExecutor
 from .protocols import ArtifactStore, EventSink, TaskStore
+from .validation_policy import DeterministicValidator, ValidationPolicy
 
 
 def wire_orchestration(
@@ -16,14 +18,25 @@ def wire_orchestration(
     artifact_store: ArtifactStore,
     config: Optional[OrchestrationConfig] = None,
     event_sink: Optional[EventSink] = None,
+    *,
+    validation_policy: Optional[ValidationPolicy] = None,
+    deterministic_validator: Optional[DeterministicValidator] = None,
+    evidence_provider: Optional[EvidenceProvider] = None,
 ) -> TaskCoordinator:
-    """Wire ports and adapters without importing any business project."""
+    """Wire generic orchestration ports without importing a business project.
+
+    Validation and evidence dependencies are optional keyword-only extension
+    points. Existing positional calls keep the default Coordinator behavior.
+    """
     coordinator = TaskCoordinator(
         task_store=task_store,
         artifact_store=artifact_store,
         trace_store=runner.trace_store,
         config=config or OrchestrationConfig(),
         event_sink=event_sink,
+        validation_policy=validation_policy,
+        deterministic_validator=deterministic_validator,
+        evidence_provider=evidence_provider,
     )
     executor = LocalAgentExecutor(runner)
     coordinator.set_executor(executor)

+ 122 - 1
agent/tests/test_orchestration_validation_policy.py

@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import asyncio
+from types import SimpleNamespace
 
 import pytest
 
@@ -26,14 +27,21 @@ from agent.orchestration.models import (
     ValidationVerdict,
 )
 from agent.orchestration.protocols import WorkerRunResult
+from agent.orchestration.store import FileSystemArtifactStore, FileSystemTaskStore
 from agent.orchestration.validation_policy import (
     DefaultValidationPolicy,
     DeterministicRuleResult,
     RuleBasedDeterministicValidator,
     ValidationContext,
 )
+from agent.orchestration.wiring import wire_orchestration
 
-from test_coordinator_integration import FakeExecutor, create_task, make_coordinator
+from test_coordinator_integration import (
+    ROOT_TASK_SPEC,
+    FakeExecutor,
+    create_task,
+    make_coordinator,
+)
 
 
 def make_context(root: str = "root") -> ValidationContext:
@@ -235,6 +243,63 @@ async def test_coordinator_executes_deterministic_plan_without_agent_validator(
     assert report.criterion_results[0].criterion_id == "c1"
 
 
+def test_wire_orchestration_preserves_legacy_positional_defaults(tmp_path):
+    runner = SimpleNamespace(trace_store=None, task_coordinator=None)
+    config = OrchestrationConfig(max_parallel_tasks=2)
+    event_sink = object()
+
+    coordinator = wire_orchestration(
+        runner,
+        FileSystemTaskStore(str(tmp_path)),
+        FileSystemArtifactStore(str(tmp_path)),
+        config,
+        event_sink,
+    )
+
+    assert runner.task_coordinator is coordinator
+    assert coordinator.config is config
+    assert coordinator.event_sink is event_sink
+    assert isinstance(coordinator.validation_policy, DefaultValidationPolicy)
+    assert coordinator.deterministic_validator is None
+    assert coordinator.evidence_provider is None
+
+
+@pytest.mark.asyncio
+async def test_wire_orchestration_applies_custom_validation_dependencies(tmp_path):
+    runner = SimpleNamespace(trace_store=None, task_coordinator=None)
+    policy = DeterministicPolicy()
+    validator = RuleBasedDeterministicValidator(
+        [StaticRule("c1", ValidationVerdict.PASSED)]
+    )
+    provider = EvidenceProvider()
+    coordinator = wire_orchestration(
+        runner,
+        FileSystemTaskStore(str(tmp_path)),
+        FileSystemArtifactStore(str(tmp_path)),
+        validation_policy=policy,
+        deterministic_validator=validator,
+        evidence_provider=provider,
+    )
+    executor = FakeExecutor([])
+    executor.coordinator = coordinator
+    coordinator.set_executor(executor)
+    await coordinator.ensure_ledger("root", ROOT_TASK_SPEC)
+    task_id = await create_task(coordinator, "wired deterministic validation")
+
+    result = (await coordinator.dispatch_tasks("root", [task_id]))[0]
+    report = (await coordinator.task_store.load("root")).validations[
+        result.validation_id
+    ]
+
+    assert runner.task_coordinator is coordinator
+    assert coordinator.validation_policy is policy
+    assert coordinator.deterministic_validator is validator
+    assert coordinator.evidence_provider is provider
+    assert executor.validator_calls == 0
+    assert report.validation_plan.mode == ValidationMode.DETERMINISTIC
+    assert report.verdict == ValidationVerdict.PASSED
+
+
 @pytest.mark.asyncio
 @pytest.mark.parametrize("rule_ids", [[], ["unknown"]])
 async def test_deterministic_plan_must_cover_hard_criteria_without_unknowns(
@@ -363,6 +428,62 @@ async def running_evidence_validation(tmp_path, provider, *, max_queries=1, time
     return coordinator, store, operation, actor, request
 
 
+@pytest.mark.asyncio
+async def test_wire_orchestration_applies_custom_evidence_provider(tmp_path):
+    runner = SimpleNamespace(trace_store=None, task_coordinator=None)
+    provider = EvidenceProvider()
+    policy = EvidencePolicy(max_queries=1, timeout=0.1)
+    coordinator = wire_orchestration(
+        runner,
+        FileSystemTaskStore(str(tmp_path)),
+        FileSystemArtifactStore(str(tmp_path)),
+        OrchestrationConfig(stop_grace_seconds=0),
+        validation_policy=policy,
+        evidence_provider=provider,
+    )
+    executor = BlockingValidatorExecutor()
+    executor.coordinator = coordinator
+    coordinator.set_executor(executor)
+    await coordinator.ensure_ledger("root", ROOT_TASK_SPEC)
+    task_id = await create_task(coordinator, "wired evidence provider")
+    operation = await coordinator.start_operation(
+        "root",
+        "dispatch",
+        task_ids=[task_id],
+    )
+    try:
+        await asyncio.wait_for(executor.context_ready.wait(), timeout=1)
+        context = executor.context
+        actor = {
+            **context,
+            "role": AgentRole.VALIDATOR.value,
+            "trace_id": context["validator_trace_id"],
+            "tool_call_id": "wired-evidence-1",
+        }
+        request = EvidenceQuery(
+            root_trace_id="root",
+            task_id=task_id,
+            attempt_id=context["attempt_id"],
+            snapshot_id=context["snapshot_id"],
+            validation_id=context["validation_id"],
+            query="wired facts",
+            limit=100,
+        )
+        response = await coordinator.query_evidence(actor, request)
+        report = (await coordinator.task_store.load("root")).validations[
+            request.validation_id
+        ]
+
+        assert coordinator.validation_policy is policy
+        assert coordinator.evidence_provider is provider
+        assert len(provider.requests) == 1
+        assert provider.requests[0].limit == 1
+        assert response.items == [{"index": 0}]
+        assert report.evidence_queries_used == 1
+    finally:
+        await coordinator.stop_operation("root", operation.operation_id)
+
+
 @pytest.mark.asyncio
 async def test_coordinator_evidence_ownership_and_persistent_budget(tmp_path):
     provider = EvidenceProvider()

+ 3 - 0
智能创作构建系统产品方案.md

@@ -929,6 +929,7 @@ Task、Attempt、Validation、Decision 和 Trace 可用于解释这份脚本如
 - 父任务只消费明确接受的直接子结果;
 - Validator 独立核验同一候选版本;
 - Validator 通过不自动完成,仍由主 Agent 决定;
+- 业务 Host 可以通过通用装配入口按需注入验证策略、确定性 Validator 和只读 Evidence Provider;全部注入项可选,旧框架调用保持兼容;
 - 整个 Mission 的任务、尝试、验证、决定和运行事件可以统一观察;
 - 可以按角色、任务、尝试、验证和操作关联实际运行过程。
 
@@ -936,6 +937,7 @@ Task、Attempt、Validation、Decision 和 Trace 可用于解释这份脚本如
 
 - 老版输入适配与挂载关系校验;
 - 四类取数 Agent 和其他数据工具接入;
+- ScriptBuild 领域 ValidationPolicy、DeterministicValidator 和 EvidenceProvider 的实现与注入;
 - 结构化脚本候选版本与正式版本管理;
 - 旧写入工具的候选隔离与冲突规则;
 - ScriptBuildRecord、Paragraph、Element、Link 兼容输出;
@@ -976,6 +978,7 @@ Task、Attempt、Validation、Decision 和 Trace 可用于解释这份脚本如
 
 - 接入 Topic、Persona、Strategy、脚本快照;
 - 接入四类专业取数能力和图片理解;
+- 通过框架通用装配入口注入 ScriptBuild 验证策略、确定性规则和只读证据 Provider;
 - 用独立取数任务验证“查询—证据—接受—后续消费”链路;
 - 不改变正式脚本输出。
 

+ 5 - 2
智能创作构建系统技术方案.md

@@ -330,6 +330,7 @@ script_build_host/
 - Attempt、固定 child Decision 输入;
 - ArtifactSnapshot、Validation、Planner Decision;
 - capability 权限边界;
+- `wire_orchestration()` 的通用可选注入点:`validation_policy`、`deterministic_validator`、`evidence_provider`;旧位置参数调用保持兼容;
 - Operation、Event、Trace 和 V2 读取接口;
 - `FileSystemTaskStore` 的 revision/CAS、文件锁和原子替换,以及 `FileSystemArtifactStore` 的文件锁/first-write-wins;TraceStore 不具同等级跨进程写保证。
 
@@ -339,7 +340,7 @@ script_build_host/
 - 创建 Root TaskSpec 和 Planner 初始消息;
 - 脚本角色 preset 和业务工具;
 - 候选正文持久化、冻结和 digest;
-- 验证规则和证据读取
+- 实现脚本领域的 ValidationPolicy、DeterministicValidator 和 EvidenceProvider,并通过通用 wiring 注入
 - Root ACCEPT 后的正式发布;
 - 旧 API 和状态兼容。
 
@@ -563,7 +564,7 @@ async def run(self, script_build_id: int) -> None:
         raise
 ```
 
-`AgentRunner.run()` 是 async iterator,不能直接 `await`;服务模式使用 `run_result()` 消费完整事件流,若需要流式投影则显式 `async for`。`RunConfig` 必须使用 `explicit_validation`、`script_planner` preset 和 Root Task draft dict。当前 `wire_orchestration()` 尚未暴露 validation policy、deterministic validator、evidence provider 等全部 Coordinator 参数;阶段一可在 composition root 直接构造 `TaskCoordinator`,设置 `LocalAgentExecutor` 与 `runner.task_coordinator`,或先做向后兼容 wiring 扩展,不能假定参数已经被装配
+`AgentRunner.run()` 是 async iterator,不能直接 `await`;服务模式使用 `run_result()` 消费完整事件流,若需要流式投影则显式 `async for`。`RunConfig` 必须使用 `explicit_validation`、`script_planner` preset 和 Root Task draft dict。当前 `wire_orchestration()` 已以可选关键字参数暴露 `validation_policy`、`deterministic_validator`、`evidence_provider`,并保留原有 3~5 个位置参数调用语义;阶段一由 composition root 实例化脚本领域实现后显式注入,不再由业务 Host 直接构造 `TaskCoordinator` 或复制 `LocalAgentExecutor`、`runner.task_coordinator` 的框架装配逻辑。未传入时仍使用原有默认 Agent Validator,Evidence Provider 仍为空
 
 ### 6.5 stop/resume/recovery
 
@@ -1188,6 +1189,7 @@ Root Validator 通过后,Planner ACCEPT。Publisher 将同一 frozen Artifact
 实现:
 
 - `script_build_host/` composition 和配置;
+- 通过框架 `wire_orchestration()` 注入 ScriptBuild ValidationPolicy、DeterministicValidator 和 EvidenceProvider;
 - 四张业务表的 migration 基础版本;
 - InputSnapshotService 和真实旧数据 Adapter;
 - MissionFactory、MissionService.start/run/stop;
@@ -1244,6 +1246,7 @@ Root Validator 通过后,Planner ACCEPT。Publisher 将同一 frozen Artifact
 - ArtifactRef/build/task/attempt 归属检查;
 - Direction 和 StructuredScript schema;
 - Validator criterion 覆盖;
+- 框架 wiring 的旧位置参数兼容,以及自定义 Validation/Evidence 依赖经装配入口真实生效;
 - Publisher 幂等和 readback mismatch;
 - OwnerLease token 交接、嵌套 finalize 不重入加锁、重复 start/resume/finalize 的稳定 409;
 - 旧六状态映射,尤其 stopping、partial、Root completed+publication failed;