Add CI workflow and bump Sonnet to 4.6 #3
4 changed files with 67 additions and 15 deletions
50
.github/workflows/ci.yaml
vendored
Normal file
50
.github/workflows/ci.yaml
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install ruff
|
||||||
|
run: pip install ruff
|
||||||
|
|
||||||
|
- name: Ruff check
|
||||||
|
run: ruff check .
|
||||||
|
|
||||||
|
- name: Ruff format check
|
||||||
|
run: ruff format --check .
|
||||||
|
|
||||||
|
test-collect:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
cache: pip
|
||||||
|
cache-dependency-path: requirements.txt
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install pytest python-dotenv
|
||||||
|
|
||||||
|
- name: Pytest collect-only
|
||||||
|
# Verifies all test modules + their imports resolve cleanly.
|
||||||
|
# Live test execution requires ANTHROPIC_API_KEY + COMPOSIO_API_KEY
|
||||||
|
# and runs locally before push, not in CI.
|
||||||
|
run: pytest --collect-only -q
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -6,3 +6,4 @@ __pycache__/
|
||||||
.cache/
|
.cache/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
|
.DS_Store
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from langchain_google_genai import ChatGoogleGenerativeAI
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
|
|
||||||
# Model IDs — single source of truth. Bump here when families ship new revs.
|
# Model IDs — single source of truth. Bump here when families ship new revs.
|
||||||
CLAUDE_SONNET = "claude-sonnet-4-20250514"
|
CLAUDE_SONNET = "claude-sonnet-4-6"
|
||||||
CLAUDE_HAIKU = "claude-haiku-4-5-20251001"
|
CLAUDE_HAIKU = "claude-haiku-4-5-20251001"
|
||||||
OPENAI_CROSS_REVIEWER = "gpt-4.1"
|
OPENAI_CROSS_REVIEWER = "gpt-4.1"
|
||||||
GEMINI_SCANNER = "gemini-2.5-pro"
|
GEMINI_SCANNER = "gemini-2.5-pro"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
"""Golden-set routing test for the structured router.
|
"""Golden-set routing test for the structured router.
|
||||||
|
|
||||||
20 labelled tasks → expected agent. Skipped if provider keys are missing
|
20 labelled tasks → expected agent. Live tests require ANTHROPIC_API_KEY +
|
||||||
(ANTHROPIC for the router LLM, COMPOSIO because importing the graph eagerly
|
COMPOSIO_API_KEY; without them the parametrized cases skip at runtime but
|
||||||
loads tools). Run with:
|
the module still collects (so `pytest --collect-only` in CI works without
|
||||||
|
secrets). Run with:
|
||||||
|
|
||||||
pytest tests/test_routing_golden.py -v
|
pytest tests/test_routing_golden.py -v
|
||||||
"""
|
"""
|
||||||
|
|
@ -14,17 +15,12 @@ from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv(os.path.join(os.path.dirname(__file__), "..", ".env"))
|
load_dotenv(os.path.join(os.path.dirname(__file__), "..", ".env"))
|
||||||
|
|
||||||
if not os.getenv("ANTHROPIC_API_KEY"):
|
_REQUIRED_KEYS = ("ANTHROPIC_API_KEY", "COMPOSIO_API_KEY")
|
||||||
pytest.skip(
|
_missing = [k for k in _REQUIRED_KEYS if not os.getenv(k)]
|
||||||
"ANTHROPIC_API_KEY not set; skipping live router tests.",
|
requires_keys = pytest.mark.skipif(
|
||||||
allow_module_level=True,
|
bool(_missing),
|
||||||
)
|
reason=f"Requires {', '.join(_REQUIRED_KEYS)}; missing: {', '.join(_missing)}",
|
||||||
if not os.getenv("COMPOSIO_API_KEY"):
|
)
|
||||||
pytest.skip(
|
|
||||||
"COMPOSIO_API_KEY not set; skipping live router tests.", allow_module_level=True
|
|
||||||
)
|
|
||||||
|
|
||||||
from graph import router_node # noqa: E402
|
|
||||||
|
|
||||||
|
|
||||||
GOLDEN_SET: list[tuple[str, str]] = [
|
GOLDEN_SET: list[tuple[str, str]] = [
|
||||||
|
|
@ -107,8 +103,13 @@ def test_golden_set_size():
|
||||||
assert len(GOLDEN_SET) == 20
|
assert len(GOLDEN_SET) == 20
|
||||||
|
|
||||||
|
|
||||||
|
@requires_keys
|
||||||
@pytest.mark.parametrize("task,expected", GOLDEN_SET)
|
@pytest.mark.parametrize("task,expected", GOLDEN_SET)
|
||||||
def test_router_picks_expected_agent(task: str, expected: str):
|
def test_router_picks_expected_agent(task: str, expected: str):
|
||||||
|
# Lazy import — graph.py loads composio_tools at module import and needs
|
||||||
|
# COMPOSIO_API_KEY. Keep the import inside the skipif-guarded test body.
|
||||||
|
from graph import router_node
|
||||||
|
|
||||||
out = router_node({"task": task, "messages": []})
|
out = router_node({"task": task, "messages": []})
|
||||||
assert out["route"] == expected, (
|
assert out["route"] == expected, (
|
||||||
f"task={task!r} got={out['route']} expected={expected}"
|
f"task={task!r} got={out['route']} expected={expected}"
|
||||||
|
|
|
||||||
Reference in a new issue