diff --git a/poetry.lock b/poetry.lock index 3fcb5ee..a5450fe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1274,13 +1274,13 @@ tqdm = ["tqdm"] [[package]] name = "galileo-core" -version = "4.3.0" +version = "4.4.0" description = "" optional = false python-versions = ">=3.10.0" groups = ["main", "test"] files = [ - {file = "galileo_core-4.3.0-py3-none-any.whl", hash = "sha256:49a0b9da82274f88a6637eab69393f07d6fc58a4042a61aceee6e6cc7ee53540"}, + {file = "galileo_core-4.4.0-py3-none-any.whl", hash = "sha256:eb7995c43fe09583127b807cc9635941f623f5b08207bae95362ce63460c74e8"}, ] [package.dependencies] @@ -6548,4 +6548,4 @@ otel = ["grpcio", "opentelemetry-api", "opentelemetry-exporter-otlp", "opentelem [metadata] lock-version = "2.1" python-versions = "^3.11,<3.15" -content-hash = "f75a182570e1129b6b65927aac9d700eae02d507391e7d2d6850d8910e4d55bf" +content-hash = "e890c46c8d3a36639d6982dacb420ec5256c63f9522a5a80c01627373bda9009" diff --git a/pyproject.toml b/pyproject.toml index fb68b27..c90cbef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ langchain = { version = ">=0.0.0,<2.0.0", optional = true } openai = { version = ">=2.8.0,<3.0.0", optional = true } openai-agents = { version = ">=0.4.0,<1.0.0", optional = true } litellm = { version = ">=1.83.14,<2.0.0", optional = true, python = ">=3.11,<3.14" } -galileo-core = "^4.3.0" +galileo-core = "^4.4.0" starlette = { version = ">=0.27.0", optional = true } backoff = "^2.2.1" crewai = { version = ">=0.152.0,<2.0.0", optional = true, python = ">=3.11,<3.14" } @@ -64,7 +64,7 @@ pytest-xdist = "^3.7.0" pytest-socket = "^0.7" pytest-asyncio = "^1.0.0" requests-mock = "^1.11.0" -galileo-core = { extras = ["testing"], version = "^4.3.0" } +galileo-core = { extras = ["testing"], version = "^4.4.0" } pytest-env = "^1.1.5" pytest-timeout = "^2.3" diff --git a/tests/schemas/test_logged.py b/tests/schemas/test_logged.py index 3299eea..7a7c535 100644 --- a/tests/schemas/test_logged.py +++ b/tests/schemas/test_logged.py @@ -1,14 +1,8 @@ """Tests for SDK-local ingestion models (Logged variants and content blocks).""" -import builtins -import importlib - import pytest from pydantic import ValidationError -import splunk_ao.logger.control as control_module -import splunk_ao.logger.logger as logger_module -import splunk_ao.schema.logged as logged_module from galileo_core.schemas.logging.llm import MessageRole from galileo_core.schemas.logging.span import AgentSpan, LlmSpan, RetrieverSpan, ToolSpan, WorkflowSpan from galileo_core.schemas.logging.trace import Trace @@ -341,76 +335,20 @@ def test_full_ingest_request_roundtrip(self) -> None: assert type(tool) is ToolSpan assert tool.output == "4" - def test_logged_trace_roundtrip_with_fallback_control_span(self, monkeypatch: pytest.MonkeyPatch) -> None: - # Given: splunk_ao.logger.control is reloaded without native ControlSpan support - if not control_module.HAS_NATIVE_CONTROL_SPAN: - control_payload = control_module.ControlSpan(input="selected text").model_dump(mode="python") - trace = logged_module.LoggedTrace(input="query", spans=[control_payload]) - restored = logged_module.LoggedTrace.model_validate(trace.model_dump(mode="json")) - - assert restored.spans[0].type == "control" - assert restored.spans[0].__class__.__name__ == "LoggedControlSpan" - assert restored.spans[0].model_dump(mode="json")["input"] == "selected text" - return - - original_import = builtins.__import__ - - def force_fallback_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == "galileo_core.schemas.logging.control": - raise ImportError("forced fallback for test") - return original_import(name, globals, locals, fromlist, level) - - try: - with monkeypatch.context() as context: - context.setattr(builtins, "__import__", force_fallback_import) - importlib.reload(control_module) - importlib.reload(logged_module) - - # When: constructing and validating a LoggedTrace containing the fallback ControlSpan payload - control_payload = control_module.ControlSpan(input="selected text").model_dump(mode="python") - trace = logged_module.LoggedTrace(input="query", spans=[control_payload]) - restored = logged_module.LoggedTrace.model_validate(trace.model_dump(mode="json")) - - # Then: the discriminated union resolves the fallback ControlSpan cleanly - assert control_module.HAS_NATIVE_CONTROL_SPAN is False - assert restored.spans[0].type == "control" - assert restored.spans[0].__class__.__name__ == "LoggedControlSpan" - assert restored.spans[0].model_dump(mode="json")["input"] == "selected text" - finally: - importlib.reload(control_module) - importlib.reload(logged_module) - logger_module.LoggedControlSpan = logged_module.LoggedControlSpan + def test_logged_trace_roundtrip_with_control_span(self) -> None: + from splunk_ao.logger.control import ControlSpan + control_payload = ControlSpan(input="selected text").model_dump(mode="python") + trace = LoggedTrace(input="query", spans=[control_payload]) + restored = LoggedTrace.model_validate(trace.model_dump(mode="json")) + assert restored.spans[0].type == "control" + assert type(restored.spans[0]) is LoggedControlSpan + assert restored.spans[0].model_dump(mode="json")["input"] == "selected text" @pytest.mark.parametrize("field_name", ["id", "session_id", "trace_id", "parent_id"]) - def test_fallback_control_span_rejects_non_uuidish_id_fields( - self, field_name: str, monkeypatch: pytest.MonkeyPatch - ) -> None: - # Given: splunk_ao.logger.control is reloaded without native ControlSpan support - if not control_module.HAS_NATIVE_CONTROL_SPAN: - with pytest.raises(ValidationError): - control_module.ControlSpan(input="selected text", **{field_name: 123}) - return - - original_import = builtins.__import__ - - def force_fallback_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == "galileo_core.schemas.logging.control": - raise ImportError("forced fallback for test") - return original_import(name, globals, locals, fromlist, level) - - try: - with monkeypatch.context() as context: - context.setattr(builtins, "__import__", force_fallback_import) - importlib.reload(control_module) - - # When/Then: non-UUID-ish id fields are rejected by the fallback schema - assert control_module.HAS_NATIVE_CONTROL_SPAN is False - with pytest.raises(ValidationError): - control_module.ControlSpan(input="selected text", **{field_name: 123}) - finally: - importlib.reload(control_module) - importlib.reload(logged_module) - logger_module.LoggedControlSpan = logged_module.LoggedControlSpan + def test_control_span_rejects_non_uuidish_id_fields(self, field_name: str) -> None: + from splunk_ao.logger.control import ControlSpan + with pytest.raises(ValidationError): + ControlSpan(input="selected text", **{field_name: 123}) class TestLoggedAndCoreParity: diff --git a/tests/test_agent_control_bridge.py b/tests/test_agent_control_bridge.py index 71f76e2..943cb6e 100644 --- a/tests/test_agent_control_bridge.py +++ b/tests/test_agent_control_bridge.py @@ -1,10 +1,8 @@ from __future__ import annotations import asyncio -import builtins import dataclasses import datetime -import importlib import sys import types import uuid @@ -13,9 +11,6 @@ import pytest -import splunk_ao.logger.control as control_module -import splunk_ao.logger.logger as logger_module -import splunk_ao.schema.logged as logged_module from splunk_ao.handlers.agent_control import setup_agent_control_bridge from splunk_ao.logger.control import ControlResult, ControlSpan from splunk_ao.logger.logger import SplunkAOLogger @@ -476,52 +471,20 @@ def test_agent_control_event_streams_immediately_in_distributed_mode( @patch("splunk_ao.logger.logger.LogStreams") @patch("splunk_ao.logger.logger.Projects") @patch("splunk_ao.logger.logger.Traces") -def test_add_control_span_uses_model_default_name_in_fallback_mode( - mock_traces_client: Mock, mock_projects_client: Mock, mock_logstreams_client: Mock, monkeypatch: pytest.MonkeyPatch +def test_add_control_span_uses_model_default_name( + mock_traces_client: Mock, mock_projects_client: Mock, mock_logstreams_client: Mock ) -> None: - # Given: the logger is using the fallback ControlSpan model setup_mock_traces_client(mock_traces_client) setup_mock_projects_client(mock_projects_client) setup_mock_logstreams_client(mock_logstreams_client) - original_import = builtins.__import__ - - def assert_fallback_default_name() -> None: - logger = SplunkAOLogger(project="my_project", log_stream="my_log_stream") - logger.start_trace(input="trace input") - workflow = logger.add_workflow_span(input="workflow input", name="workflow") - - # When: adding a control span without an explicit name - control_span = logger.add_control_span(input="selected text", name=None) - - # Then: the fallback model default is applied and the span is preserved - assert control_span is not None - assert control_module.HAS_NATIVE_CONTROL_SPAN is False - assert isinstance(control_span, logged_module.LoggedControlSpan) - assert isinstance(control_span, control_module.ControlSpan) - assert control_span.name == "control" - assert workflow.spans == [control_span] - - if not control_module.HAS_NATIVE_CONTROL_SPAN: - assert_fallback_default_name() - return - - def force_fallback_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == "galileo_core.schemas.logging.control": - raise ImportError("forced fallback for test") - return original_import(name, globals, locals, fromlist, level) - - try: - with monkeypatch.context() as context: - context.setattr(builtins, "__import__", force_fallback_import) - importlib.reload(control_module) - importlib.reload(logged_module) - context.setattr(logger_module, "LoggedControlSpan", logged_module.LoggedControlSpan) - - assert_fallback_default_name() - finally: - importlib.reload(control_module) - importlib.reload(logged_module) - logger_module.LoggedControlSpan = logged_module.LoggedControlSpan + logger = SplunkAOLogger(project="my_project", log_stream="my_log_stream") + logger.start_trace(input="trace input") + workflow = logger.add_workflow_span(input="workflow input", name="workflow") + control_span = logger.add_control_span(input="selected text", name=None) + assert control_span is not None + assert isinstance(control_span, ControlSpan) + assert control_span.name == ControlSpan.model_fields["name"].default + assert workflow.spans == [control_span] @patch("splunk_ao.logger.logger.LogStreams")