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
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ jobs:
- name: Test
run: pnpm --filter @grafana/sigil-sdk-js run test:ci

python-lint:
name: Python Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Setup mise
uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3
with:
install: true
install_args: ruff
cache: true

- name: ruff check
run: mise run lint:py

python:
name: Python SDK
runs-on: ubuntu-latest
Expand Down
20 changes: 18 additions & 2 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[tools]
ruff = "0.15.10"

[env]
PYTHON_BIN = "python3"

Expand All @@ -20,13 +23,17 @@ while IFS= read -r moddir; do
done < <(find . -name go.mod -not -path './node_modules/*' -exec dirname {} \\; | sort)
"""

[tasks."format:py"]
description = "Format Python SDK code"
run = "ruff format python/ python-providers/ python-frameworks/"

[tasks."format:cs"]
description = "Format .NET SDK code"
run = "dotnet format dotnet/Sigil.DotNet.sln"

[tasks.format]
description = "Format all code"
depends = ["format:go", "format:cs"]
depends = ["format:go", "format:py", "format:cs"]

# --- Linting ---

Expand All @@ -45,13 +52,22 @@ while IFS= read -r moddir; do
done < <(find . -name go.mod -not -path './node_modules/*' -exec dirname {} \\; | sort)
"""

[tasks."lint:py"]
description = "Lint and format-check Python SDK code"
run = """
#!/usr/bin/env bash
set -euo pipefail
ruff check python/ python-providers/ python-frameworks/
ruff format --check python/ python-providers/ python-frameworks/
"""

[tasks."lint:cs"]
description = "Verify .NET SDK formatting and analyzer checks"
run = "dotnet format dotnet/Sigil.DotNet.sln --verify-no-changes"

[tasks.lint]
description = "Run all linting"
depends = ["lint:go", "lint:cs"]
depends = ["lint:go", "lint:py", "lint:cs"]

# --- Type checking ---

Expand Down
12 changes: 9 additions & 3 deletions python-frameworks/google-adk/sigil_sdk_google_adk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
try: # pragma: no cover - imported from google-adk at runtime
from google.adk.plugins import BasePlugin
except Exception: # pragma: no cover - lightweight fallback for local unit tests

class BasePlugin: # type: ignore[no-redef]
"""Fallback BasePlugin shape used when google-adk isn't installed."""

Expand Down Expand Up @@ -52,6 +53,7 @@ async def after_tool_callback(
del tool, tool_args, tool_context, result
return None


_adk_callback_fields = (
"before_model_callback",
"after_model_callback",
Expand Down Expand Up @@ -170,7 +172,9 @@ async def before_tool_callback(self, tool: Any, args: dict[str, Any], tool_conte
)
return None

async def after_tool_callback(self, tool: Any, args: dict[str, Any], tool_context: Any, result: dict[str, Any]) -> None:
async def after_tool_callback(
self, tool: Any, args: dict[str, Any], tool_context: Any, result: dict[str, Any]
) -> None:
del tool, args
invocation_key = self._invocation_key(tool_context)
function_call_id = _as_string(_read(tool_context, "function_call_id"))
Expand All @@ -184,7 +188,9 @@ async def after_tool_callback(self, tool: Any, args: dict[str, Any], tool_contex
await _invoke_handler(self._sigil_handler, "on_tool_end", result, run_id=run_id)
return None

async def on_tool_error_callback(self, tool: Any, args: dict[str, Any], tool_context: Any, error: Exception) -> None:
async def on_tool_error_callback(
self, tool: Any, args: dict[str, Any], tool_context: Any, error: Exception
) -> None:
del tool, args
invocation_key = self._invocation_key(tool_context)
function_call_id = _as_string(_read(tool_context, "function_call_id"))
Expand Down Expand Up @@ -454,7 +460,7 @@ def with_sigil_google_adk_plugins(
plugins = _as_list(getattr(target, "plugins", None))
if not _contains_sigil_plugin(plugins):
plugins.append(plugin)
setattr(target, "plugins", plugins)
target.plugins = plugins
return target


Expand Down
12 changes: 7 additions & 5 deletions python-frameworks/google-adk/tests/test_sigil_sdk_google_adk.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
from sigil_sdk_google_adk import (
SigilAsyncGoogleAdkHandler,
SigilGoogleAdkCallbacks,
SigilGoogleAdkPlugin,
SigilGoogleAdkHandler,
with_sigil_google_adk_callbacks,
SigilGoogleAdkPlugin,
create_sigil_google_adk_handler,
create_sigil_google_adk_plugin,
with_sigil_google_adk_callbacks,
with_sigil_google_adk_plugins,
)

Expand All @@ -32,8 +32,7 @@ def export_generations(self, request):
self.requests.append(request)
return ExportGenerationsResponse(
results=[
ExportGenerationResult(generation_id=generation.id, accepted=True)
for generation in request.generations
ExportGenerationResult(generation_id=generation.id, accepted=True) for generation in request.generations
]
)

Expand Down Expand Up @@ -205,7 +204,10 @@ def test_sigil_sdk_google_adk_generation_span_tracks_active_parent_span_and_expo
run_id=run_id,
parent_run_id=uuid4(),
invocation_params={"model": "gpt-5"},
metadata={"conversation_id": "framework-conversation-lineage-42", "thread_id": "framework-thread-lineage-42"},
metadata={
"conversation_id": "framework-conversation-lineage-42",
"thread_id": "framework-thread-lineage-42",
},
)
handler.on_llm_end(
{"generations": [[{"text": "world"}]], "llm_output": {"model_name": "gpt-5", "finish_reason": "stop"}},
Expand Down
1 change: 1 addition & 0 deletions python-frameworks/langchain/sigil_sdk_langchain/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
try:
from langchain_core.callbacks import AsyncCallbackHandler, BaseCallbackHandler
except ModuleNotFoundError: # pragma: no cover - handled by package dependency in normal installs

class BaseCallbackHandler: # type: ignore[no-redef]
"""Fallback base class when langchain-core is unavailable."""

Expand Down
13 changes: 9 additions & 4 deletions python-frameworks/langchain/tests/test_langchain_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ def export_generations(self, request):
self.requests.append(request)
return ExportGenerationsResponse(
results=[
ExportGenerationResult(generation_id=generation.id, accepted=True)
for generation in request.generations
ExportGenerationResult(generation_id=generation.id, accepted=True) for generation in request.generations
]
)

Expand Down Expand Up @@ -61,7 +60,11 @@ def test_langchain_sync_lifecycle_sets_framework_tags_and_metadata() -> None:
agent_version="v1",
provider_resolver="auto",
extra_tags={"env": "test", "sigil.framework.name": "override"},
extra_metadata={"seed": 7, "sigil.framework.run_id": "override-run", "sigil.framework.thread_id": "override-thread"},
extra_metadata={
"seed": 7,
"sigil.framework.run_id": "override-run",
"sigil.framework.thread_id": "override-thread",
},
)

handler.on_chat_model_start(
Expand Down Expand Up @@ -422,7 +425,9 @@ def test_langchain_tool_chain_and_retriever_callbacks_emit_spans() -> None:
spans = span_exporter.get_finished_spans()
tool_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "execute_tool")
chain_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "framework_chain")
retriever_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "framework_retriever")
retriever_span = next(
span for span in spans if span.attributes.get("gen_ai.operation.name") == "framework_retriever"
)

assert tool_span.attributes.get("gen_ai.tool.name") == "weather"
assert tool_span.attributes.get("gen_ai.conversation.id") == "chain-thread-42"
Expand Down
1 change: 1 addition & 0 deletions python-frameworks/langgraph/sigil_sdk_langgraph/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
try:
from langchain_core.callbacks import AsyncCallbackHandler, BaseCallbackHandler
except ModuleNotFoundError: # pragma: no cover - handled by package dependency in normal installs

class BaseCallbackHandler: # type: ignore[no-redef]
"""Fallback base class when langchain-core is unavailable."""

Expand Down
13 changes: 9 additions & 4 deletions python-frameworks/langgraph/tests/test_langgraph_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ def export_generations(self, request):
self.requests.append(request)
return ExportGenerationsResponse(
results=[
ExportGenerationResult(generation_id=generation.id, accepted=True)
for generation in request.generations
ExportGenerationResult(generation_id=generation.id, accepted=True) for generation in request.generations
]
)

Expand Down Expand Up @@ -59,7 +58,11 @@ def test_langgraph_sync_lifecycle_sets_framework_tags_and_metadata() -> None:
agent_version="v1",
provider_resolver="auto",
extra_tags={"env": "test", "sigil.framework.name": "override"},
extra_metadata={"seed": 7, "sigil.framework.run_id": "override-run", "sigil.framework.thread_id": "override-thread"},
extra_metadata={
"seed": 7,
"sigil.framework.run_id": "override-run",
"sigil.framework.thread_id": "override-thread",
},
)

handler.on_chat_model_start(
Expand Down Expand Up @@ -299,7 +302,9 @@ def test_langgraph_tool_chain_and_retriever_callbacks_emit_spans() -> None:
spans = span_exporter.get_finished_spans()
tool_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "execute_tool")
chain_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "framework_chain")
retriever_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "framework_retriever")
retriever_span = next(
span for span in spans if span.attributes.get("gen_ai.operation.name") == "framework_retriever"
)

assert tool_span.attributes.get("gen_ai.tool.name") == "weather"
assert tool_span.attributes.get("gen_ai.conversation.id") == "graph-thread-42"
Expand Down
36 changes: 22 additions & 14 deletions python-frameworks/litellm/sigil_sdk_litellm/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@

logger = logging.getLogger(__name__)

_CHAT_CALL_TYPES = frozenset({
"completion",
"acompletion",
"text_completion",
"atext_completion",
})
_CHAT_CALL_TYPES = frozenset(
{
"completion",
"acompletion",
"text_completion",
"atext_completion",
}
)


def _make_tool_call_part(*, call_id: str, name: str, arguments: str) -> Part:
Expand Down Expand Up @@ -247,12 +249,14 @@ def _map_tool_definitions(kwargs: dict[str, Any]) -> list[ToolDefinition]:
description = function.get("description", "")
parameters = function.get("parameters")
schema_json = json.dumps(parameters).encode("utf-8") if parameters else b""
out.append(ToolDefinition(
name=name,
description=description,
type=tool_type,
input_schema_json=schema_json,
))
out.append(
ToolDefinition(
name=name,
description=description,
type=tool_type,
input_schema_json=schema_json,
)
)
return out


Expand Down Expand Up @@ -336,10 +340,14 @@ def log_success_event(self, kwargs: dict, response_obj: Any, start_time: datetim
def log_failure_event(self, kwargs: dict, response_obj: Any, start_time: datetime, end_time: datetime) -> None:
self._log_event(kwargs, response_obj, start_time, end_time, is_failure=True)

async def async_log_success_event(self, kwargs: dict, response_obj: Any, start_time: datetime, end_time: datetime) -> None:
async def async_log_success_event(
self, kwargs: dict, response_obj: Any, start_time: datetime, end_time: datetime
) -> None:
self._log_event(kwargs, response_obj, start_time, end_time, is_failure=False)

async def async_log_failure_event(self, kwargs: dict, response_obj: Any, start_time: datetime, end_time: datetime) -> None:
async def async_log_failure_event(
self, kwargs: dict, response_obj: Any, start_time: datetime, end_time: datetime
) -> None:
self._log_event(kwargs, response_obj, start_time, end_time, is_failure=True)

def _log_event(
Expand Down
23 changes: 14 additions & 9 deletions python-frameworks/litellm/tests/test_litellm_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
GenerationMode,
MessageRole,
PartKind,
ToolDefinition,
)
from sigil_sdk_litellm import SigilLiteLLMLogger, create_sigil_litellm_logger

Expand All @@ -25,10 +24,7 @@ def __init__(self) -> None:
def export_generations(self, request: Any) -> ExportGenerationsResponse:
self.requests.append(request)
return ExportGenerationsResponse(
results=[
ExportGenerationResult(generation_id=g.id, accepted=True)
for g in request.generations
]
results=[ExportGenerationResult(generation_id=g.id, accepted=True) for g in request.generations]
)

def shutdown(self) -> None:
Expand Down Expand Up @@ -695,7 +691,10 @@ def test_litellm_session_id_used_as_conversation_id() -> None:
},
}
handler.log_success_event(
kwargs=kwargs, response_obj=None, start_time=_START, end_time=_END,
kwargs=kwargs,
response_obj=None,
start_time=_START,
end_time=_END,
)
client.flush()

Expand All @@ -720,7 +719,10 @@ def test_litellm_trace_id_used_as_conversation_id() -> None:
},
}
handler.log_success_event(
kwargs=kwargs, response_obj=None, start_time=_START, end_time=_END,
kwargs=kwargs,
response_obj=None,
start_time=_START,
end_time=_END,
)
client.flush()

Expand All @@ -745,7 +747,10 @@ def test_metadata_conversation_id_takes_precedence_over_litellm_session() -> Non
},
}
handler.log_success_event(
kwargs=kwargs, response_obj=None, start_time=_START, end_time=_END,
kwargs=kwargs,
response_obj=None,
start_time=_START,
end_time=_END,
)
client.flush()

Expand Down Expand Up @@ -987,7 +992,7 @@ def test_non_utc_timezone_converted_to_utc() -> None:

tz_plus5 = timezone(timedelta(hours=5))
start = datetime(2024, 1, 1, 15, 0, 0, tzinfo=tz_plus5) # = 10:00 UTC
end = datetime(2024, 1, 1, 15, 0, 1, tzinfo=tz_plus5) # = 10:00:01 UTC
end = datetime(2024, 1, 1, 15, 0, 1, tzinfo=tz_plus5) # = 10:00:01 UTC

handler.log_success_event(
kwargs=_make_kwargs(slo),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from llama_index.core.callbacks import CallbackManager
from llama_index.core.callbacks.base_handler import BaseCallbackHandler
except Exception: # pragma: no cover - lightweight fallback for local unit tests

class BaseCallbackHandler: # type: ignore[no-redef]
"""Fallback BaseCallbackHandler shape used when llama-index isn't installed."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ def export_generations(self, request):
self.requests.append(request)
return ExportGenerationsResponse(
results=[
ExportGenerationResult(generation_id=generation.id, accepted=True)
for generation in request.generations
ExportGenerationResult(generation_id=generation.id, accepted=True) for generation in request.generations
]
)

Expand Down Expand Up @@ -202,7 +201,10 @@ def test_sigil_sdk_llamaindex_generation_span_tracks_active_parent_span_and_expo
run_id=run_id,
parent_run_id=uuid4(),
invocation_params={"model": "gpt-5"},
metadata={"conversation_id": "framework-conversation-lineage-42", "thread_id": "framework-thread-lineage-42"},
metadata={
"conversation_id": "framework-conversation-lineage-42",
"thread_id": "framework-thread-lineage-42",
},
)
handler.on_llm_end(
{"generations": [[{"text": "world"}]], "llm_output": {"model_name": "gpt-5", "finish_reason": "stop"}},
Expand Down
Loading
Loading