From 13f9fc453a7d08287f700502a3e6700bd149e245 Mon Sep 17 00:00:00 2001 From: ProjectPAIE Date: Thu, 7 Aug 2025 03:21:19 -1000 Subject: [PATCH] examples: add local Ollama integration --- examples/ollama-local/README.md | 234 ++++++++++++++++++++++ examples/ollama-local/config.yaml.example | 17 ++ examples/ollama-local/semantic.py | 156 +++++++++++++++ examples/ollama-local/system_prompt.txt | 18 ++ 4 files changed, 425 insertions(+) create mode 100644 examples/ollama-local/README.md create mode 100644 examples/ollama-local/config.yaml.example create mode 100644 examples/ollama-local/semantic.py create mode 100644 examples/ollama-local/system_prompt.txt diff --git a/examples/ollama-local/README.md b/examples/ollama-local/README.md new file mode 100644 index 000000000..d56a20219 --- /dev/null +++ b/examples/ollama-local/README.md @@ -0,0 +1,234 @@ +# Ollama Local Integration Example + +This example demonstrates how to use Pydantic AI with local Ollama models for semantic analysis and structured output generation. + +## ๐Ÿš€ Quick Start + +### 1. Install Dependencies + +```bash +# Install pydantic-ai with Ollama support +pip install "pydantic-ai[ollama]" + +# Or install from source +pip install . +``` + +### 2. Setup Ollama + +```bash +# Install Ollama (if not already installed) +# Visit https://ollama.ai for installation instructions + +# Start Ollama server +ollama serve + +# Download a model (in another terminal) +ollama pull phi3 +``` + +### 3. Configure the Example + +```bash +# Copy the example configuration +cp config.yaml.example config.yaml + +# Edit config.yaml to match your setup +# - Change model_name if using a different model +# - Adjust ollama_url if running on a different port +``` + +### 4. Run the Example + +```bash +# Test the integration +python semantic.py + +# Or use in your own code +python -c " +from semantic import analyze +from pydantic import BaseModel +from typing import List, Optional + +class SearchQuery(BaseModel): + queries: List[str] + domain: Optional[str] = None + year_filter: Optional[int] = None + tags: List[str] = [] + +result = analyze('Find documents about AI in 2023', SearchQuery) +print(result.model_dump_json(indent=2)) +" +``` + +## ๐Ÿ“ Files + +- **`config.yaml.example`** - Configuration template +- **`system_prompt.txt`** - System prompt for semantic translation +- **`semantic.py`** - Main integration module +- **`README.md`** - This file + +## ๐Ÿ”ง Configuration + +### config.yaml + +```yaml +provider: "openai" # Use OpenAI-compatible client +model_name: "phi3" # Your local Ollama model +temperature: 0.0 # Creativity level + +# Ollama settings +ollama_url: "http://localhost:11434" +ollama_api_key: "ollama" +``` + +### Available Models + +You can use any Ollama model by changing `model_name`: + +```bash +# Download different models +ollama pull llama3.2 +ollama pull mistral +ollama pull gemma +ollama pull dolphin-mixtral +``` + +Then update your `config.yaml`: +```yaml +model_name: "llama3.2" # or "mistral", "gemma", etc. +``` + +## ๐Ÿ“– Usage Examples + +### Basic Usage + +```python +from semantic import analyze +from pydantic import BaseModel +from typing import List, Optional + +class SearchQuery(BaseModel): + queries: List[str] + domain: Optional[str] = None + year_filter: Optional[int] = None + tags: List[str] = [] + +# Analyze a query +result = analyze( + "Find documents about the financial crisis in 2008", + SearchQuery +) + +print(result.queries) # ["financial", "crisis", "2008"] +print(result.year_filter) # 2008 +print(result.tags) # ["economics"] +``` + +### Custom Schema + +```python +from semantic import analyze +from pydantic import BaseModel +from typing import List + +class DocumentClassification(BaseModel): + category: str + confidence: float + tags: List[str] + summary: str + +# Classify a document +result = analyze( + "This document discusses machine learning algorithms in healthcare", + DocumentClassification +) + +print(result.category) # "technology" +print(result.confidence) # 0.92 +``` + +### Quick Analysis + +```python +from semantic import quick_analyze + +# Get structured output as dictionary +result = quick_analyze("Find AI research papers from 2022") +print(result) +# { +# "queries": ["AI", "research", "papers"], +# "domain": null, +# "year_filter": 2022, +# "tags": ["technology"] +# } +``` + +## ๐Ÿ” Troubleshooting + +### Common Issues + +1. **"Connection refused" error** + ```bash + # Make sure Ollama is running + ollama serve + ``` + +2. **"Model not found" error** + ```bash + # Download the model first + ollama pull phi3 + ``` + +3. **"Configuration file not found" error** + ```bash + # Copy the example config + cp config.yaml.example config.yaml + ``` + +4. **"Invalid API key" error** + - This is normal for Ollama - the integration handles it automatically + - If you see this, check that Ollama is running and accessible + +### Debug Mode + +Enable verbose output in `config.yaml`: +```yaml +verbose: true +``` + +## ๐Ÿงช Testing + +Run the built-in test: +```bash +python semantic.py +``` + +Expected output: +``` +โœ… Analysis successful! +Input: Find documents about machine learning in 2023 +Output: {'queries': ['machine', 'learning', '2023'], 'domain': None, 'year_filter': 2023, 'tags': ['technology']} +``` + +## ๐Ÿ”— Integration with Pydantic AI + +This example shows how to: + +1. **Configure local models** with Pydantic AI +2. **Use custom schemas** for structured output +3. **Handle environment setup** automatically +4. **Provide fallback mechanisms** for errors + +The `analyze()` function wraps Pydantic AI's `Agent` with Ollama-specific configuration, making it easy to use local models with the same interface as cloud providers. + +## ๐Ÿ“š Next Steps + +- Try different models and compare results +- Create custom schemas for your use case +- Integrate with your existing Pydantic AI workflows +- Explore other Pydantic AI examples + +--- + +**Happy coding with local AI! ๐Ÿš€** diff --git a/examples/ollama-local/config.yaml.example b/examples/ollama-local/config.yaml.example new file mode 100644 index 000000000..dc8adf7e7 --- /dev/null +++ b/examples/ollama-local/config.yaml.example @@ -0,0 +1,17 @@ +# Ollama Local Configuration Example +# Copy this file to config.yaml and adjust as needed + +provider: "openai" # Use OpenAI-compatible client +model_name: "phi3" # Your local Ollama model name +temperature: 0.0 # Creativity level (0.0 = deterministic) + +# Ollama settings +ollama_url: "http://localhost:11434" # Ollama server URL +ollama_api_key: "ollama" # Ollama API key (usually "ollama") + +# Optional: System prompt configuration +system_prompt: "semantic_translator" # Default prompt type + +# Optional: Output configuration +output_format: "json" # json, pretty, minimal +verbose: false # Enable debug output diff --git a/examples/ollama-local/semantic.py b/examples/ollama-local/semantic.py new file mode 100644 index 000000000..b01019de2 --- /dev/null +++ b/examples/ollama-local/semantic.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +""" +Ollama Local Integration for Pydantic AI +Provides a simple interface for running semantic analysis with local Ollama models. +""" + +import os +import yaml +from typing import Optional, Type, TypeVar +from pydantic import BaseModel + +from pydantic_ai import Agent +from pydantic_ai.settings import ModelSettings + +T = TypeVar('T', bound=BaseModel) + +class OllamaModel: + """Ollama model configuration and setup""" + + def __init__(self, config_path: str = "config.yaml"): + """Initialize Ollama model with configuration""" + self.config = self._load_config(config_path) + self._setup_environment() + + def _load_config(self, config_path: str) -> dict: + """Load configuration from YAML file""" + try: + with open(config_path, 'r', encoding='utf-8') as f: + return yaml.safe_load(f) + except FileNotFoundError: + raise FileNotFoundError(f"Configuration file not found: {config_path}") + + def _setup_environment(self): + """Setup environment variables for Ollama""" + ollama_url = self.config["ollama_url"].rstrip("/") + ollama_key = self.config["ollama_api_key"] + + # Set environment variables for OpenAI client + os.environ["OPENAI_API_BASE"] = f"{ollama_url}/v1" + os.environ["OPENAI_API_KEY"] = ollama_key + + if self.config.get("verbose", False): + print(f"๐Ÿ”ง Configured for Ollama at: {ollama_url}/v1") + + def get_model_name(self) -> str: + """Get the configured model name""" + return self.config["model_name"] + + def get_temperature(self) -> float: + """Get the configured temperature""" + return self.config.get("temperature", 0.0) + + def get_system_prompt(self) -> str: + """Get the system prompt from file""" + try: + with open("system_prompt.txt", "r", encoding="utf-8") as f: + return f.read().strip() + except FileNotFoundError: + # Fallback to default prompt + return "You are a semanticโ€layer translator. Convert natural language queries into structured search queries." + +def analyze( + prompt: str, + output_type: Type[T], + config_path: str = "config.yaml", + system_prompt: Optional[str] = None +) -> T: + """ + Analyze a prompt using local Ollama model and return structured output. + + Args: + prompt: The input text to analyze + output_type: The Pydantic model class for structured output + config_path: Path to configuration file + system_prompt: Optional custom system prompt (overrides file) + + Returns: + Structured output as Pydantic model instance + + Example: + from pydantic import BaseModel + from typing import List, Optional + + class SearchQuery(BaseModel): + queries: List[str] + domain: Optional[str] = None + year_filter: Optional[int] = None + tags: List[str] = [] + + result = analyze( + "Find documents about the financial crisis in 2008", + SearchQuery + ) + print(result.queries) # ["financial", "crisis", "2008"] + """ + # Initialize Ollama model + ollama = OllamaModel(config_path) + + # Get system prompt + if system_prompt is None: + system_prompt = ollama.get_system_prompt() + + # Create agent with Ollama model + model_name = f"openai:{ollama.get_model_name()}" + agent = Agent(model_name) + + # Run analysis + result = agent.run_sync( + prompt, + output_type=output_type, + model_settings=ModelSettings( + temperature=ollama.get_temperature() + ), + ) + + return result.output + +# Convenience function for quick testing +def quick_analyze(prompt: str, config_path: str = "config.yaml") -> dict: + """ + Quick analysis function that returns a simple SearchQuery structure. + + Args: + prompt: The input text to analyze + config_path: Path to configuration file + + Returns: + Dictionary with structured search query + """ + from pydantic import BaseModel + from typing import List, Optional + + class SearchQuery(BaseModel): + queries: List[str] + domain: Optional[str] = None + year_filter: Optional[int] = None + tags: List[str] = [] + + result = analyze(prompt, SearchQuery, config_path) + return result.model_dump() + +if __name__ == "__main__": + # Example usage + test_prompt = "Find documents about machine learning in 2023" + + try: + result = quick_analyze(test_prompt) + print("โœ… Analysis successful!") + print(f"Input: {test_prompt}") + print(f"Output: {result}") + except Exception as e: + print(f"โŒ Error: {e}") + print("\nMake sure:") + print("1. Ollama is running (ollama serve)") + print("2. Model is downloaded (ollama pull phi3)") + print("3. config.yaml exists and is properly configured") diff --git a/examples/ollama-local/system_prompt.txt b/examples/ollama-local/system_prompt.txt new file mode 100644 index 000000000..419048166 --- /dev/null +++ b/examples/ollama-local/system_prompt.txt @@ -0,0 +1,18 @@ +You are a semanticโ€layer translator. Convert natural language queries into structured search queries. + +Output ONLY valid JSON matching this exact SearchQuery schema: +{ + "queries": ["list", "of", "search", "terms"], + "domain": "optional domain filter", + "year_filter": 2008, + "tags": ["optional", "tags"] +} + +Rules: +- Extract key search terms from the query into the "queries" array +- If a year is mentioned, set "year_filter" to that year +- If a specific domain is mentioned, set "domain" +- Add relevant tags if applicable +- Do NOT include markdown formatting, just pure JSON +- Do NOT include any other fields or structure +- Do NOT continue generating content beyond the JSON