Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand All @@ -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"
Expand Down
86 changes: 12 additions & 74 deletions tests/schemas/test_logged.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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:
Expand Down
57 changes: 10 additions & 47 deletions tests/test_agent_control_bridge.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from __future__ import annotations

import asyncio
import builtins
import dataclasses
import datetime
import importlib
import sys
import types
import uuid
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand Down
Loading