commit
e188051fb3
9 changed files with 138 additions and 2 deletions
|
|
@ -1,6 +1,13 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **LM Studio Provider**: Added support for LM Studio as a provider for embeddings and QA/research models
|
||||||
|
- Configure with `provider: lm_studio` in embeddings, QA, or research model settings
|
||||||
|
- Supports thinking control for reasoning models (gpt-oss, etc.)
|
||||||
|
- Default base URL: `http://localhost:1234`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **Configuration**: Fixed `init-config` command generating invalid configuration files (#165)
|
- **Configuration**: Fixed `init-config` command generating invalid configuration files (#165)
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ Retrieval-Augmented Generation (RAG) library built on LanceDB.
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Local LanceDB**: No external servers required, supports also LanceDB cloud storage, S3, Google Cloud & Azure
|
- **Local LanceDB**: No external servers required, supports also LanceDB cloud storage, S3, Google Cloud & Azure
|
||||||
- **Multiple embedding providers**: Ollama, VoyageAI, OpenAI, vLLM
|
- **Multiple embedding providers**: Ollama, LM Studio, VoyageAI, OpenAI, vLLM
|
||||||
- **Multiple QA providers**: Any provider/model supported by Pydantic AI
|
- **Multiple QA providers**: Any provider/model supported by Pydantic AI (Ollama, LM Studio, OpenAI, Anthropic, etc.)
|
||||||
- **Native hybrid search**: Vector + full-text search with native LanceDB RRF reranking
|
- **Native hybrid search**: Vector + full-text search with native LanceDB RRF reranking
|
||||||
- **Reranking**: Default search result reranking with MixedBread AI, Cohere, Zero Entropy, or vLLM
|
- **Reranking**: Default search result reranking with MixedBread AI, Cohere, Zero Entropy, or vLLM
|
||||||
- **Question answering**: Built-in QA agents on your documents
|
- **Question answering**: Built-in QA agents on your documents
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ See the [Pydantic AI thinking documentation](https://ai.pydantic.dev/thinking/)
|
||||||
- **Bedrock**: Claude, OpenAI, and Qwen models
|
- **Bedrock**: Claude, OpenAI, and Qwen models
|
||||||
- **Ollama**: Models supporting reasoning (gpt-oss, etc.)
|
- **Ollama**: Models supporting reasoning (gpt-oss, etc.)
|
||||||
- **vLLM**: Models supporting reasoning (gpt-oss, etc.)
|
- **vLLM**: Models supporting reasoning (gpt-oss, etc.)
|
||||||
|
- **LM Studio**: Models supporting reasoning (gpt-oss, etc.)
|
||||||
|
|
||||||
**When to use:**
|
**When to use:**
|
||||||
- Disable for simple queries, RAG workflows, speed-critical applications
|
- Disable for simple queries, RAG workflows, speed-critical applications
|
||||||
|
|
@ -148,6 +149,23 @@ providers:
|
||||||
|
|
||||||
**Note:** You need to run a vLLM server separately with an embedding model loaded.
|
**Note:** You need to run a vLLM server separately with an embedding model loaded.
|
||||||
|
|
||||||
|
### LM Studio
|
||||||
|
|
||||||
|
[LM Studio](https://lmstudio.ai/) provides a local OpenAI-compatible API server for running models:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
embeddings:
|
||||||
|
provider: lm_studio
|
||||||
|
model: text-embedding-qwen3-embedding-4b
|
||||||
|
vector_dim: 2560
|
||||||
|
|
||||||
|
providers:
|
||||||
|
lm_studio:
|
||||||
|
base_url: http://localhost:1234
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** LM Studio must be running with an embedding model loaded. The default URL is `http://localhost:1234`.
|
||||||
|
|
||||||
## Question Answering Providers
|
## Question Answering Providers
|
||||||
|
|
||||||
Configure which LLM provider to use for question answering. Any provider and model supported by [Pydantic AI](https://ai.pydantic.dev/models/) can be used.
|
Configure which LLM provider to use for question answering. Any provider and model supported by [Pydantic AI](https://ai.pydantic.dev/models/) can be used.
|
||||||
|
|
@ -226,6 +244,29 @@ providers:
|
||||||
|
|
||||||
**Note:** You need to run a vLLM server separately with a model that supports tool calling loaded. Consult the specific model's documentation for proper vLLM serving configuration.
|
**Note:** You need to run a vLLM server separately with a model that supports tool calling loaded. Consult the specific model's documentation for proper vLLM serving configuration.
|
||||||
|
|
||||||
|
### LM Studio
|
||||||
|
|
||||||
|
Use LM Studio for local question answering and research:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
qa:
|
||||||
|
model:
|
||||||
|
provider: lm_studio
|
||||||
|
name: openai/gpt-oss-20b
|
||||||
|
enable_thinking: false
|
||||||
|
|
||||||
|
research:
|
||||||
|
model:
|
||||||
|
provider: lm_studio
|
||||||
|
name: openai/gpt-oss-20b
|
||||||
|
|
||||||
|
providers:
|
||||||
|
lm_studio:
|
||||||
|
base_url: http://localhost:1234
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** LM Studio must be running with a chat model that supports tool calling loaded.
|
||||||
|
|
||||||
### Other Providers
|
### Other Providers
|
||||||
|
|
||||||
Any provider supported by Pydantic AI can be used. Examples:
|
Any provider supported by Pydantic AI can be used. Examples:
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from haiku.rag.config.models import (
|
||||||
ConversionOptions,
|
ConversionOptions,
|
||||||
EmbeddingsConfig,
|
EmbeddingsConfig,
|
||||||
LanceDBConfig,
|
LanceDBConfig,
|
||||||
|
LMStudioConfig,
|
||||||
MonitorConfig,
|
MonitorConfig,
|
||||||
OllamaConfig,
|
OllamaConfig,
|
||||||
ProcessingConfig,
|
ProcessingConfig,
|
||||||
|
|
@ -36,6 +37,7 @@ __all__ = [
|
||||||
"ResearchConfig",
|
"ResearchConfig",
|
||||||
"ProcessingConfig",
|
"ProcessingConfig",
|
||||||
"OllamaConfig",
|
"OllamaConfig",
|
||||||
|
"LMStudioConfig",
|
||||||
"VLLMConfig",
|
"VLLMConfig",
|
||||||
"ProvidersConfig",
|
"ProvidersConfig",
|
||||||
"find_config_file",
|
"find_config_file",
|
||||||
|
|
|
||||||
|
|
@ -135,9 +135,14 @@ class DoclingServeConfig(BaseModel):
|
||||||
timeout: int = 300
|
timeout: int = 300
|
||||||
|
|
||||||
|
|
||||||
|
class LMStudioConfig(BaseModel):
|
||||||
|
base_url: str = "http://localhost:1234"
|
||||||
|
|
||||||
|
|
||||||
class ProvidersConfig(BaseModel):
|
class ProvidersConfig(BaseModel):
|
||||||
ollama: OllamaConfig = Field(default_factory=OllamaConfig)
|
ollama: OllamaConfig = Field(default_factory=OllamaConfig)
|
||||||
vllm: VLLMConfig = Field(default_factory=VLLMConfig)
|
vllm: VLLMConfig = Field(default_factory=VLLMConfig)
|
||||||
|
lm_studio: LMStudioConfig = Field(default_factory=LMStudioConfig)
|
||||||
docling_serve: DoclingServeConfig = Field(default_factory=DoclingServeConfig)
|
docling_serve: DoclingServeConfig = Field(default_factory=DoclingServeConfig)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,4 +46,11 @@ def get_embedder(config: AppConfig = Config) -> EmbedderBase:
|
||||||
config.embeddings.model, config.embeddings.vector_dim, config
|
config.embeddings.model, config.embeddings.vector_dim, config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if config.embeddings.provider == "lm_studio":
|
||||||
|
from haiku.rag.embeddings.lm_studio import Embedder as LMStudioEmbedder
|
||||||
|
|
||||||
|
return LMStudioEmbedder(
|
||||||
|
config.embeddings.model, config.embeddings.vector_dim, config
|
||||||
|
)
|
||||||
|
|
||||||
raise ValueError(f"Unsupported embedding provider: {config.embeddings.provider}")
|
raise ValueError(f"Unsupported embedding provider: {config.embeddings.provider}")
|
||||||
|
|
|
||||||
28
haiku_rag_slim/haiku/rag/embeddings/lm_studio.py
Normal file
28
haiku_rag_slim/haiku/rag/embeddings/lm_studio.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
from typing import overload
|
||||||
|
|
||||||
|
from openai import AsyncOpenAI
|
||||||
|
|
||||||
|
from haiku.rag.embeddings.base import EmbedderBase
|
||||||
|
|
||||||
|
|
||||||
|
class Embedder(EmbedderBase):
|
||||||
|
@overload
|
||||||
|
async def embed(self, text: str) -> list[float]: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
async def embed(self, text: list[str]) -> list[list[float]]: ...
|
||||||
|
|
||||||
|
async def embed(self, text: str | list[str]) -> list[float] | list[list[float]]:
|
||||||
|
client = AsyncOpenAI(
|
||||||
|
base_url=f"{self._config.providers.lm_studio.base_url}/v1", api_key="dummy"
|
||||||
|
)
|
||||||
|
if not text:
|
||||||
|
return []
|
||||||
|
response = await client.embeddings.create(
|
||||||
|
model=self._model,
|
||||||
|
input=text,
|
||||||
|
)
|
||||||
|
if isinstance(text, str):
|
||||||
|
return response.data[0].embedding
|
||||||
|
else:
|
||||||
|
return [item.embedding for item in response.data]
|
||||||
|
|
@ -233,6 +233,29 @@ def get_model(
|
||||||
settings=vllm_settings,
|
settings=vllm_settings,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
elif provider == "lm_studio":
|
||||||
|
model_settings = None
|
||||||
|
|
||||||
|
# Apply thinking control for gpt-oss
|
||||||
|
if model == "gpt-oss" and model_config.enable_thinking is not None:
|
||||||
|
if model_config.enable_thinking is False:
|
||||||
|
model_settings = OpenAIChatModelSettings(openai_reasoning_effort="low")
|
||||||
|
else:
|
||||||
|
model_settings = OpenAIChatModelSettings(openai_reasoning_effort="high")
|
||||||
|
|
||||||
|
model_settings = apply_common_settings(
|
||||||
|
model_settings, OpenAIChatModelSettings, model_config
|
||||||
|
)
|
||||||
|
|
||||||
|
return OpenAIChatModel(
|
||||||
|
model_name=model,
|
||||||
|
provider=OpenAIProvider(
|
||||||
|
base_url=f"{app_config.providers.lm_studio.base_url}/v1",
|
||||||
|
api_key="dummy",
|
||||||
|
),
|
||||||
|
settings=model_settings,
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# For any other provider, use string format and let Pydantic AI handle it
|
# For any other provider, use string format and let Pydantic AI handle it
|
||||||
return f"{provider}:{model}"
|
return f"{provider}:{model}"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import pytest
|
||||||
from haiku.rag.config import (
|
from haiku.rag.config import (
|
||||||
AppConfig,
|
AppConfig,
|
||||||
EmbeddingsConfig,
|
EmbeddingsConfig,
|
||||||
|
LMStudioConfig,
|
||||||
OllamaConfig,
|
OllamaConfig,
|
||||||
ProvidersConfig,
|
ProvidersConfig,
|
||||||
VLLMConfig,
|
VLLMConfig,
|
||||||
|
|
@ -71,6 +72,28 @@ def test_openai_embedder_uses_config():
|
||||||
assert embedder._config == custom_config
|
assert embedder._config == custom_config
|
||||||
|
|
||||||
|
|
||||||
|
def test_lm_studio_embedder_uses_config():
|
||||||
|
"""Test that lm_studio embedder uses the config passed to get_embedder."""
|
||||||
|
custom_config = AppConfig(
|
||||||
|
embeddings=EmbeddingsConfig(
|
||||||
|
provider="lm_studio",
|
||||||
|
model="custom-lm-studio-model",
|
||||||
|
vector_dim=1024,
|
||||||
|
),
|
||||||
|
providers=ProvidersConfig(
|
||||||
|
lm_studio=LMStudioConfig(base_url="http://custom-lmstudio:5678"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
embedder = get_embedder(custom_config)
|
||||||
|
|
||||||
|
assert embedder._model == "custom-lm-studio-model"
|
||||||
|
assert embedder._vector_dim == 1024
|
||||||
|
assert (
|
||||||
|
embedder._config.providers.lm_studio.base_url == "http://custom-lmstudio:5678"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
True, reason="VoyageAI is an optional dependency, may not be installed"
|
True, reason="VoyageAI is an optional dependency, may not be installed"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue