Compare commits

...

12 Commits

Author SHA1 Message Date
GitHub Actions
faf5ae3533 chore: release v0.1.1 2025-07-24 23:36:23 +00:00
Andy Lee
a44dccecac fix: make TestPyPI upload optional and non-blocking
- Add continue-on-error to TestPyPI step
- Check if TEST_PYPI_API_TOKEN exists before attempting upload
- Add graceful failure handling with clear messages
- Update docs to explain TestPyPI token configuration
- Clarify that TestPyPI testing is optional

Now the release won't fail if TestPyPI is not configured or upload fails
2025-07-24 16:02:07 -07:00
yichuan520030910320
9cf9358b9c Merge branch 'main' of https://github.com/yichuan-w/LEANN 2025-07-24 14:40:39 -07:00
yichuan520030910320
de252fef31 [chat] update 30s example 2025-07-24 14:40:33 -07:00
Andy Lee
9076bc27b8 fix: resolve CI run detection issues in release workflow
- Add 'actions: read' permission to access workflow runs
- Use workflow name instead of filename for gh run list
- Look for CI run on HEAD~1 (before version bump commit)
- Improve error messages for better debugging

Fixes HTTP 403 error when trying to find successful CI runs
2025-07-24 14:27:26 -07:00
Andy Lee
50686c0819 refactor: use CI artifacts in release workflow instead of rebuilding
- Download pre-built wheels from successful CI runs
- Avoids duplicate builds and ensures consistency
- CI artifacts are already tested across all platforms
- Faster release process (no build time)
- Updates release documentation to reflect new flow

This ensures the released packages are exactly what was tested in CI.
2025-07-24 14:24:03 -07:00
Andy Lee
1614203786 fix: make bump_version.sh work on both macOS and Linux
- macOS uses sed -i '' while Linux uses sed -i
- Add OS detection to use correct syntax
- Ensures script works in CI (Linux) and local dev (macOS)
2025-07-24 14:13:31 -07:00
Andy Lee
3d4c75a56c fix: add missing scripts directory to git
- Remove scripts/ from .gitignore
- Add build_and_test.sh for local testing
- Add bump_version.sh for version updates (used by CI)
- Add release.sh and upload_to_pypi.sh for publishing
- Fixes CI error: ./scripts/bump_version.sh: No such file or directory
2025-07-24 14:13:14 -07:00
Andy Lee
2684ee71dc fix: ensure uv build uses correct Python version in CI
- Add --python python flag to uv build commands
- This ensures wheels are built with the correct Python version (cp313 for Python 3.13, etc)
- Fixes issue where Python 3.13 CI was building cp311 wheels
- Also adds Python version verification before build
2025-07-24 13:44:02 -07:00
Andy Lee
1d321953ba ci: update all GitHub Actions to latest versions
- Update actions/upload-artifact from v3 to v4 (v3 deprecated April 2024)
- Update actions/setup-python from v4 to v5 (latest version)
- Add Python 3.12 and 3.13 to CI test matrix
- Ensure compatibility with latest Python versions and GitHub Actions
2025-07-24 13:36:21 -07:00
Andy Lee
b3cb251369 ci: add Python 3.12 and 3.13 to test matrix
- Add Python 3.12 and 3.13 to CI test matrix
- Ensure compatibility with latest Python versions
- Python 3.12 is stable, 3.13 was released in Oct 2024
2025-07-24 13:32:29 -07:00
Andy Lee
0a17d2c9d8 feat: implement comprehensive CI/CD pipeline with two-stage release
- Add ci.yml for continuous integration on every commit
  - Test builds on Ubuntu/macOS with Python 3.9/3.10/3.11
  - Ensure code quality before any release

- Add release-manual.yml for controlled releases
  - Manual trigger prevents accidental releases
  - Version validation and tag creation
  - Optional TestPyPI testing before production
  - Only creates tag after validation passes

- Keep build-and-publish.yml for automated PyPI deployment
  - Triggered by new tags (separation of concerns)
  - Handles multi-platform wheel building
  - Allows retry if PyPI upload fails

- Update RELEASE.md with clear prerequisites and workflow

This setup ensures:
1. Every commit is tested (CI)
2. Releases are deliberate (manual trigger)
3. Failed CI won't create broken tags
4. PyPI publish can be retried independently
2025-07-24 13:29:21 -07:00
16 changed files with 591 additions and 71 deletions

View File

@@ -90,7 +90,7 @@ jobs:
- name: Build wheel
run: |
cd packages/leann-backend-hnsw
uv build --wheel
uv build --wheel --python python
- name: Repair wheel (Linux)
if: runner.os == 'Linux'
@@ -167,7 +167,7 @@ jobs:
- name: Build wheel
run: |
cd packages/leann-backend-diskann
uv build --wheel
uv build --wheel --python python
- name: Repair wheel (Linux)
if: runner.os == 'Linux'

110
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,110 @@
name: CI - Build and Test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install system dependencies (Ubuntu)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libomp-dev libboost-all-dev libzmq3-dev \
pkg-config libopenblas-dev patchelf \
libaio-dev protobuf-compiler libprotobuf-dev libabsl-dev
# Install Intel MKL for DiskANN
wget https://registrationcenter-download.intel.com/akdlm/IRC_NAS/79153e0f-74d7-45af-b8c2-258941adf58a/intel-onemkl-2025.0.0.940.sh
sudo sh intel-onemkl-2025.0.0.940.sh -a --components intel.oneapi.lin.mkl.devel --action install --eula accept -s
source /opt/intel/oneapi/setvars.sh
echo "MKLROOT=/opt/intel/oneapi/mkl/latest" >> $GITHUB_ENV
echo "LD_LIBRARY_PATH=/opt/intel/oneapi/mkl/latest/lib/intel64:$LD_LIBRARY_PATH" >> $GITHUB_ENV
- name: Install system dependencies (macOS)
if: runner.os == 'macOS'
run: |
brew install libomp boost zeromq protobuf
- name: Build all packages
run: |
echo "🔨 Building on ${{ matrix.os }} with Python ${{ matrix.python-version }}..."
export UV_SYSTEM_PYTHON=1
# Verify Python version
python --version
which python
# Build each package
for pkg in leann-core leann-backend-hnsw leann-backend-diskann leann; do
echo "Building $pkg..."
cd packages/$pkg
rm -rf dist/ build/ _skbuild/
# Use explicit python interpreter
uv build --wheel --python python
if [ ! -f dist/*.whl ]; then
echo "❌ Failed to build $pkg!"
exit 1
fi
echo "✅ $pkg built successfully"
cd ../..
done
- name: Install and test packages
run: |
# Create clean test environment
python -m venv test_env
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
source test_env/Scripts/activate
else
source test_env/bin/activate
fi
# Install built packages
pip install packages/*/dist/*.whl
# Basic import test
python -c "import leann; print('✅ LEANN imported successfully')"
python -c "import leann_backend_hnsw; print('✅ HNSW backend imported')"
python -c "import leann_backend_diskann; print('✅ DiskANN backend imported')"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}-py${{ matrix.python-version }}
path: packages/*/dist/*.whl
retention-days: 7
# Summary job to ensure all builds pass
ci-success:
needs: build-test
runs-on: ubuntu-latest
steps:
- name: CI Success
run: |
echo "✅ All CI builds passed!"
echo "Ready for manual release when needed."

194
.github/workflows/release-manual.yml vendored Normal file
View File

@@ -0,0 +1,194 @@
name: Manual Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., 0.1.1)'
required: true
type: string
test_pypi:
description: 'Test on TestPyPI first'
required: false
type: boolean
default: true
jobs:
validate-and-release:
runs-on: ubuntu-latest
permissions:
contents: write
actions: read
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Check CI status
run: |
echo " This workflow will download build artifacts from the latest CI run."
echo " CI must have completed successfully on the current commit."
echo ""
- name: Validate version format
run: |
if ! [[ "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "❌ Invalid version format. Use semantic versioning (e.g., 0.1.1)"
exit 1
fi
echo "✅ Version format valid: ${{ inputs.version }}"
- name: Check if version already exists
run: |
if git tag | grep -q "^v${{ inputs.version }}$"; then
echo "❌ Version v${{ inputs.version }} already exists!"
exit 1
fi
echo "✅ Version is new"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Update versions
run: |
./scripts/bump_version.sh ${{ inputs.version }}
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add packages/*/pyproject.toml
git commit -m "chore: release v${{ inputs.version }}"
- name: Get CI run ID
id: get-ci-run
run: |
# Get the latest successful CI run on the previous commit (before version bump)
COMMIT_SHA=$(git rev-parse HEAD~1)
RUN_ID=$(gh run list \
--workflow="CI - Build and Test" \
--status=success \
--commit=$COMMIT_SHA \
--json databaseId \
--jq '.[0].databaseId')
if [ -z "$RUN_ID" ]; then
echo "❌ No successful CI run found for commit $COMMIT_SHA"
echo ""
echo "This usually means:"
echo "1. CI hasn't run on the latest commit yet"
echo "2. CI failed on the latest commit"
echo ""
echo "Please ensure CI passes on main branch before releasing."
exit 1
fi
echo "✅ Found CI run: $RUN_ID"
echo "run-id=$RUN_ID" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download artifacts from CI
run: |
echo "📦 Downloading artifacts from CI run ${{ steps.get-ci-run.outputs.run-id }}..."
# Download all wheel artifacts
gh run download ${{ steps.get-ci-run.outputs.run-id }} \
--pattern "wheels-*" \
--dir ./dist-downloads
# Consolidate all wheels into packages/*/dist/
mkdir -p packages/leann-core/dist
mkdir -p packages/leann-backend-hnsw/dist
mkdir -p packages/leann-backend-diskann/dist
mkdir -p packages/leann/dist
find ./dist-downloads -name "*.whl" -exec cp {} ./packages/ \;
# Move wheels to correct package directories
for wheel in packages/*.whl; do
if [[ $wheel == *"leann_core"* ]]; then
mv "$wheel" packages/leann-core/dist/
elif [[ $wheel == *"leann_backend_hnsw"* ]]; then
mv "$wheel" packages/leann-backend-hnsw/dist/
elif [[ $wheel == *"leann_backend_diskann"* ]]; then
mv "$wheel" packages/leann-backend-diskann/dist/
elif [[ $wheel == *"leann-"* ]] && [[ $wheel != *"backend"* ]] && [[ $wheel != *"core"* ]]; then
mv "$wheel" packages/leann/dist/
fi
done
# List downloaded wheels
echo "✅ Downloaded wheels:"
find packages/*/dist -name "*.whl" -type f | sort
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Test on TestPyPI (optional)
if: inputs.test_pypi
continue-on-error: true
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
run: |
if [ -z "$TWINE_PASSWORD" ]; then
echo "⚠️ TEST_PYPI_API_TOKEN not configured, skipping TestPyPI upload"
echo " To enable TestPyPI testing, add TEST_PYPI_API_TOKEN to repository secrets"
exit 0
fi
pip install twine
echo "📦 Uploading to TestPyPI..."
twine upload --repository testpypi packages/*/dist/* --verbose || {
echo "⚠️ TestPyPI upload failed, but continuing with release"
echo " This is optional and won't block the release"
exit 0
}
echo "✅ Test upload successful!"
echo "📋 Check packages at: https://test.pypi.org/user/your-username/"
echo ""
echo "To test installation:"
echo "pip install -i https://test.pypi.org/simple/ leann"
- name: Create and push tag
run: |
git tag "v${{ inputs.version }}"
git push origin main
git push origin "v${{ inputs.version }}"
echo "✅ Tag v${{ inputs.version }} created and pushed"
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ inputs.version }}
name: Release v${{ inputs.version }}
body: |
## 🚀 Release v${{ inputs.version }}
### What's Changed
See the [full changelog](https://github.com/${{ github.repository }}/compare/...v${{ inputs.version }})
### Installation
```bash
pip install leann==${{ inputs.version }}
```
### Test Installation (if using TestPyPI)
```bash
pip install -i https://test.pypi.org/simple/ leann==${{ inputs.version }}
```
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Trigger PyPI publish
run: |
echo "🚀 Triggering PyPI publish workflow..."
# The existing build-and-publish.yml will be triggered by the tag push
echo "✅ Release process completed! The publish workflow will run automatically."

1
.gitignore vendored
View File

@@ -12,7 +12,6 @@ outputs/
*.idx
*.map
.history/
scripts/
lm_eval.egg-info/
demo/experiment_results/**/*.json
*.jsonl

View File

@@ -44,8 +44,8 @@
" from .autonotebook import tqdm as notebook_tqdm\n",
"INFO:sentence_transformers.SentenceTransformer:Load pretrained SentenceTransformer: facebook/contriever\n",
"WARNING:sentence_transformers.SentenceTransformer:No sentence-transformers model found with name facebook/contriever. Creating a new one with mean pooling.\n",
"Writing passages: 100%|██████████| 5/5 [00:00<00:00, 31254.13chunk/s]\n",
"Batches: 100%|██████████| 1/1 [00:00<00:00, 12.19it/s]\n",
"Writing passages: 100%|██████████| 5/5 [00:00<00:00, 27887.66chunk/s]\n",
"Batches: 100%|██████████| 1/1 [00:00<00:00, 13.51it/s]\n",
"WARNING:leann_backend_hnsw.hnsw_backend:Converting data to float32, shape: (5, 768)\n",
"INFO:leann_backend_hnsw.hnsw_backend:INFO: Converting HNSW index to CSR-pruned format...\n"
]
@@ -64,32 +64,32 @@
" Reading vector (dtype=<class 'numpy.int32'>, fmt='i')... Count=7, Bytes=28\n",
"[0.11s] Read cum_nneighbor_per_level (7)\n",
" Reading vector (dtype=<class 'numpy.int32'>, fmt='i')... Count=5, Bytes=20\n",
"[0.23s] Read levels (5)\n",
"[0.34s] Probing for compact storage flag...\n",
"[0.34s] Found compact flag: False\n",
"[0.34s] Compact flag is False, reading original format...\n",
"[0.34s] Probing for potential extra byte before non-compact offsets...\n",
"[0.34s] Found and consumed an unexpected 0x00 byte.\n",
"[0.21s] Read levels (5)\n",
"[0.30s] Probing for compact storage flag...\n",
"[0.30s] Found compact flag: False\n",
"[0.30s] Compact flag is False, reading original format...\n",
"[0.30s] Probing for potential extra byte before non-compact offsets...\n",
"[0.30s] Found and consumed an unexpected 0x00 byte.\n",
" Reading vector (dtype=<class 'numpy.uint64'>, fmt='Q')... Count=6, Bytes=48\n",
"[0.34s] Read offsets (6)\n",
"[0.44s] Attempting to read neighbors vector...\n",
"[0.30s] Read offsets (6)\n",
"[0.40s] Attempting to read neighbors vector...\n",
" Reading vector (dtype=<class 'numpy.int32'>, fmt='i')... Count=320, Bytes=1280\n",
"[0.44s] Read neighbors (320)\n",
"[0.54s] Read scalar params (ep=4, max_lvl=0)\n",
"[0.54s] Checking for storage data...\n",
"[0.54s] Found storage fourcc: 49467849.\n",
"[0.54s] Converting to CSR format...\n",
"[0.54s] Conversion loop finished. \n",
"[0.54s] Running validation checks...\n",
"[0.40s] Read neighbors (320)\n",
"[0.50s] Read scalar params (ep=4, max_lvl=0)\n",
"[0.50s] Checking for storage data...\n",
"[0.50s] Found storage fourcc: 49467849.\n",
"[0.50s] Converting to CSR format...\n",
"[0.50s] Conversion loop finished. \n",
"[0.50s] Running validation checks...\n",
" Checking total valid neighbor count...\n",
" OK: Total valid neighbors = 20\n",
" Checking final pointer indices...\n",
" OK: Final pointers match data size.\n",
"[0.54s] Deleting original neighbors and offsets arrays...\n",
"[0.50s] Deleting original neighbors and offsets arrays...\n",
" CSR Stats: |data|=20, |level_ptr|=10\n",
"[0.63s] Writing CSR HNSW graph data in FAISS-compatible order...\n",
"[0.59s] Writing CSR HNSW graph data in FAISS-compatible order...\n",
" Pruning embeddings: Writing NULL storage marker.\n",
"[0.73s] Conversion complete.\n"
"[0.69s] Conversion complete.\n"
]
},
{
@@ -105,8 +105,8 @@
"from leann.api import LeannBuilder\n",
"\n",
"builder = LeannBuilder(backend_name=\"hnsw\")\n",
"builder.add_text(\"C# is a powerful programming language\")\n",
"builder.add_text(\"Python is a powerful programming language and it is very popular\")\n",
"builder.add_text(\"C# is a powerful programming language and it is good at game development\")\n",
"builder.add_text(\"Python is a powerful programming language and it is good at machine learning tasks\")\n",
"builder.add_text(\"Machine learning transforms industries\")\n",
"builder.add_text(\"Neural networks process complex data\")\n",
"builder.add_text(\"Leann is a great storage saving engine for RAG on your MacBook\")\n",
@@ -136,19 +136,14 @@
"INFO:leann.embedding_server_manager:Port 5557 has incompatible server, trying next port...\n",
"INFO:leann.embedding_server_manager:Port 5558 has incompatible server, trying next port...\n",
"INFO:leann.embedding_server_manager:Port 5559 has incompatible server, trying next port...\n",
"INFO:leann.embedding_server_manager:Found compatible server on port 5560\n",
"INFO:leann.embedding_server_manager:Using existing compatible server on port 5560\n",
"INFO:leann.api: Launching server time: 0.05758476257324219 seconds\n",
"INFO:leann.embedding_server_manager:Found compatible server on port 5560\n",
"INFO:leann.embedding_server_manager:Using existing compatible server on port 5560\n",
"INFO:leann.api: Generated embedding shape: (1, 768)\n",
"INFO:leann.api: Embedding time: 0.05983591079711914 seconds\n",
"INFO:leann.api: Search time: 0.039762258529663086 seconds\n",
"INFO:leann.api: Backend returned: labels=2 results\n",
"INFO:leann.api: Processing 2 passage IDs:\n",
"INFO:leann.api: 1. passage_id='0' -> SUCCESS: C# is a powerful programming language...\n",
"INFO:leann.api: 2. passage_id='1' -> SUCCESS: Python is a powerful programming language and it is very popular...\n",
"INFO:leann.api: Final enriched results: 2 passages\n"
"INFO:leann.embedding_server_manager:Using port 5560 instead of 5557\n",
"INFO:leann.embedding_server_manager:Starting embedding server on port 5560...\n",
"INFO:leann.embedding_server_manager:Command: /Users/yichuan/Desktop/code/LEANN/leann/.venv/bin/python -m leann_backend_hnsw.hnsw_embedding_server --zmq-port 5560 --model-name facebook/contriever --passages-file knowledge.leann.meta.json\n",
"huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
"To disable this warning, you can either:\n",
"\t- Avoid using `tokenizers` before the fork if possible\n",
"\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
"INFO:leann.embedding_server_manager:Server process started with PID: 4574\n"
]
},
{
@@ -167,14 +162,46 @@
"[read_HNSW NL v4] Read neighbors data, size: 20\n",
"[read_HNSW NL v4] Finished reading metadata and CSR indices.\n",
"INFO: Skipping external storage loading, since is_recompute is true.\n",
"INFO: Registering backend 'hnsw'\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"INFO:leann.embedding_server_manager:Embedding server is ready!\n",
"INFO:leann.api: Launching server time: 1.078078269958496 seconds\n",
"INFO:leann.embedding_server_manager:Existing server process (PID 4574) is compatible\n",
"INFO:sentence_transformers.SentenceTransformer:Load pretrained SentenceTransformer: facebook/contriever\n",
"WARNING:sentence_transformers.SentenceTransformer:No sentence-transformers model found with name facebook/contriever. Creating a new one with mean pooling.\n",
"INFO:leann.api: Generated embedding shape: (1, 768)\n",
"INFO:leann.api: Embedding time: 2.9307072162628174 seconds\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"ZmqDistanceComputer initialized: d=768, metric=0\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"INFO:leann.api: Search time: 0.27327895164489746 seconds\n",
"INFO:leann.api: Backend returned: labels=2 results\n",
"INFO:leann.api: Processing 2 passage IDs:\n",
"INFO:leann.api: 1. passage_id='0' -> SUCCESS: C# is a powerful programming language and it is good at game development...\n",
"INFO:leann.api: 2. passage_id='1' -> SUCCESS: Python is a powerful programming language and it is good at machine learning tasks...\n",
"INFO:leann.api: Final enriched results: 2 passages\n"
]
},
{
"data": {
"text/plain": [
"[SearchResult(id='0', score=np.float32(0.9646692), text='C# is a powerful programming language', metadata={}),\n",
" SearchResult(id='1', score=np.float32(0.91955304), text='Python is a powerful programming language and it is very popular', metadata={})]"
"[SearchResult(id='0', score=np.float32(0.9874103), text='C# is a powerful programming language and it is good at game development', metadata={}),\n",
" SearchResult(id='1', score=np.float32(0.8922168), text='Python is a powerful programming language and it is good at machine learning tasks', metadata={})]"
]
},
"execution_count": 2,
@@ -199,7 +226,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {},
"outputs": [
{
@@ -207,14 +234,14 @@
"output_type": "stream",
"text": [
"INFO:leann.chat:Attempting to create LLM of type='hf' with model='Qwen/Qwen3-0.6B'\n",
"INFO:leann.chat:Initializing HFChat with model='Qwen/Qwen3-0.6B'\n"
"INFO:leann.chat:Initializing HFChat with model='Qwen/Qwen3-0.6B'\n",
"INFO:leann.chat:MPS is available. Using Apple Silicon GPU.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"INFO: Registering backend 'hnsw'\n",
"[read_HNSW - CSR NL v4] Reading metadata & CSR indices (manual offset)...\n",
"[read_HNSW NL v4] Read levels vector, size: 5\n",
"[read_HNSW NL v4] Reading Compact Storage format indices...\n",
@@ -233,11 +260,8 @@
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/yichuan/Desktop/code/LEANN/leann/.venv/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
" from .autonotebook import tqdm as notebook_tqdm\n",
"INFO:leann.chat:MPS is available. Using Apple Silicon GPU.\n",
"INFO:leann.api:🔍 LeannSearcher.search() called:\n",
"INFO:leann.api: Query: 'Compare the two retrieved programming languages and say which one is more popular today.'\n",
"INFO:leann.api: Query: 'Compare the two retrieved programming languages and tell me their advantages.'\n",
"INFO:leann.api: Top_k: 2\n",
"INFO:leann.api: Additional kwargs: {}\n",
"INFO:leann.embedding_server_manager:Port 5557 has incompatible server, trying next port...\n",
@@ -245,18 +269,18 @@
"INFO:leann.embedding_server_manager:Port 5559 has incompatible server, trying next port...\n",
"INFO:leann.embedding_server_manager:Found compatible server on port 5560\n",
"INFO:leann.embedding_server_manager:Using existing compatible server on port 5560\n",
"INFO:leann.api: Launching server time: 0.11421084403991699 seconds\n",
"INFO:leann.api: Launching server time: 0.04932403564453125 seconds\n",
"INFO:leann.embedding_server_manager:Found compatible server on port 5560\n",
"INFO:leann.embedding_server_manager:Using existing compatible server on port 5560\n",
"INFO:leann.api: Generated embedding shape: (1, 768)\n",
"INFO:leann.api: Embedding time: 0.1147918701171875 seconds\n",
"INFO:leann.api: Search time: 0.05468583106994629 seconds\n",
"INFO:leann.api: Embedding time: 0.06902289390563965 seconds\n",
"INFO:leann.api: Search time: 0.026793241500854492 seconds\n",
"INFO:leann.api: Backend returned: labels=2 results\n",
"INFO:leann.api: Processing 2 passage IDs:\n",
"INFO:leann.api: 1. passage_id='1' -> SUCCESS: Python is a powerful programming language and it is very popular...\n",
"INFO:leann.api: 2. passage_id='0' -> SUCCESS: C# is a powerful programming language...\n",
"INFO:leann.api: 1. passage_id='0' -> SUCCESS: C# is a powerful programming language and it is good at game development...\n",
"INFO:leann.api: 2. passage_id='1' -> SUCCESS: Python is a powerful programming language and it is good at machine learning tasks...\n",
"INFO:leann.api: Final enriched results: 2 passages\n",
"INFO:leann.chat:Generating with HuggingFace model, config: {'max_new_tokens': 512, 'temperature': 0.7, 'top_p': 0.9, 'do_sample': True, 'pad_token_id': 151645, 'eos_token_id': 151645}\n"
"INFO:leann.chat:Generating with HuggingFace model, config: {'max_new_tokens': 128, 'temperature': 0.7, 'top_p': 0.9, 'do_sample': True, 'pad_token_id': 151645, 'eos_token_id': 151645}\n"
]
},
{
@@ -269,10 +293,10 @@
{
"data": {
"text/plain": [
"'<think>\\n\\n</think>\\n\\nBased on the context provided, both Python and C# are mentioned as powerful programming languages, but no specific information is given about their popularity today. However, generally, Python is more popular for data science, web development, and other tasks, while C# is widely used in enterprise applications and game development. Since the context does not explicitly state which is more popular, but Python is often considered more popular in many cases, the best answer would be:\\n\\n**Python is more popular today.**'"
"\"<think>\\n\\n</think>\\n\\nBased on the context provided, here's a comparison of the two retrieved programming languages:\\n\\n**C#** is known for being a powerful programming language and is well-suited for game development. It is often used in game development and is popular among developers working on Windows applications.\\n\\n**Python**, on the other hand, is also a powerful language and is well-suited for machine learning tasks. It is widely used for data analysis, scientific computing, and other applications that require handling large datasets or performing complex calculations.\\n\\n**Advantages**:\\n- C#: Strong for game development and cross-platform compatibility.\\n- Python: Strong for\""
]
},
"execution_count": 1,
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
@@ -282,13 +306,14 @@
"\n",
"llm_config = {\n",
" \"type\": \"hf\",\n",
" \"model\": \"Qwen/Qwen3-0.6B\"\n",
" \"model\": \"Qwen/Qwen3-0.6B\",\n",
"}\n",
"\n",
"chat = LeannChat(index_path=\"knowledge.leann\", llm_config=llm_config)\n",
"response = chat.ask(\n",
" \"Compare the two retrieved programming languages and say which one is more popular today.\",\n",
" \"Compare the two retrieved programming languages and tell me their advantages.\",\n",
" top_k=2,\n",
" llm_kwargs={\"max_tokens\": 128}\n",
")\n",
"response"
]

View File

@@ -1,17 +1,63 @@
# Release Guide
## One-line Release 🚀
## 📋 Prerequisites
Before releasing, ensure:
1. ✅ All code changes are committed and pushed
2. ✅ CI has passed on the latest commit (check [Actions](https://github.com/yichuan-w/LEANN/actions/workflows/ci.yml))
3. ✅ You have determined the new version number
### Optional: TestPyPI Configuration
To enable TestPyPI testing (recommended but not required):
1. Get a TestPyPI API token from https://test.pypi.org/manage/account/token/
2. Add it to repository secrets: Settings → Secrets → Actions → New repository secret
- Name: `TEST_PYPI_API_TOKEN`
- Value: Your TestPyPI token (starts with `pypi-`)
**Note**: TestPyPI testing is optional. If not configured, the release will skip TestPyPI and proceed.
## 🚀 Recommended: Manual Release Workflow
### Via GitHub UI (Most Reliable)
1. **Verify CI Status**: Check that the latest commit has a green checkmark ✅
2. Go to [Actions → Manual Release](https://github.com/yichuan-w/LEANN/actions/workflows/release-manual.yml)
3. Click "Run workflow"
4. Enter version (e.g., `0.1.1`)
5. Toggle "Test on TestPyPI first" if desired
6. Click "Run workflow"
**What happens:**
- ✅ Validates version format
- ✅ Downloads pre-built packages from CI (no rebuild needed!)
- ✅ Updates all package versions
- ✅ Optionally tests on TestPyPI
- ✅ Creates tag and GitHub release
- ✅ Automatically triggers PyPI publish
### Via Command Line
```bash
gh workflow run release-manual.yml -f version=0.1.1 -f test_pypi=true
```
## ⚡ Quick Release (One-Line)
For experienced users who want the fastest path:
```bash
./scripts/release.sh 0.1.1
```
That's it! This script will:
This script will:
1. Update all package versions
2. Commit and push changes
3. Create GitHub release
4. CI automatically builds and publishes to PyPI
⚠️ **Note**: If CI fails, you'll need to manually fix and re-tag
## Manual Testing Before Release
For testing specific packages locally (especially DiskANN on macOS):
@@ -32,13 +78,6 @@ pip install packages/*/dist/*.whl
./scripts/upload_to_pypi.sh prod
```
### Why Manual Build for DiskANN?
DiskANN's complex dependencies (protobuf, abseil, etc.) sometimes require local testing before release. The build script will:
- Compile the C++ extension
- Use `delocate` (macOS) or `auditwheel` (Linux) to bundle system libraries
- Create a self-contained wheel with no external dependencies
## First-time setup
1. Install GitHub CLI:

View File

@@ -222,7 +222,6 @@ async def query_leann_index(index_path: str, query: str):
recompute_beighbor_embeddings=True,
complexity=32,
beam_width=1,
)
end_time = time.time()
print(f"Time taken: {end_time - start_time} seconds")

View File

@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
[project]
name = "leann-backend-diskann"
version = "0.1.0"
version = "0.1.1"
dependencies = ["leann-core==0.1.0", "numpy"]
[tool.scikit-build]

View File

@@ -6,7 +6,7 @@ build-backend = "scikit_build_core.build"
[project]
name = "leann-backend-hnsw"
version = "0.1.0"
version = "0.1.1"
description = "Custom-built HNSW (Faiss) backend for the Leann toolkit."
dependencies = [
"leann-core==0.1.0",

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "leann-core"
version = "0.1.0"
version = "0.1.1"
description = "Core API and plugin system for LEANN"
readme = "README.md"
requires-python = ">=3.9"

View File

@@ -549,6 +549,7 @@ class HFChat(LLMInterface):
self.tokenizer.pad_token = self.tokenizer.eos_token
def ask(self, prompt: str, **kwargs) -> str:
print('kwargs in HF: ', kwargs)
# Check if this is a Qwen model and add /no_think by default
is_qwen_model = "qwen" in self.model.config._name_or_path.lower()

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "leann"
version = "0.1.0"
version = "0.1.1"
description = "LEANN - The smallest vector index in the world. RAG Everything with LEANN!"
readme = "README.md"
requires-python = ">=3.9"

87
scripts/build_and_test.sh Executable file
View File

@@ -0,0 +1,87 @@
#!/bin/bash
# Manual build and test script for local testing
PACKAGE=${1:-"all"} # Default to all packages
echo "Building package: $PACKAGE"
# Ensure we're in a virtual environment
if [ -z "$VIRTUAL_ENV" ]; then
echo "Error: Please activate a virtual environment first"
echo "Run: source .venv/bin/activate (or .venv/bin/activate.fish for fish shell)"
exit 1
fi
# Install build tools
uv pip install build twine delocate auditwheel scikit-build-core cmake pybind11 numpy
build_package() {
local package_dir=$1
local package_name=$(basename $package_dir)
echo "Building $package_name..."
cd $package_dir
# Clean previous builds
rm -rf dist/ build/ _skbuild/
# Build directly with pip wheel (avoids sdist issues)
pip wheel . --no-deps -w dist
# Repair wheel for binary packages
if [[ "$package_name" != "leann-core" ]] && [[ "$package_name" != "leann" ]]; then
if [[ "$OSTYPE" == "darwin"* ]]; then
# For macOS
for wheel in dist/*.whl; do
if [[ -f "$wheel" ]]; then
delocate-wheel -w dist_repaired -v "$wheel"
fi
done
if [[ -d dist_repaired ]]; then
rm -rf dist/*.whl
mv dist_repaired/*.whl dist/
rmdir dist_repaired
fi
else
# For Linux
for wheel in dist/*.whl; do
if [[ -f "$wheel" ]]; then
auditwheel repair "$wheel" -w dist_repaired
fi
done
if [[ -d dist_repaired ]]; then
rm -rf dist/*.whl
mv dist_repaired/*.whl dist/
rmdir dist_repaired
fi
fi
fi
echo "Built wheels in $package_dir/dist/"
ls -la dist/
cd - > /dev/null
}
# Build specific package or all
if [ "$PACKAGE" == "diskann" ]; then
build_package "packages/leann-backend-diskann"
elif [ "$PACKAGE" == "hnsw" ]; then
build_package "packages/leann-backend-hnsw"
elif [ "$PACKAGE" == "core" ]; then
build_package "packages/leann-core"
elif [ "$PACKAGE" == "meta" ]; then
build_package "packages/leann"
elif [ "$PACKAGE" == "all" ]; then
build_package "packages/leann-core"
build_package "packages/leann-backend-hnsw"
build_package "packages/leann-backend-diskann"
build_package "packages/leann"
else
echo "Unknown package: $PACKAGE"
echo "Usage: $0 [diskann|hnsw|core|meta|all]"
exit 1
fi
echo -e "\nBuild complete! Test with:"
echo "uv pip install packages/*/dist/*.whl"

18
scripts/bump_version.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
if [ $# -eq 0 ]; then
echo "Usage: $0 <new_version>"
exit 1
fi
NEW_VERSION=$1
# Update all pyproject.toml files
# Use different sed syntax for macOS vs Linux
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s/version = \".*\"/version = \"$NEW_VERSION\"/" packages/*/pyproject.toml
else
sed -i "s/version = \".*\"/version = \"$NEW_VERSION\"/" packages/*/pyproject.toml
fi
echo "Version updated to $NEW_VERSION"

18
scripts/release.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
if [ $# -eq 0 ]; then
echo "Usage: $0 <version>"
echo "Example: $0 0.1.1"
exit 1
fi
VERSION=$1
# Update version
./scripts/bump_version.sh $VERSION
# Commit and push
git add . && git commit -m "chore: bump version to $VERSION" && git push
# Create release (triggers CI)
gh release create v$VERSION --generate-notes

30
scripts/upload_to_pypi.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/bin/bash
# Manual upload script for testing
TARGET=${1:-"test"} # Default to test pypi
if [ "$TARGET" != "test" ] && [ "$TARGET" != "prod" ]; then
echo "Usage: $0 [test|prod]"
exit 1
fi
# Check for built packages
if ! ls packages/*/dist/*.whl >/dev/null 2>&1; then
echo "No built packages found. Run ./scripts/build_and_test.sh first"
exit 1
fi
if [ "$TARGET" == "test" ]; then
echo "Uploading to Test PyPI..."
twine upload --repository testpypi packages/*/dist/*
else
echo "Uploading to PyPI..."
echo "Are you sure? (y/N)"
read -r response
if [ "$response" == "y" ]; then
twine upload packages/*/dist/*
else
echo "Cancelled"
fi
fi