From 69cb4d06c6df2d3a74e9f8f43b68334aa43d7ecf Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:06:59 -0700 Subject: [PATCH] Enhancement (dev): Use OpenAI-like backend (#12668) --- docs/configuration.md | 36 +++++---- docs/usage.md | 10 ++- pyproject.toml | 4 +- src-ui/src/app/data/paperless-config.ts | 4 +- src/documents/caching.py | 3 +- src/documents/tests/test_api_app_config.py | 2 +- src/documents/tests/test_api_status.py | 4 +- src/documents/tests/test_views.py | 6 +- ...cationconfiguration_ai_enabled_and_more.py | 7 +- src/paperless/models.py | 4 +- src/paperless/settings/__init__.py | 4 +- src/paperless_ai/client.py | 16 ++-- src/paperless_ai/embedding.py | 10 +-- src/paperless_ai/tests/test_ai_indexing.py | 2 +- src/paperless_ai/tests/test_client.py | 10 ++- src/paperless_ai/tests/test_embedding.py | 16 ++-- uv.lock | 81 ++++++++++++------- 17 files changed, 136 insertions(+), 83 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f1192acb9..397676a53 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -2014,49 +2014,57 @@ suggestions. This setting is required to be set to true in order to use the AI f #### [`PAPERLESS_AI_LLM_EMBEDDING_BACKEND=`](#PAPERLESS_AI_LLM_EMBEDDING_BACKEND) {#PAPERLESS_AI_LLM_EMBEDDING_BACKEND} -: The embedding backend to use for RAG. This can be either "openai" or "huggingface". +: The embedding backend to use for RAG. This can be either "openai-like" or "huggingface". The +"openai-like" backend uses an OpenAI-compatible embeddings API. Defaults to None. #### [`PAPERLESS_AI_LLM_EMBEDDING_MODEL=`](#PAPERLESS_AI_LLM_EMBEDDING_MODEL) {#PAPERLESS_AI_LLM_EMBEDDING_MODEL} -: The model to use for the embedding backend for RAG. This can be set to any of the embedding models supported by the current embedding backend. If not supplied, defaults to "text-embedding-3-small" for OpenAI and "sentence-transformers/all-MiniLM-L6-v2" for Huggingface. +: The model to use for the embedding backend for RAG. This can be set to any of the embedding +models supported by the current embedding backend. If not supplied, defaults to +"text-embedding-3-small" for the OpenAI-compatible backend and +"sentence-transformers/all-MiniLM-L6-v2" for Huggingface. Defaults to None. #### [`PAPERLESS_AI_LLM_BACKEND=`](#PAPERLESS_AI_LLM_BACKEND) {#PAPERLESS_AI_LLM_BACKEND} -: The AI backend to use. This can be either "openai" or "ollama". If set to "ollama", the AI -features will be run locally on your machine. If set to "openai", the AI features will be run -using the OpenAI API. This setting is required to be set to use the AI features. +: The AI backend to use. This can be either "openai-like" or "ollama". If set to "ollama", the AI +features will be run locally on your machine. If set to "openai-like", the AI features will use +an OpenAI-compatible API endpoint, including OpenAI itself and compatible providers. This +setting is required to be set to use the AI features. Defaults to None. !!! note - The OpenAI API is a paid service. You will need to set up an OpenAI account and - will be charged for usage incurred by Paperless-ngx features and your document data - will (of course) be sent to the OpenAI API. Paperless-ngx does not endorse the use of the - OpenAI API in any way. + Remote AI providers may be paid services. If you use a hosted OpenAI-compatible API, you + are responsible for any usage charges incurred by Paperless-ngx features, and your + document data will be sent to the provider you configure. - Refer to the OpenAI terms of service, and use at your own risk. + Paperless-ngx does not endorse any specific provider. Refer to your provider's terms of + service and privacy policy, and use at your own risk. #### [`PAPERLESS_AI_LLM_MODEL=`](#PAPERLESS_AI_LLM_MODEL) {#PAPERLESS_AI_LLM_MODEL} -: The model to use for the AI backend, i.e. "gpt-3.5-turbo", "gpt-4" or any of the models supported by the -current backend. If not supplied, defaults to "gpt-3.5-turbo" for OpenAI and "llama3.1" for Ollama. +: The model to use for the AI backend, i.e. "gpt-3.5-turbo", "gpt-4" or any of the models supported +by the current backend. If not supplied, defaults to "gpt-3.5-turbo" for the OpenAI-compatible +backend and "llama3.1" for Ollama. Defaults to None. #### [`PAPERLESS_AI_LLM_API_KEY=`](#PAPERLESS_AI_LLM_API_KEY) {#PAPERLESS_AI_LLM_API_KEY} -: The API key to use for the AI backend. This is required for the OpenAI backend (optional for others). +: The API key to use for the AI backend. This is typically required for the OpenAI-compatible +backend (optional for others). Defaults to None. #### [`PAPERLESS_AI_LLM_ENDPOINT=`](#PAPERLESS_AI_LLM_ENDPOINT) {#PAPERLESS_AI_LLM_ENDPOINT} -: The endpoint / url to use for the AI backend. This is required for the Ollama backend (optional for others). +: The endpoint / url to use for the AI backend. This is required for the Ollama backend and may be +used with the OpenAI-compatible backend to target a custom provider or local gateway. Defaults to None. diff --git a/docs/usage.md b/docs/usage.md index 3c5f6222a..27d3dee9e 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -302,13 +302,19 @@ Paperless-ngx includes several features that use AI to enhance the document mana !!! warning - Remember that Paperless-ngx will send document content to the AI provider you have configured, so consider the privacy implications of using these features, especially if using a remote model (e.g. OpenAI), instead of the default local model. + Remember that Paperless-ngx will send document content to the AI provider you have configured, + so consider the privacy implications of using these features, especially if using a remote + model or API provider instead of the default local model. The AI features work by creating an embedding of the text content and metadata of documents, which is then used for various tasks such as similarity search and question answering. This uses the FAISS vector store. ### AI-Enhanced Suggestions -If enabled, Paperless-ngx can use an AI LLM model to suggest document titles, dates, tags, correspondents and document types for documents. This feature will always be "opt-in" and does not disable the existing classifier-based suggestion system. Currently, both remote (via the OpenAI API) and local (via Ollama) models are supported, see [configuration](configuration.md#ai) for details. +If enabled, Paperless-ngx can use an AI LLM model to suggest document titles, dates, tags, +correspondents and document types for documents. This feature will always be "opt-in" and does not +disable the existing classifier-based suggestion system. Currently, both remote +(via OpenAI-compatible APIs) and local (via Ollama) models are supported, see +[configuration](configuration.md#ai) for details. ### Document Chat diff --git a/pyproject.toml b/pyproject.toml index 8485307cf..2cc20f180 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,9 +53,9 @@ dependencies = [ "langdetect~=1.0.9", "llama-index-core>=0.14.12", "llama-index-embeddings-huggingface>=0.6.1", - "llama-index-embeddings-openai>=0.5.1", + "llama-index-embeddings-openai-like>=0.2.2", "llama-index-llms-ollama>=0.9.1", - "llama-index-llms-openai>=0.6.13", + "llama-index-llms-openai-like>=0.7.1", "llama-index-vector-stores-faiss>=0.5.2", "nltk~=3.9.1", "ocrmypdf~=17.4.0", diff --git a/src-ui/src/app/data/paperless-config.ts b/src-ui/src/app/data/paperless-config.ts index 38af21e3c..2df5cac49 100644 --- a/src-ui/src/app/data/paperless-config.ts +++ b/src-ui/src/app/data/paperless-config.ts @@ -55,12 +55,12 @@ export const ConfigCategory = { } export const LLMEmbeddingBackendConfig = { - OPENAI: 'openai', + OPENAI_LIKE: 'openai-like', HUGGINGFACE: 'huggingface', } export const LLMBackendConfig = { - OPENAI: 'openai', + OPENAI_LIKE: 'openai-like', OLLAMA: 'ollama', } diff --git a/src/documents/caching.py b/src/documents/caching.py index f2911e51e..ad7b9bd5d 100644 --- a/src/documents/caching.py +++ b/src/documents/caching.py @@ -218,7 +218,8 @@ def set_llm_suggestions_cache( timeout: int = CACHE_50_MINUTES, ) -> None: """ - Cache LLM-generated suggestions using a backend-specific identifier (e.g. 'openai:gpt-4'). + Cache LLM-generated suggestions using a backend-specific identifier + (e.g. 'openai-like:gpt-4'). """ doc_key = get_suggestion_cache_key(document_id) cache.set( diff --git a/src/documents/tests/test_api_app_config.py b/src/documents/tests/test_api_app_config.py index a24b471eb..ec1f45391 100644 --- a/src/documents/tests/test_api_app_config.py +++ b/src/documents/tests/test_api_app_config.py @@ -848,7 +848,7 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase): json.dumps( { "ai_enabled": True, - "llm_embedding_backend": "openai", + "llm_embedding_backend": "openai-like", }, ), content_type="application/json", diff --git a/src/documents/tests/test_api_status.py b/src/documents/tests/test_api_status.py index 72f1e269b..bfe6cc9ee 100644 --- a/src/documents/tests/test_api_status.py +++ b/src/documents/tests/test_api_status.py @@ -404,7 +404,7 @@ class TestSystemStatus(APITestCase): THEN: - The response contains the correct AI status """ - with override_settings(AI_ENABLED=True, LLM_EMBEDDING_BACKEND="openai"): + with override_settings(AI_ENABLED=True, LLM_EMBEDDING_BACKEND="openai-like"): self.client.force_login(self.user) # No tasks found @@ -431,7 +431,7 @@ class TestSystemStatus(APITestCase): THEN: - The response contains the correct AI status """ - with override_settings(AI_ENABLED=True, LLM_EMBEDDING_BACKEND="openai"): + with override_settings(AI_ENABLED=True, LLM_EMBEDDING_BACKEND="openai-like"): PaperlessTaskFactory( task_type=PaperlessTask.TaskType.LLM_INDEX, trigger_source=PaperlessTask.TriggerSource.SCHEDULED, diff --git a/src/documents/tests/test_views.py b/src/documents/tests/test_views.py index 1b7d953c8..3fe171fd6 100644 --- a/src/documents/tests/test_views.py +++ b/src/documents/tests/test_views.py @@ -359,7 +359,7 @@ class TestAISuggestions(DirectoriesMixin, TestCase): @patch("documents.views.get_ai_document_classification") @override_settings( AI_ENABLED=True, - LLM_BACKEND="openai", + LLM_BACKEND="openai-like", ) def test_suggestions_with_invalid_ai_configuration( self, @@ -379,7 +379,9 @@ class TestAISuggestions(DirectoriesMixin, TestCase): "ai": ["Invalid AI configuration."], }, ) - self.assertIsNone(get_llm_suggestion_cache(self.document.pk, backend="openai")) + self.assertIsNone( + get_llm_suggestion_cache(self.document.pk, backend="openai-like"), + ) def test_invalidate_suggestions_cache(self) -> None: self.client.force_login(user=self.user) diff --git a/src/paperless/migrations/0005_applicationconfiguration_ai_enabled_and_more.py b/src/paperless/migrations/0005_applicationconfiguration_ai_enabled_and_more.py index f8a71ea6b..b7d415b2b 100644 --- a/src/paperless/migrations/0005_applicationconfiguration_ai_enabled_and_more.py +++ b/src/paperless/migrations/0005_applicationconfiguration_ai_enabled_and_more.py @@ -34,7 +34,7 @@ class Migration(migrations.Migration): name="llm_backend", field=models.CharField( blank=True, - choices=[("openai", "OpenAI"), ("ollama", "Ollama")], + choices=[("openai-like", "OpenAI-compatible"), ("ollama", "Ollama")], max_length=128, null=True, verbose_name="Sets the LLM backend", @@ -45,7 +45,10 @@ class Migration(migrations.Migration): name="llm_embedding_backend", field=models.CharField( blank=True, - choices=[("openai", "OpenAI"), ("huggingface", "Huggingface")], + choices=[ + ("openai-like", "OpenAI-compatible"), + ("huggingface", "Huggingface"), + ], max_length=128, null=True, verbose_name="Sets the LLM embedding backend", diff --git a/src/paperless/models.py b/src/paperless/models.py index 07fe9207a..91ab4a496 100644 --- a/src/paperless/models.py +++ b/src/paperless/models.py @@ -75,7 +75,7 @@ class ColorConvertChoices(models.TextChoices): class LLMEmbeddingBackend(models.TextChoices): - OPENAI = ("openai", _("OpenAI")) + OPENAI_LIKE = ("openai-like", _("OpenAI-compatible")) HUGGINGFACE = ("huggingface", _("Huggingface")) @@ -84,7 +84,7 @@ class LLMBackend(models.TextChoices): Matches to --llm-backend """ - OPENAI = ("openai", _("OpenAI")) + OPENAI_LIKE = ("openai-like", _("OpenAI-compatible")) OLLAMA = ("ollama", _("Ollama")) diff --git a/src/paperless/settings/__init__.py b/src/paperless/settings/__init__.py index 79dd98ee3..95bbe46c5 100644 --- a/src/paperless/settings/__init__.py +++ b/src/paperless/settings/__init__.py @@ -1174,9 +1174,9 @@ REMOTE_OCR_ENDPOINT = os.getenv("PAPERLESS_REMOTE_OCR_ENDPOINT") AI_ENABLED = get_bool_from_env("PAPERLESS_AI_ENABLED", "NO") LLM_EMBEDDING_BACKEND = os.getenv( "PAPERLESS_AI_LLM_EMBEDDING_BACKEND", -) # "huggingface" or "openai" +) # "huggingface" or "openai-like" LLM_EMBEDDING_MODEL = os.getenv("PAPERLESS_AI_LLM_EMBEDDING_MODEL") -LLM_BACKEND = os.getenv("PAPERLESS_AI_LLM_BACKEND") # "ollama" or "openai" +LLM_BACKEND = os.getenv("PAPERLESS_AI_LLM_BACKEND") # "ollama" or "openai-like" LLM_MODEL = os.getenv("PAPERLESS_AI_LLM_MODEL") LLM_API_KEY = os.getenv("PAPERLESS_AI_LLM_API_KEY") LLM_ENDPOINT = os.getenv("PAPERLESS_AI_LLM_ENDPOINT") diff --git a/src/paperless_ai/client.py b/src/paperless_ai/client.py index 95b61b508..8a653baef 100644 --- a/src/paperless_ai/client.py +++ b/src/paperless_ai/client.py @@ -1,10 +1,12 @@ import logging from typing import TYPE_CHECKING +from paperless.models import LLMBackend + if TYPE_CHECKING: from llama_index.core.llms import ChatMessage from llama_index.llms.ollama import Ollama - from llama_index.llms.openai import OpenAI + from llama_index.llms.openai_like import OpenAILike from paperless.config import AIConfig from paperless.network import validate_outbound_http_url @@ -22,8 +24,8 @@ class AIClient: self.settings = AIConfig() self.llm = self.get_llm() - def get_llm(self) -> "Ollama | OpenAI": - if self.settings.llm_backend == "ollama": + def get_llm(self) -> "Ollama | OpenAILike": + if self.settings.llm_backend == LLMBackend.OLLAMA: from llama_index.llms.ollama import Ollama endpoint = self.settings.llm_endpoint or "http://localhost:11434" @@ -36,8 +38,8 @@ class AIClient: base_url=endpoint, request_timeout=120, ) - elif self.settings.llm_backend == "openai": - from llama_index.llms.openai import OpenAI + elif self.settings.llm_backend == LLMBackend.OPENAI_LIKE: + from llama_index.llms.openai_like import OpenAILike endpoint = self.settings.llm_endpoint or None if endpoint: @@ -45,10 +47,12 @@ class AIClient: endpoint, allow_internal=self.settings.llm_allow_internal_endpoints, ) - return OpenAI( + return OpenAILike( model=self.settings.llm_model or "gpt-3.5-turbo", api_base=endpoint, api_key=self.settings.llm_api_key, + is_chat_model=True, + is_function_calling_model=True, ) else: raise ValueError(f"Unsupported LLM backend: {self.settings.llm_backend}") diff --git a/src/paperless_ai/embedding.py b/src/paperless_ai/embedding.py index 72ad8e14a..a96dd2429 100644 --- a/src/paperless_ai/embedding.py +++ b/src/paperless_ai/embedding.py @@ -19,8 +19,8 @@ def get_embedding_model() -> "BaseEmbedding": config = AIConfig() match config.llm_embedding_backend: - case LLMEmbeddingBackend.OPENAI: - from llama_index.embeddings.openai import OpenAIEmbedding + case LLMEmbeddingBackend.OPENAI_LIKE: + from llama_index.embeddings.openai_like import OpenAILikeEmbedding endpoint = config.llm_endpoint or None if endpoint: @@ -28,8 +28,8 @@ def get_embedding_model() -> "BaseEmbedding": endpoint, allow_internal=config.llm_allow_internal_endpoints, ) - return OpenAIEmbedding( - model=config.llm_embedding_model or "text-embedding-3-small", + return OpenAILikeEmbedding( + model_name=config.llm_embedding_model or "text-embedding-3-small", api_key=config.llm_api_key, api_base=endpoint, ) @@ -54,7 +54,7 @@ def get_embedding_dim() -> int: config = AIConfig() model = config.llm_embedding_model or ( "text-embedding-3-small" - if config.llm_embedding_backend == "openai" + if config.llm_embedding_backend == LLMEmbeddingBackend.OPENAI_LIKE else "sentence-transformers/all-MiniLM-L6-v2" ) diff --git a/src/paperless_ai/tests/test_ai_indexing.py b/src/paperless_ai/tests/test_ai_indexing.py index 7d9f3cdd5..d02cf3b96 100644 --- a/src/paperless_ai/tests/test_ai_indexing.py +++ b/src/paperless_ai/tests/test_ai_indexing.py @@ -98,7 +98,7 @@ def test_update_llm_index_removes_meta( config = AIConfig() expected_model = config.llm_embedding_model or ( "text-embedding-3-small" - if config.llm_embedding_backend == "openai" + if config.llm_embedding_backend == "openai-like" else "sentence-transformers/all-MiniLM-L6-v2" ) assert meta == {"embedding_model": expected_model, "dim": 384} diff --git a/src/paperless_ai/tests/test_client.py b/src/paperless_ai/tests/test_client.py index e1325b107..35a881400 100644 --- a/src/paperless_ai/tests/test_client.py +++ b/src/paperless_ai/tests/test_client.py @@ -25,8 +25,8 @@ def mock_ollama_llm(): @pytest.fixture def mock_openai_llm(): - with patch("llama_index.llms.openai.OpenAI") as MockOpenAI: - yield MockOpenAI + with patch("llama_index.llms.openai_like.OpenAILike") as MockOpenAILike: + yield MockOpenAILike def test_get_llm_ollama(mock_ai_config, mock_ollama_llm): @@ -45,7 +45,7 @@ def test_get_llm_ollama(mock_ai_config, mock_ollama_llm): def test_get_llm_openai(mock_ai_config, mock_openai_llm): - mock_ai_config.llm_backend = "openai" + mock_ai_config.llm_backend = "openai-like" mock_ai_config.llm_model = "test_model" mock_ai_config.llm_api_key = "test_api_key" mock_ai_config.llm_endpoint = "http://test-url" @@ -56,12 +56,14 @@ def test_get_llm_openai(mock_ai_config, mock_openai_llm): model="test_model", api_base="http://test-url", api_key="test_api_key", + is_chat_model=True, + is_function_calling_model=True, ) assert client.llm == mock_openai_llm.return_value def test_get_llm_openai_blocks_internal_endpoint_when_disallowed(mock_ai_config): - mock_ai_config.llm_backend = "openai" + mock_ai_config.llm_backend = "openai-like" mock_ai_config.llm_model = "test_model" mock_ai_config.llm_api_key = "test_api_key" mock_ai_config.llm_endpoint = "http://127.0.0.1:1234" diff --git a/src/paperless_ai/tests/test_embedding.py b/src/paperless_ai/tests/test_embedding.py index 74151ff89..e4e80cdf1 100644 --- a/src/paperless_ai/tests/test_embedding.py +++ b/src/paperless_ai/tests/test_embedding.py @@ -54,15 +54,17 @@ def mock_document(): def test_get_embedding_model_openai(mock_ai_config): - mock_ai_config.return_value.llm_embedding_backend = LLMEmbeddingBackend.OPENAI + mock_ai_config.return_value.llm_embedding_backend = LLMEmbeddingBackend.OPENAI_LIKE mock_ai_config.return_value.llm_embedding_model = "text-embedding-3-small" mock_ai_config.return_value.llm_api_key = "test_api_key" mock_ai_config.return_value.llm_endpoint = "http://test-url" - with patch("llama_index.embeddings.openai.OpenAIEmbedding") as MockOpenAIEmbedding: + with patch( + "llama_index.embeddings.openai_like.OpenAILikeEmbedding", + ) as MockOpenAIEmbedding: model = get_embedding_model() MockOpenAIEmbedding.assert_called_once_with( - model="text-embedding-3-small", + model_name="text-embedding-3-small", api_key="test_api_key", api_base="http://test-url", ) @@ -72,7 +74,7 @@ def test_get_embedding_model_openai(mock_ai_config): def test_get_embedding_model_openai_blocks_internal_endpoint_when_disallowed( mock_ai_config, ): - mock_ai_config.return_value.llm_embedding_backend = LLMEmbeddingBackend.OPENAI + mock_ai_config.return_value.llm_embedding_backend = LLMEmbeddingBackend.OPENAI_LIKE mock_ai_config.return_value.llm_embedding_model = "text-embedding-3-small" mock_ai_config.return_value.llm_api_key = "test_api_key" mock_ai_config.return_value.llm_endpoint = "http://127.0.0.1:11434" @@ -109,7 +111,7 @@ def test_get_embedding_model_invalid_backend(mock_ai_config): def test_get_embedding_dim_infers_and_saves(temp_llm_index_dir, mock_ai_config): - mock_ai_config.return_value.llm_embedding_backend = "openai" + mock_ai_config.return_value.llm_embedding_backend = "openai-like" mock_ai_config.return_value.llm_embedding_model = None class DummyEmbedding: @@ -129,7 +131,7 @@ def test_get_embedding_dim_infers_and_saves(temp_llm_index_dir, mock_ai_config): def test_get_embedding_dim_reads_existing_meta(temp_llm_index_dir, mock_ai_config): - mock_ai_config.return_value.llm_embedding_backend = "openai" + mock_ai_config.return_value.llm_embedding_backend = "openai-like" mock_ai_config.return_value.llm_embedding_model = None (temp_llm_index_dir / "meta.json").write_text( @@ -142,7 +144,7 @@ def test_get_embedding_dim_reads_existing_meta(temp_llm_index_dir, mock_ai_confi def test_get_embedding_dim_raises_on_model_change(temp_llm_index_dir, mock_ai_config): - mock_ai_config.return_value.llm_embedding_backend = "openai" + mock_ai_config.return_value.llm_embedding_backend = "openai-like" mock_ai_config.return_value.llm_embedding_model = None (temp_llm_index_dir / "meta.json").write_text( diff --git a/uv.lock b/uv.lock index 49a5fd6a4..85d3141f5 100644 --- a/uv.lock +++ b/uv.lock @@ -2199,6 +2199,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/d1/4bb0b80f4057903110060f617ef519197194b3ff5dd6153d850c8f5676fa/llama_index_embeddings_openai-0.6.0-py3-none-any.whl", hash = "sha256:039bb1007ad4267e25ddb89a206dfdab862bfb87d58da4271a3919e4f9df4d61", size = 7666, upload-time = "2026-03-12T20:21:28.079Z" }, ] +[[package]] +name = "llama-index-embeddings-openai-like" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-embeddings-openai", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/df/79e4748196213b55931d5f8377141fff41135f5988d5501860824cc95390/llama_index_embeddings_openai_like-0.3.1.tar.gz", hash = "sha256:cef7af4bce284e8e6730532dbd0aa325e77398a5d5524edb2d2e3acb122fb5b6", size = 3854, upload-time = "2026-03-13T16:15:20.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/8e/b9ea889f88318f2faa20b615989e12a15a133c9273630f9266fcf69f35a6/llama_index_embeddings_openai_like-0.3.1-py3-none-any.whl", hash = "sha256:167c7e462cde7d53ea907ceaffbbf10a750676c7c9f7bcc9bc9686a41921387a", size = 3631, upload-time = "2026-03-13T16:15:19.58Z" }, +] + [[package]] name = "llama-index-instrumentation" version = "0.4.2" @@ -2238,6 +2250,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/62/a847e9a94c2f92926c30188259f9f86e019dcc45122bbb222dea35a74c02/llama_index_llms_openai-0.7.5-py3-none-any.whl", hash = "sha256:c302c6386873420df3714c3d538f45379b6de27ab6a531f30c67419b39a538f5", size = 28492, upload-time = "2026-03-30T16:30:32.979Z" }, ] +[[package]] +name = "llama-index-llms-openai-like" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "llama-index-llms-openai", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/a3/16410b28d131aa113ada79f856b78cb68a8e92a1e27255ea9c36c27a5dec/llama_index_llms_openai_like-0.7.2.tar.gz", hash = "sha256:ed9ff73f975dce470f98ac61c982151ba78eedfa3fb9b03894bc1d1312b213ff", size = 5389, upload-time = "2026-04-23T23:05:32.525Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/0c/fdddaee5391d915d3d568d2d8dbdb7c95647e65bb94d4ddb31d47cef5daf/llama_index_llms_openai_like-0.7.2-py3-none-any.whl", hash = "sha256:1f45a7b1cec8fb3f5997684327ffe6c19f93e789c2fff35dc5522465850faf0b", size = 6602, upload-time = "2026-04-23T23:05:31.708Z" }, +] + [[package]] name = "llama-index-vector-stores-faiss" version = "0.6.0" @@ -2881,9 +2906,9 @@ dependencies = [ { name = "langdetect", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "llama-index-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "llama-index-embeddings-huggingface", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, - { name = "llama-index-embeddings-openai", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "llama-index-embeddings-openai-like", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "llama-index-llms-ollama", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, - { name = "llama-index-llms-openai", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "llama-index-llms-openai-like", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "llama-index-vector-stores-faiss", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "nltk", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "ocrmypdf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -3030,9 +3055,9 @@ requires-dist = [ { name = "langdetect", specifier = "~=1.0.9" }, { name = "llama-index-core", specifier = ">=0.14.12" }, { name = "llama-index-embeddings-huggingface", specifier = ">=0.6.1" }, - { name = "llama-index-embeddings-openai", specifier = ">=0.5.1" }, + { name = "llama-index-embeddings-openai-like", specifier = ">=0.2.2" }, { name = "llama-index-llms-ollama", specifier = ">=0.9.1" }, - { name = "llama-index-llms-openai", specifier = ">=0.6.13" }, + { name = "llama-index-llms-openai-like", specifier = ">=0.7.1" }, { name = "llama-index-vector-stores-faiss", specifier = ">=0.5.2" }, { name = "mysqlclient", marker = "extra == 'mariadb'", specifier = "~=2.2.7" }, { name = "nltk", specifier = "~=3.9.1" }, @@ -4919,12 +4944,12 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, ] wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d75eadcd97fe0dc7cd0eedc4d72152484c19cb2cfe46ce55766c8e129116425f" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43b35116802c85fb88d99f4a396b8bd4472bfca1dd82e69499e5a4f9b8b4e252" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:442ec9dc78592564fdad69cf0beaa9da2f82ab810ccb4f13903869a90bf3f15d" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cc3a195701bba2239c313ee311487f80f8aaebe9e89b9073dddbcf2f93b5a0ba" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:072a0d6e4865e8b0dc0dbfe6ebed68fae235124222835ef03e5814d414d8c012" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23ec7789017da9d95b6d543d790814785e6f30905c5443efa8257d1490d73f79" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d75eadcd97fe0dc7cd0eedc4d72152484c19cb2cfe46ce55766c8e129116425f", upload-time = "2026-03-23T15:16:54Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43b35116802c85fb88d99f4a396b8bd4472bfca1dd82e69499e5a4f9b8b4e252", upload-time = "2026-03-23T15:16:58Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:442ec9dc78592564fdad69cf0beaa9da2f82ab810ccb4f13903869a90bf3f15d", upload-time = "2026-03-23T15:17:02Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cc3a195701bba2239c313ee311487f80f8aaebe9e89b9073dddbcf2f93b5a0ba", upload-time = "2026-03-23T15:17:06Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:072a0d6e4865e8b0dc0dbfe6ebed68fae235124222835ef03e5814d414d8c012", upload-time = "2026-03-23T15:17:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23ec7789017da9d95b6d543d790814785e6f30905c5443efa8257d1490d73f79", upload-time = "2026-03-23T15:17:14Z" }, ] [[package]] @@ -4947,24 +4972,24 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform == 'linux'" }, ] wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp311-cp311-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp311-cp311-linux_s390x.whl", hash = "sha256:5214b203ee187f8746c66f1378b72611b7c1e15c5cb325037541899e705ea24e", upload-time = "2026-04-27T21:55:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:46fbb0aa257bb781efbfad648f5b045c0e232573b661f1461593db61342e9096", upload-time = "2026-04-28T00:05:38Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8a56a8c95531ef0e454510ba8bbd9d11dc7a9000337265210b10f6bfeacdd485", upload-time = "2026-04-28T00:05:47Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:2db3ae5404e32cb42b5fcbd94f13607761eaec0cf1687fde95095289d1e26cfb", upload-time = "2026-04-28T00:06:06Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:70ecb2659af6373b7c5336e692e665605b0201ea21ff51aaea47e1d75ea6b5aa", upload-time = "2026-04-28T00:06:14Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f82e2ae20c1545bb03997d1cc3143d94e14b800038669ee1aca45808a9acc338", upload-time = "2026-04-28T00:06:24Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:d1eff25ccc454faf21c9666c81bfab8e405e87c12d300708d4559620bc191a36", upload-time = "2026-04-28T00:06:42Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:48b3e21a311445acdd0b27f13830e21d93adef70d4721e051e9f059baeb9b8f9", upload-time = "2026-04-28T00:06:51Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:45025d7752dbc6b4c784c03afaee9c5f19730ce084b2e43fc9a2fe1677d9ff86", upload-time = "2026-04-28T00:07:02Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-linux_s390x.whl", hash = "sha256:65d427a196ab0abe359b93c5bffedd76ded02df2b1b1d2d9f11a2609b69f426a", upload-time = "2026-04-28T00:07:19Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8f13dc7075ae04ca5f876a9f40b4e47522a04c23e30824b4409f42a3f3e57aa4", upload-time = "2026-04-28T00:07:27Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8713bb8679376ea0ec25742100b6cfb8447e0904c48bddefb9eb0ac1abbfa60a", upload-time = "2026-04-28T00:07:37Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-linux_s390x.whl", hash = "sha256:c9a14c367f470623b978e273a4e1915995b4ba7a0ae999178b06c273eea3536f", upload-time = "2026-04-28T00:07:54Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:71676f6a9a84bbd385e010198b51fa1c2324fb8f3c512a32d2c81af65f68f4c9", upload-time = "2026-04-28T00:08:02Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:f8481ea9088e4e5b81178a75aabdbb658bde8639bc1a15fd5d8f930abc966735", upload-time = "2026-04-28T00:08:11Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-linux_s390x.whl", hash = "sha256:825f1596878280a3a4c861441674888bc2d792e4ab7b045cb35feeab3f4f5dd7", upload-time = "2026-04-28T00:08:27Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c8a0bdfb2fd915b6c2cd27c856f63f729c366a4917772eba6b2b02aa3bce70d5", upload-time = "2026-04-28T00:08:36Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:768f22924a25cad2adeb9c6cbac5159e71067c8d4019b1511960d7435a5ca652", upload-time = "2026-04-28T00:08:47Z" }, ] [[package]]