mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-25 08:26: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
162
tests/unit/test_agent/test_explainability_parsing.py
Normal file
162
tests/unit/test_agent/test_explainability_parsing.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
"""
|
||||
Unit tests for explainability API parsing — verifies that from_triples()
|
||||
correctly dispatches and parses the new orchestrator entity types.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from trustgraph.api.explainability import (
|
||||
ExplainEntity,
|
||||
Decomposition,
|
||||
Finding,
|
||||
Plan,
|
||||
StepResult,
|
||||
Synthesis,
|
||||
Analysis,
|
||||
Conclusion,
|
||||
TG_DECOMPOSITION,
|
||||
TG_FINDING,
|
||||
TG_PLAN_TYPE,
|
||||
TG_STEP_RESULT,
|
||||
TG_SYNTHESIS,
|
||||
TG_ANSWER_TYPE,
|
||||
TG_ANALYSIS,
|
||||
TG_CONCLUSION,
|
||||
TG_DOCUMENT,
|
||||
TG_SUBAGENT_GOAL,
|
||||
TG_PLAN_STEP,
|
||||
RDF_TYPE,
|
||||
)
|
||||
|
||||
PROV_ENTITY = "http://www.w3.org/ns/prov#Entity"
|
||||
|
||||
|
||||
def _make_triples(uri, types, extras=None):
|
||||
"""Build a list of (s, p, o) tuples for testing."""
|
||||
triples = [(uri, RDF_TYPE, t) for t in types]
|
||||
if extras:
|
||||
triples.extend((uri, p, o) for p, o in extras)
|
||||
return triples
|
||||
|
||||
|
||||
class TestFromTriplesDispatch:
|
||||
|
||||
def test_dispatches_decomposition(self):
|
||||
triples = _make_triples("urn:d", [PROV_ENTITY, TG_DECOMPOSITION])
|
||||
entity = ExplainEntity.from_triples("urn:d", triples)
|
||||
assert isinstance(entity, Decomposition)
|
||||
|
||||
def test_dispatches_finding(self):
|
||||
triples = _make_triples("urn:f",
|
||||
[PROV_ENTITY, TG_FINDING, TG_ANSWER_TYPE])
|
||||
entity = ExplainEntity.from_triples("urn:f", triples)
|
||||
assert isinstance(entity, Finding)
|
||||
|
||||
def test_dispatches_plan(self):
|
||||
triples = _make_triples("urn:p", [PROV_ENTITY, TG_PLAN_TYPE])
|
||||
entity = ExplainEntity.from_triples("urn:p", triples)
|
||||
assert isinstance(entity, Plan)
|
||||
|
||||
def test_dispatches_step_result(self):
|
||||
triples = _make_triples("urn:sr",
|
||||
[PROV_ENTITY, TG_STEP_RESULT, TG_ANSWER_TYPE])
|
||||
entity = ExplainEntity.from_triples("urn:sr", triples)
|
||||
assert isinstance(entity, StepResult)
|
||||
|
||||
def test_dispatches_synthesis(self):
|
||||
triples = _make_triples("urn:s",
|
||||
[PROV_ENTITY, TG_SYNTHESIS, TG_ANSWER_TYPE])
|
||||
entity = ExplainEntity.from_triples("urn:s", triples)
|
||||
assert isinstance(entity, Synthesis)
|
||||
|
||||
def test_dispatches_analysis_unchanged(self):
|
||||
triples = _make_triples("urn:a", [PROV_ENTITY, TG_ANALYSIS])
|
||||
entity = ExplainEntity.from_triples("urn:a", triples)
|
||||
assert isinstance(entity, Analysis)
|
||||
|
||||
def test_dispatches_conclusion_unchanged(self):
|
||||
triples = _make_triples("urn:c",
|
||||
[PROV_ENTITY, TG_CONCLUSION, TG_ANSWER_TYPE])
|
||||
entity = ExplainEntity.from_triples("urn:c", triples)
|
||||
assert isinstance(entity, Conclusion)
|
||||
|
||||
def test_finding_takes_precedence_over_synthesis(self):
|
||||
"""Finding has Answer mixin but should dispatch to Finding, not
|
||||
Synthesis, because Finding is checked first."""
|
||||
triples = _make_triples("urn:f",
|
||||
[PROV_ENTITY, TG_FINDING, TG_ANSWER_TYPE])
|
||||
entity = ExplainEntity.from_triples("urn:f", triples)
|
||||
assert isinstance(entity, Finding)
|
||||
assert not isinstance(entity, Synthesis)
|
||||
|
||||
|
||||
class TestDecompositionParsing:
|
||||
|
||||
def test_parses_goals(self):
|
||||
triples = _make_triples("urn:d", [TG_DECOMPOSITION], [
|
||||
(TG_SUBAGENT_GOAL, "What is X?"),
|
||||
(TG_SUBAGENT_GOAL, "What is Y?"),
|
||||
])
|
||||
entity = Decomposition.from_triples("urn:d", triples)
|
||||
assert set(entity.goals) == {"What is X?", "What is Y?"}
|
||||
|
||||
def test_entity_type_field(self):
|
||||
triples = _make_triples("urn:d", [TG_DECOMPOSITION])
|
||||
entity = Decomposition.from_triples("urn:d", triples)
|
||||
assert entity.entity_type == "decomposition"
|
||||
|
||||
def test_empty_goals(self):
|
||||
triples = _make_triples("urn:d", [TG_DECOMPOSITION])
|
||||
entity = Decomposition.from_triples("urn:d", triples)
|
||||
assert entity.goals == []
|
||||
|
||||
|
||||
class TestFindingParsing:
|
||||
|
||||
def test_parses_goal_and_document(self):
|
||||
triples = _make_triples("urn:f", [TG_FINDING, TG_ANSWER_TYPE], [
|
||||
(TG_SUBAGENT_GOAL, "What is X?"),
|
||||
(TG_DOCUMENT, "urn:doc/finding"),
|
||||
])
|
||||
entity = Finding.from_triples("urn:f", triples)
|
||||
assert entity.goal == "What is X?"
|
||||
assert entity.document == "urn:doc/finding"
|
||||
|
||||
def test_entity_type_field(self):
|
||||
triples = _make_triples("urn:f", [TG_FINDING])
|
||||
entity = Finding.from_triples("urn:f", triples)
|
||||
assert entity.entity_type == "finding"
|
||||
|
||||
|
||||
class TestPlanParsing:
|
||||
|
||||
def test_parses_steps(self):
|
||||
triples = _make_triples("urn:p", [TG_PLAN_TYPE], [
|
||||
(TG_PLAN_STEP, "Define X"),
|
||||
(TG_PLAN_STEP, "Research Y"),
|
||||
(TG_PLAN_STEP, "Analyse Z"),
|
||||
])
|
||||
entity = Plan.from_triples("urn:p", triples)
|
||||
assert set(entity.steps) == {"Define X", "Research Y", "Analyse Z"}
|
||||
|
||||
def test_entity_type_field(self):
|
||||
triples = _make_triples("urn:p", [TG_PLAN_TYPE])
|
||||
entity = Plan.from_triples("urn:p", triples)
|
||||
assert entity.entity_type == "plan"
|
||||
|
||||
|
||||
class TestStepResultParsing:
|
||||
|
||||
def test_parses_step_and_document(self):
|
||||
triples = _make_triples("urn:sr", [TG_STEP_RESULT, TG_ANSWER_TYPE], [
|
||||
(TG_PLAN_STEP, "Define X"),
|
||||
(TG_DOCUMENT, "urn:doc/step"),
|
||||
])
|
||||
entity = StepResult.from_triples("urn:sr", triples)
|
||||
assert entity.step == "Define X"
|
||||
assert entity.document == "urn:doc/step"
|
||||
|
||||
def test_entity_type_field(self):
|
||||
triples = _make_triples("urn:sr", [TG_STEP_RESULT])
|
||||
entity = StepResult.from_triples("urn:sr", triples)
|
||||
assert entity.entity_type == "step-result"
|
||||
Loading…
Add table
Add a link
Reference in a new issue