mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-28 18:06:21 +02:00
Update tests for agent-orchestrator (#745)
Add 96 tests covering the orchestrator's aggregation, provenance, routing, and explainability parsing. These verify the supervisor fan-out/fan-in lifecycle, the new RDF provenance types (Decomposition, Finding, Plan, StepResult, Synthesis), and their round-trip through the wire format. Unit tests (84): - Aggregator: register, record completion, peek, build synthesis, cleanup - Provenance triple builders: types, provenance links, goals/steps, labels - Explainability parsing: from_triples dispatch, field extraction for all new entity types, precedence over existing types - PatternBase: is_subagent detection, emit_subagent_completion message shape - Completion dispatch: detection logic, full aggregator integration flow, synthesis request not re-intercepted as completion - MetaRouter: task type identification, pattern selection, valid_patterns constraints, fallback on LLM error or unknown response Contract tests (12): - Orchestration fields on AgentRequest round-trip correctly - subagent-completion and synthesise step types in request history - Plan steps with status and dependencies - Provenance triple builder → wire format → from_triples round-trip for all five new entity types
This commit is contained in:
parent
7b734148b3
commit
816a8cfcf6
8 changed files with 1517 additions and 0 deletions
177
tests/contract/test_orchestrator_contracts.py
Normal file
177
tests/contract/test_orchestrator_contracts.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
"""
|
||||
Contract tests for orchestrator message schemas.
|
||||
|
||||
Verifies that AgentRequest/AgentStep with orchestration fields
|
||||
serialise and deserialise correctly through the Pulsar schema layer.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
|
||||
from trustgraph.schema import AgentRequest, AgentStep, PlanStep
|
||||
|
||||
|
||||
@pytest.mark.contract
|
||||
class TestOrchestrationFieldContracts:
|
||||
"""Contract tests for orchestration fields on AgentRequest."""
|
||||
|
||||
def test_agent_request_orchestration_fields_roundtrip(self):
|
||||
req = AgentRequest(
|
||||
question="Test question",
|
||||
user="testuser",
|
||||
collection="default",
|
||||
correlation_id="corr-123",
|
||||
parent_session_id="parent-sess",
|
||||
subagent_goal="What is X?",
|
||||
expected_siblings=4,
|
||||
pattern="react",
|
||||
task_type="research",
|
||||
framing="Focus on accuracy",
|
||||
conversation_id="conv-456",
|
||||
)
|
||||
|
||||
assert req.correlation_id == "corr-123"
|
||||
assert req.parent_session_id == "parent-sess"
|
||||
assert req.subagent_goal == "What is X?"
|
||||
assert req.expected_siblings == 4
|
||||
assert req.pattern == "react"
|
||||
assert req.task_type == "research"
|
||||
assert req.framing == "Focus on accuracy"
|
||||
assert req.conversation_id == "conv-456"
|
||||
|
||||
def test_agent_request_orchestration_fields_default_empty(self):
|
||||
req = AgentRequest(
|
||||
question="Test question",
|
||||
user="testuser",
|
||||
)
|
||||
|
||||
assert req.correlation_id == ""
|
||||
assert req.parent_session_id == ""
|
||||
assert req.subagent_goal == ""
|
||||
assert req.expected_siblings == 0
|
||||
assert req.pattern == ""
|
||||
assert req.task_type == ""
|
||||
assert req.framing == ""
|
||||
|
||||
|
||||
@pytest.mark.contract
|
||||
class TestSubagentCompletionStepContract:
|
||||
"""Contract tests for subagent-completion step type."""
|
||||
|
||||
def test_subagent_completion_step_fields(self):
|
||||
step = AgentStep(
|
||||
thought="Subagent completed",
|
||||
action="complete",
|
||||
arguments={},
|
||||
observation="The answer text",
|
||||
step_type="subagent-completion",
|
||||
)
|
||||
|
||||
assert step.step_type == "subagent-completion"
|
||||
assert step.observation == "The answer text"
|
||||
assert step.thought == "Subagent completed"
|
||||
assert step.action == "complete"
|
||||
|
||||
def test_subagent_completion_in_request_history(self):
|
||||
step = AgentStep(
|
||||
thought="Subagent completed",
|
||||
action="complete",
|
||||
arguments={},
|
||||
observation="answer",
|
||||
step_type="subagent-completion",
|
||||
)
|
||||
req = AgentRequest(
|
||||
question="goal",
|
||||
user="testuser",
|
||||
correlation_id="corr-123",
|
||||
history=[step],
|
||||
)
|
||||
|
||||
assert len(req.history) == 1
|
||||
assert req.history[0].step_type == "subagent-completion"
|
||||
assert req.history[0].observation == "answer"
|
||||
|
||||
|
||||
@pytest.mark.contract
|
||||
class TestSynthesisStepContract:
|
||||
"""Contract tests for synthesis step type with subagent_results."""
|
||||
|
||||
def test_synthesis_step_with_results(self):
|
||||
results = {"goal-a": "answer-a", "goal-b": "answer-b"}
|
||||
step = AgentStep(
|
||||
thought="All subagents completed",
|
||||
action="aggregate",
|
||||
arguments={},
|
||||
observation=json.dumps(results),
|
||||
step_type="synthesise",
|
||||
subagent_results=results,
|
||||
)
|
||||
|
||||
assert step.step_type == "synthesise"
|
||||
assert step.subagent_results == results
|
||||
assert json.loads(step.observation) == results
|
||||
|
||||
def test_synthesis_request_matches_supervisor_expectations(self):
|
||||
"""The synthesis request built by the aggregator must be
|
||||
recognisable by SupervisorPattern._synthesise()."""
|
||||
results = {"goal-a": "answer-a", "goal-b": "answer-b"}
|
||||
step = AgentStep(
|
||||
thought="All subagents completed",
|
||||
action="aggregate",
|
||||
arguments={},
|
||||
observation=json.dumps(results),
|
||||
step_type="synthesise",
|
||||
subagent_results=results,
|
||||
)
|
||||
|
||||
req = AgentRequest(
|
||||
question="Original question",
|
||||
user="testuser",
|
||||
pattern="supervisor",
|
||||
correlation_id="",
|
||||
session_id="parent-sess",
|
||||
history=[step],
|
||||
)
|
||||
|
||||
# SupervisorPattern checks for step_type='synthesise' with
|
||||
# subagent_results
|
||||
has_results = bool(
|
||||
req.history
|
||||
and any(
|
||||
getattr(h, 'step_type', '') == 'synthesise'
|
||||
and getattr(h, 'subagent_results', None)
|
||||
for h in req.history
|
||||
)
|
||||
)
|
||||
assert has_results
|
||||
|
||||
# Pattern must be supervisor
|
||||
assert req.pattern == "supervisor"
|
||||
|
||||
# Correlation ID must be empty (not re-intercepted)
|
||||
assert req.correlation_id == ""
|
||||
|
||||
|
||||
@pytest.mark.contract
|
||||
class TestPlanStepContract:
|
||||
"""Contract tests for plan steps in history."""
|
||||
|
||||
def test_plan_step_in_history(self):
|
||||
plan = [
|
||||
PlanStep(goal="Step 1", tool_hint="knowledge-query",
|
||||
depends_on=[], status="completed", result="done"),
|
||||
PlanStep(goal="Step 2", tool_hint="",
|
||||
depends_on=[0], status="pending", result=""),
|
||||
]
|
||||
step = AgentStep(
|
||||
thought="Created plan",
|
||||
action="plan",
|
||||
step_type="plan",
|
||||
plan=plan,
|
||||
)
|
||||
|
||||
assert step.step_type == "plan"
|
||||
assert len(step.plan) == 2
|
||||
assert step.plan[0].goal == "Step 1"
|
||||
assert step.plan[0].status == "completed"
|
||||
assert step.plan[1].depends_on == [0]
|
||||
129
tests/contract/test_provenance_wire_format.py
Normal file
129
tests/contract/test_provenance_wire_format.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
"""
|
||||
Contract tests for provenance triple wire format — verifies that triples
|
||||
built by the provenance library can be parsed by the explainability API
|
||||
through the wire format conversion.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from trustgraph.schema import IRI, LITERAL
|
||||
|
||||
from trustgraph.provenance import (
|
||||
agent_decomposition_triples,
|
||||
agent_finding_triples,
|
||||
agent_plan_triples,
|
||||
agent_step_result_triples,
|
||||
agent_synthesis_triples,
|
||||
)
|
||||
|
||||
from trustgraph.api.explainability import (
|
||||
ExplainEntity,
|
||||
Decomposition,
|
||||
Finding,
|
||||
Plan,
|
||||
StepResult,
|
||||
Synthesis,
|
||||
wire_triples_to_tuples,
|
||||
)
|
||||
|
||||
|
||||
def _triples_to_wire(triples):
|
||||
"""Convert provenance Triple objects to the wire format dicts
|
||||
that the gateway/socket client would produce."""
|
||||
wire = []
|
||||
for t in triples:
|
||||
entry = {
|
||||
"s": _term_to_wire(t.s),
|
||||
"p": _term_to_wire(t.p),
|
||||
"o": _term_to_wire(t.o),
|
||||
}
|
||||
wire.append(entry)
|
||||
return wire
|
||||
|
||||
|
||||
def _term_to_wire(term):
|
||||
"""Convert a Term to wire format dict."""
|
||||
if term.type == IRI:
|
||||
return {"t": "i", "i": term.iri}
|
||||
elif term.type == LITERAL:
|
||||
return {"t": "l", "v": term.value}
|
||||
return {"t": "l", "v": str(term)}
|
||||
|
||||
|
||||
def _roundtrip(triples, uri):
|
||||
"""Convert triples through wire format and parse via from_triples."""
|
||||
wire = _triples_to_wire(triples)
|
||||
tuples = wire_triples_to_tuples(wire)
|
||||
return ExplainEntity.from_triples(uri, tuples)
|
||||
|
||||
|
||||
@pytest.mark.contract
|
||||
class TestDecompositionWireFormat:
|
||||
|
||||
def test_roundtrip(self):
|
||||
triples = agent_decomposition_triples(
|
||||
"urn:decompose", "urn:session",
|
||||
["What is X?", "What is Y?"],
|
||||
)
|
||||
entity = _roundtrip(triples, "urn:decompose")
|
||||
|
||||
assert isinstance(entity, Decomposition)
|
||||
assert set(entity.goals) == {"What is X?", "What is Y?"}
|
||||
|
||||
|
||||
@pytest.mark.contract
|
||||
class TestFindingWireFormat:
|
||||
|
||||
def test_roundtrip(self):
|
||||
triples = agent_finding_triples(
|
||||
"urn:finding", "urn:decompose", "What is X?",
|
||||
document_id="urn:doc/finding",
|
||||
)
|
||||
entity = _roundtrip(triples, "urn:finding")
|
||||
|
||||
assert isinstance(entity, Finding)
|
||||
assert entity.goal == "What is X?"
|
||||
assert entity.document == "urn:doc/finding"
|
||||
|
||||
|
||||
@pytest.mark.contract
|
||||
class TestPlanWireFormat:
|
||||
|
||||
def test_roundtrip(self):
|
||||
triples = agent_plan_triples(
|
||||
"urn:plan", "urn:session",
|
||||
["Step 1", "Step 2", "Step 3"],
|
||||
)
|
||||
entity = _roundtrip(triples, "urn:plan")
|
||||
|
||||
assert isinstance(entity, Plan)
|
||||
assert set(entity.steps) == {"Step 1", "Step 2", "Step 3"}
|
||||
|
||||
|
||||
@pytest.mark.contract
|
||||
class TestStepResultWireFormat:
|
||||
|
||||
def test_roundtrip(self):
|
||||
triples = agent_step_result_triples(
|
||||
"urn:step", "urn:plan", "Define X",
|
||||
document_id="urn:doc/step",
|
||||
)
|
||||
entity = _roundtrip(triples, "urn:step")
|
||||
|
||||
assert isinstance(entity, StepResult)
|
||||
assert entity.step == "Define X"
|
||||
assert entity.document == "urn:doc/step"
|
||||
|
||||
|
||||
@pytest.mark.contract
|
||||
class TestSynthesisWireFormat:
|
||||
|
||||
def test_roundtrip(self):
|
||||
triples = agent_synthesis_triples(
|
||||
"urn:synthesis", "urn:previous",
|
||||
document_id="urn:doc/synthesis",
|
||||
)
|
||||
entity = _roundtrip(triples, "urn:synthesis")
|
||||
|
||||
assert isinstance(entity, Synthesis)
|
||||
assert entity.document == "urn:doc/synthesis"
|
||||
Loading…
Add table
Add a link
Reference in a new issue