ci: add Python CI job (ruff, mypy, pytest) for the llm-abstraction package (#2364)
* ci: add ruff + mypy to the Python CI job and fix pyproject tool config The python-tests job runs pytest but not lint/type checks, and the ruff and mypy configuration in pyproject.toml was silently broken, so neither tool could run at all. - add ruff and mypy steps to the existing python-tests job - fix invalid pyproject keys: [tool.ruff] src-path -> src, [tool.mypy] src_paths -> mypy_path - ignore ruff UP042 (the (str, Enum) mixin is intentional) - resolve ruff findings (unused/unsorted imports) across src and tests - fix mypy errors in tools/executor.py and prompt/builder.py * fix(ci): satisfy Python lint after main refresh --------- Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
This commit is contained in:
@@ -172,7 +172,7 @@ jobs:
|
||||
continue-on-error: false
|
||||
|
||||
python-tests:
|
||||
name: Python Tests
|
||||
name: Python Lint, Type Check & Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
@@ -190,6 +190,12 @@ jobs:
|
||||
- name: Install Python dependencies
|
||||
run: python -m pip install --upgrade pip && python -m pip install -e '.[dev]'
|
||||
|
||||
- name: Run ruff (lint)
|
||||
run: python -m ruff check src tests
|
||||
|
||||
- name: Run mypy (type check)
|
||||
run: python -m mypy src
|
||||
|
||||
- name: Run Python tests
|
||||
run: python -m pytest tests/test_*.py -m "not integration"
|
||||
|
||||
|
||||
+7
-3
@@ -65,15 +65,19 @@ exclude_lines = [
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
src-path = ["src"]
|
||||
src = ["src"]
|
||||
target-version = "py311"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I", "N", "W", "UP"]
|
||||
ignore = ["E501"]
|
||||
# E501: line length is handled by the formatter, not enforced here.
|
||||
# UP042: the (str, Enum) mixin is intentional — enum members must compare
|
||||
# and serialize as plain strings across providers. StrEnum changes
|
||||
# str() semantics, so the explicit mixin is kept deliberately.
|
||||
ignore = ["E501", "UP042"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
src_paths = ["src"]
|
||||
mypy_path = "src"
|
||||
warn_return_any = true
|
||||
warn_unused_ignores = true
|
||||
|
||||
+9
-2
@@ -4,11 +4,18 @@ LLM Abstraction Layer
|
||||
Provider-agnostic interface for multiple LLM backends.
|
||||
"""
|
||||
|
||||
from llm.cli.selector import interactive_select
|
||||
from llm.core.interface import LLMProvider
|
||||
from llm.core.types import LLMInput, LLMOutput, Message, ToolCall, ToolDefinition, ToolResult
|
||||
from llm.core.types import (
|
||||
LLMInput,
|
||||
LLMOutput,
|
||||
Message,
|
||||
ToolCall,
|
||||
ToolDefinition,
|
||||
ToolResult,
|
||||
)
|
||||
from llm.providers import get_provider
|
||||
from llm.tools import ToolExecutor, ToolRegistry
|
||||
from llm.cli.selector import interactive_select
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
"""Prompt module for prompt building and normalization."""
|
||||
|
||||
from llm.prompt.builder import PromptBuilder, adapt_messages_for_provider, get_provider_builder
|
||||
from llm.prompt.builder import (
|
||||
PromptBuilder,
|
||||
adapt_messages_for_provider,
|
||||
get_provider_builder,
|
||||
)
|
||||
from llm.prompt.templates import (
|
||||
TEMPLATES,
|
||||
clear_templates,
|
||||
|
||||
+18
-11
@@ -5,10 +5,7 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from llm.core.types import LLMInput, Message, Role, ToolDefinition
|
||||
from llm.providers.claude import ClaudeProvider
|
||||
from llm.providers.openai import OpenAIProvider
|
||||
from llm.providers.ollama import OllamaProvider
|
||||
from llm.core.types import Message, Role, ToolDefinition
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -36,13 +33,23 @@ class PromptBuilder:
|
||||
raise ValueError("Pass either config or PromptBuilder keyword options, not both")
|
||||
|
||||
if config is None:
|
||||
overrides = {
|
||||
"system_template": system_template,
|
||||
"user_template": user_template,
|
||||
"include_tools_in_system": include_tools_in_system,
|
||||
"tool_format": tool_format,
|
||||
}
|
||||
config = PromptConfig(**{key: value for key, value in overrides.items() if value is not None})
|
||||
defaults = PromptConfig()
|
||||
config = PromptConfig(
|
||||
system_template=(
|
||||
system_template if system_template is not None else defaults.system_template
|
||||
),
|
||||
user_template=(
|
||||
user_template if user_template is not None else defaults.user_template
|
||||
),
|
||||
include_tools_in_system=(
|
||||
include_tools_in_system
|
||||
if include_tools_in_system is not None
|
||||
else defaults.include_tools_in_system
|
||||
),
|
||||
tool_format=(
|
||||
tool_format if tool_format is not None else defaults.tool_format
|
||||
),
|
||||
)
|
||||
|
||||
self.config = config
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
from llm.providers.astraflow import AstraflowCNProvider, AstraflowProvider
|
||||
from llm.providers.atlas import AtlasProvider
|
||||
from llm.providers.claude import ClaudeProvider
|
||||
from llm.providers.openai import OpenAIProvider
|
||||
from llm.providers.ollama import OllamaProvider
|
||||
from llm.providers.openai import OpenAIProvider
|
||||
from llm.providers.resolver import get_provider, register_provider
|
||||
|
||||
__all__ = (
|
||||
|
||||
@@ -11,7 +11,13 @@ from llm.core.interface import (
|
||||
LLMProvider,
|
||||
RateLimitError,
|
||||
)
|
||||
from llm.core.types import LLMInput, LLMOutput, Message, ModelInfo, ProviderType, ToolCall
|
||||
from llm.core.types import (
|
||||
LLMInput,
|
||||
LLMOutput,
|
||||
ModelInfo,
|
||||
ProviderType,
|
||||
ToolCall,
|
||||
)
|
||||
|
||||
|
||||
class OllamaProvider(LLMProvider):
|
||||
@@ -52,8 +58,8 @@ class OllamaProvider(LLMProvider):
|
||||
]
|
||||
|
||||
def generate(self, input: LLMInput) -> LLMOutput:
|
||||
import urllib.request
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
try:
|
||||
url = f"{self.base_url}/api/chat"
|
||||
|
||||
@@ -14,7 +14,13 @@ from llm.core.interface import (
|
||||
LLMProvider,
|
||||
RateLimitError,
|
||||
)
|
||||
from llm.core.types import LLMInput, LLMOutput, Message, ModelInfo, ProviderType, ToolCall
|
||||
from llm.core.types import (
|
||||
LLMInput,
|
||||
LLMOutput,
|
||||
ModelInfo,
|
||||
ProviderType,
|
||||
ToolCall,
|
||||
)
|
||||
from llm.providers.constants import EMPTY_FILTERED_RESPONSE_ERROR
|
||||
|
||||
|
||||
|
||||
@@ -10,9 +10,8 @@ from llm.core.types import ProviderType
|
||||
from llm.providers.astraflow import AstraflowCNProvider, AstraflowProvider
|
||||
from llm.providers.atlas import AtlasProvider
|
||||
from llm.providers.claude import ClaudeProvider
|
||||
from llm.providers.openai import OpenAIProvider
|
||||
from llm.providers.ollama import OllamaProvider
|
||||
|
||||
from llm.providers.openai import OpenAIProvider
|
||||
|
||||
_PROVIDER_MAP: dict[ProviderType, type[LLMProvider]] = {
|
||||
ProviderType.ASTRAFLOW: AstraflowProvider,
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Callable
|
||||
|
||||
from llm.core.interface import ToolExecutionError
|
||||
from llm.core.types import LLMInput, LLMOutput, Message, Role, ToolCall, ToolDefinition, ToolResult
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from llm.core.types import (
|
||||
LLMInput,
|
||||
LLMOutput,
|
||||
Message,
|
||||
Role,
|
||||
ToolCall,
|
||||
ToolDefinition,
|
||||
ToolResult,
|
||||
)
|
||||
|
||||
ToolFunc = Callable[..., Any]
|
||||
|
||||
@@ -86,7 +92,7 @@ class ReActAgent:
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
output = self.provider.generate(input_copy)
|
||||
output: LLMOutput = self.provider.generate(input_copy)
|
||||
|
||||
if not output.has_tool_calls:
|
||||
return output
|
||||
@@ -99,7 +105,7 @@ class ReActAgent:
|
||||
)
|
||||
)
|
||||
|
||||
results = self.executor.execute_all(output.tool_calls)
|
||||
results = self.executor.execute_all(output.tool_calls or [])
|
||||
|
||||
for result in results:
|
||||
messages.append(
|
||||
|
||||
@@ -7,7 +7,6 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SCRIPT = ROOT / "scripts" / "hooks" / "insaits-security-monitor.py"
|
||||
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from llm.core.types import LLMInput, Message, ProviderType, Role, ToolDefinition, ToolCall
|
||||
from llm.providers.astraflow import ASTRAFLOW_BASE_URL, ASTRAFLOW_CN_BASE_URL, AstraflowCNProvider, AstraflowProvider
|
||||
from llm.core.types import (
|
||||
LLMInput,
|
||||
Message,
|
||||
ProviderType,
|
||||
Role,
|
||||
ToolCall,
|
||||
ToolDefinition,
|
||||
)
|
||||
from llm.providers.astraflow import (
|
||||
ASTRAFLOW_BASE_URL,
|
||||
ASTRAFLOW_CN_BASE_URL,
|
||||
AstraflowCNProvider,
|
||||
AstraflowProvider,
|
||||
)
|
||||
|
||||
|
||||
def _tool() -> ToolDefinition:
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from llm.core.types import LLMInput, Message, ProviderType, Role, ToolCall, ToolDefinition
|
||||
from llm.providers.atlas import ATLAS_BASE_URL, DEFAULT_ATLAS_MAX_TOKENS, DEFAULT_ATLAS_MODEL, AtlasProvider
|
||||
from llm.core.types import (
|
||||
LLMInput,
|
||||
Message,
|
||||
ProviderType,
|
||||
Role,
|
||||
ToolCall,
|
||||
ToolDefinition,
|
||||
)
|
||||
from llm.providers.atlas import (
|
||||
ATLAS_BASE_URL,
|
||||
DEFAULT_ATLAS_MAX_TOKENS,
|
||||
DEFAULT_ATLAS_MODEL,
|
||||
AtlasProvider,
|
||||
)
|
||||
|
||||
|
||||
def _tool() -> ToolDefinition:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
from llm.core.types import LLMInput, Message, Role, ToolDefinition
|
||||
|
||||
from llm.core.types import Message, Role, ToolDefinition
|
||||
from llm.prompt import PromptBuilder, adapt_messages_for_provider
|
||||
from llm.prompt.builder import PromptConfig
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import pytest
|
||||
from llm.core.types import ToolCall, ToolDefinition, ToolResult
|
||||
from llm.core.types import ToolCall, ToolDefinition
|
||||
from llm.tools import ToolExecutor, ToolRegistry
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
_SKILL_COMPLY_ROOT = Path(__file__).resolve().parent.parent / "skills" / "skill-comply"
|
||||
if str(_SKILL_COMPLY_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_SKILL_COMPLY_ROOT))
|
||||
|
||||
from scripts.runner import _setup_sandbox # noqa: E402
|
||||
from scripts.scenario_generator import Scenario # noqa: E402
|
||||
from scripts.runner import _setup_sandbox # noqa: E402
|
||||
from scripts.scenario_generator import Scenario # noqa: E402
|
||||
|
||||
_GLOBAL_MARKER = "/tmp/runner_test_pwned_marker"
|
||||
|
||||
|
||||
+10
-1
@@ -1,6 +1,15 @@
|
||||
import pytest
|
||||
|
||||
from llm.core.types import ProviderType
|
||||
from llm.providers import AstraflowCNProvider, AstraflowProvider, AtlasProvider, ClaudeProvider, OpenAIProvider, OllamaProvider, get_provider
|
||||
from llm.providers import (
|
||||
AstraflowCNProvider,
|
||||
AstraflowProvider,
|
||||
AtlasProvider,
|
||||
ClaudeProvider,
|
||||
OllamaProvider,
|
||||
OpenAIProvider,
|
||||
get_provider,
|
||||
)
|
||||
|
||||
|
||||
class TestGetProvider:
|
||||
|
||||
@@ -7,7 +7,6 @@ from urllib.parse import urlsplit
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
SELECTOR_PATH = Path(__file__).parents[1] / "src" / "llm" / "cli" / "selector.py"
|
||||
SPEC = importlib.util.spec_from_file_location("ecc_selector", SELECTOR_PATH)
|
||||
assert SPEC is not None and SPEC.loader is not None
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import pytest
|
||||
from llm.core.types import (
|
||||
LLMInput,
|
||||
LLMOutput,
|
||||
|
||||
Reference in New Issue
Block a user