mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-05-27 00:05:13 +02:00
Merge branch 'release/v2.3'
This commit is contained in:
commit
59e269185d
29 changed files with 146 additions and 110 deletions
|
|
@ -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"""
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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",)}
|
||||||
|
|
|
||||||
|
|
@ -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 = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue