2026-03-13 14:27:42 +00:00
|
|
|
"""
|
|
|
|
|
Tests for agent provenance triple builder functions.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from trustgraph.schema import Triple, Term, IRI, LITERAL
|
|
|
|
|
|
|
|
|
|
from trustgraph.provenance.agent import (
|
|
|
|
|
agent_session_triples,
|
|
|
|
|
agent_iteration_triples,
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
agent_observation_triples,
|
2026-03-13 14:27:42 +00:00
|
|
|
agent_final_triples,
|
Additional agent DAG tests (#750)
- test_agent_provenance.py: test_session_parent_uri,
test_session_no_parent_uri, and 6 synthesis tests (types,
single/multiple parents, document, label)
- test_on_action_callback.py: 3 tests — fires before tool, skipped
for Final, works when None
- test_callback_message_id.py: 7 tests — message_id on think/observe/
answer callbacks (streaming + non-streaming) and
send_final_response
- test_parse_chunk_message_id.py (5 tests) - _parse_chunk propagates
message_id for thought, observation, answer; handles missing
gracefully
- test_explainability_parsing.py (+1) -
test_dispatches_analysis_with_tooluse - Analysis+ToolUse mixin still
dispatches to Analysis
- test_explainability.py (+1) -
test_observation_found_via_subtrace_synthesis
- chain walker follows from sub-trace Synthesis to find Observation
and
Conclusion in correct order
- test_agent_provenance.py (+8) - session parent_uri (2), synthesis
single/multiple parents, types, document, label (6)
2026-04-01 13:59:34 +01:00
|
|
|
agent_synthesis_triples,
|
2026-03-13 14:27:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
from trustgraph.provenance.namespaces import (
|
|
|
|
|
RDF_TYPE, RDFS_LABEL,
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
PROV_ENTITY, PROV_WAS_DERIVED_FROM,
|
|
|
|
|
PROV_STARTED_AT_TIME,
|
|
|
|
|
TG_QUERY, TG_THOUGHT, TG_ACTION, TG_ARGUMENTS,
|
2026-03-13 14:27:42 +00:00
|
|
|
TG_QUESTION, TG_ANALYSIS, TG_CONCLUSION, TG_DOCUMENT,
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
TG_ANSWER_TYPE, TG_REFLECTION_TYPE, TG_THOUGHT_TYPE, TG_OBSERVATION_TYPE,
|
Additional agent DAG tests (#750)
- test_agent_provenance.py: test_session_parent_uri,
test_session_no_parent_uri, and 6 synthesis tests (types,
single/multiple parents, document, label)
- test_on_action_callback.py: 3 tests — fires before tool, skipped
for Final, works when None
- test_callback_message_id.py: 7 tests — message_id on think/observe/
answer callbacks (streaming + non-streaming) and
send_final_response
- test_parse_chunk_message_id.py (5 tests) - _parse_chunk propagates
message_id for thought, observation, answer; handles missing
gracefully
- test_explainability_parsing.py (+1) -
test_dispatches_analysis_with_tooluse - Analysis+ToolUse mixin still
dispatches to Analysis
- test_explainability.py (+1) -
test_observation_found_via_subtrace_synthesis
- chain walker follows from sub-trace Synthesis to find Observation
and
Conclusion in correct order
- test_agent_provenance.py (+8) - session parent_uri (2), synthesis
single/multiple parents, types, document, label (6)
2026-04-01 13:59:34 +01:00
|
|
|
TG_TOOL_USE, TG_SYNTHESIS,
|
2026-03-13 14:27:42 +00:00
|
|
|
TG_AGENT_QUESTION,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Helpers
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def find_triple(triples, predicate, subject=None):
|
|
|
|
|
for t in triples:
|
|
|
|
|
if t.p.iri == predicate:
|
|
|
|
|
if subject is None or t.s.iri == subject:
|
|
|
|
|
return t
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_triples(triples, predicate, subject=None):
|
|
|
|
|
return [
|
|
|
|
|
t for t in triples
|
|
|
|
|
if t.p.iri == predicate and (subject is None or t.s.iri == subject)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def has_type(triples, subject, rdf_type):
|
|
|
|
|
for t in triples:
|
|
|
|
|
if (t.s.iri == subject and t.p.iri == RDF_TYPE
|
|
|
|
|
and t.o.type == IRI and t.o.iri == rdf_type):
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# agent_session_triples
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class TestAgentSessionTriples:
|
|
|
|
|
|
|
|
|
|
SESSION_URI = "urn:trustgraph:agent:test-session"
|
|
|
|
|
|
|
|
|
|
def test_session_types(self):
|
|
|
|
|
triples = agent_session_triples(
|
|
|
|
|
self.SESSION_URI, "What is X?", "2024-01-01T00:00:00Z"
|
|
|
|
|
)
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
assert has_type(triples, self.SESSION_URI, PROV_ENTITY)
|
2026-03-13 14:27:42 +00:00
|
|
|
assert has_type(triples, self.SESSION_URI, TG_QUESTION)
|
|
|
|
|
assert has_type(triples, self.SESSION_URI, TG_AGENT_QUESTION)
|
|
|
|
|
|
|
|
|
|
def test_session_query_text(self):
|
|
|
|
|
triples = agent_session_triples(
|
|
|
|
|
self.SESSION_URI, "What is X?", "2024-01-01T00:00:00Z"
|
|
|
|
|
)
|
|
|
|
|
query = find_triple(triples, TG_QUERY, self.SESSION_URI)
|
|
|
|
|
assert query is not None
|
|
|
|
|
assert query.o.value == "What is X?"
|
|
|
|
|
|
|
|
|
|
def test_session_timestamp(self):
|
|
|
|
|
triples = agent_session_triples(
|
|
|
|
|
self.SESSION_URI, "Q", "2024-06-15T10:00:00Z"
|
|
|
|
|
)
|
|
|
|
|
ts = find_triple(triples, PROV_STARTED_AT_TIME, self.SESSION_URI)
|
|
|
|
|
assert ts is not None
|
|
|
|
|
assert ts.o.value == "2024-06-15T10:00:00Z"
|
|
|
|
|
|
|
|
|
|
def test_session_default_timestamp(self):
|
|
|
|
|
triples = agent_session_triples(self.SESSION_URI, "Q")
|
|
|
|
|
ts = find_triple(triples, PROV_STARTED_AT_TIME, self.SESSION_URI)
|
|
|
|
|
assert ts is not None
|
|
|
|
|
assert len(ts.o.value) > 0
|
|
|
|
|
|
|
|
|
|
def test_session_label(self):
|
|
|
|
|
triples = agent_session_triples(
|
|
|
|
|
self.SESSION_URI, "Q", "2024-01-01T00:00:00Z"
|
|
|
|
|
)
|
|
|
|
|
label = find_triple(triples, RDFS_LABEL, self.SESSION_URI)
|
|
|
|
|
assert label is not None
|
|
|
|
|
assert label.o.value == "Agent Question"
|
|
|
|
|
|
|
|
|
|
def test_session_triple_count(self):
|
|
|
|
|
triples = agent_session_triples(
|
|
|
|
|
self.SESSION_URI, "Q", "2024-01-01T00:00:00Z"
|
|
|
|
|
)
|
|
|
|
|
assert len(triples) == 6
|
|
|
|
|
|
Additional agent DAG tests (#750)
- test_agent_provenance.py: test_session_parent_uri,
test_session_no_parent_uri, and 6 synthesis tests (types,
single/multiple parents, document, label)
- test_on_action_callback.py: 3 tests — fires before tool, skipped
for Final, works when None
- test_callback_message_id.py: 7 tests — message_id on think/observe/
answer callbacks (streaming + non-streaming) and
send_final_response
- test_parse_chunk_message_id.py (5 tests) - _parse_chunk propagates
message_id for thought, observation, answer; handles missing
gracefully
- test_explainability_parsing.py (+1) -
test_dispatches_analysis_with_tooluse - Analysis+ToolUse mixin still
dispatches to Analysis
- test_explainability.py (+1) -
test_observation_found_via_subtrace_synthesis
- chain walker follows from sub-trace Synthesis to find Observation
and
Conclusion in correct order
- test_agent_provenance.py (+8) - session parent_uri (2), synthesis
single/multiple parents, types, document, label (6)
2026-04-01 13:59:34 +01:00
|
|
|
def test_session_parent_uri(self):
|
|
|
|
|
"""Subagent sessions derive from a parent entity (e.g. Decomposition)."""
|
|
|
|
|
parent = "urn:trustgraph:agent:parent/decompose"
|
|
|
|
|
triples = agent_session_triples(
|
|
|
|
|
self.SESSION_URI, "Q", "2024-01-01T00:00:00Z",
|
|
|
|
|
parent_uri=parent,
|
|
|
|
|
)
|
|
|
|
|
derived = find_triple(triples, PROV_WAS_DERIVED_FROM, self.SESSION_URI)
|
|
|
|
|
assert derived is not None
|
|
|
|
|
assert derived.o.iri == parent
|
|
|
|
|
|
|
|
|
|
def test_session_no_parent_uri(self):
|
|
|
|
|
"""Top-level sessions have no wasDerivedFrom."""
|
|
|
|
|
triples = agent_session_triples(
|
|
|
|
|
self.SESSION_URI, "Q", "2024-01-01T00:00:00Z"
|
|
|
|
|
)
|
|
|
|
|
derived = find_triple(triples, PROV_WAS_DERIVED_FROM, self.SESSION_URI)
|
|
|
|
|
assert derived is None
|
|
|
|
|
|
2026-03-13 14:27:42 +00:00
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# agent_iteration_triples
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class TestAgentIterationTriples:
|
|
|
|
|
|
|
|
|
|
ITER_URI = "urn:trustgraph:agent:test-session/i1"
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
SESSION_URI = "urn:trustgraph:agent:test-session"
|
|
|
|
|
PREV_URI = "urn:trustgraph:agent:test-session/i0"
|
2026-03-13 14:27:42 +00:00
|
|
|
|
|
|
|
|
def test_iteration_types(self):
|
|
|
|
|
triples = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.ITER_URI, question_uri=self.SESSION_URI,
|
|
|
|
|
action="search",
|
2026-03-13 14:27:42 +00:00
|
|
|
)
|
|
|
|
|
assert has_type(triples, self.ITER_URI, PROV_ENTITY)
|
|
|
|
|
assert has_type(triples, self.ITER_URI, TG_ANALYSIS)
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
assert has_type(triples, self.ITER_URI, TG_TOOL_USE)
|
2026-03-13 14:27:42 +00:00
|
|
|
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
def test_first_iteration_derived_from_question(self):
|
|
|
|
|
"""First iteration uses wasDerivedFrom to link to question entity."""
|
2026-03-13 14:27:42 +00:00
|
|
|
triples = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.ITER_URI, question_uri=self.SESSION_URI,
|
|
|
|
|
action="search",
|
|
|
|
|
)
|
|
|
|
|
derived = find_triple(triples, PROV_WAS_DERIVED_FROM, self.ITER_URI)
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
assert derived is not None
|
|
|
|
|
assert derived.o.iri == self.SESSION_URI
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
|
|
|
|
|
def test_subsequent_iteration_derived_from_previous(self):
|
|
|
|
|
"""Subsequent iterations use wasDerivedFrom to link to previous iteration."""
|
|
|
|
|
triples = agent_iteration_triples(
|
|
|
|
|
self.ITER_URI, previous_uri=self.PREV_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
action="search",
|
|
|
|
|
)
|
|
|
|
|
derived = find_triple(triples, PROV_WAS_DERIVED_FROM, self.ITER_URI)
|
|
|
|
|
assert derived is not None
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
assert derived.o.iri == self.PREV_URI
|
2026-03-13 14:27:42 +00:00
|
|
|
|
|
|
|
|
def test_iteration_label_includes_action(self):
|
|
|
|
|
triples = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.ITER_URI, question_uri=self.SESSION_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
action="graph-rag-query",
|
|
|
|
|
)
|
|
|
|
|
label = find_triple(triples, RDFS_LABEL, self.ITER_URI)
|
|
|
|
|
assert label is not None
|
|
|
|
|
assert "graph-rag-query" in label.o.value
|
|
|
|
|
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
def test_iteration_thought_sub_entity(self):
|
|
|
|
|
"""Thought is a sub-entity with Reflection and Thought types."""
|
|
|
|
|
thought_uri = "urn:trustgraph:agent:test-session/i1/thought"
|
|
|
|
|
thought_doc = "urn:doc:thought-1"
|
2026-03-13 14:27:42 +00:00
|
|
|
triples = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.ITER_URI, question_uri=self.SESSION_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
action="search",
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
thought_uri=thought_uri,
|
|
|
|
|
thought_document_id=thought_doc,
|
2026-03-13 14:27:42 +00:00
|
|
|
)
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
# Iteration links to thought sub-entity
|
|
|
|
|
thought_link = find_triple(triples, TG_THOUGHT, self.ITER_URI)
|
|
|
|
|
assert thought_link is not None
|
|
|
|
|
assert thought_link.o.iri == thought_uri
|
|
|
|
|
# Thought has correct types
|
|
|
|
|
assert has_type(triples, thought_uri, TG_REFLECTION_TYPE)
|
|
|
|
|
assert has_type(triples, thought_uri, TG_THOUGHT_TYPE)
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
# Thought was derived from iteration
|
|
|
|
|
derived = find_triple(triples, PROV_WAS_DERIVED_FROM, thought_uri)
|
|
|
|
|
assert derived is not None
|
|
|
|
|
assert derived.o.iri == self.ITER_URI
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
# Thought has document reference
|
|
|
|
|
doc = find_triple(triples, TG_DOCUMENT, thought_uri)
|
|
|
|
|
assert doc is not None
|
|
|
|
|
assert doc.o.iri == thought_doc
|
2026-03-13 14:27:42 +00:00
|
|
|
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
def test_iteration_no_observation_sub_entity(self):
|
|
|
|
|
"""Iteration no longer embeds observation — it's a separate entity."""
|
2026-03-13 14:27:42 +00:00
|
|
|
triples = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.ITER_URI, question_uri=self.SESSION_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
action="search",
|
|
|
|
|
)
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
# No TG_OBSERVATION predicate on the iteration
|
|
|
|
|
for t in triples:
|
|
|
|
|
assert "observation" not in t.p.iri.lower() or "Observation" not in t.p.iri
|
2026-03-13 14:27:42 +00:00
|
|
|
|
|
|
|
|
def test_iteration_action_recorded(self):
|
|
|
|
|
triples = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.ITER_URI, question_uri=self.SESSION_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
action="graph-rag-query",
|
|
|
|
|
)
|
|
|
|
|
action = find_triple(triples, TG_ACTION, self.ITER_URI)
|
|
|
|
|
assert action is not None
|
|
|
|
|
assert action.o.value == "graph-rag-query"
|
|
|
|
|
|
|
|
|
|
def test_iteration_arguments_json_encoded(self):
|
|
|
|
|
args = {"query": "test query", "limit": 10}
|
|
|
|
|
triples = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.ITER_URI, question_uri=self.SESSION_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
action="search",
|
|
|
|
|
arguments=args,
|
|
|
|
|
)
|
|
|
|
|
arguments = find_triple(triples, TG_ARGUMENTS, self.ITER_URI)
|
|
|
|
|
assert arguments is not None
|
|
|
|
|
parsed = json.loads(arguments.o.value)
|
|
|
|
|
assert parsed == args
|
|
|
|
|
|
|
|
|
|
def test_iteration_default_arguments_empty_dict(self):
|
|
|
|
|
triples = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.ITER_URI, question_uri=self.SESSION_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
action="search",
|
|
|
|
|
)
|
|
|
|
|
arguments = find_triple(triples, TG_ARGUMENTS, self.ITER_URI)
|
|
|
|
|
assert arguments is not None
|
|
|
|
|
parsed = json.loads(arguments.o.value)
|
|
|
|
|
assert parsed == {}
|
|
|
|
|
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
def test_iteration_no_thought(self):
|
|
|
|
|
"""Minimal iteration with just action — no thought triples."""
|
2026-03-13 14:27:42 +00:00
|
|
|
triples = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.ITER_URI, question_uri=self.SESSION_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
action="noop",
|
|
|
|
|
)
|
|
|
|
|
thought = find_triple(triples, TG_THOUGHT, self.ITER_URI)
|
|
|
|
|
assert thought is None
|
|
|
|
|
|
|
|
|
|
def test_iteration_chaining(self):
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
"""Both first and second iterations use wasDerivedFrom."""
|
2026-03-13 14:27:42 +00:00
|
|
|
iter1_uri = "urn:trustgraph:agent:sess/i1"
|
|
|
|
|
iter2_uri = "urn:trustgraph:agent:sess/i2"
|
|
|
|
|
|
|
|
|
|
triples1 = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
iter1_uri, question_uri=self.SESSION_URI, action="step1",
|
2026-03-13 14:27:42 +00:00
|
|
|
)
|
|
|
|
|
triples2 = agent_iteration_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
iter2_uri, previous_uri=iter1_uri, action="step2",
|
2026-03-13 14:27:42 +00:00
|
|
|
)
|
|
|
|
|
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
derived1 = find_triple(triples1, PROV_WAS_DERIVED_FROM, iter1_uri)
|
|
|
|
|
assert derived1.o.iri == self.SESSION_URI
|
2026-03-13 14:27:42 +00:00
|
|
|
|
|
|
|
|
derived2 = find_triple(triples2, PROV_WAS_DERIVED_FROM, iter2_uri)
|
|
|
|
|
assert derived2.o.iri == iter1_uri
|
|
|
|
|
|
|
|
|
|
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# agent_observation_triples
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class TestAgentObservationTriples:
|
|
|
|
|
|
|
|
|
|
OBS_URI = "urn:trustgraph:agent:test-session/i1/observation"
|
|
|
|
|
ITER_URI = "urn:trustgraph:agent:test-session/i1"
|
|
|
|
|
|
|
|
|
|
def test_observation_types(self):
|
|
|
|
|
triples = agent_observation_triples(
|
|
|
|
|
self.OBS_URI, self.ITER_URI,
|
|
|
|
|
)
|
|
|
|
|
assert has_type(triples, self.OBS_URI, PROV_ENTITY)
|
|
|
|
|
assert has_type(triples, self.OBS_URI, TG_OBSERVATION_TYPE)
|
|
|
|
|
|
|
|
|
|
def test_observation_derived_from_iteration(self):
|
|
|
|
|
triples = agent_observation_triples(
|
|
|
|
|
self.OBS_URI, self.ITER_URI,
|
|
|
|
|
)
|
|
|
|
|
derived = find_triple(triples, PROV_WAS_DERIVED_FROM, self.OBS_URI)
|
|
|
|
|
assert derived is not None
|
|
|
|
|
assert derived.o.iri == self.ITER_URI
|
|
|
|
|
|
|
|
|
|
def test_observation_label(self):
|
|
|
|
|
triples = agent_observation_triples(
|
|
|
|
|
self.OBS_URI, self.ITER_URI,
|
|
|
|
|
)
|
|
|
|
|
label = find_triple(triples, RDFS_LABEL, self.OBS_URI)
|
|
|
|
|
assert label is not None
|
|
|
|
|
assert label.o.value == "Observation"
|
|
|
|
|
|
|
|
|
|
def test_observation_document(self):
|
|
|
|
|
doc_id = "urn:doc:obs-1"
|
|
|
|
|
triples = agent_observation_triples(
|
|
|
|
|
self.OBS_URI, self.ITER_URI, document_id=doc_id,
|
|
|
|
|
)
|
|
|
|
|
doc = find_triple(triples, TG_DOCUMENT, self.OBS_URI)
|
|
|
|
|
assert doc is not None
|
|
|
|
|
assert doc.o.iri == doc_id
|
|
|
|
|
|
|
|
|
|
def test_observation_no_document(self):
|
|
|
|
|
triples = agent_observation_triples(
|
|
|
|
|
self.OBS_URI, self.ITER_URI,
|
|
|
|
|
)
|
|
|
|
|
doc = find_triple(triples, TG_DOCUMENT, self.OBS_URI)
|
|
|
|
|
assert doc is None
|
|
|
|
|
|
|
|
|
|
|
2026-03-13 14:27:42 +00:00
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# agent_final_triples
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class TestAgentFinalTriples:
|
|
|
|
|
|
|
|
|
|
FINAL_URI = "urn:trustgraph:agent:test-session/final"
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
PREV_URI = "urn:trustgraph:agent:test-session/i3"
|
|
|
|
|
SESSION_URI = "urn:trustgraph:agent:test-session"
|
2026-03-13 14:27:42 +00:00
|
|
|
|
|
|
|
|
def test_final_types(self):
|
|
|
|
|
triples = agent_final_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.FINAL_URI, previous_uri=self.PREV_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
)
|
|
|
|
|
assert has_type(triples, self.FINAL_URI, PROV_ENTITY)
|
|
|
|
|
assert has_type(triples, self.FINAL_URI, TG_CONCLUSION)
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
assert has_type(triples, self.FINAL_URI, TG_ANSWER_TYPE)
|
2026-03-13 14:27:42 +00:00
|
|
|
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
def test_final_derived_from_previous(self):
|
|
|
|
|
"""Conclusion with iterations uses wasDerivedFrom."""
|
2026-03-13 14:27:42 +00:00
|
|
|
triples = agent_final_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.FINAL_URI, previous_uri=self.PREV_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
)
|
|
|
|
|
derived = find_triple(triples, PROV_WAS_DERIVED_FROM, self.FINAL_URI)
|
|
|
|
|
assert derived is not None
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
assert derived.o.iri == self.PREV_URI
|
|
|
|
|
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
def test_final_derived_from_question_when_no_iterations(self):
|
|
|
|
|
"""When agent answers immediately, final uses wasDerivedFrom to question."""
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
triples = agent_final_triples(
|
|
|
|
|
self.FINAL_URI, question_uri=self.SESSION_URI,
|
|
|
|
|
)
|
|
|
|
|
derived = find_triple(triples, PROV_WAS_DERIVED_FROM, self.FINAL_URI)
|
Split Analysis into Analysis+ToolUse and Observation, add message_id (#747)
Refactor agent provenance so that the decision (thought + tool
selection) and the result (observation) are separate DAG entities:
Question ← Analysis+ToolUse ← Observation ← ... ← Conclusion
Analysis gains tg:ToolUse as a mixin RDF type and is emitted
before tool execution via an on_action callback in react().
This ensures sub-traces (e.g. GraphRAG) appear after their
parent Analysis in the streaming event order.
Observation becomes a standalone prov:Entity with tg:Observation
type, emitted after tool execution. The linear DAG chain runs
through Observation — subsequent iterations and the Conclusion
derive from it, not from the Analysis.
message_id is populated on streaming AgentResponse for thought
and observation chunks, using the provenance URI of the entity
being built. This lets clients group streamed chunks by entity.
Wire changes:
- provenance/agent.py: Add ToolUse type, new
agent_observation_triples(), remove observation from iteration
- agent_manager.py: Add on_action callback between reason() and
tool execution
- orchestrator/pattern_base.py: Split emit, wire message_id,
chain through observation URIs
- orchestrator/react_pattern.py: Emit Analysis via on_action
before tool runs
- agent/react/service.py: Same for non-orchestrator path
- api/explainability.py: New Observation class, updated dispatch
and chain walker
- api/types.py: Add message_id to AgentThought/AgentObservation
- cli: Render Observation separately, [analysis: tool] labels
2026-03-31 17:51:22 +01:00
|
|
|
assert derived is not None
|
|
|
|
|
assert derived.o.iri == self.SESSION_URI
|
2026-03-13 14:27:42 +00:00
|
|
|
|
|
|
|
|
def test_final_label(self):
|
|
|
|
|
triples = agent_final_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.FINAL_URI, previous_uri=self.PREV_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
)
|
|
|
|
|
label = find_triple(triples, RDFS_LABEL, self.FINAL_URI)
|
|
|
|
|
assert label is not None
|
|
|
|
|
assert label.o.value == "Conclusion"
|
|
|
|
|
|
|
|
|
|
def test_final_document_reference(self):
|
|
|
|
|
triples = agent_final_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.FINAL_URI, previous_uri=self.PREV_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
document_id="urn:trustgraph:agent:sess/answer",
|
|
|
|
|
)
|
|
|
|
|
doc = find_triple(triples, TG_DOCUMENT, self.FINAL_URI)
|
|
|
|
|
assert doc is not None
|
|
|
|
|
assert doc.o.type == IRI
|
|
|
|
|
assert doc.o.iri == "urn:trustgraph:agent:sess/answer"
|
|
|
|
|
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
def test_final_no_document(self):
|
2026-03-13 14:27:42 +00:00
|
|
|
triples = agent_final_triples(
|
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding (#697)
Enhance retrieval pipelines: 4-stage GraphRAG, DocRAG grounding,
consistent PROV-O
GraphRAG:
- Split retrieval into 4 prompt stages: extract-concepts,
kg-edge-scoring,
kg-edge-reasoning, kg-synthesis (was single-stage)
- Add concept extraction (grounding) for per-concept embedding
- Filter main query to default graph, ignoring
provenance/explainability edges
- Add source document edges to knowledge graph
DocumentRAG:
- Add grounding step with concept extraction, matching GraphRAG's
pattern:
Question → Grounding → Exploration → Synthesis
- Per-concept embedding and chunk retrieval with deduplication
Cross-pipeline:
- Make PROV-O derivation links consistent: wasGeneratedBy for first
entity from Activity, wasDerivedFrom for entity-to-entity chains
- Update CLIs (tg-invoke-agent, tg-invoke-graph-rag,
tg-invoke-document-rag) for new explainability structure
- Fix all affected unit and integration tests
2026-03-16 12:12:13 +00:00
|
|
|
self.FINAL_URI, previous_uri=self.PREV_URI,
|
2026-03-13 14:27:42 +00:00
|
|
|
)
|
|
|
|
|
doc = find_triple(triples, TG_DOCUMENT, self.FINAL_URI)
|
|
|
|
|
assert doc is None
|
Additional agent DAG tests (#750)
- test_agent_provenance.py: test_session_parent_uri,
test_session_no_parent_uri, and 6 synthesis tests (types,
single/multiple parents, document, label)
- test_on_action_callback.py: 3 tests — fires before tool, skipped
for Final, works when None
- test_callback_message_id.py: 7 tests — message_id on think/observe/
answer callbacks (streaming + non-streaming) and
send_final_response
- test_parse_chunk_message_id.py (5 tests) - _parse_chunk propagates
message_id for thought, observation, answer; handles missing
gracefully
- test_explainability_parsing.py (+1) -
test_dispatches_analysis_with_tooluse - Analysis+ToolUse mixin still
dispatches to Analysis
- test_explainability.py (+1) -
test_observation_found_via_subtrace_synthesis
- chain walker follows from sub-trace Synthesis to find Observation
and
Conclusion in correct order
- test_agent_provenance.py (+8) - session parent_uri (2), synthesis
single/multiple parents, types, document, label (6)
2026-04-01 13:59:34 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# agent_synthesis_triples
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class TestAgentSynthesisTriples:
|
|
|
|
|
|
|
|
|
|
SYNTH_URI = "urn:trustgraph:agent:test-session/synthesis"
|
|
|
|
|
FINDING_0 = "urn:trustgraph:agent:test-session/finding/0"
|
|
|
|
|
FINDING_1 = "urn:trustgraph:agent:test-session/finding/1"
|
|
|
|
|
FINDING_2 = "urn:trustgraph:agent:test-session/finding/2"
|
|
|
|
|
|
|
|
|
|
def test_synthesis_types(self):
|
|
|
|
|
triples = agent_synthesis_triples(self.SYNTH_URI, self.FINDING_0)
|
|
|
|
|
assert has_type(triples, self.SYNTH_URI, PROV_ENTITY)
|
|
|
|
|
assert has_type(triples, self.SYNTH_URI, TG_SYNTHESIS)
|
|
|
|
|
assert has_type(triples, self.SYNTH_URI, TG_ANSWER_TYPE)
|
|
|
|
|
|
|
|
|
|
def test_synthesis_single_parent_string(self):
|
|
|
|
|
"""Single parent passed as string."""
|
|
|
|
|
triples = agent_synthesis_triples(self.SYNTH_URI, self.FINDING_0)
|
|
|
|
|
derived = find_triples(triples, PROV_WAS_DERIVED_FROM, self.SYNTH_URI)
|
|
|
|
|
assert len(derived) == 1
|
|
|
|
|
assert derived[0].o.iri == self.FINDING_0
|
|
|
|
|
|
|
|
|
|
def test_synthesis_multiple_parents(self):
|
|
|
|
|
"""Multiple parents for supervisor fan-in."""
|
|
|
|
|
parents = [self.FINDING_0, self.FINDING_1, self.FINDING_2]
|
|
|
|
|
triples = agent_synthesis_triples(self.SYNTH_URI, parents)
|
|
|
|
|
derived = find_triples(triples, PROV_WAS_DERIVED_FROM, self.SYNTH_URI)
|
|
|
|
|
assert len(derived) == 3
|
|
|
|
|
derived_uris = {t.o.iri for t in derived}
|
|
|
|
|
assert derived_uris == set(parents)
|
|
|
|
|
|
|
|
|
|
def test_synthesis_single_parent_as_list(self):
|
|
|
|
|
"""Single parent passed as list."""
|
|
|
|
|
triples = agent_synthesis_triples(self.SYNTH_URI, [self.FINDING_0])
|
|
|
|
|
derived = find_triples(triples, PROV_WAS_DERIVED_FROM, self.SYNTH_URI)
|
|
|
|
|
assert len(derived) == 1
|
|
|
|
|
assert derived[0].o.iri == self.FINDING_0
|
|
|
|
|
|
|
|
|
|
def test_synthesis_document(self):
|
|
|
|
|
triples = agent_synthesis_triples(
|
|
|
|
|
self.SYNTH_URI, self.FINDING_0,
|
|
|
|
|
document_id="urn:doc:synth",
|
|
|
|
|
)
|
|
|
|
|
doc = find_triple(triples, TG_DOCUMENT, self.SYNTH_URI)
|
|
|
|
|
assert doc is not None
|
|
|
|
|
assert doc.o.iri == "urn:doc:synth"
|
|
|
|
|
|
|
|
|
|
def test_synthesis_label(self):
|
|
|
|
|
triples = agent_synthesis_triples(self.SYNTH_URI, self.FINDING_0)
|
|
|
|
|
label = find_triple(triples, RDFS_LABEL, self.SYNTH_URI)
|
|
|
|
|
assert label is not None
|
|
|
|
|
assert label.o.value == "Synthesis"
|