edgequake-llm 0.5.0

Multi-provider LLM abstraction library with caching, rate limiting, and cost tracking
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
name: Publish edgequake-litellm to PyPI

# Triggered by pushing a Python version tag:  git tag py-v0.1.0 && git push --tags
# Also supports manual dispatch with optional dry-run.
on:
  push:
    tags:
      - "py-v[0-9]+.[0-9]+.[0-9]+"
  workflow_dispatch:
    inputs:
      dry-run:
        description: "Dry run — build wheels but do NOT upload to PyPI"
        required: false
        default: "true"
        type: choice
        options: ["true", "false"]

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1
  WORKING_DIR: edgequake-litellm

# Only one publish at a time
concurrency:
  group: python-publish
  cancel-in-progress: false

jobs:
  # ─── 0. Pre-publish quality gate ───────────────────────────────────────────
  preflight:
    name: Pre-publish checks
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python 3.11
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install stable Rust + clippy + rustfmt
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt

      - name: Cache Cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-publish-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: ${{ runner.os }}-cargo-publish-

      - name: Install Python build tools
        working-directory: ${{ env.WORKING_DIR }}
        run: pip install maturin "ruff>=0.3" "mypy>=1.8" "pytest>=8.0" "pytest-asyncio>=0.24"

      # 1. Rust formatting
      - name: Check Rust formatting
        working-directory: ${{ env.WORKING_DIR }}
        run: cargo fmt --all -- --check

      # 2. Rust lint
      - name: Clippy
        working-directory: ${{ env.WORKING_DIR }}
        run: cargo clippy --all-features -- -D warnings

      # 3. Python lint
      - name: Ruff check
        working-directory: ${{ env.WORKING_DIR }}
        run: ruff check python/

      # 4. Build dev wheel and run unit tests
      - name: Build (PEP-517) and run unit tests
        working-directory: ${{ env.WORKING_DIR }}
        run: |
          pip install . -v
          pytest tests/ -q -k "not e2e" --tb=short

      # 5. Verify tag matches pyproject.toml version (tag-triggered runs only)
      - name: Verify tag matches pyproject.toml version
        if: startsWith(github.ref, 'refs/tags/py-v')
        shell: bash
        run: |
          TAG="${GITHUB_REF_NAME}"                    # e.g. py-v0.1.0
          EXPECTED="py-v$(grep '^version' ${{ env.WORKING_DIR }}/pyproject.toml | head -1 | cut -d'"' -f2)"
          echo "Git tag       : $TAG"
          echo "pyproject.toml: $EXPECTED"
          if [ "$TAG" != "$EXPECTED" ]; then
            echo "::error::Tag ($TAG) does not match pyproject.toml version ($EXPECTED). Bump version before tagging."
            exit 1
          fi

      # 6. Verify pyproject.toml and Cargo.toml versions match
      - name: Verify pyproject.toml and Cargo.toml versions match
        shell: bash
        run: |
          PY_VER=$(grep '^version' ${{ env.WORKING_DIR }}/pyproject.toml | head -1 | cut -d'"' -f2)
          RS_VER=$(grep '^version' ${{ env.WORKING_DIR }}/Cargo.toml | head -1 | cut -d'"' -f2)
          echo "pyproject.toml: $PY_VER"
          echo "Cargo.toml    : $RS_VER"
          if [ "$PY_VER" != "$RS_VER" ]; then
            echo "::error::Version mismatch — pyproject.toml ($PY_VER) != Cargo.toml ($RS_VER)"
            exit 1
          fi

      # 7. Maturin dry-run build to catch packaging errors early
      - name: Maturin dry-run wheel build
        working-directory: ${{ env.WORKING_DIR }}
        run: maturin build --release --out /tmp/maturin-check

  # ─── 1. Build source distribution (sdist) ──────────────────────────────────
  sdist:
    name: Build sdist
    runs-on: ubuntu-latest
    needs: preflight
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python 3.11
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install maturin
        run: pip install maturin

      - name: Build sdist
        working-directory: ${{ env.WORKING_DIR }}
        run: maturin sdist --out dist

      - name: Upload sdist
        uses: actions/upload-artifact@v4
        with:
          name: sdist
          path: ${{ env.WORKING_DIR }}/dist/*.tar.gz

  # ─── 2. Build binary wheels (all platforms + architectures) ────────────────
  #
  #  Platform / Arch matrix:
  #    Linux   manylinux  x86_64
  #    Linux   manylinux  aarch64   (cross via QEMU)
  #    Linux   musllinux  x86_64    (Alpine / Docker)
  #    Linux   musllinux  aarch64   (Alpine ARM, cross via QEMU)
  #    macOS              x86_64    (Intel — macos-13)
  #    macOS              arm64     (Apple Silicon — macos-latest)
  #    Windows            x86_64
  #    Windows            aarch64   (cross-compile on x86_64 runner)
  #
  build-wheels:
    name: Build wheel (${{ matrix.label }})
    needs: preflight
    strategy:
      fail-fast: false
      matrix:
        include:
          # ── Linux manylinux x86_64 ──────────────────────────────────────────
          - label: linux-x86_64-manylinux
            os: ubuntu-latest
            target: x86_64
            manylinux: auto

          # ── Linux manylinux aarch64 (QEMU cross-compile) ────────────────────
          - label: linux-aarch64-manylinux
            os: ubuntu-latest
            target: aarch64
            manylinux: auto
            can-fail: true  # ring crate cross-compile issues with old GCC in manylinux2014
          # ── Linux musllinux x86_64 (Alpine) ────────────────────────────────
          - label: linux-x86_64-musl
            os: ubuntu-latest
            target: x86_64
            manylinux: musllinux_1_2

          # ── Linux musllinux aarch64 (Alpine ARM, QEMU cross-compile) ────────
          - label: linux-aarch64-musl
            os: ubuntu-latest
            target: aarch64
            manylinux: musllinux_1_2
            can-fail: true  # ring crate cross-compile may be flaky on QEMU

          # ── macOS Intel (x86_64) ────────────────────────────────────────────
          - label: macos-x86_64
            os: macos-latest  # ARM64 runner cross-compiles x86_64 (avoids flaky macos-13)
            target: x86_64
            manylinux: ""

          # ── macOS Apple Silicon (arm64) ─────────────────────────────────────
          - label: macos-arm64
            os: macos-latest
            target: aarch64
            manylinux: ""

          # ── Windows x86_64 ──────────────────────────────────────────────────
          - label: windows-x86_64
            os: windows-latest
            target: x86_64
            manylinux: ""


    runs-on: ${{ matrix.os }}
    continue-on-error: ${{ matrix.can-fail == true }}

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python 3.11
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Build wheel (maturin-action)
        uses: PyO3/maturin-action@v1
        with:
          working-directory: ${{ env.WORKING_DIR }}
          target: ${{ matrix.target }}
          manylinux: ${{ matrix.manylinux }}
          # abi3 wheel — one wheel per platform covers Python 3.9–3.13+
          args: --release --out dist
          sccache: "true"
          # Ensure Python 3.9+ is the default inside the manylinux container
          # so that PyO3's abi3-py39 feature generates correct wheel tags.
          before-script-linux: |
            if [ -d /opt/python/cp39-cp39/bin ]; then
              export PATH=/opt/python/cp39-cp39/bin:$PATH
            fi
        env:
          # Fix ring crate cross-compilation for aarch64 Linux
          # -D__ARM_ARCH=8 needed for old GCC in manylinux2014 container (glibc)
          CFLAGS_aarch64_unknown_linux_gnu: "-march=armv8-a -D__ARM_ARCH=8"
          # Same fix for musl aarch64 (different target triple env var)
          CFLAGS_aarch64_unknown_linux_musl: "-march=armv8-a -D__ARM_ARCH=8"

      - name: Upload wheel artifact
        uses: actions/upload-artifact@v4
        with:
          name: wheel-${{ matrix.label }}
          path: ${{ env.WORKING_DIR }}/dist/*.whl

  # ─── 3. Smoke test the native platform wheels ──────────────────────────────
  smoke-test:
    name: Smoke test (${{ matrix.os }})
    needs: build-wheels
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            wheel-pattern: "wheel-linux-x86_64-manylinux"
          - os: macos-latest
            wheel-pattern: "wheel-macos-arm64"
          # Note: macos-x86_64 wheel is cross-compiled; skip smoke test (arch mismatch)
          - os: windows-latest
            wheel-pattern: "wheel-windows-x86_64"
    runs-on: ${{ matrix.os }}
    continue-on-error: ${{ matrix.can-fail == true }}

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python 3.11
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Download wheel
        uses: actions/download-artifact@v4
        with:
          name: ${{ matrix.wheel-pattern }}
          path: dist

      - name: Install wheel
        run: pip install edgequake-litellm --find-links dist --no-index

      - name: Smoke test — mock provider (no API keys needed)
        shell: python
        run: |
          import edgequake_litellm as eq

          # 1. Basic completion
          resp = eq.completion("mock/test", [{"role": "user", "content": "hi"}])
          assert resp.content is not None, "resp.content is None"
          assert isinstance(resp.content, str), f"resp.content is {type(resp.content)}"

          # 2. litellm-compatible access path
          assert resp.choices[0].message.content is not None, "choices path failed"

          # 3. Version
          assert eq.__version__ != "0.0.0-dev", f"dev version: {eq.__version__}"

          print(f"Smoke test PASSED — version={eq.__version__}, content={resp.content!r}")

      - name: Smoke test — unit tests (no API keys)
        run: |
          pip install "pytest>=8.0" "pytest-asyncio>=0.24"
          pytest edgequake-litellm/tests/ -q -k "not e2e" --tb=short

  # ─── 4. Publish to PyPI ─────────────────────────────────────────────────────
  publish:
    name: Publish to PyPI
    needs: [build-wheels, sdist]
    runs-on: ubuntu-latest
    # Run publish even if some wheel builds failed (continue-on-error platforms)
    if: always() && needs.sdist.result == 'success'
    permissions:
      id-token: write  # Required for OIDC Trusted Publishers on PyPI
      contents: write

    steps:
      - name: Download all wheels
        uses: actions/download-artifact@v4
        with:
          pattern: wheel-*
          path: dist
          merge-multiple: true

      - name: Download sdist
        uses: actions/download-artifact@v4
        with:
          name: sdist
          path: dist

      - name: List dist contents
        run: ls -la dist/

      - name: Determine publish method
        if: >-
          startsWith(github.ref, 'refs/tags/py-v') ||
          (github.event_name == 'workflow_dispatch' && github.event.inputs.dry-run == 'false')
        id: pub-method
        env:
          PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        run: |
          if [ -n "$PYPI_API_TOKEN" ]; then
            echo "method=token" >> "$GITHUB_OUTPUT"
            echo "Using API token auth (PYPI_API_TOKEN secret found)"
          else
            echo "method=oidc" >> "$GITHUB_OUTPUT"
            echo "Using OIDC Trusted Publisher auth (no PYPI_API_TOKEN secret)"
          fi

      - name: Publish to PyPI (OIDC Trusted Publisher)
        if: >-
          steps.pub-method.outputs.method == 'oidc' &&
          (startsWith(github.ref, 'refs/tags/py-v') ||
          (github.event_name == 'workflow_dispatch' && github.event.inputs.dry-run == 'false'))
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: dist/
          verbose: true
          attestations: false
          # Configure pending publisher at: https://pypi.org/manage/account/publishing/
          # Required settings: Project name=edgequake-litellm, Owner=raphaelmansuy,
          #   Repository=edgequake-llm, Workflow=python-publish.yml, Environment=(blank)

      - name: Publish to PyPI (API Token)
        if: >-
          steps.pub-method.outputs.method == 'token' &&
          (startsWith(github.ref, 'refs/tags/py-v') ||
          (github.event_name == 'workflow_dispatch' && github.event.inputs.dry-run == 'false'))
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: dist/
          user: __token__
          password: ${{ secrets.PYPI_API_TOKEN }}
          verbose: true
          attestations: false

      - name: Dry-run summary (no upload)
        if: >-
          github.event_name == 'workflow_dispatch' && github.event.inputs.dry-run == 'true'
        run: |
          echo "DRY RUN — wheels built but NOT uploaded to PyPI."
          echo ""
          echo "Artifacts ready to publish:"
          ls -la dist/
          echo ""
          echo "To publish for real, push a tag:  git tag py-v<version> && git push --tags"
          echo "Or re-run this workflow with dry-run=false."

      - name: Extract release notes from changelog
        if: startsWith(github.ref, 'refs/tags/py-v')
        shell: bash
        run: |
          VERSION="${GITHUB_REF_NAME#py-v}"
          awk -v ver="[$VERSION]" '
            $0 ~ "^## \\[" && found { exit }
            $0 ~ "^## \\[" ver "\\]" { found=1 }
            found { print }
          ' "${WORKING_DIR}/CHANGELOG.md" > RELEASE_NOTES.md

          if [ ! -s RELEASE_NOTES.md ]; then
            {
              echo "## edgequake-litellm ${VERSION}"
              echo
              echo "See edgequake-litellm/CHANGELOG.md for release details."
            } > RELEASE_NOTES.md
          fi

      - name: Create GitHub release
        if: startsWith(github.ref, 'refs/tags/py-v')
        uses: softprops/action-gh-release@v2
        with:
          body_path: RELEASE_NOTES.md
          files: dist/*
          generate_release_notes: false