Skip to content
Merged
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
8 changes: 4 additions & 4 deletions .github/workflows/sync-models.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ jobs:
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-sync-models-${{ hashFiles('tools/requirements.txt') }}
key: ${{ runner.os }}-pip-sync-models-${{ hashFiles('tools/sync_models/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-sync-models-

- name: Install sync script dependencies
run: pip install -r tools/requirements.txt
run: pip install -r tools/sync_models/requirements.txt

# Partition providers by whether their API key secret is configured.
# Providers WITH a key run in strict mode (native API discovery only).
Expand Down Expand Up @@ -98,7 +98,7 @@ jobs:
shell: bash
run: |
# Capture markdown PR body to a file; tee to stdout so it is also visible in CI logs.
python tools/src/sync_models.py ${{ steps.partition.outputs.with_key }} --enable-discovery --apply --pr-body | tee /tmp/body-strict.md
python tools/sync_models/src/sync_models.py ${{ steps.partition.outputs.with_key }} --enable-discovery --apply --pr-body | tee /tmp/body-strict.md
Comment thread
asclearuc marked this conversation as resolved.
env:
ROCKETRIDE_OPENAI_KEY: ${{ secrets.ROCKETRIDE_OPENAI_KEY }}
ROCKETRIDE_ANTHROPIC_KEY: ${{ secrets.ROCKETRIDE_ANTHROPIC_KEY }}
Expand All @@ -119,7 +119,7 @@ jobs:
if: steps.partition.outputs.without_key != ''
shell: bash
run: |
python tools/src/sync_models.py ${{ steps.partition.outputs.without_key }} --enable-discovery --allow-fallback-discovery --apply --pr-body | tee /tmp/body-fallback.md
python tools/sync_models/src/sync_models.py ${{ steps.partition.outputs.without_key }} --enable-discovery --allow-fallback-discovery --apply --pr-body | tee /tmp/body-fallback.md

# Combine the two PR bodies and write a single SYNC_OUTPUT to GITHUB_ENV.
# This overrides whatever each --pr-body step appended individually.
Expand Down
2 changes: 1 addition & 1 deletion tools/dependabot-smoke/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ These scripts exist to give a reviewer a 30-second answer to "does this dep bump

| Script | Use when Dependabot bumps anything in… |
| --- | --- |
| `smoke-litellm.sh` | `tools/requirements.txt` (litellm consumer) |
| `smoke-litellm.sh` | `tools/sync_models/requirements.txt` (litellm consumer) |
| `smoke-nodes.sh` | `nodes/src/nodes/<node>/*.txt` (Python pipeline node deps) |

## Manual invocation (from a Dependabot PR branch)
Expand Down
12 changes: 6 additions & 6 deletions tools/dependabot-smoke/smoke-litellm.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# Dependabot smoke test: litellm version bumps
#
# Verifies that `tools/src/sync_models.py` (the consumer of litellm in this repo)
# continues to work with the current pin in `tools/requirements.txt`. PR CI runs
# Verifies that `tools/sync_models/src/sync_models.py` (the consumer of litellm in this repo)
# continues to work with the current pin in `tools/sync_models/requirements.txt`. PR CI runs
# build/lint only, not this tool, so a bumped litellm could silently break the
# weekly sync-models.yml cron without anyone noticing until Monday.
#
Expand All @@ -24,8 +24,8 @@ echo "==> Creating venv at $VENV"
python3 -m venv "$VENV"
"$VENV/bin/pip" install --quiet --upgrade pip

echo "==> Installing tools/requirements.txt"
"$VENV/bin/pip" install --quiet -r tools/requirements.txt
echo "==> Installing tools/sync_models/requirements.txt"
"$VENV/bin/pip" install --quiet -r tools/sync_models/requirements.txt

echo "==> Smoke: litellm import + core APIs"
"$VENV/bin/python" - <<'PY'
Expand Down Expand Up @@ -53,7 +53,7 @@ print(f"litellm {litellm_version}: model_cost={len(litellm.model_cost)} entries;
PY

echo "==> Smoke: project consumer imports + calls"
PYTHONPATH="$REPO_ROOT/tools/src" "$VENV/bin/python" - <<'PY'
PYTHONPATH="$REPO_ROOT/tools/sync_models/src" "$VENV/bin/python" - <<'PY'
from core.merger import _litellm_info
# This helper does the direct-lookup + model_cost scan dance. Any breakage
# in either litellm API surfaces here.
Expand All @@ -63,7 +63,7 @@ assert ctx is not None or out is not None, "both context and output token counts
PY

echo "==> Smoke: sync_models.py --help (verifies argparse + module load)"
"$VENV/bin/python" tools/src/sync_models.py --help > /dev/null
"$VENV/bin/python" tools/sync_models/src/sync_models.py --help > /dev/null

echo
echo "PASS: litellm smoke test."
45 changes: 23 additions & 22 deletions tools/SYNC_MODELS.md → tools/sync_models/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ the results into `nodes/src/nodes/*/services.json` profile lists.
**Direct (Python):**

```bash
python tools/src/sync_models.py --provider <PROVIDER> [--provider <PROVIDER> ...]
python tools/src/sync_models.py --all
python tools/sync_models/src/sync_models.py --provider <PROVIDER> [--provider <PROVIDER> ...]
python tools/sync_models/src/sync_models.py --all
```

**Via the engine:**

```bash
engine run tools/src/sync_models.py --provider <PROVIDER> [--provider <PROVIDER> ...]
engine run tools/src/sync_models.py --all
engine run tools/sync_models/src/sync_models.py --provider <PROVIDER> [--provider <PROVIDER> ...]
engine run tools/sync_models/src/sync_models.py --all
```

**Via the builder** (runs sync + Prettier in one step):
Expand Down Expand Up @@ -48,19 +48,19 @@ Validation: `--model-source` may not list duplicate values. `--allow-fallback-di

```bash
# Default: dry-run, enrichment-only — updates token data on existing profiles, no new profiles added
python tools/src/sync_models.py --provider llm_openai
python tools/sync_models/src/sync_models.py --provider llm_openai

# Production CI path: discovery on, strict mode — only providers with keys get new profiles
python tools/src/sync_models.py --all --enable-discovery --apply
python tools/sync_models/src/sync_models.py --all --enable-discovery --apply

# Dev workflow without API keys, explicit fallback opt-in (may add OpenRouter aliases)
python tools/src/sync_models.py --provider llm_openai --enable-discovery --allow-fallback-discovery
python tools/sync_models/src/sync_models.py --provider llm_openai --enable-discovery --allow-fallback-discovery

# Custom source ordering — LiteLLM first for token data, OpenRouter as backup, no provider API
python tools/src/sync_models.py --provider llm_openai --model-source litellm --model-source openrouter
python tools/sync_models/src/sync_models.py --provider llm_openai --model-source litellm --model-source openrouter

# Discovery from OpenRouter alone, suitable for an aggregator-style node
python tools/src/sync_models.py --provider llm_openai --model-source openrouter --enable-discovery --allow-fallback-discovery
python tools/sync_models/src/sync_models.py --provider llm_openai --model-source openrouter --enable-discovery --allow-fallback-discovery
```

---
Expand Down Expand Up @@ -158,7 +158,7 @@ The sync has two distinct modes:

---

## Configuration — `tools/src/sync_models.config.json`
## Configuration — `tools/sync_models/src/sync_models.config.json`

### Top-level keys

Expand Down Expand Up @@ -260,10 +260,10 @@ passes the sync tool will automatically re-evaluate the profile against the prov

## Dependencies

Managed in `tools/requirements.txt`. Install with:
Managed in `tools/sync_models/requirements.txt`. Install with:

```bash
pip install -r tools/requirements.txt
pip install -r tools/sync_models/requirements.txt
```

| Package | Purpose |
Expand All @@ -282,10 +282,10 @@ pip install -r tools/requirements.txt

```bash
# Offline logic tests (no API key, no server)
pytest tools/test/test_sync_logic.py
pytest tools/sync_models/test/test_sync_logic.py

# Live API tests (skipped if keys not set)
pytest tools/test/test_sync_live.py
pytest tools/sync_models/test/test_sync_live.py
```

---
Expand All @@ -295,22 +295,23 @@ pytest tools/test/test_sync_live.py
`.github/workflows/sync-models.yml` runs every Monday at 05:00 UTC and on
manual dispatch. It:

1. Runs a dry-run first (`python tools/src/sync_models.py --all --enable-discovery`) — fails fast if the script errors.
1. Runs a dry-run first (`python tools/sync_models/src/sync_models.py --all --enable-discovery`) — fails fast if the script errors.
2. Runs with `--apply --pr-body` to write changes and capture the report.
3. Opens a PR via `peter-evans/create-pull-request` with the report as the body.

The workflow uses `--enable-discovery` (so model lists grow over time) but **does NOT** use `--allow-fallback-discovery`. This is intentional: when a provider's secret is missing from the GitHub Actions environment, the resulting PR body shows `Discovery skipped — set ROCKETRIDE_APIKEY_<PROVIDER>` for that provider. A reviewer sees the gap and can decide whether to add the secret rather than silently shipping fallback-discovered profiles to production.
The workflow uses `--enable-discovery` (so model lists grow over time) but **does NOT** use `--allow-fallback-discovery`. This is intentional: when a provider's secret is missing from the GitHub Actions environment, the resulting PR body shows a `Discovery skipped — set the provider API key` note for that provider. A reviewer sees the gap and can decide whether to add the secret rather than silently shipping fallback-discovered profiles to production.

Provider API keys are stored as GitHub Actions secrets named
`ROCKETRIDE_APIKEY_<PROVIDER>` (see `.github/workflows/sync-models.yml` for
the full list).
`ROCKETRIDE_<PROVIDER>_KEY` (e.g. `ROCKETRIDE_OPENAI_KEY`,
`ROCKETRIDE_ANTHROPIC_KEY`; see `.github/workflows/sync-models.yml` for the
full list).

---

## Adding a New Provider

1. Create `tools/src/providers/<name>.py` subclassing `CloudProvider`
1. Create `tools/sync_models/src/providers/<name>.py` subclassing `CloudProvider`
2. Implement `make_client(api_key)` and `fetch_models(client)`
3. Add an entry to `_PROVIDER_REGISTRY` and `_SERVICES_JSON_PATHS` in `tools/src/sync_models.py`
4. Add a provider config block to `tools/src/sync_models.config.json`
5. Run `python tools/src/sync_models.py --provider <name>` to verify
3. Add an entry to `_PROVIDER_REGISTRY` and `_SERVICES_JSON_PATHS` in `tools/sync_models/src/sync_models.py`
4. Add a provider config block to `tools/sync_models/src/sync_models.config.json`
5. Run `python tools/sync_models/src/sync_models.py --provider <name>` to verify
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
*/

const path = require('path');
const { execCommand, PROJECT_ROOT, DIST_ROOT } = require('../../scripts/lib');
const { execCommand, PROJECT_ROOT, DIST_ROOT } = require('../../../scripts/lib');

const TOOLS_SRC = path.join(__dirname, '..', 'src', 'sync_models.py');

// Maps provider key → relative path to its services.json from the repo root.
// Mirrors _SERVICES_JSON_PATHS in tools/src/sync_models.py.
// Mirrors _SERVICES_JSON_PATHS in tools/sync_models/src/sync_models.py.
const SERVICES_JSON_PATHS = {
llm_openai: 'nodes/src/nodes/llm_openai/services.json',
embedding_openai: 'nodes/src/nodes/embedding_openai/services.json',
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,6 @@ def format_pr_body(report: SyncReport) -> str:
lines.append('')

lines.append('---')
lines.append('*Generated by `tools/src/sync_models.py`*')
lines.append('*Generated by `tools/sync_models/src/sync_models.py`*')

return '\n'.join(lines)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

Usage
-----
python tools/src/sync_models.py --provider openai [--apply]
python tools/src/sync_models.py --provider anthropic [--apply]
python tools/src/sync_models.py --all [--apply]
python tools/sync_models/src/sync_models.py --provider openai [--apply]
python tools/sync_models/src/sync_models.py --provider anthropic [--apply]
python tools/sync_models/src/sync_models.py --all [--apply]

Without ``--apply`` the script runs in dry-run mode: it prints what would
change but does not write any files.
Expand Down Expand Up @@ -33,7 +33,7 @@
except ImportError:
pass # python-dotenv not installed — rely on shell environment

# Make tools/src importable when running as a script from any CWD
# Make tools/sync_models/src importable when running as a script from any CWD
_TOOLS_SRC = Path(__file__).parent
if str(_TOOLS_SRC) not in sys.path:
sys.path.insert(0, str(_TOOLS_SRC))
Expand Down Expand Up @@ -135,7 +135,7 @@ def _find_repo_root() -> Path:
Raises:
RuntimeError: If the repo root cannot be found
"""
candidate = _TOOLS_SRC.parent.parent # tools/src -> tools -> repo root
candidate = _TOOLS_SRC.parent.parent.parent # tools/sync_models/src -> sync_models -> tools -> repo root
if (candidate / 'nodes').exists():
return candidate
# Try CWD as fallback
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""
conftest.py — pytest fixtures and configuration for tools/test/.
conftest.py — pytest fixtures and configuration for tools/sync_models/test/.

Provides:
- sys.path setup so tools/src/ modules are importable
- sys.path setup so tools/sync_models/src/ modules are importable
- mock_provider_api fixture for offline tests
- sample_services_json fixture for patcher tests

Expand All @@ -22,7 +22,7 @@
import pytest

# ---------------------------------------------------------------------------
# Path setup — must happen before any tools/src imports
# Path setup — must happen before any tools/sync_models/src imports
# ---------------------------------------------------------------------------

_TOOLS_TEST = Path(__file__).parent
Expand All @@ -42,7 +42,7 @@

if load_dotenv is not None:
try:
load_dotenv(_TOOLS_TEST.parent.parent / '.env')
load_dotenv(_TOOLS_TEST.parent.parent.parent / '.env')
except OSError as exc:
import warnings

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
All tests are skipped when the required API key environment variable is not set.

Run with all keys:
pytest tools/test/test_sync_live.py
pytest tools/sync_models/test/test_sync_live.py

Run for a single provider:
ROCKETRIDE_OPENAI_KEY=sk-... pytest tools/test/test_sync_live.py -k openai
ROCKETRIDE_OPENAI_KEY=sk-... pytest tools/sync_models/test/test_sync_live.py -k openai
"""

from __future__ import annotations
Expand All @@ -20,7 +20,7 @@
from pathlib import Path
from typing import Dict, Any, Set

# markers.py is a regular module in tools/test/ (importable unlike conftest)
# markers.py is a regular module in tools/sync_models/test/ (importable unlike conftest)
from markers import (
requires_openai,
requires_anthropic,
Expand All @@ -39,7 +39,7 @@
# Helpers
# ---------------------------------------------------------------------------

_REPO_ROOT = Path(__file__).parent.parent.parent
_REPO_ROOT = Path(__file__).parent.parent.parent.parent


def _load_profiles(node_name: str) -> Dict[str, Any]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
They test the merge, deprecation, smoke-test gate, and comment-preservation
logic against mocked provider responses.

Run: pytest tools/test/test_sync_logic.py
Run: pytest tools/sync_models/test/test_sync_logic.py
"""

from __future__ import annotations
Expand All @@ -15,7 +15,7 @@

import pytest

# tools/src is added to sys.path by conftest.py
# tools/sync_models/src is added to sys.path by conftest.py
from core.merger import merge, _make_profile_key, _derive_title
from core.smoke import run
from core.patcher import load as patcher_load, patch as patcher_patch, get_profiles
Expand Down Expand Up @@ -500,8 +500,8 @@ def _run_cli(self, *args):
import subprocess
import sys

repo_root = Path(__file__).resolve().parents[2]
script = repo_root / 'tools' / 'src' / 'sync_models.py'
repo_root = Path(__file__).resolve().parents[3]
script = repo_root / 'tools' / 'sync_models' / 'src' / 'sync_models.py'
result = subprocess.run(
[sys.executable, str(script), *args],
capture_output=True,
Expand Down
Loading