diff --git a/Dockerfile b/Dockerfile index c8569fb95..1d1351c72 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,7 +62,7 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends nodejs \ && rm -f /tmp/nodesource_setup.sh \ && python -m pip install --no-cache-dir uv \ - && uv sync \ + && uv sync --extra seekdb \ && apt-get purge -y --auto-remove curl git gnupg \ && rm -rf /var/lib/apt/lists/* \ && touch /.dockerenv diff --git a/docs/PYPI_INSTALLATION.md b/docs/PYPI_INSTALLATION.md index 1144d5cb3..6b55aed59 100644 --- a/docs/PYPI_INSTALLATION.md +++ b/docs/PYPI_INSTALLATION.md @@ -10,6 +10,19 @@ uvx langbot This will automatically download and run the latest version of LangBot. +SeekDB support is optional and is not installed by the command above. If you +want to use the SeekDB vector database or the built-in SeekDB embedding model, +run LangBot with the `seekdb` extra: + +```bash +uvx --from 'langbot[seekdb]@latest' langbot +``` + +The extra includes native dependencies whose supported operating systems may +be narrower than LangBot's. In particular, the current Apple Silicon wheels +require macOS 15 or later. The default Chroma backend does not have this +requirement. + ## Install with pip/uv You can also install LangBot as a regular Python package: @@ -20,6 +33,10 @@ pip install langbot # Using uv uv pip install langbot + +# Include optional SeekDB support +pip install 'langbot[seekdb]' +# or: uv pip install 'langbot[seekdb]' ``` Then run it: @@ -101,7 +118,7 @@ uvx langbot ## System Requirements -- Python 3.10.1 or higher +- Python 3.11 or higher (lower than Python 4) - Operating System: Linux, macOS, or Windows ## Differences from Source Installation diff --git a/docs/SEEKDB_INTEGRATION.md b/docs/SEEKDB_INTEGRATION.md index b5ae7f9c3..a38eb9f08 100644 --- a/docs/SEEKDB_INTEGRATION.md +++ b/docs/SEEKDB_INTEGRATION.md @@ -16,12 +16,20 @@ This document describes how to use OceanBase SeekDB as the vector database backe ## Installation -SeekDB support is automatically included when you install LangBot. The required dependency `pyseekdb` is listed in `pyproject.toml`. +SeekDB is an optional LangBot feature. A normal LangBot installation uses +Chroma by default and does not install `pyseekdb` or its native bindings. -If you need to install it manually: +Choose the command that matches how you run LangBot: ```bash -pip install pyseekdb +# PyPI / uvx +uvx --from 'langbot[seekdb]@latest' langbot + +# Installed package +pip install 'langbot[seekdb]' + +# Source checkout +uv sync --extra seekdb ``` ## ⚠️ Platform Compatibility @@ -30,31 +38,36 @@ pip install pyseekdb | Platform | Status | Notes | |----------|--------|-------| -| Linux | ✅ Supported | Full embedded mode support via `pylibseekdb` | -| macOS | ❌ Not Supported | `pylibseekdb` is Linux-only; use server mode instead | -| Windows | ❌ Not Supported | `pylibseekdb` is Linux-only; use server mode instead | +| Linux x86_64 / ARM64 | ✅ Supported | Full embedded mode support via `pylibseekdb` | +| macOS 15+ on Apple Silicon | ✅ Supported | Requires the macOS ARM64 `pylibseekdb` wheel | +| macOS 14 or earlier on Apple Silicon | ❌ Not currently supported | The published native wheel requires macOS 15+; follow [oceanbase/seekdb#1324](https://github.com/oceanbase/seekdb/issues/1324) | +| macOS on Intel | ❌ Not currently supported | No embedded binding is selected by `pyseekdb` | +| Windows | ❌ Not currently supported | No Windows `pylibseekdb` wheel is published | -**Important**: Embedded mode requires the `pylibseekdb` library, which is only available on Linux. If you're on macOS or Windows, you must use server mode. +**Important**: Embedded mode requires a compatible `pylibseekdb` wheel. Do not +force-install or retag a wheel built for a newer macOS release: the bundled +binaries also declare macOS 15 as their minimum deployment target. ### Server Mode (Docker) | Platform | Status | Notes | |----------|--------|-------| | Linux | ✅ Supported | Full Docker support | -| macOS | ⚠️ Known Issue | Docker container initialization failure - [See Issue #36](https://github.com/oceanbase/seekdb/issues/36) | -| Windows | ⚠️ Untested | Should work but not yet tested | - -**macOS Users**: Currently, SeekDB Docker containers have an initialization issue on macOS ([oceanbase/seekdb#36](https://github.com/oceanbase/seekdb/issues/36)). Until this is resolved, we recommend: -- Using ChromaDB or Qdrant as alternatives -- Connecting to a remote SeekDB server on Linux if available +| macOS | ✅ Supported by Docker Desktop | The previous slow-disk startup issue was fixed upstream in [oceanbase/seekdb#36](https://github.com/oceanbase/seekdb/issues/36) | +| Windows | ⚠️ Depends on the container runtime | Use a Linux container and follow the upstream image documentation | ### Server Mode (Remote Connection) | Platform | Status | Notes | |----------|--------|-------| -| All Platforms | ✅ Supported | Connect to SeekDB running on a remote Linux server | +| Linux | ✅ Supported | Install the `seekdb` extra and connect to the remote server | +| macOS 15+ on Apple Silicon | ✅ Supported | Install the `seekdb` extra and connect to the remote server | +| macOS 14 or earlier on Apple Silicon | ⚠️ Blocked by upstream packaging | `pyseekdb` currently requires the unavailable native wheel even for server-only use; follow [#1324](https://github.com/oceanbase/seekdb/issues/1324) | +| macOS on Intel / Windows | ✅ Server mode only | Embedded bindings are not available | -**Recommendation for macOS/Windows users**: Deploy SeekDB on a Linux server and connect via server mode configuration. +Remote server mode does not use embedded storage at runtime. However, whether +the Python client can be installed still depends on `pyseekdb`'s package +metadata for the current platform. ## Configuration @@ -170,22 +183,23 @@ Key methods: ### Import Error -If you see: `ImportError: pyseekdb is not installed` +If you see: `SeekDB support is not installed` Solution: ```bash -pip install pyseekdb +uv sync --extra seekdb +# or: uvx --from 'langbot[seekdb]@latest' langbot ``` -### Embedded Mode Error on macOS/Windows +### Embedded Mode Is Unavailable on the Current Platform **Error**: ``` RuntimeError: Embedded Client is not available because pylibseekdb is not available. -Please install pylibseekdb (Linux only) or use RemoteServerClient (host/port) instead. ``` -**Cause**: `pylibseekdb` is only available on Linux platforms. +**Cause**: No compatible `pylibseekdb` wheel is installed for the current OS, +CPU architecture, Python version, and macOS deployment target. **Solution**: Use server mode instead: 1. Deploy SeekDB on a Linux server or VM @@ -208,29 +222,6 @@ vdb: use: chroma # or qdrant ``` -### Docker Container Fails on macOS - -**Symptoms**: -```bash -docker run -d -p 2881:2881 oceanbase/seekdb:latest -# Container exits immediately with code 30 -``` - -**Error in logs**: -``` -[ERROR] Code: Agent.SeekDB.Not.Exists -Message: initialize failed: init agent failed: SeekDB not exists in current directory. -``` - -**Cause**: This is a known issue with SeekDB Docker containers on macOS. See [oceanbase/seekdb#36](https://github.com/oceanbase/seekdb/issues/36). - -**Status**: Under investigation by OceanBase team. - -**Workaround Options**: -1. **Use alternatives**: ChromaDB or Qdrant work perfectly on macOS -2. **Remote server**: Deploy SeekDB on a Linux server and connect remotely -3. **Wait for fix**: Monitor the GitHub issue for updates - ### Connection Error (Server Mode) If SeekDB server is not reachable, check: diff --git a/pyproject.toml b/pyproject.toml index 9e226a9c0..4692b7b94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,6 @@ dependencies = [ "langchain-text-splitters>=1.1.2", "chromadb>=1.0.0,<2.0.0", "qdrant-client (>=1.15.1,<2.0.0)", - "pyseekdb==1.1.0.post3", "langbot-plugin==0.5.5", "asyncpg>=0.30.0", "line-bot-sdk>=3.19.0", @@ -108,6 +107,11 @@ classifiers = [ "Topic :: Communications :: Chat", ] +[project.optional-dependencies] +seekdb = [ + "pyseekdb==1.1.0.post3", +] + [project.urls] Homepage = "https://langbot.app" Documentation = "https://docs.langbot.app" diff --git a/src/langbot/pkg/provider/modelmgr/requesters/seekdbembed.py b/src/langbot/pkg/provider/modelmgr/requesters/seekdbembed.py index 4b881dbfe..ff7d1d642 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/seekdbembed.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/seekdbembed.py @@ -24,7 +24,10 @@ class SeekDBEmbedding(requester.ProviderAPIRequester): try: import pyseekdb except ImportError: - raise ImportError('pyseekdb is not installed. Install it with: pip install pyseekdb') + raise ImportError( + "SeekDB support is not installed. Install LangBot with the 'seekdb' extra: " + "uv sync --extra seekdb (source) or uvx --from 'langbot[seekdb]@latest' langbot (PyPI)." + ) self._embedding_function = pyseekdb.get_default_embedding_function() diff --git a/src/langbot/pkg/vector/vdbs/seekdb.py b/src/langbot/pkg/vector/vdbs/seekdb.py index fc82298e0..5be28b458 100644 --- a/src/langbot/pkg/vector/vdbs/seekdb.py +++ b/src/langbot/pkg/vector/vdbs/seekdb.py @@ -42,7 +42,10 @@ class SeekDBVectorDatabase(VectorDatabase): def __init__(self, ap: app.Application): if not SEEKDB_AVAILABLE: - raise ImportError('pyseekdb is not installed. Install it with: pip install pyseekdb') + raise ImportError( + "SeekDB support is not installed. Install LangBot with the 'seekdb' extra: " + "uv sync --extra seekdb (source) or uvx --from 'langbot[seekdb]@latest' langbot (PyPI)." + ) self.ap = ap config = self.ap.instance_config.data['vdb']['seekdb'] diff --git a/src/langbot/templates/config.yaml b/src/langbot/templates/config.yaml index c21c9b320..ab98d7af8 100644 --- a/src/langbot/templates/config.yaml +++ b/src/langbot/templates/config.yaml @@ -181,6 +181,11 @@ vdb: host: localhost port: 6333 api_key: '' + # SeekDB is optional. Native/package installs need the `seekdb` extra: + # `uv sync --extra seekdb` (source) or + # `uvx --from 'langbot[seekdb]@latest' langbot` (PyPI). + # The official Docker image already includes it. + # Embedded-mode platform support depends on the native pylibseekdb wheels. seekdb: mode: embedded # 'embedded' or 'server' # Embedded mode options: diff --git a/tests/unit_tests/test_optional_dependencies.py b/tests/unit_tests/test_optional_dependencies.py new file mode 100644 index 000000000..841344906 --- /dev/null +++ b/tests/unit_tests/test_optional_dependencies.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +import tomllib +from pathlib import Path + + +def test_seekdb_is_only_declared_as_an_optional_dependency() -> None: + project_root = Path(__file__).resolve().parents[2] + with (project_root / 'pyproject.toml').open('rb') as pyproject_file: + pyproject = tomllib.load(pyproject_file) + + project = pyproject['project'] + base_dependencies = project['dependencies'] + assert not any(dependency.lower().startswith('pyseekdb') for dependency in base_dependencies) + assert project['optional-dependencies']['seekdb'] == ['pyseekdb==1.1.0.post3'] diff --git a/tests/unit_tests/vector/test_seekdb_optional.py b/tests/unit_tests/vector/test_seekdb_optional.py new file mode 100644 index 000000000..f30e6ac20 --- /dev/null +++ b/tests/unit_tests/vector/test_seekdb_optional.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import importlib +from unittest.mock import MagicMock + +import pytest + +from tests.utils.import_isolation import isolated_sys_modules + + +_INSTALL_HINT = "Install LangBot with the 'seekdb' extra" + + +def test_seekdb_vector_backend_reports_missing_optional_extra() -> None: + module_name = 'langbot.pkg.vector.vdbs.seekdb' + + with isolated_sys_modules({'pyseekdb': None}, clear=[module_name]): + seekdb_module = importlib.import_module(module_name) + + assert seekdb_module.SEEKDB_AVAILABLE is False + with pytest.raises(ImportError, match=_INSTALL_HINT): + seekdb_module.SeekDBVectorDatabase(MagicMock()) + + +@pytest.mark.asyncio +async def test_seekdb_embedding_reports_missing_optional_extra() -> None: + module_name = 'langbot.pkg.provider.modelmgr.requesters.seekdbembed' + + with isolated_sys_modules({'pyseekdb': None}, clear=[module_name]): + seekdb_embedding_module = importlib.import_module(module_name) + requester = seekdb_embedding_module.SeekDBEmbedding.__new__(seekdb_embedding_module.SeekDBEmbedding) + + with pytest.raises(ImportError, match=_INSTALL_HINT): + await requester.initialize() diff --git a/uv.lock b/uv.lock index bce5afde2..743cdf599 100644 --- a/uv.lock +++ b/uv.lock @@ -9,10 +9,10 @@ resolution-markers = [ "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version < '3.12' and sys_platform == 'win32'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", - "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] @@ -2063,7 +2063,6 @@ dependencies = [ { name = "pymilvus" }, { name = "pynacl" }, { name = "pypdf2" }, - { name = "pyseekdb" }, { name = "python-docx" }, { name = "python-multipart" }, { name = "python-socks" }, @@ -2089,6 +2088,11 @@ dependencies = [ { name = "websockets" }, ] +[package.optional-dependencies] +seekdb = [ + { name = "pyseekdb" }, +] + [package.dev-dependencies] dev = [ { name = "moto" }, @@ -2153,7 +2157,7 @@ requires-dist = [ { name = "pymilvus", specifier = ">=2.6.4" }, { name = "pynacl", specifier = ">=1.5.0" }, { name = "pypdf2", specifier = ">=3.0.1" }, - { name = "pyseekdb", specifier = "==1.1.0.post3" }, + { name = "pyseekdb", marker = "extra == 'seekdb'", specifier = "==1.1.0.post3" }, { name = "python-docx", specifier = ">=1.1.0" }, { name = "python-multipart", specifier = ">=0.0.27" }, { name = "python-socks", specifier = ">=2.7.1" }, @@ -2178,6 +2182,7 @@ requires-dist = [ { name = "valkey-glide", marker = "sys_platform != 'win32'", specifier = ">=2.4.1,<3.0.0" }, { name = "websockets", specifier = ">=15.0.1" }, ] +provides-extras = ["seekdb"] [package.metadata.requires-dev] dev = [