trustgraph/trustgraph-base/trustgraph/base/agent_client.py
cybermaggedon d2751553a3
Add agent explainability instrumentation and unify envelope field naming (#795)
Addresses recommendations from the UX developer's agent experience report.
Adds provenance predicates, DAG structure changes, error resilience, and
a published OWL ontology.

Explainability additions:

- Tool candidates: tg:toolCandidate on Analysis events lists the tools
  visible to the LLM for each iteration (names only, descriptions in config)
- Termination reason: tg:terminationReason on Conclusion/Synthesis events
  (final-answer, plan-complete, subagents-complete)
- Step counter: tg:stepNumber on iteration events
- Pattern decision: new tg:PatternDecision entity in the DAG between
  session and first iteration, carrying tg:pattern and tg:taskType
- Latency: tg:llmDurationMs on Analysis events, tg:toolDurationMs on
  Observation events
- Token counts on events: tg:inToken/tg:outToken/tg:llmModel on
  Grounding, Focus, Synthesis, and Analysis events
- Tool/parse errors: tg:toolError on Observation events with tg:Error
  mixin type. Parse failures return as error observations instead of
  crashing the agent, giving it a chance to retry.

Envelope unification:

- Rename chunk_type to message_type across AgentResponse schema,
  translator, SDK types, socket clients, CLI, and all tests.
  Agent and RAG services now both use message_type on the wire.

Ontology:

- specs/ontology/trustgraph.ttl — OWL vocabulary covering all 26 classes,
  7 object properties, and 36+ datatype properties including new predicates.

DAG structure tests:

- tests/unit/test_provenance/test_dag_structure.py verifies the
  wasDerivedFrom chain for GraphRAG, DocumentRAG, and all three agent
  patterns (react, plan, supervisor) including the pattern-decision link.
2026-04-13 16:16:42 +01:00

80 lines
2.7 KiB
Python

from . request_response_spec import RequestResponse, RequestResponseSpec
from .. schema import AgentRequest, AgentResponse
from .. knowledge import Uri, Literal
class AgentClient(RequestResponse):
async def invoke(self, question, plan=None, state=None,
history=[], think=None, observe=None, answer_callback=None,
timeout=300):
"""
Invoke the agent with optional streaming callbacks.
Args:
question: The question to ask
plan: Optional plan context
state: Optional state context
history: Conversation history
think: Optional async callback(content, end_of_message) for thought chunks
observe: Optional async callback(content, end_of_message) for observation chunks
answer_callback: Optional async callback(content, end_of_message) for answer chunks
timeout: Request timeout in seconds
Returns:
Complete answer text (accumulated from all answer chunks)
"""
accumulated_answer = []
async def recipient(resp):
if resp.error:
raise RuntimeError(resp.error.message)
# Handle thought chunks
if resp.message_type == 'thought':
if think:
await think(resp.content, resp.end_of_message)
return False # Continue receiving
# Handle observation chunks
if resp.message_type == 'observation':
if observe:
await observe(resp.content, resp.end_of_message)
return False # Continue receiving
# Handle answer chunks
if resp.message_type == 'answer':
if resp.content:
accumulated_answer.append(resp.content)
if answer_callback:
await answer_callback(resp.content, resp.end_of_message)
# Complete when dialog ends
if resp.end_of_dialog:
return True
return False # Continue receiving
await self.request(
AgentRequest(
question = question,
state = state or "",
history = history,
),
recipient=recipient,
timeout=timeout,
)
return "".join(accumulated_answer)
class AgentClientSpec(RequestResponseSpec):
def __init__(
self, request_name, response_name,
):
super(AgentClientSpec, self).__init__(
request_name = request_name,
request_schema = AgentRequest,
response_name = response_name,
response_schema = AgentResponse,
impl = AgentClient,
)