Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cea1f6f87c | ||
|
|
6c0e39372b | ||
|
|
2bec67d2b6 | ||
|
|
133e715832 | ||
|
|
95cf2f16e2 | ||
|
|
47a4c153eb | ||
|
|
faf5ae3533 | ||
|
|
a44dccecac | ||
|
|
9cf9358b9c | ||
|
|
de252fef31 | ||
|
|
9076bc27b8 | ||
|
|
50686c0819 | ||
|
|
1614203786 | ||
|
|
3d4c75a56c | ||
|
|
2684ee71dc | ||
|
|
1d321953ba | ||
|
|
b3cb251369 | ||
|
|
0a17d2c9d8 |
34
.github/workflows/build-and-publish.yml
vendored
34
.github/workflows/build-and-publish.yml
vendored
@@ -1,15 +1,14 @@
|
||||
name: Build and Publish to PyPI
|
||||
name: CI - Build Multi-Platform Packages
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: 'Publish to PyPI'
|
||||
description: 'Publish to PyPI (only use for emergency fixes)'
|
||||
required: true
|
||||
default: 'false'
|
||||
type: choice
|
||||
@@ -90,7 +89,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 +166,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'
|
||||
@@ -221,12 +220,12 @@ jobs:
|
||||
name: leann-meta-dist
|
||||
path: packages/leann/dist/
|
||||
|
||||
# Publish to PyPI
|
||||
# Publish to PyPI (only for emergency fixes or manual triggers)
|
||||
publish:
|
||||
name: Publish to PyPI
|
||||
name: Publish to PyPI (Emergency)
|
||||
needs: [build-core, build-hnsw, build-diskann, build-meta]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'release' || github.event.inputs.publish != 'false'
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish != 'false'
|
||||
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
@@ -240,17 +239,24 @@ jobs:
|
||||
find dist -name "*.whl" -exec cp {} all_wheels/ \;
|
||||
find dist -name "*.tar.gz" -exec cp {} all_wheels/ \;
|
||||
|
||||
- name: Show what will be published
|
||||
run: |
|
||||
echo "📦 Packages to be published:"
|
||||
ls -la all_wheels/
|
||||
|
||||
- name: Publish to Test PyPI
|
||||
if: github.event.inputs.publish == 'test' || github.event_name == 'workflow_dispatch'
|
||||
if: github.event.inputs.publish == 'test'
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
packages-dir: all_wheels/
|
||||
skip-existing: true
|
||||
|
||||
- name: Publish to PyPI
|
||||
if: github.event_name == 'release' || github.event.inputs.publish == 'prod'
|
||||
if: github.event.inputs.publish == 'prod'
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages-dir: all_wheels/
|
||||
packages-dir: all_wheels/
|
||||
skip-existing: true
|
||||
206
.github/workflows/release-manual.yml
vendored
Normal file
206
.github/workflows/release-manual.yml
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
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 Multi-Platform Packages" \
|
||||
--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
|
||||
run: |
|
||||
echo "📦 Downloading artifacts from CI run ${{ steps.get-ci-run.outputs.run-id }}..."
|
||||
|
||||
# Download all artifacts (not just wheels-*)
|
||||
gh run download ${{ steps.get-ci-run.outputs.run-id }} \
|
||||
--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: Publish to PyPI
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$TWINE_PASSWORD" ]; then
|
||||
echo "❌ PYPI_API_TOKEN not configured!"
|
||||
echo " Please add PYPI_API_TOKEN to repository secrets"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pip install twine
|
||||
echo "📦 Publishing to PyPI..."
|
||||
|
||||
# Collect all wheels in one place
|
||||
mkdir -p all_wheels
|
||||
find packages/*/dist -name "*.whl" -exec cp {} all_wheels/ \;
|
||||
find packages/*/dist -name "*.tar.gz" -exec cp {} all_wheels/ \;
|
||||
|
||||
echo "📋 Packages to publish:"
|
||||
ls -la all_wheels/
|
||||
|
||||
# Upload to PyPI
|
||||
twine upload all_wheels/* --skip-existing --verbose
|
||||
|
||||
echo "✅ Published to PyPI!"
|
||||
echo "🎉 Check packages at: https://pypi.org/project/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 }}
|
||||
```
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,7 +12,6 @@ outputs/
|
||||
*.idx
|
||||
*.map
|
||||
.history/
|
||||
scripts/
|
||||
lm_eval.egg-info/
|
||||
demo/experiment_results/**/*.json
|
||||
*.jsonl
|
||||
|
||||
133
demo.ipynb
133
demo.ipynb
@@ -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"
|
||||
]
|
||||
|
||||
@@ -1,16 +1,69 @@
|
||||
# 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
|
||||
|
||||
### Required: PyPI Configuration
|
||||
|
||||
To enable PyPI publishing:
|
||||
1. Get a PyPI API token from https://pypi.org/manage/account/token/
|
||||
2. Add it to repository secrets: Settings → Secrets → Actions → New repository secret
|
||||
- Name: `PYPI_API_TOKEN`
|
||||
- Value: Your PyPI token (starts with `pypi-`)
|
||||
|
||||
### 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:**
|
||||
- ✅ Downloads pre-built packages from CI (no rebuild needed!)
|
||||
- ✅ Updates all package versions
|
||||
- ✅ Optionally tests on TestPyPI
|
||||
- ✅ **Publishes directly to PyPI**
|
||||
- ✅ Creates tag and GitHub release
|
||||
|
||||
### 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
|
||||
4. **Manual Release workflow will automatically publish to PyPI**
|
||||
|
||||
⚠️ **Note**: If CI fails, you'll need to manually fix and re-tag
|
||||
|
||||
## Manual Testing Before Release
|
||||
|
||||
@@ -32,13 +85,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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -4,8 +4,8 @@ build-backend = "scikit_build_core.build"
|
||||
|
||||
[project]
|
||||
name = "leann-backend-diskann"
|
||||
version = "0.1.0"
|
||||
dependencies = ["leann-core==0.1.0", "numpy"]
|
||||
version = "0.1.2"
|
||||
dependencies = ["leann-core==0.1.2", "numpy"]
|
||||
|
||||
[tool.scikit-build]
|
||||
# Key: simplified CMake path
|
||||
|
||||
@@ -6,10 +6,10 @@ build-backend = "scikit_build_core.build"
|
||||
|
||||
[project]
|
||||
name = "leann-backend-hnsw"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
description = "Custom-built HNSW (Faiss) backend for the Leann toolkit."
|
||||
dependencies = [
|
||||
"leann-core==0.1.0",
|
||||
"leann-core==0.1.2",
|
||||
"numpy",
|
||||
"pyzmq>=23.0.0",
|
||||
"msgpack>=1.0.0",
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "leann-core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
description = "Core API and plugin system for LEANN"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "leann"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
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
87
scripts/build_and_test.sh
Executable 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"
|
||||
31
scripts/bump_version.sh
Executable file
31
scripts/bump_version.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: $0 <new_version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_VERSION=$1
|
||||
|
||||
# Get the directory where the script is located
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
|
||||
|
||||
# Update all pyproject.toml files
|
||||
echo "Updating versions in $PROJECT_ROOT/packages/"
|
||||
|
||||
# Use different sed syntax for macOS vs Linux
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# Update version fields
|
||||
find "$PROJECT_ROOT/packages" -name "pyproject.toml" -exec sed -i '' "s/version = \".*\"/version = \"$NEW_VERSION\"/" {} \;
|
||||
# Update leann-core dependencies
|
||||
find "$PROJECT_ROOT/packages" -name "pyproject.toml" -exec sed -i '' "s/leann-core==[0-9.]*/leann-core==$NEW_VERSION/" {} \;
|
||||
else
|
||||
# Update version fields
|
||||
find "$PROJECT_ROOT/packages" -name "pyproject.toml" -exec sed -i "s/version = \".*\"/version = \"$NEW_VERSION\"/" {} \;
|
||||
# Update leann-core dependencies
|
||||
find "$PROJECT_ROOT/packages" -name "pyproject.toml" -exec sed -i "s/leann-core==[0-9.]*/leann-core==$NEW_VERSION/" {} \;
|
||||
fi
|
||||
|
||||
echo "✅ Version updated to $NEW_VERSION"
|
||||
echo "✅ Dependencies updated to use leann-core==$NEW_VERSION"
|
||||
18
scripts/release.sh
Executable file
18
scripts/release.sh
Executable 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
30
scripts/upload_to_pypi.sh
Executable 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
|
||||
Reference in New Issue
Block a user