From bbdd7fe2b53e3b40ec545bf0996f0c25702d5500 Mon Sep 17 00:00:00 2001 From: Chris Coutinho Date: Wed, 18 Feb 2026 13:10:21 +0100 Subject: [PATCH 1/3] Add remote OpenAI-compatible embedding API support Allow using a remote embedding provider (Ollama, OpenAI, LiteLLM, etc.) instead of a local SentenceTransformer model for semantic search. This adds EMBEDDING_BASE_URL and EMBEDDING_API_KEY config options, documentation in README and CONTRIBUTING, and an optional Ollama service in the devcontainer. Closes #775 Co-Authored-By: Claude Opus 4.6 --- .devcontainer/docker-compose.yml | 19 +++- CONTRIBUTING.md | 23 +++++ README.md | 27 +++++ gramps_webapi/api/search/__init__.py | 6 +- gramps_webapi/api/search/embeddings.py | 27 +++++ gramps_webapi/app.py | 14 ++- gramps_webapi/config.py | 4 +- tests/test_embeddings.py | 130 +++++++++++++++++++++++++ 8 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 tests/test_embeddings.py diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index f7cfe4ee..4df7581b 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3.8" services: devcontainer: build: @@ -25,6 +24,11 @@ services: GRAMPSWEB_SECRET_KEY: QAVoeYDzkQves9iDZ5PkxfdUoVElVMVYPqz-QXha6yE GRAMPSWEB_CORS_ORIGINS: "*" GRAMPSWEB_VECTOR_EMBEDDING_MODEL: sentence-transformers/distiluse-base-multilingual-cased-v2 + # To use Ollama for embeddings instead of local SentenceTransformer, + # start with: docker compose --profile ollama up -d + # Then uncomment the following lines and comment out the line above: + # GRAMPSWEB_EMBEDDING_BASE_URL: http://ollama:11434 + # GRAMPSWEB_VECTOR_EMBEDDING_MODEL: nomic-embed-text GRAMPSWEB_CELERY_CONFIG__broker_url: redis://redis:6379/0 GRAMPSWEB_CELERY_CONFIG__result_backend: redis://redis:6379/0 GRAMPSWEB_LOG_LEVEL: DEBUG @@ -35,3 +39,16 @@ services: restart: unless-stopped ports: - "6379:6379" + + ollama: + image: ollama/ollama:latest + profiles: + - ollama + restart: unless-stopped + ports: + - "11434:11434" + volumes: + - ollama-data:/root/.ollama + +volumes: + ollama-data: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4751dd61..fa34c273 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,6 +15,29 @@ Welcome, and thank you for your interest in contributing to Gramps Web API! Your - Follow the [developer documentation](https://www.grampsweb.org/development/dev/) for setup, coding standards, and API details. - Ensure your changes include appropriate tests and documentation updates where applicable. +#### Testing Remote Embeddings (Optional) + +The devcontainer includes an optional [Ollama](https://ollama.com/) service for testing the remote embedding API without external dependencies. + +1. Start the Ollama service: + ```bash + docker compose -f .devcontainer/docker-compose.yml --profile ollama up -d ollama + ``` + +2. Pull an embedding model: + ```bash + docker compose -f .devcontainer/docker-compose.yml exec ollama ollama pull nomic-embed-text + ``` + +3. In `.devcontainer/docker-compose.yml`, comment out the local `GRAMPSWEB_VECTOR_EMBEDDING_MODEL` line and uncomment the Ollama lines: + ```yaml + # GRAMPSWEB_VECTOR_EMBEDDING_MODEL: sentence-transformers/distiluse-base-multilingual-cased-v2 + GRAMPSWEB_EMBEDDING_BASE_URL: http://ollama:11434 + GRAMPSWEB_VECTOR_EMBEDDING_MODEL: nomic-embed-text + ``` + +4. Restart the devcontainer to pick up the new environment variables. + ### Code of Conduct - Please read and adhere to our [Code of Conduct](CODE_OF_CONDUCT.md) to ensure a welcoming and inclusive environment for all contributors. diff --git a/README.md b/README.md index fc932dca..97010f50 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,33 @@ Gramps Web API is the backend of [Gramps Web](https://www.grampsweb.org/), a gen - Developer documentation for Gramps Web API: https://www.grampsweb.org/dev-backend/ - Documentation for Gramps Web: https://www.grampsweb.org +## Remote Embedding API + +By default, Gramps Web API uses a local [SentenceTransformers](https://www.sbert.net/) model for semantic search embeddings. You can optionally use a remote OpenAI-compatible embedding API (e.g. Ollama, OpenAI, LiteLLM) instead. + +| Environment Variable | Description | Default | +|---|---|---| +| `GRAMPSWEB_VECTOR_EMBEDDING_MODEL` | Model name for semantic search embeddings | `""` (disabled) | +| `GRAMPSWEB_EMBEDDING_BASE_URL` | Base URL for a remote OpenAI-compatible embedding API | `None` (use local model) | +| `GRAMPSWEB_EMBEDDING_API_KEY` | API key for authenticated embedding providers | `None` | + +**Ollama example:** + +```bash +GRAMPSWEB_VECTOR_EMBEDDING_MODEL=nomic-embed-text +GRAMPSWEB_EMBEDDING_BASE_URL=http://localhost:11434 +``` + +**OpenAI example:** + +```bash +GRAMPSWEB_VECTOR_EMBEDDING_MODEL=text-embedding-3-small +GRAMPSWEB_EMBEDDING_BASE_URL=https://api.openai.com/v1 +GRAMPSWEB_EMBEDDING_API_KEY=sk-... +``` + +> **Note:** Changing the embedding model requires reindexing all records, since different models produce vectors with different dimensions. + ## Related projects - Gramps Web frontend repository: https://github.com/gramps-project/gramps-web diff --git a/gramps_webapi/api/search/__init__.py b/gramps_webapi/api/search/__init__.py index 20ffeb5e..fdad065c 100644 --- a/gramps_webapi/api/search/__init__.py +++ b/gramps_webapi/api/search/__init__.py @@ -68,9 +68,9 @@ def get_search_indexer(tree: str) -> SearchIndexer: def get_semantic_search_indexer(tree: str) -> SemanticSearchIndexer: """Get the search indexer for the tree.""" db_url = _get_search_index_db_url() - model = current_app.config.get("_INITIALIZED_VECTOR_EMBEDDING_MODEL") - if not model: + embedding_function = current_app.config.get("_EMBEDDING_FUNCTION") + if not embedding_function: raise ValueError("VECTOR_EMBEDDING_MODEL option not set") return SemanticSearchIndexer( - db_url=db_url, tree=tree, embedding_function=model.encode + db_url=db_url, tree=tree, embedding_function=embedding_function ) diff --git a/gramps_webapi/api/search/embeddings.py b/gramps_webapi/api/search/embeddings.py index c83ae8b6..d8f3a86d 100644 --- a/gramps_webapi/api/search/embeddings.py +++ b/gramps_webapi/api/search/embeddings.py @@ -1,5 +1,9 @@ """Functions to compute vector embeddings.""" +from typing import Callable, List, Optional + +import requests + from ..util import get_logger @@ -16,3 +20,26 @@ def load_model(model_name: str): model = SentenceTransformer(model_name) logger.debug("Done initializing embedding model.") return model + + +def create_remote_embedding_function( + base_url: str, model_name: str, api_key: Optional[str] = None +) -> Callable[[List[str]], List[List[float]]]: + """Create an embedding function that calls a remote OpenAI-compatible API. + + Returns a callable with signature (texts: list[str]) -> list[list[float]]. + """ + url = f"{base_url.rstrip('/')}/v1/embeddings" + + def _embed(texts: List[str]) -> List[List[float]]: + headers = {"Content-Type": "application/json"} + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + payload = {"model": model_name, "input": texts} + response = requests.post(url, json=payload, headers=headers) + response.raise_for_status() + data = response.json()["data"] + data.sort(key=lambda item: item["index"]) + return [item["embedding"] for item in data] + + return _embed diff --git a/gramps_webapi/app.py b/gramps_webapi/app.py index 06a42ca2..cb6e311b 100644 --- a/gramps_webapi/app.py +++ b/gramps_webapi/app.py @@ -36,7 +36,7 @@ from .api import api_blueprint from .api.cache import persistent_cache, request_cache, thumbnail_cache from .api.ratelimiter import limiter -from .api.search.embeddings import load_model +from .api.search.embeddings import create_remote_embedding_function, load_model from .api.tasks import run_task, send_telemetry_task from .api.telemetry import should_send_telemetry from .api.util import close_db, get_tree_from_jwt @@ -258,9 +258,15 @@ def close_user_db_connection(exception) -> None: user_db.session.remove() # pylint: disable=no-member if app.config.get("VECTOR_EMBEDDING_MODEL"): - app.config["_INITIALIZED_VECTOR_EMBEDDING_MODEL"] = load_model( - app.config["VECTOR_EMBEDDING_MODEL"] - ) + if app.config.get("EMBEDDING_BASE_URL"): + app.config["_EMBEDDING_FUNCTION"] = create_remote_embedding_function( + base_url=app.config["EMBEDDING_BASE_URL"], + model_name=app.config["VECTOR_EMBEDDING_MODEL"], + api_key=app.config.get("EMBEDDING_API_KEY"), + ) + else: + model = load_model(app.config["VECTOR_EMBEDDING_MODEL"]) + app.config["_EMBEDDING_FUNCTION"] = model.encode @app.route("/ready", methods=["GET"]) def ready(): diff --git a/gramps_webapi/config.py b/gramps_webapi/config.py index d77f46eb..282cc828 100644 --- a/gramps_webapi/config.py +++ b/gramps_webapi/config.py @@ -79,7 +79,9 @@ class DefaultConfig(object): LLM_MODEL = "" LLM_MAX_CONTEXT_LENGTH = 50000 LLM_SYSTEM_PROMPT = None - VECTOR_EMBEDDING_MODEL = "" + VECTOR_EMBEDDING_MODEL = "" # Model name for semantic search embeddings + EMBEDDING_BASE_URL = None # If set, use remote OpenAI-compatible API instead of local model + EMBEDDING_API_KEY = None # Optional API key for authenticated embedding providers DISABLE_TELEMETRY = False OIDC_ISSUER = "" OIDC_CLIENT_ID = "" diff --git a/tests/test_embeddings.py b/tests/test_embeddings.py new file mode 100644 index 00000000..1d3e7499 --- /dev/null +++ b/tests/test_embeddings.py @@ -0,0 +1,130 @@ +"""Tests for remote embedding function.""" + +from unittest.mock import patch + +import pytest + +from gramps_webapi.api.search.embeddings import create_remote_embedding_function + + +@pytest.fixture +def mock_response_data(): + """Sample embedding API response with out-of-order indices.""" + return { + "object": "list", + "data": [ + {"object": "embedding", "index": 1, "embedding": [0.4, 0.5, 0.6]}, + {"object": "embedding", "index": 0, "embedding": [0.1, 0.2, 0.3]}, + {"object": "embedding", "index": 2, "embedding": [0.7, 0.8, 0.9]}, + ], + "model": "test-model", + "usage": {"prompt_tokens": 10, "total_tokens": 10}, + } + + +class TestCreateRemoteEmbeddingFunction: + """Tests for create_remote_embedding_function.""" + + @patch("gramps_webapi.api.search.embeddings.requests.post") + def test_returns_embeddings_in_order(self, mock_post, mock_response_data): + mock_post.return_value.json.return_value = mock_response_data + mock_post.return_value.raise_for_status.return_value = None + + embed = create_remote_embedding_function( + base_url="http://localhost:11434", + model_name="test-model", + ) + result = embed(["hello", "world", "foo"]) + + assert result == [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9]] + + @patch("gramps_webapi.api.search.embeddings.requests.post") + def test_posts_to_correct_url(self, mock_post, mock_response_data): + mock_post.return_value.json.return_value = mock_response_data + mock_post.return_value.raise_for_status.return_value = None + + embed = create_remote_embedding_function( + base_url="http://localhost:11434", + model_name="test-model", + ) + embed(["hello"]) + + mock_post.assert_called_once() + call_args = mock_post.call_args + assert call_args[0][0] == "http://localhost:11434/v1/embeddings" + + @patch("gramps_webapi.api.search.embeddings.requests.post") + def test_strips_trailing_slash_from_base_url(self, mock_post, mock_response_data): + mock_post.return_value.json.return_value = mock_response_data + mock_post.return_value.raise_for_status.return_value = None + + embed = create_remote_embedding_function( + base_url="http://localhost:11434/", + model_name="test-model", + ) + embed(["hello"]) + + call_args = mock_post.call_args + assert call_args[0][0] == "http://localhost:11434/v1/embeddings" + + @patch("gramps_webapi.api.search.embeddings.requests.post") + def test_sends_model_and_input(self, mock_post, mock_response_data): + mock_post.return_value.json.return_value = mock_response_data + mock_post.return_value.raise_for_status.return_value = None + + embed = create_remote_embedding_function( + base_url="http://localhost:11434", + model_name="nomic-embed-text", + ) + embed(["hello", "world"]) + + call_args = mock_post.call_args + assert call_args[1]["json"] == { + "model": "nomic-embed-text", + "input": ["hello", "world"], + } + + @patch("gramps_webapi.api.search.embeddings.requests.post") + def test_sends_api_key_header(self, mock_post, mock_response_data): + mock_post.return_value.json.return_value = mock_response_data + mock_post.return_value.raise_for_status.return_value = None + + embed = create_remote_embedding_function( + base_url="http://localhost:11434", + model_name="test-model", + api_key="sk-test-key-123", + ) + embed(["hello"]) + + call_args = mock_post.call_args + headers = call_args[1]["headers"] + assert headers["Authorization"] == "Bearer sk-test-key-123" + + @patch("gramps_webapi.api.search.embeddings.requests.post") + def test_no_auth_header_without_api_key(self, mock_post, mock_response_data): + mock_post.return_value.json.return_value = mock_response_data + mock_post.return_value.raise_for_status.return_value = None + + embed = create_remote_embedding_function( + base_url="http://localhost:11434", + model_name="test-model", + ) + embed(["hello"]) + + call_args = mock_post.call_args + headers = call_args[1]["headers"] + assert "Authorization" not in headers + + @patch("gramps_webapi.api.search.embeddings.requests.post") + def test_raises_on_http_error(self, mock_post): + from requests.exceptions import HTTPError + + mock_post.return_value.raise_for_status.side_effect = HTTPError("500 Server Error") + + embed = create_remote_embedding_function( + base_url="http://localhost:11434", + model_name="test-model", + ) + + with pytest.raises(HTTPError): + embed(["hello"]) From 59fec974fc6e60dfd4574baee6e079ee3b43b193 Mon Sep 17 00:00:00 2001 From: Chris Coutinho Date: Wed, 1 Apr 2026 09:19:58 +0200 Subject: [PATCH 2/3] Address PR review: fix /v1 URL duplication, add timeout, fix lint - Normalize base_url to strip trailing /v1 to prevent /v1/v1/embeddings - Add timeout=30 to requests.post() to prevent indefinite hangs - Move inline comments above assignments in config.py for flake8 - Fix OpenAI example URL in README to not include /v1 - Add test for base_url containing /v1 path segment Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 2 +- gramps_webapi/api/search/embeddings.py | 7 +++++-- gramps_webapi/config.py | 6 ++++-- tests/test_embeddings.py | 14 ++++++++++++++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 97010f50..1027d558 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ GRAMPSWEB_EMBEDDING_BASE_URL=http://localhost:11434 ```bash GRAMPSWEB_VECTOR_EMBEDDING_MODEL=text-embedding-3-small -GRAMPSWEB_EMBEDDING_BASE_URL=https://api.openai.com/v1 +GRAMPSWEB_EMBEDDING_BASE_URL=https://api.openai.com GRAMPSWEB_EMBEDDING_API_KEY=sk-... ``` diff --git a/gramps_webapi/api/search/embeddings.py b/gramps_webapi/api/search/embeddings.py index d8f3a86d..4e0e312a 100644 --- a/gramps_webapi/api/search/embeddings.py +++ b/gramps_webapi/api/search/embeddings.py @@ -29,14 +29,17 @@ def create_remote_embedding_function( Returns a callable with signature (texts: list[str]) -> list[list[float]]. """ - url = f"{base_url.rstrip('/')}/v1/embeddings" + stripped = base_url.rstrip("/") + if stripped.endswith("/v1"): + stripped = stripped[:-3] + url = f"{stripped}/v1/embeddings" def _embed(texts: List[str]) -> List[List[float]]: headers = {"Content-Type": "application/json"} if api_key: headers["Authorization"] = f"Bearer {api_key}" payload = {"model": model_name, "input": texts} - response = requests.post(url, json=payload, headers=headers) + response = requests.post(url, json=payload, headers=headers, timeout=30) response.raise_for_status() data = response.json()["data"] data.sort(key=lambda item: item["index"]) diff --git a/gramps_webapi/config.py b/gramps_webapi/config.py index bb71234c..a90c1d42 100644 --- a/gramps_webapi/config.py +++ b/gramps_webapi/config.py @@ -90,8 +90,10 @@ class DefaultConfig(object): LLM_MAX_CONTEXT_LENGTH = 50000 LLM_SYSTEM_PROMPT = None VECTOR_EMBEDDING_MODEL = "" # Model name for semantic search embeddings - EMBEDDING_BASE_URL = None # If set, use remote OpenAI-compatible API instead of local model - EMBEDDING_API_KEY = None # Optional API key for authenticated embedding providers + # If set, use remote OpenAI-compatible API instead of local model + EMBEDDING_BASE_URL = None + # Optional API key for authenticated embedding providers + EMBEDDING_API_KEY = None DISABLE_TELEMETRY = False OIDC_ISSUER = "" OIDC_CLIENT_ID = "" diff --git a/tests/test_embeddings.py b/tests/test_embeddings.py index 1d3e7499..779ad59d 100644 --- a/tests/test_embeddings.py +++ b/tests/test_embeddings.py @@ -115,6 +115,20 @@ def test_no_auth_header_without_api_key(self, mock_post, mock_response_data): headers = call_args[1]["headers"] assert "Authorization" not in headers + @patch("gramps_webapi.api.search.embeddings.requests.post") + def test_base_url_with_v1_segment(self, mock_post, mock_response_data): + mock_post.return_value.json.return_value = mock_response_data + mock_post.return_value.raise_for_status.return_value = None + + embed = create_remote_embedding_function( + base_url="http://localhost:11434/v1", + model_name="test-model", + ) + embed(["hello"]) + + call_args = mock_post.call_args + assert call_args[0][0] == "http://localhost:11434/v1/embeddings" + @patch("gramps_webapi.api.search.embeddings.requests.post") def test_raises_on_http_error(self, mock_post): from requests.exceptions import HTTPError From 9f991e73b864ec6e5c90c111ad7c77f8f6cd2eb1 Mon Sep 17 00:00:00 2001 From: Chris Coutinho Date: Tue, 7 Apr 2026 15:14:50 +0200 Subject: [PATCH 3/3] Rename EMBEDDING_BASE_URL/API_KEY to VECTOR_EMBEDDING_* prefix and remove README docs Use VECTOR_EMBEDDING_BASE_URL and VECTOR_EMBEDDING_API_KEY for consistency with existing VECTOR_EMBEDDING_MODEL config option. Move documentation to gramps-web-docs repo per maintainer review. Co-Authored-By: Claude Opus 4.6 (1M context) --- .devcontainer/docker-compose.yml | 2 +- CONTRIBUTING.md | 2 +- README.md | 27 --------------------------- gramps_webapi/app.py | 6 +++--- gramps_webapi/config.py | 4 ++-- 5 files changed, 7 insertions(+), 34 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 4df7581b..c0829f10 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -27,7 +27,7 @@ services: # To use Ollama for embeddings instead of local SentenceTransformer, # start with: docker compose --profile ollama up -d # Then uncomment the following lines and comment out the line above: - # GRAMPSWEB_EMBEDDING_BASE_URL: http://ollama:11434 + # GRAMPSWEB_VECTOR_EMBEDDING_BASE_URL: http://ollama:11434 # GRAMPSWEB_VECTOR_EMBEDDING_MODEL: nomic-embed-text GRAMPSWEB_CELERY_CONFIG__broker_url: redis://redis:6379/0 GRAMPSWEB_CELERY_CONFIG__result_backend: redis://redis:6379/0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fa34c273..f780a911 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ The devcontainer includes an optional [Ollama](https://ollama.com/) service for 3. In `.devcontainer/docker-compose.yml`, comment out the local `GRAMPSWEB_VECTOR_EMBEDDING_MODEL` line and uncomment the Ollama lines: ```yaml # GRAMPSWEB_VECTOR_EMBEDDING_MODEL: sentence-transformers/distiluse-base-multilingual-cased-v2 - GRAMPSWEB_EMBEDDING_BASE_URL: http://ollama:11434 + GRAMPSWEB_VECTOR_EMBEDDING_BASE_URL: http://ollama:11434 GRAMPSWEB_VECTOR_EMBEDDING_MODEL: nomic-embed-text ``` diff --git a/README.md b/README.md index 1027d558..fc932dca 100644 --- a/README.md +++ b/README.md @@ -12,33 +12,6 @@ Gramps Web API is the backend of [Gramps Web](https://www.grampsweb.org/), a gen - Developer documentation for Gramps Web API: https://www.grampsweb.org/dev-backend/ - Documentation for Gramps Web: https://www.grampsweb.org -## Remote Embedding API - -By default, Gramps Web API uses a local [SentenceTransformers](https://www.sbert.net/) model for semantic search embeddings. You can optionally use a remote OpenAI-compatible embedding API (e.g. Ollama, OpenAI, LiteLLM) instead. - -| Environment Variable | Description | Default | -|---|---|---| -| `GRAMPSWEB_VECTOR_EMBEDDING_MODEL` | Model name for semantic search embeddings | `""` (disabled) | -| `GRAMPSWEB_EMBEDDING_BASE_URL` | Base URL for a remote OpenAI-compatible embedding API | `None` (use local model) | -| `GRAMPSWEB_EMBEDDING_API_KEY` | API key for authenticated embedding providers | `None` | - -**Ollama example:** - -```bash -GRAMPSWEB_VECTOR_EMBEDDING_MODEL=nomic-embed-text -GRAMPSWEB_EMBEDDING_BASE_URL=http://localhost:11434 -``` - -**OpenAI example:** - -```bash -GRAMPSWEB_VECTOR_EMBEDDING_MODEL=text-embedding-3-small -GRAMPSWEB_EMBEDDING_BASE_URL=https://api.openai.com -GRAMPSWEB_EMBEDDING_API_KEY=sk-... -``` - -> **Note:** Changing the embedding model requires reindexing all records, since different models produce vectors with different dimensions. - ## Related projects - Gramps Web frontend repository: https://github.com/gramps-project/gramps-web diff --git a/gramps_webapi/app.py b/gramps_webapi/app.py index 4f301124..b7dc8962 100644 --- a/gramps_webapi/app.py +++ b/gramps_webapi/app.py @@ -336,11 +336,11 @@ def close_user_db_connection(exception) -> None: user_db.session.remove() # pylint: disable=no-member if app.config.get("VECTOR_EMBEDDING_MODEL"): - if app.config.get("EMBEDDING_BASE_URL"): + if app.config.get("VECTOR_EMBEDDING_BASE_URL"): app.config["_EMBEDDING_FUNCTION"] = create_remote_embedding_function( - base_url=app.config["EMBEDDING_BASE_URL"], + base_url=app.config["VECTOR_EMBEDDING_BASE_URL"], model_name=app.config["VECTOR_EMBEDDING_MODEL"], - api_key=app.config.get("EMBEDDING_API_KEY"), + api_key=app.config.get("VECTOR_EMBEDDING_API_KEY"), ) else: model = load_model(app.config["VECTOR_EMBEDDING_MODEL"]) diff --git a/gramps_webapi/config.py b/gramps_webapi/config.py index a90c1d42..b7bed03d 100644 --- a/gramps_webapi/config.py +++ b/gramps_webapi/config.py @@ -91,9 +91,9 @@ class DefaultConfig(object): LLM_SYSTEM_PROMPT = None VECTOR_EMBEDDING_MODEL = "" # Model name for semantic search embeddings # If set, use remote OpenAI-compatible API instead of local model - EMBEDDING_BASE_URL = None + VECTOR_EMBEDDING_BASE_URL = None # Optional API key for authenticated embedding providers - EMBEDDING_API_KEY = None + VECTOR_EMBEDDING_API_KEY = None DISABLE_TELEMETRY = False OIDC_ISSUER = "" OIDC_CLIENT_ID = ""