Allow any provider/model supported by pydantic AI

This commit is contained in:
Yiorgis Gozadinos 2025-08-17 14:55:50 +02:00
parent 4ac658e9dd
commit d6d79d5e94
No known key found for this signature in database
7 changed files with 63 additions and 42 deletions

View file

@ -8,7 +8,7 @@ Retrieval-Augmented Generation (RAG) library on SQLite.
- **Local SQLite**: No external servers required - **Local SQLite**: No external servers required
- **Multiple embedding providers**: Ollama, VoyageAI, OpenAI - **Multiple embedding providers**: Ollama, VoyageAI, OpenAI
- **Multiple QA providers**: Ollama, OpenAI, Anthropic - **Multiple QA providers**: Any provider/model supported by Pydantic AI
- **Hybrid search**: Vector + full-text search with Reciprocal Rank Fusion - **Hybrid search**: Vector + full-text search with Reciprocal Rank Fusion
- **Reranking**: Default search result reranking with MixedBread AI or Cohere - **Reranking**: Default search result reranking with MixedBread AI or Cohere
- **Question answering**: Built-in QA agents on your documents - **Question answering**: Built-in QA agents on your documents

View file

@ -55,13 +55,12 @@ OPENAI_API_KEY="your-api-key"
## Question Answering Providers ## Question Answering Providers
Configure which LLM provider to use for question answering. Configure which LLM provider to use for question answering using the `provider:model` format. Any provider and model supported by [Pydantic AI](https://ai.pydantic.dev/models/) can be used.
### Ollama (Default) ### Ollama (Default)
```bash ```bash
QA_PROVIDER="ollama" QA_PROVIDER="ollama:qwen3"
QA_MODEL="qwen3"
OLLAMA_BASE_URL="http://localhost:11434" OLLAMA_BASE_URL="http://localhost:11434"
``` ```
@ -70,8 +69,7 @@ OLLAMA_BASE_URL="http://localhost:11434"
OpenAI QA is included in the default installation. Simply configure: OpenAI QA is included in the default installation. Simply configure:
```bash ```bash
QA_PROVIDER="openai" QA_PROVIDER="openai:gpt-4o-mini" # or openai:gpt-4, openai:gpt-3.5-turbo, etc.
QA_MODEL="gpt-4o-mini" # or gpt-4, gpt-3.5-turbo, etc.
OPENAI_API_KEY="your-api-key" OPENAI_API_KEY="your-api-key"
``` ```
@ -80,11 +78,27 @@ OPENAI_API_KEY="your-api-key"
Anthropic QA is included in the default installation. Simply configure: Anthropic QA is included in the default installation. Simply configure:
```bash ```bash
QA_PROVIDER="anthropic" QA_PROVIDER="anthropic:claude-3-5-haiku-20241022" # or anthropic:claude-3-5-sonnet-20241022, etc.
QA_MODEL="claude-3-5-haiku-20241022" # or claude-3-5-sonnet-20241022, etc.
ANTHROPIC_API_KEY="your-api-key" ANTHROPIC_API_KEY="your-api-key"
``` ```
### Other Providers
Any provider supported by Pydantic AI can be used. Examples include:
```bash
# Google Gemini
QA_PROVIDER="gemini:gemini-1.5-flash"
# Groq
QA_PROVIDER="groq:llama-3.3-70b-versatile"
# Mistral
QA_PROVIDER="mistral:mistral-small-latest"
```
See the [Pydantic AI documentation](https://ai.pydantic.dev/models/) for the complete list of supported providers and models.
## Reranking ## Reranking
Reranking improves search quality by re-ordering the initial search results using specialized models. When enabled, the system retrieves more candidates (3x the requested limit) and then reranks them to return the most relevant results. Reranking improves search quality by re-ordering the initial search results using specialized models. When enabled, the system retrieves more candidates (3x the requested limit) and then reranks them to return the most relevant results.

View file

@ -22,8 +22,7 @@ class AppConfig(BaseModel):
RERANK_PROVIDER: str = "ollama" RERANK_PROVIDER: str = "ollama"
RERANK_MODEL: str = "qwen3" RERANK_MODEL: str = "qwen3"
QA_PROVIDER: str = "ollama" QA_PROVIDER: str = "ollama:qwen3"
QA_MODEL: str = "qwen3"
CHUNK_SIZE: int = 256 CHUNK_SIZE: int = 256
CONTEXT_CHUNK_RADIUS: int = 0 CONTEXT_CHUNK_RADIUS: int = 0

View file

@ -4,15 +4,10 @@ from haiku.rag.qa.agent import QuestionAnswerAgent
def get_qa_agent(client: HaikuRAG, use_citations: bool = False) -> QuestionAnswerAgent: def get_qa_agent(client: HaikuRAG, use_citations: bool = False) -> QuestionAnswerAgent:
provider = Config.QA_PROVIDER provider_model = Config.QA_PROVIDER
model_name = Config.QA_MODEL
if provider in ("openai", "anthropic", "ollama"): return QuestionAnswerAgent(
return QuestionAnswerAgent( client=client,
client=client, provider_model=provider_model,
provider=provider, use_citations=use_citations,
model=model_name, )
use_citations=use_citations,
)
raise ValueError(f"Unsupported QA provider: {provider}")

View file

@ -23,15 +23,14 @@ class QuestionAnswerAgent:
def __init__( def __init__(
self, self,
client: HaikuRAG, client: HaikuRAG,
provider: str, provider_model: str,
model: str,
use_citations: bool = False, use_citations: bool = False,
q: float = 0.0, q: float = 0.0,
): ):
self._client = client self._client = client
system_prompt = SYSTEM_PROMPT_WITH_CITATIONS if use_citations else SYSTEM_PROMPT system_prompt = SYSTEM_PROMPT_WITH_CITATIONS if use_citations else SYSTEM_PROMPT
model_obj = self._get_model(provider, model) model_obj = self._get_model(provider_model)
self._agent = Agent( self._agent = Agent(
model=model_obj, model=model_obj,
@ -58,19 +57,21 @@ class QuestionAnswerAgent:
for chunk, score in expanded_results for chunk, score in expanded_results
] ]
def _get_model(self, provider: str, model: str): def _get_model(self, provider_model: str):
"""Get the appropriate model object for the provider.""" """Get the appropriate model object for the provider:model format."""
if provider == "openai": if ":" not in provider_model:
return f"openai:{model}" raise ValueError(f"Invalid provider:model format: {provider_model}")
elif provider == "anthropic":
return f"anthropic:{model}" provider, model = provider_model.split(":", 1)
elif provider == "ollama":
if provider == "ollama":
return OpenAIModel( return OpenAIModel(
model_name=model, model_name=model,
provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"), provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"),
) )
else: else:
raise ValueError(f"Unsupported provider: {provider}") # For other providers, use the provider:model string directly
return provider_model
async def answer(self, question: str) -> str: async def answer(self, question: str) -> str:
"""Answer a question using the RAG system.""" """Answer a question using the RAG system."""

View file

@ -37,14 +37,24 @@ class LLMJudgeResponseSchema(BaseModel):
class LLMJudge: class LLMJudge:
"""LLM-as-judge for evaluating answer equivalence using Pydantic AI.""" """LLM-as-judge for evaluating answer equivalence using Pydantic AI."""
def __init__(self, model: str = Config.QA_MODEL): def __init__(self, provider_model: str = Config.QA_PROVIDER):
self.model = model self.provider_model = provider_model
# Create Ollama model # Parse provider:model format
ollama_model = OpenAIModel( if ":" not in provider_model:
model_name=model, raise ValueError(f"Invalid provider:model format: {provider_model}")
provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"),
) provider, model = provider_model.split(":", 1)
if provider == "ollama":
# Create Ollama model
ollama_model = OpenAIModel(
model_name=model,
provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"),
)
else:
# For other providers, use the provider:model string directly
ollama_model = provider_model
# Create Pydantic AI agent # Create Pydantic AI agent
self._agent = Agent( self._agent = Agent(

View file

@ -15,7 +15,7 @@ ANTHROPIC_AVAILABLE = bool(Config.ANTHROPIC_API_KEY)
async def test_qa_ollama(qa_corpus: Dataset): async def test_qa_ollama(qa_corpus: Dataset):
"""Test Ollama QA with LLM judge.""" """Test Ollama QA with LLM judge."""
client = HaikuRAG(":memory:") client = HaikuRAG(":memory:")
qa = QuestionAnswerAgent(client, "ollama", "qwen3") qa = QuestionAnswerAgent(client, provider_model="ollama:qwen3")
llm_judge = LLMJudge() llm_judge = LLMJudge()
doc = qa_corpus[1] doc = qa_corpus[1]
@ -39,7 +39,7 @@ async def test_qa_ollama(qa_corpus: Dataset):
async def test_qa_openai(qa_corpus: Dataset): async def test_qa_openai(qa_corpus: Dataset):
"""Test OpenAI QA with LLM judge.""" """Test OpenAI QA with LLM judge."""
client = HaikuRAG(":memory:") client = HaikuRAG(":memory:")
qa = QuestionAnswerAgent(client, "openai", "gpt-4o-mini") qa = QuestionAnswerAgent(client, provider_model="openai:gpt-4o-mini")
llm_judge = LLMJudge() llm_judge = LLMJudge()
doc = qa_corpus[1] doc = qa_corpus[1]
@ -63,7 +63,9 @@ async def test_qa_openai(qa_corpus: Dataset):
async def test_qa_anthropic(qa_corpus: Dataset): async def test_qa_anthropic(qa_corpus: Dataset):
"""Test Anthropic QA with LLM judge.""" """Test Anthropic QA with LLM judge."""
client = HaikuRAG(":memory:") client = HaikuRAG(":memory:")
qa = QuestionAnswerAgent(client, "anthropic", "claude-3-5-haiku-20241022") qa = QuestionAnswerAgent(
client, provider_model="anthropic:claude-3-5-haiku-20241022"
)
llm_judge = LLMJudge() llm_judge = LLMJudge()
doc = qa_corpus[1] doc = qa_corpus[1]