Merge branch 'release/v2.3'

This commit is contained in:
Cyber MacGeddon 2026-04-16 11:01:21 +01:00
commit 59e269185d
29 changed files with 146 additions and 110 deletions

View file

@ -18,6 +18,20 @@ from trustgraph.schema import ExtractedObject, Metadata, RowSchema, Field
class TestRowsCassandraIntegration: class TestRowsCassandraIntegration:
"""Integration tests for Cassandra row storage with unified table""" """Integration tests for Cassandra row storage with unified table"""
@pytest.fixture(autouse=True)
def patch_async_execute(self):
"""Route async_execute through session.execute so the mock's
side_effect handles all CQL (DDL and DML) uniformly and every
call lands in mock_session.execute.call_args_list."""
async def _fake(session, query, params=None):
session.execute(query, params)
return []
with patch(
'trustgraph.storage.rows.cassandra.write.async_execute',
new=_fake,
):
yield
@pytest.fixture @pytest.fixture
def mock_cassandra_session(self): def mock_cassandra_session(self):
"""Mock Cassandra session for integration tests""" """Mock Cassandra session for integration tests"""

View file

@ -1,6 +1,5 @@
[pytest] [pytest]
testpaths = tests testpaths = tests
python_paths = .
python_files = test_*.py python_files = test_*.py
python_classes = Test* python_classes = Test*
python_functions = test_* python_functions = test_*
@ -8,7 +7,7 @@ addopts =
-v -v
--tb=short --tb=short
--strict-markers --strict-markers
--disable-warnings # --disable-warnings
# --cov-fail-under=80 # --cov-fail-under=80
asyncio_mode = auto asyncio_mode = auto
markers = markers =

View file

@ -9,6 +9,7 @@ tool usage patterns.
import pytest import pytest
from unittest.mock import Mock, AsyncMock from unittest.mock import Mock, AsyncMock
import asyncio import asyncio
import inspect
from collections import defaultdict from collections import defaultdict
@ -133,7 +134,7 @@ class TestToolCoordinationLogic:
resolved_params[key] = value resolved_params[key] = value
# Execute tool # Execute tool
if asyncio.iscoroutinefunction(tool_function): if inspect.iscoroutinefunction(tool_function):
result = await tool_function(**resolved_params) result = await tool_function(**resolved_params)
else: else:
result = tool_function(**resolved_params) result = tool_function(**resolved_params)
@ -227,7 +228,7 @@ class TestToolCoordinationLogic:
# Simulate async execution with delay # Simulate async execution with delay
await asyncio.sleep(0.001) # Small delay to simulate work await asyncio.sleep(0.001) # Small delay to simulate work
if asyncio.iscoroutinefunction(tool_function): if inspect.iscoroutinefunction(tool_function):
result = await tool_function(**parameters) result = await tool_function(**parameters)
else: else:
result = tool_function(**parameters) result = tool_function(**parameters)
@ -337,7 +338,7 @@ class TestToolCoordinationLogic:
if attempt > 0: if attempt > 0:
await asyncio.sleep(0.001 * (self.backoff_factor ** attempt)) await asyncio.sleep(0.001 * (self.backoff_factor ** attempt))
if asyncio.iscoroutinefunction(tool_function): if inspect.iscoroutinefunction(tool_function):
result = await tool_function(**parameters) result = await tool_function(**parameters)
else: else:
result = tool_function(**parameters) result = tool_function(**parameters)

View file

@ -45,7 +45,7 @@ def test_setup_logging_without_loki_configures_console(monkeypatch):
kwargs = basic_config.call_args.kwargs kwargs = basic_config.call_args.kwargs
assert kwargs["level"] == logging.DEBUG assert kwargs["level"] == logging.DEBUG
assert kwargs["force"] is True assert kwargs["force"] is True
assert "processor-1" in kwargs["format"] assert "%(processor_id)s" in kwargs["format"]
assert len(kwargs["handlers"]) == 1 assert len(kwargs["handlers"]) == 1
logger.info.assert_called_once_with("Logging configured with level: debug") logger.info.assert_called_once_with("Logging configured with level: debug")
@ -60,11 +60,14 @@ def test_setup_logging_with_loki_enables_queue_listener(monkeypatch):
queue_listener = MagicMock() queue_listener = MagicMock()
loki_handler = MagicMock() loki_handler = MagicMock()
noisy_logger = MagicMock()
logger_map = { logger_map = {
None: root_logger, None: root_logger,
"trustgraph.base.logging": module_logger, "trustgraph.base.logging": module_logger,
"urllib3": urllib3_logger, "urllib3": urllib3_logger,
"urllib3.connectionpool": connectionpool_logger, "urllib3.connectionpool": connectionpool_logger,
"pika": noisy_logger,
"cassandra": noisy_logger,
} }
monkeypatch.setattr(logging, "basicConfig", basic_config) monkeypatch.setattr(logging, "basicConfig", basic_config)

View file

@ -330,7 +330,8 @@ class TestUnifiedTableQueries:
"""Test queries against the unified rows table""" """Test queries against the unified rows table"""
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_query_with_index_match(self): @patch('trustgraph.query.rows.cassandra.service.async_execute', new_callable=AsyncMock)
async def test_query_with_index_match(self, mock_async_execute):
"""Test query execution with matching index""" """Test query execution with matching index"""
processor = MagicMock() processor = MagicMock()
processor.session = MagicMock() processor.session = MagicMock()
@ -340,10 +341,10 @@ class TestUnifiedTableQueries:
processor.find_matching_index = Processor.find_matching_index.__get__(processor, Processor) processor.find_matching_index = Processor.find_matching_index.__get__(processor, Processor)
processor.query_cassandra = Processor.query_cassandra.__get__(processor, Processor) processor.query_cassandra = Processor.query_cassandra.__get__(processor, Processor)
# Mock session execute to return test data # Mock async_execute to return test data
mock_row = MagicMock() mock_row = MagicMock()
mock_row.data = {"id": "123", "name": "Test Product", "category": "electronics"} mock_row.data = {"id": "123", "name": "Test Product", "category": "electronics"}
processor.session.execute.return_value = [mock_row] mock_async_execute.return_value = [mock_row]
schema = RowSchema( schema = RowSchema(
name="products", name="products",
@ -366,12 +367,12 @@ class TestUnifiedTableQueries:
# Verify Cassandra was connected and queried # Verify Cassandra was connected and queried
processor.connect_cassandra.assert_called_once() processor.connect_cassandra.assert_called_once()
processor.session.execute.assert_called_once() mock_async_execute.assert_called_once()
# Verify query structure - should query unified rows table # Verify query structure - should query unified rows table
call_args = processor.session.execute.call_args call_args = mock_async_execute.call_args
query = call_args[0][0] query = call_args[0][1]
params = call_args[0][1] params = call_args[0][2]
assert "SELECT data, source FROM test_user.rows" in query assert "SELECT data, source FROM test_user.rows" in query
assert "collection = %s" in query assert "collection = %s" in query
@ -390,7 +391,8 @@ class TestUnifiedTableQueries:
assert results[0]["category"] == "electronics" assert results[0]["category"] == "electronics"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_query_without_index_match(self): @patch('trustgraph.query.rows.cassandra.service.async_execute', new_callable=AsyncMock)
async def test_query_without_index_match(self, mock_async_execute):
"""Test query execution without matching index (scan mode)""" """Test query execution without matching index (scan mode)"""
processor = MagicMock() processor = MagicMock()
processor.session = MagicMock() processor.session = MagicMock()
@ -401,12 +403,12 @@ class TestUnifiedTableQueries:
processor._matches_filters = Processor._matches_filters.__get__(processor, Processor) processor._matches_filters = Processor._matches_filters.__get__(processor, Processor)
processor.query_cassandra = Processor.query_cassandra.__get__(processor, Processor) processor.query_cassandra = Processor.query_cassandra.__get__(processor, Processor)
# Mock session execute to return test data # Mock async_execute to return test data
mock_row1 = MagicMock() mock_row1 = MagicMock()
mock_row1.data = {"id": "1", "name": "Product A", "price": "100"} mock_row1.data = {"id": "1", "name": "Product A", "price": "100"}
mock_row2 = MagicMock() mock_row2 = MagicMock()
mock_row2.data = {"id": "2", "name": "Product B", "price": "200"} mock_row2.data = {"id": "2", "name": "Product B", "price": "200"}
processor.session.execute.return_value = [mock_row1, mock_row2] mock_async_execute.return_value = [mock_row1, mock_row2]
schema = RowSchema( schema = RowSchema(
name="products", name="products",
@ -428,8 +430,8 @@ class TestUnifiedTableQueries:
) )
# Query should use ALLOW FILTERING for scan # Query should use ALLOW FILTERING for scan
call_args = processor.session.execute.call_args call_args = mock_async_execute.call_args
query = call_args[0][0] query = call_args[0][1]
assert "ALLOW FILTERING" in query assert "ALLOW FILTERING" in query

View file

@ -72,7 +72,6 @@ def processor(mock_pulsar_client, sample_schemas):
return proc return proc
@pytest.mark.asyncio
class TestNLPQueryProcessor: class TestNLPQueryProcessor:
"""Test NLP Query service processor""" """Test NLP Query service processor"""

View file

@ -36,7 +36,6 @@ def processor(mock_pulsar_client):
return proc return proc
@pytest.mark.asyncio
class TestStructuredQueryProcessor: class TestStructuredQueryProcessor:
"""Test Structured Query service processor""" """Test Structured Query service processor"""

View file

@ -160,7 +160,8 @@ class TestRowsCassandraStorageLogic:
assert id_field.primary is True assert id_field.primary is True
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_object_processing_stores_data_map(self): @patch('trustgraph.storage.rows.cassandra.write.async_execute', new_callable=AsyncMock)
async def test_object_processing_stores_data_map(self, mock_async_execute):
"""Test that row processing stores data as map<text, text>""" """Test that row processing stores data as map<text, text>"""
processor = MagicMock() processor = MagicMock()
processor.schemas = { processor.schemas = {
@ -184,6 +185,8 @@ class TestRowsCassandraStorageLogic:
processor.collection_exists = MagicMock(return_value=True) processor.collection_exists = MagicMock(return_value=True)
processor.on_object = Processor.on_object.__get__(processor, Processor) processor.on_object = Processor.on_object.__get__(processor, Processor)
mock_async_execute.return_value = []
# Create test object # Create test object
test_obj = ExtractedObject( test_obj = ExtractedObject(
metadata=Metadata( metadata=Metadata(
@ -205,10 +208,10 @@ class TestRowsCassandraStorageLogic:
await processor.on_object(msg, None, None) await processor.on_object(msg, None, None)
# Verify insert was executed # Verify insert was executed
processor.session.execute.assert_called() mock_async_execute.assert_called()
insert_call = processor.session.execute.call_args insert_call = mock_async_execute.call_args
insert_cql = insert_call[0][0] insert_cql = insert_call[0][1]
values = insert_call[0][1] values = insert_call[0][2]
# Verify using unified rows table # Verify using unified rows table
assert "INSERT INTO test_user.rows" in insert_cql assert "INSERT INTO test_user.rows" in insert_cql
@ -222,7 +225,8 @@ class TestRowsCassandraStorageLogic:
assert values[5] == "" # source assert values[5] == "" # source
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_object_processing_multiple_indexes(self): @patch('trustgraph.storage.rows.cassandra.write.async_execute', new_callable=AsyncMock)
async def test_object_processing_multiple_indexes(self, mock_async_execute):
"""Test that row is written once per indexed field""" """Test that row is written once per indexed field"""
processor = MagicMock() processor = MagicMock()
processor.schemas = { processor.schemas = {
@ -246,6 +250,8 @@ class TestRowsCassandraStorageLogic:
processor.collection_exists = MagicMock(return_value=True) processor.collection_exists = MagicMock(return_value=True)
processor.on_object = Processor.on_object.__get__(processor, Processor) processor.on_object = Processor.on_object.__get__(processor, Processor)
mock_async_execute.return_value = []
test_obj = ExtractedObject( test_obj = ExtractedObject(
metadata=Metadata( metadata=Metadata(
id="test-001", id="test-001",
@ -264,12 +270,12 @@ class TestRowsCassandraStorageLogic:
await processor.on_object(msg, None, None) await processor.on_object(msg, None, None)
# Should have 3 inserts (one per indexed field: id, category, status) # Should have 3 inserts (one per indexed field: id, category, status)
assert processor.session.execute.call_count == 3 assert mock_async_execute.call_count == 3
# Check that different index_names were used # Check that different index_names were used
index_names_used = set() index_names_used = set()
for call in processor.session.execute.call_args_list: for call in mock_async_execute.call_args_list:
values = call[0][1] values = call[0][2]
index_names_used.add(values[2]) # index_name is 3rd value index_names_used.add(values[2]) # index_name is 3rd value
assert index_names_used == {"id", "category", "status"} assert index_names_used == {"id", "category", "status"}
@ -279,7 +285,8 @@ class TestRowsCassandraStorageBatchLogic:
"""Test batch processing logic for unified table implementation""" """Test batch processing logic for unified table implementation"""
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_batch_object_processing(self): @patch('trustgraph.storage.rows.cassandra.write.async_execute', new_callable=AsyncMock)
async def test_batch_object_processing(self, mock_async_execute):
"""Test processing of batch ExtractedObjects""" """Test processing of batch ExtractedObjects"""
processor = MagicMock() processor = MagicMock()
processor.schemas = { processor.schemas = {
@ -302,6 +309,8 @@ class TestRowsCassandraStorageBatchLogic:
processor.collection_exists = MagicMock(return_value=True) processor.collection_exists = MagicMock(return_value=True)
processor.on_object = Processor.on_object.__get__(processor, Processor) processor.on_object = Processor.on_object.__get__(processor, Processor)
mock_async_execute.return_value = []
# Create batch object with multiple values # Create batch object with multiple values
batch_obj = ExtractedObject( batch_obj = ExtractedObject(
metadata=Metadata( metadata=Metadata(
@ -325,12 +334,12 @@ class TestRowsCassandraStorageBatchLogic:
await processor.on_object(msg, None, None) await processor.on_object(msg, None, None)
# Should have 3 inserts (one per row, one index per row since only primary key) # Should have 3 inserts (one per row, one index per row since only primary key)
assert processor.session.execute.call_count == 3 assert mock_async_execute.call_count == 3
# Check each insert has different id # Check each insert has different id
ids_inserted = set() ids_inserted = set()
for call in processor.session.execute.call_args_list: for call in mock_async_execute.call_args_list:
values = call[0][1] values = call[0][2]
ids_inserted.add(tuple(values[3])) # index_value is 4th value ids_inserted.add(tuple(values[3])) # index_value is 4th value
assert ids_inserted == {("001",), ("002",), ("003",)} assert ids_inserted == {("001",), ("002",), ("003",)}

View file

@ -9,7 +9,7 @@ with hand-built fake rows.
""" """
import pytest import pytest
from unittest.mock import Mock from unittest.mock import Mock, AsyncMock, patch
from trustgraph.tables.knowledge import KnowledgeTableStore from trustgraph.tables.knowledge import KnowledgeTableStore
from trustgraph.schema import ( from trustgraph.schema import (
@ -35,7 +35,10 @@ def _make_store():
class TestGetGraphEmbeddings: class TestGetGraphEmbeddings:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_row_converts_to_entity_embeddings_with_singular_vector(self): @patch('trustgraph.tables.knowledge.async_execute', new_callable=AsyncMock)
async def test_row_converts_to_entity_embeddings_with_singular_vector(
self, mock_async_execute
):
""" """
Cassandra rows return entities as a list of [entity_tuple, vector] Cassandra rows return entities as a list of [entity_tuple, vector]
pairs in row[3]. The deserializer must construct EntityEmbeddings pairs in row[3]. The deserializer must construct EntityEmbeddings
@ -56,8 +59,8 @@ class TestGetGraphEmbeddings:
store = _make_store() store = _make_store()
store.cassandra = Mock() store.cassandra = Mock()
store.cassandra.execute = Mock(return_value=[fake_row])
store.get_graph_embeddings_stmt = Mock() store.get_graph_embeddings_stmt = Mock()
mock_async_execute.return_value = [fake_row]
received = [] received = []
@ -72,7 +75,8 @@ class TestGetGraphEmbeddings:
) )
# Assert # Assert
store.cassandra.execute.assert_called_once_with( mock_async_execute.assert_called_once_with(
store.cassandra,
store.get_graph_embeddings_stmt, store.get_graph_embeddings_stmt,
("alice", "doc-1"), ("alice", "doc-1"),
) )
@ -102,15 +106,16 @@ class TestGetGraphEmbeddings:
assert ge.entities[2].entity.value == "a literal entity" assert ge.entities[2].entity.value == "a literal entity"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_empty_entities_blob_yields_empty_list(self): @patch('trustgraph.tables.knowledge.async_execute', new_callable=AsyncMock)
async def test_empty_entities_blob_yields_empty_list(self, mock_async_execute):
"""row[3] being None / empty must produce a GraphEmbeddings with """row[3] being None / empty must produce a GraphEmbeddings with
no entities, not raise.""" no entities, not raise."""
fake_row = (None, None, None, None) fake_row = (None, None, None, None)
store = _make_store() store = _make_store()
store.cassandra = Mock() store.cassandra = Mock()
store.cassandra.execute = Mock(return_value=[fake_row])
store.get_graph_embeddings_stmt = Mock() store.get_graph_embeddings_stmt = Mock()
mock_async_execute.return_value = [fake_row]
received = [] received = []
@ -123,7 +128,8 @@ class TestGetGraphEmbeddings:
assert received[0].entities == [] assert received[0].entities == []
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_multiple_rows_each_emit_one_message(self): @patch('trustgraph.tables.knowledge.async_execute', new_callable=AsyncMock)
async def test_multiple_rows_each_emit_one_message(self, mock_async_execute):
fake_rows = [ fake_rows = [
(None, None, None, [ (None, None, None, [
(("http://example.org/a", True), [1.0]), (("http://example.org/a", True), [1.0]),
@ -135,8 +141,8 @@ class TestGetGraphEmbeddings:
store = _make_store() store = _make_store()
store.cassandra = Mock() store.cassandra = Mock()
store.cassandra.execute = Mock(return_value=fake_rows)
store.get_graph_embeddings_stmt = Mock() store.get_graph_embeddings_stmt = Mock()
mock_async_execute.return_value = fake_rows
received = [] received = []
@ -157,7 +163,8 @@ class TestGetTriples:
the same Metadata construction. Cover it for parity.""" the same Metadata construction. Cover it for parity."""
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_row_converts_to_triples(self): @patch('trustgraph.tables.knowledge.async_execute', new_callable=AsyncMock)
async def test_row_converts_to_triples(self, mock_async_execute):
# row[3] is a list of (s_val, s_uri, p_val, p_uri, o_val, o_uri) # row[3] is a list of (s_val, s_uri, p_val, p_uri, o_val, o_uri)
fake_row = ( fake_row = (
None, None, None, None, None, None,
@ -172,8 +179,8 @@ class TestGetTriples:
store = _make_store() store = _make_store()
store.cassandra = Mock() store.cassandra = Mock()
store.cassandra.execute = Mock(return_value=[fake_row])
store.get_triples_stmt = Mock() store.get_triples_stmt = Mock()
mock_async_execute.return_value = [fake_row]
received = [] received = []

View file

@ -1,11 +1,11 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
Agent manager service completion base class Agent manager service completion base class
""" """
from __future__ import annotations
from argparse import ArgumentParser
import time import time
import logging import logging
from prometheus_client import Histogram from prometheus_client import Histogram

View file

@ -1,12 +1,12 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
Document embeddings query service. Input is vectors. Output is list of Document embeddings query service. Input is vectors. Output is list of
embeddings. embeddings.
""" """
from __future__ import annotations
from argparse import ArgumentParser
import logging import logging
from .. schema import DocumentEmbeddingsRequest, DocumentEmbeddingsResponse from .. schema import DocumentEmbeddingsRequest, DocumentEmbeddingsResponse

View file

@ -1,11 +1,11 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
Document embeddings store base class Document embeddings store base class
""" """
from __future__ import annotations
from argparse import ArgumentParser
import logging import logging
from .. schema import DocumentEmbeddings from .. schema import DocumentEmbeddings

View file

@ -1,7 +1,3 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
Base class for dynamically pluggable tool services. Base class for dynamically pluggable tool services.
@ -14,6 +10,10 @@ Uses direct Pulsar topics (no flow configuration required):
- Response: non-persistent://tg/response/{topic} - Response: non-persistent://tg/response/{topic}
""" """
from __future__ import annotations
from argparse import ArgumentParser
import json import json
import logging import logging
import asyncio import asyncio

View file

@ -1,11 +1,11 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
Embeddings resolution base class Embeddings resolution base class
""" """
from __future__ import annotations
from argparse import ArgumentParser
import time import time
import logging import logging
from prometheus_client import Histogram from prometheus_client import Histogram

View file

@ -1,12 +1,12 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
Graph embeddings query service. Input is vectors. Output is list of Graph embeddings query service. Input is vectors. Output is list of
embeddings. embeddings.
""" """
from __future__ import annotations
from argparse import ArgumentParser
import logging import logging
from .. schema import GraphEmbeddingsRequest, GraphEmbeddingsResponse from .. schema import GraphEmbeddingsRequest, GraphEmbeddingsResponse

View file

@ -1,11 +1,11 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
Graph embeddings store base class Graph embeddings store base class
""" """
from __future__ import annotations
from argparse import ArgumentParser
import logging import logging
from .. schema import GraphEmbeddings from .. schema import GraphEmbeddings

View file

@ -1,11 +1,11 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
LLM text completion base class LLM text completion base class
""" """
from __future__ import annotations
from argparse import ArgumentParser
import time import time
import logging import logging
from prometheus_client import Histogram, Info from prometheus_client import Histogram, Info

View file

@ -1,6 +1,7 @@
import json import json
import asyncio import asyncio
import inspect
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, Any from typing import Optional, Any
@ -80,7 +81,7 @@ class PromptClient(RequestResponse):
if resp.text is not None: if resp.text is not None:
if chunk_callback: if chunk_callback:
if asyncio.iscoroutinefunction(chunk_callback): if inspect.iscoroutinefunction(chunk_callback):
await chunk_callback(resp.text, end_stream) await chunk_callback(resp.text, end_stream)
else: else:
chunk_callback(resp.text, end_stream) chunk_callback(resp.text, end_stream)

View file

@ -1,11 +1,11 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
Tool invocation base class Tool invocation base class
""" """
from __future__ import annotations
from argparse import ArgumentParser
import json import json
import logging import logging
from prometheus_client import Counter from prometheus_client import Counter

View file

@ -1,12 +1,12 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
Triples query service. Input is a (s, p, o) triple, some values may be Triples query service. Input is a (s, p, o) triple, some values may be
null. Output is a list of triples. null. Output is a list of triples.
""" """
from __future__ import annotations
from argparse import ArgumentParser
import logging import logging
from .. schema import TriplesQueryRequest, TriplesQueryResponse, Error from .. schema import TriplesQueryRequest, TriplesQueryResponse, Error

View file

@ -1,11 +1,11 @@
from __future__ import annotations
from argparse import ArgumentParser
""" """
Triples store base class Triples store base class
""" """
from __future__ import annotations
from argparse import ArgumentParser
import logging import logging
from .. schema import Triples from .. schema import Triples

View file

@ -13,7 +13,7 @@ Agent provenance tracks the reasoning trace of agent sessions:
""" """
import json import json
from datetime import datetime from datetime import datetime, timezone
from typing import List, Optional, Dict, Any from typing import List, Optional, Dict, Any
from .. schema import Triple, Term, IRI, LITERAL from .. schema import Triple, Term, IRI, LITERAL
@ -87,7 +87,7 @@ def agent_session_triples(
List of Triple objects List of Triple objects
""" """
if timestamp is None: if timestamp is None:
timestamp = datetime.utcnow().isoformat() + "Z" timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
triples = [ triples = [
_triple(session_uri, RDF_TYPE, _iri(PROV_ENTITY)), _triple(session_uri, RDF_TYPE, _iri(PROV_ENTITY)),

View file

@ -2,7 +2,7 @@
Helper functions to build PROV-O triples for extraction-time provenance. Helper functions to build PROV-O triples for extraction-time provenance.
""" """
from datetime import datetime from datetime import datetime, timezone
from typing import List, Optional from typing import List, Optional
from .. schema import Triple, Term, IRI, LITERAL, TRIPLE from .. schema import Triple, Term, IRI, LITERAL, TRIPLE
@ -192,7 +192,7 @@ def derived_entity_triples(
List of Triple objects List of Triple objects
""" """
if timestamp is None: if timestamp is None:
timestamp = datetime.utcnow().isoformat() + "Z" timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
act_uri = activity_uri() act_uri = activity_uri()
agt_uri = agent_uri(component_name) agt_uri = agent_uri(component_name)
@ -309,7 +309,7 @@ def subgraph_provenance_triples(
List of Triple objects for the provenance List of Triple objects for the provenance
""" """
if timestamp is None: if timestamp is None:
timestamp = datetime.utcnow().isoformat() + "Z" timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
act_uri = activity_uri() act_uri = activity_uri()
agt_uri = agent_uri(component_name) agt_uri = agent_uri(component_name)
@ -386,7 +386,7 @@ def question_triples(
List of Triple objects List of Triple objects
""" """
if timestamp is None: if timestamp is None:
timestamp = datetime.utcnow().isoformat() + "Z" timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
triples = [ triples = [
_triple(question_uri, RDF_TYPE, _iri(PROV_ENTITY)), _triple(question_uri, RDF_TYPE, _iri(PROV_ENTITY)),
@ -640,7 +640,7 @@ def docrag_question_triples(
List of Triple objects List of Triple objects
""" """
if timestamp is None: if timestamp is None:
timestamp = datetime.utcnow().isoformat() + "Z" timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
triples = [ triples = [
_triple(question_uri, RDF_TYPE, _iri(PROV_ENTITY)), _triple(question_uri, RDF_TYPE, _iri(PROV_ENTITY)),

View file

@ -9,7 +9,7 @@ librarian integration.
import json import json
import logging import logging
import uuid import uuid
from datetime import datetime from datetime import datetime, timezone
from ... schema import AgentRequest, AgentResponse, AgentStep, Error from ... schema import AgentRequest, AgentResponse, AgentStep, Error
from ... schema import Triples, Metadata from ... schema import Triples, Metadata
@ -253,7 +253,7 @@ class PatternBase:
collection, respond, streaming, collection, respond, streaming,
parent_uri=None): parent_uri=None):
"""Emit provenance triples for a new session.""" """Emit provenance triples for a new session."""
timestamp = datetime.utcnow().isoformat() + "Z" timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
triples = set_graph( triples = set_graph(
agent_session_triples( agent_session_triples(
session_uri, question, timestamp, session_uri, question, timestamp,

View file

@ -10,7 +10,7 @@ import sys
import functools import functools
import logging import logging
import uuid import uuid
from datetime import datetime from datetime import datetime, timezone
# Module logger # Module logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -452,7 +452,7 @@ class Processor(AgentService):
# On first iteration, emit session triples # On first iteration, emit session triples
if iteration_num == 1: if iteration_num == 1:
timestamp = datetime.utcnow().isoformat() + "Z" timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
triples = set_graph( triples = set_graph(
agent_session_triples(session_uri, request.question, timestamp), agent_session_triples(session_uri, request.question, timestamp),
GRAPH_RETRIEVAL GRAPH_RETRIEVAL

View file

@ -6,6 +6,7 @@ Provides comprehensive error handling, retry logic, and graceful degradation.
import logging import logging
import time import time
import asyncio import asyncio
import inspect
from typing import Dict, Any, List, Optional, Callable, Union, Type from typing import Dict, Any, List, Optional, Callable, Union, Type
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
@ -244,7 +245,7 @@ class ErrorRecoveryStrategy:
await asyncio.sleep(delay) await asyncio.sleep(delay)
try: try:
if asyncio.iscoroutinefunction(operation): if inspect.iscoroutinefunction(operation):
return await operation(*args, **kwargs) return await operation(*args, **kwargs)
else: else:
return operation(*args, **kwargs) return operation(*args, **kwargs)
@ -260,7 +261,7 @@ class ErrorRecoveryStrategy:
if fallback_func: if fallback_func:
logger.info(f"Executing fallback for {context.category.value}") logger.info(f"Executing fallback for {context.category.value}")
try: try:
if asyncio.iscoroutinefunction(fallback_func): if inspect.iscoroutinefunction(fallback_func):
return await fallback_func(context, *args, **kwargs) return await fallback_func(context, *args, **kwargs)
else: else:
return fallback_func(context, *args, **kwargs) return fallback_func(context, *args, **kwargs)
@ -420,7 +421,7 @@ def with_error_handling(category: ErrorCategory,
@wraps(func) @wraps(func)
async def async_wrapper(*args, **kwargs): async def async_wrapper(*args, **kwargs):
try: try:
if asyncio.iscoroutinefunction(func): if inspect.iscoroutinefunction(func):
return await func(*args, **kwargs) return await func(*args, **kwargs)
else: else:
return func(*args, **kwargs) return func(*args, **kwargs)
@ -469,7 +470,7 @@ def with_error_handling(category: ErrorCategory,
cause=e cause=e
) )
if asyncio.iscoroutinefunction(func): if inspect.iscoroutinefunction(func):
return async_wrapper return async_wrapper
else: else:
return sync_wrapper return sync_wrapper

View file

@ -6,6 +6,7 @@ Provides comprehensive monitoring of system performance, query patterns, and res
import logging import logging
import time import time
import asyncio import asyncio
import inspect
import threading import threading
from typing import Dict, Any, List, Optional, Callable from typing import Dict, Any, List, Optional, Callable
from dataclasses import dataclass, field from dataclasses import dataclass, field
@ -579,7 +580,7 @@ def monitor_performance(component: str,
async def async_wrapper(*args, **kwargs): async def async_wrapper(*args, **kwargs):
if not monitor or not monitor.monitoring_enabled: if not monitor or not monitor.monitoring_enabled:
if asyncio.iscoroutinefunction(func): if inspect.iscoroutinefunction(func):
return await func(*args, **kwargs) return await func(*args, **kwargs)
else: else:
return func(*args, **kwargs) return func(*args, **kwargs)
@ -591,7 +592,7 @@ def monitor_performance(component: str,
success = True success = True
try: try:
if asyncio.iscoroutinefunction(func): if inspect.iscoroutinefunction(func):
result = await func(*args, **kwargs) result = await func(*args, **kwargs)
else: else:
result = func(*args, **kwargs) result = func(*args, **kwargs)
@ -603,7 +604,7 @@ def monitor_performance(component: str,
duration = monitor.metrics_collector.stop_timer(timer) duration = monitor.metrics_collector.stop_timer(timer)
monitor.record_request(component, operation, duration, success) monitor.record_request(component, operation, duration, success)
if asyncio.iscoroutinefunction(func): if inspect.iscoroutinefunction(func):
return async_wrapper return async_wrapper
else: else:
return wrapper return wrapper

View file

@ -2,7 +2,7 @@
import asyncio import asyncio
import logging import logging
import uuid import uuid
from datetime import datetime from datetime import datetime, timezone
# Provenance imports # Provenance imports
from trustgraph.provenance import ( from trustgraph.provenance import (
@ -199,7 +199,7 @@ class DocumentRag:
exp_uri = docrag_exploration_uri(session_id) exp_uri = docrag_exploration_uri(session_id)
syn_uri = docrag_synthesis_uri(session_id) syn_uri = docrag_synthesis_uri(session_id)
timestamp = datetime.utcnow().isoformat() + "Z" timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
# Emit question explainability immediately # Emit question explainability immediately
if explain_callback: if explain_callback:

View file

@ -7,7 +7,7 @@ import math
import time import time
import uuid import uuid
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime from datetime import datetime, timezone
from ... schema import Term, Triple as SchemaTriple, IRI, LITERAL, TRIPLE from ... schema import Term, Triple as SchemaTriple, IRI, LITERAL, TRIPLE
from ... knowledge import Uri, Literal from ... knowledge import Uri, Literal
@ -643,7 +643,7 @@ class GraphRag:
foc_uri = make_focus_uri(session_id) foc_uri = make_focus_uri(session_id)
syn_uri = make_synthesis_uri(session_id) syn_uri = make_synthesis_uri(session_id)
timestamp = datetime.utcnow().isoformat() + "Z" timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
# Emit question explainability immediately # Emit question explainability immediately
if explain_callback: if explain_callback: