hint system

This commit is contained in:
lda
2026-05-20 22:52:12 +07:00 Verified
parent 125aec72a2
commit fe5548a659
6 changed files with 1314 additions and 1 deletions
+31
View File
@@ -160,6 +160,31 @@ def test_workflow_surface_inspects_one_capability() -> None:
assert "input_schema" in payload
def test_workflow_surface_inspect_capability_includes_wrapper_hints() -> None:
service = WfMcpService(
store=FileStore(local_temp_root() / "surface_wrapper_hints_mcp"),
artifact_store=FileWorkflowArtifactStore(
local_temp_root() / "surface_wrapper_hints_artifacts"
),
)
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_specs("demo.personal", echo_tool)
handlers = WorkflowSurfaceHandlers(service)
payload = asyncio.run(
handlers.inspect_capability(qualified_name="demo.personal.echo_tool")
)
hints = payload["wrapper_hints"]
assert hints["capability_name"] == "demo.personal.echo_tool"
assert hints["declared_outcomes"] == ["ok"]
assert hints["input_map"] == {"input.text": "text"}
assert hints["output_map"] == {"echoed": "state.echoed"}
assert hints["outcome_policy"] == "preserve_declared"
def test_workflow_surface_inspects_saved_wrapper_capability() -> None:
artifact_store = FileWorkflowArtifactStore(
local_temp_root() / "surface_inspect_wrapper_cap"
@@ -179,6 +204,12 @@ def test_workflow_surface_inspects_saved_wrapper_capability() -> None:
assert payload["artifact_id"] == "echo_wrapper"
assert payload["outcomes"] == ["completed"]
assert "input_schema" in payload
hints = payload["wrapper_hints"]
assert hints["capability_name"] == "workflow.echo_wrapper.v1"
assert hints["declared_outcomes"] == ["completed"]
assert hints["suggested_wrapper_outcomes"] == ["completed"]
assert hints["input_map"] == {"input.text": "text"}
assert hints["output_map"] == {"echoed": "state.echoed"}
def test_workflow_surface_validates_deployment_dependencies() -> None:
+170
View File
@@ -0,0 +1,170 @@
from __future__ import annotations
from wf_mcp.workflow_surface.wrapper_hints import (
MissingDecision,
MissingDecisionKind,
OutcomeCandidate,
OutcomeCandidateKind,
WrapperAuthoringHints,
WrapperHintConfidence,
WrapperOutcomePolicy,
wrapper_hints_for_capability,
)
def test_wrapper_hint_models_serialize_enum_fields_as_strings() -> None:
hints = WrapperAuthoringHints(
capability_name="demo.personal.echo_tool",
confidence=WrapperHintConfidence.MEDIUM,
declared_outcomes=["ok", "error"],
suggested_wrapper_outcomes=["ok", "error"],
outcome_policy=WrapperOutcomePolicy.PRESERVE_DECLARED,
input_schema={"type": "object", "properties": {}},
state_schema={"type": "object", "properties": {}},
output_schema={"type": "object", "properties": {}},
input_map={},
output_map={},
outcome_candidates=[
OutcomeCandidate(
kind=OutcomeCandidateKind.BOOLEAN_CONTROL_FIELD,
source="output.success",
candidate_outcomes=["success", "failure"],
confidence=WrapperHintConfidence.MEDIUM,
reason="top-level boolean field with control-like name",
automatic=False,
)
],
missing_decisions=[
MissingDecision(
kind=MissingDecisionKind.CONFIRM_BOOLEAN_OUTCOMES,
message="Confirm whether output.success should control routing.",
)
],
notes=["Hints are scaffolding, not semantic guarantees."],
)
dumped = hints.model_dump(mode="json")
assert dumped["confidence"] == "medium"
assert dumped["outcome_policy"] == "preserve_declared"
assert dumped["outcome_candidates"][0]["kind"] == "boolean_control_field"
assert dumped["missing_decisions"][0]["kind"] == "confirm_boolean_outcomes"
def test_wrapper_hints_scaffold_simple_object_input_and_output() -> None:
hints = wrapper_hints_for_capability(
capability_name="demo.personal.echo_tool",
input_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
output_schema={
"type": "object",
"properties": {"echoed": {"type": "string"}},
"required": ["echoed"],
},
outcomes=["ok"],
)
dumped = hints.model_dump(mode="json")
assert dumped["confidence"] == "high"
assert dumped["declared_outcomes"] == ["ok"]
assert dumped["suggested_wrapper_outcomes"] == ["ok"]
assert dumped["outcome_policy"] == "preserve_declared"
assert dumped["input_map"] == {"input.text": "text"}
assert dumped["output_map"] == {"echoed": "state.echoed"}
assert dumped["state_schema"]["properties"]["echoed"]["type"] == "string"
assert dumped["output_schema"]["properties"]["echoed"]["type"] == "string"
assert dumped["missing_decisions"] == []
def test_wrapper_hints_offer_boolean_outcome_candidates_without_auto_mapping() -> None:
hints = wrapper_hints_for_capability(
capability_name="demo.personal.submit",
input_schema={"type": "object", "properties": {"text": {"type": "string"}}},
output_schema={
"type": "object",
"properties": {
"success": {"type": "boolean"},
"message": {"type": "string"},
},
},
outcomes=["ok"],
)
dumped = hints.model_dump(mode="json")
candidate = dumped["outcome_candidates"][0]
assert dumped["confidence"] == "medium"
assert candidate["kind"] == "boolean_control_field"
assert candidate["source"] == "output.success"
assert candidate["candidate_outcomes"] == ["success", "failure"]
assert candidate["automatic"] is False
assert dumped["outcome_policy"] == "preserve_declared"
assert dumped["suggested_wrapper_outcomes"] == ["ok"]
assert dumped["missing_decisions"][0]["kind"] == "confirm_boolean_outcomes"
def test_wrapper_hints_do_not_treat_arbitrary_booleans_as_outcomes() -> None:
hints = wrapper_hints_for_capability(
capability_name="demo.personal.profile",
input_schema={"type": "object", "properties": {"user_id": {"type": "string"}}},
output_schema={
"type": "object",
"properties": {
"is_admin": {"type": "boolean"},
"name": {"type": "string"},
},
},
outcomes=["ok"],
)
dumped = hints.model_dump(mode="json")
assert dumped["confidence"] == "high"
assert dumped["outcome_candidates"] == []
assert dumped["missing_decisions"] == []
def test_wrapper_hints_mark_nested_outputs_as_low_confidence() -> None:
hints = wrapper_hints_for_capability(
capability_name="demo.personal.search",
input_schema={"type": "object", "properties": {"query": {"type": "string"}}},
output_schema={
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {"title": {"type": "string"}},
},
}
},
},
outcomes=["ok"],
)
dumped = hints.model_dump(mode="json")
assert dumped["confidence"] == "low"
assert dumped["missing_decisions"][0]["kind"] == "review_nested_output"
assert dumped["output_map"] == {"results": "state.results"}
def test_wrapper_hints_mark_empty_output_schema_as_low_confidence() -> None:
hints = wrapper_hints_for_capability(
capability_name="demo.personal.no_output",
input_schema={"type": "object", "properties": {"text": {"type": "string"}}},
output_schema={"type": "object", "properties": {}},
outcomes=["ok"],
)
dumped = hints.model_dump(mode="json")
assert dumped["confidence"] == "low"
assert dumped["input_map"] == {"input.text": "text"}
assert dumped["output_map"] == {}
assert dumped["missing_decisions"][0]["kind"] == "choose_output_fields"