Minor agent tweaks (#692)

Update RAG and Agent clients for streaming message handling

GraphRAG now sends multiple message types in a stream:
- 'explain' messages with explain_id and explain_graph for
  provenance
- 'chunk' messages with response text fragments
- end_of_session marker for stream completion

Updated all clients to handle this properly:

CLI clients (trustgraph-base/trustgraph/clients/):
- graph_rag_client.py: Added chunk_callback and explain_callback
- document_rag_client.py: Added chunk_callback and explain_callback
- agent_client.py: Added think, observe, answer_callback,
  error_callback

Internal clients (trustgraph-base/trustgraph/base/):
- graph_rag_client.py: Async callbacks for streaming
- agent_client.py: Async callbacks for streaming

All clients now:
- Route messages by chunk_type/message_type
- Stream via optional callbacks for incremental delivery
- Wait for proper completion signals
(end_of_dialog/end_of_session/end_of_stream)
- Accumulate and return complete response for callers not using
  callbacks

Updated callers:
- extract/kg/agent/extract.py: Uses new invoke(question=...) API
- tests/integration/test_agent_kg_extraction_integration.py:
  Updated mocks

This fixes the agent infinite loop issue where knowledge_query was
returning the first 'explain' message (empty response) instead of
waiting for the actual answer chunks.

Concurrency in triples query
This commit is contained in:
cybermaggedon 2026-03-12 17:59:02 +00:00 committed by GitHub
parent 45e6ad4abc
commit aecf00f040
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 246 additions and 58 deletions

View file

@ -4,10 +4,57 @@ from .. schema import AgentRequest, AgentResponse
from .. knowledge import Uri, Literal
class AgentClient(RequestResponse):
async def invoke(self, recipient, question, plan=None, state=None,
history=[], timeout=300):
resp = await self.request(
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.chunk_type == 'thought':
if think:
await think(resp.content, resp.end_of_message)
return False # Continue receiving
# Handle observation chunks
if resp.chunk_type == 'observation':
if observe:
await observe(resp.content, resp.end_of_message)
return False # Continue receiving
# Handle answer chunks
if resp.chunk_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,
plan = plan,
@ -18,10 +65,7 @@ class AgentClient(RequestResponse):
timeout=timeout,
)
if resp.error:
raise RuntimeError(resp.error.message)
return resp.answer
return "".join(accumulated_answer)
class AgentClientSpec(RequestResponseSpec):
def __init__(