Data Model: Ollama Endpoint Support¶
Feature: 009-ollama-endpoint-support Date: 2025-11-26 Phase: Phase 1 - Design & Contracts
Overview¶
This document defines the data models and entities for Ollama LLM provider support in HoloDeck. The design reuses existing LLMProvider model with Ollama-specific validation and error handling.
Core Entities¶
1. LLMProvider (Existing - Modified)¶
Location: src/holodeck/models/llm.py:21-81
Purpose: Configuration model for all LLM providers including Ollama
Status: EXISTING - No structural changes needed, Ollama already supported
Schema:
class LLMProvider(BaseModel):
provider: ProviderEnum # Includes OLLAMA enum value
name: str # Ollama model name (e.g., "llama3", "phi3", "mistral")
temperature: float | None = 0.3 # Temperature (0.0-2.0)
max_tokens: int | None = 1000 # Maximum tokens to generate
top_p: float | None = None # Nucleus sampling parameter (0.0-1.0)
endpoint: str | None = None # Ollama endpoint URL (REQUIRED for Ollama)
api_key: str | None = None # Optional API key for remote Ollama instances
Ollama-Specific Validation (existing in check_endpoint_required() line 74-80):
- provider == ProviderEnum.OLLAMA → endpoint MUST be non-empty
- endpoint format validated by Pydantic (basic string validation)
- Environment variable substitution supported via python-dotenv
Example Configuration:
# Local Ollama instance
model:
provider: ollama
name: llama3
endpoint: http://localhost:11434
temperature: 0.7
max_tokens: 1000
# Remote Ollama instance with authentication
model:
provider: ollama
name: phi3
endpoint: ${OLLAMA_ENDPOINT} # e.g., http://192.168.1.100:11434
api_key: ${OLLAMA_API_KEY}
temperature: 0.5
max_tokens: 2000
top_p: 0.9
Validation Rules:
- name: Non-empty string (existing validator at line 42-47)
- temperature: 0.0 ≤ temperature ≤ 2.0 (existing validator at line 49-55)
- max_tokens: max_tokens > 0 (existing validator at line 57-63)
- top_p: 0.0 ≤ top_p ≤ 1.0 if provided (existing validator at line 65-71)
- endpoint: Required and non-empty for Ollama (existing validator at line 74-80)
Relationships:
- Used by Agent.model field (src/holodeck/models/agent.py:68)
- Consumed by AgentFactory._create_kernel() (src/holodeck/lib/test_runner/agent_factory.py:143-203)
2. ProviderEnum (Existing - No Changes)¶
Location: src/holodeck/models/llm.py:12-18
Purpose: Enumeration of supported LLM providers
Status: EXISTING - Ollama already included
Schema:
class ProviderEnum(str, Enum):
OPENAI = "openai"
AZURE_OPENAI = "azure_openai"
ANTHROPIC = "anthropic"
OLLAMA = "ollama" # Already present
No modifications needed.
3. OllamaDefaults (New)¶
Location: src/holodeck/config/defaults.py (new constant)
Purpose: Default configuration values for Ollama provider
Status: NEW
Schema:
OLLAMA_DEFAULTS = {
"endpoint": "http://localhost:11434", # Standard Ollama endpoint
"temperature": 0.3, # Conservative default
"max_tokens": 1000, # Reasonable default
"top_p": None, # Use model's default behavior
"api_key": None, # Local instances don't require auth
}
Usage: Applied by configuration loader when user doesn't specify values
Rationale:
- endpoint default matches Ollama's standard installation
- Other defaults align with existing HoloDeck LLM defaults
- No default name (model) - forces explicit user selection
4. OllamaConnectionError (New)¶
Location: src/holodeck/lib/errors.py (new exception class)
Purpose: Specific error type for Ollama connectivity failures
Status: NEW
Schema:
class OllamaConnectionError(AgentFactoryError):
"""Error raised when Ollama endpoint is unreachable.
Attributes:
endpoint: The Ollama endpoint URL that failed
message: Human-readable error message with resolution guidance
"""
def __init__(self, endpoint: str, original_error: Exception | None = None):
self.endpoint = endpoint
message = (
f"Failed to connect to Ollama endpoint at {endpoint}.\n"
f"Ensure Ollama is running: ollama serve\n"
f"Check endpoint URL is correct and accessible."
)
if original_error:
message += f"\nOriginal error: {original_error}"
super().__init__(message)
Usage: Raised by AgentFactory._create_kernel() when Ollama connection fails
Rationale: Provides actionable error messages per success criteria SC-004
5. OllamaModelNotFoundError (New)¶
Location: src/holodeck/lib/errors.py (new exception class)
Purpose: Specific error type when Ollama model is not available
Status: NEW
Schema:
class OllamaModelNotFoundError(AgentFactoryError):
"""Error raised when requested Ollama model is not found.
Attributes:
model_name: The model that was not found
endpoint: The Ollama endpoint that was queried
message: Human-readable error message with resolution guidance
"""
def __init__(self, model_name: str, endpoint: str):
self.model_name = model_name
self.endpoint = endpoint
message = (
f"Model '{model_name}' not found on Ollama endpoint {endpoint}.\n"
f"Pull the model first: ollama pull {model_name}\n"
f"List available models: ollama list"
)
super().__init__(message)
Usage: Raised by AgentFactory._create_kernel() when model initialization fails with "not found" error
Rationale: Provides specific resolution steps for common user error
Entity Relationships¶
┌─────────────────────────────────────────────────────────────┐
│ Agent │
│ ├─ name: str │
│ ├─ model: LLMProvider ───────────┐ │
│ ├─ instructions: Instructions │ │
│ └─ tools: list[ToolUnion] │ │
└───────────────────────────────────┴──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ LLMProvider │
│ ├─ provider: ProviderEnum (includes OLLAMA) │
│ ├─ name: str (model name) │
│ ├─ endpoint: str (required for Ollama) │
│ ├─ api_key: str | None │
│ ├─ temperature: float | None │
│ ├─ max_tokens: int | None │
│ └─ top_p: float | None │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ AgentFactory │
│ ├─ _create_kernel() ───> Semantic Kernel Integration │
│ │ └─ OllamaChatCompletion( │
│ │ ai_model_id=config.name, │
│ │ url=config.endpoint, │
│ │ api_key=config.api_key │
│ │ ) │
│ ├─ _apply_model_settings() ───> Execution Settings │
│ └─ invoke() ───> Agent Execution │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Semantic Kernel (External) │
│ └─ OllamaChatCompletion ───> Ollama Endpoint │
│ (ai_model_id, url, api_key) │
└─────────────────────────────────────────────────────────────┘
Data Flow¶
Configuration Loading Flow¶
1. User creates agent.yaml
└─ model:
provider: ollama
name: llama3
endpoint: http://localhost:11434
2. ConfigLoader.load_config(path)
└─ Parses YAML
└─ Loads .env variables
└─ Merges with OllamaDefaults
3. Pydantic validation (LLMProvider)
├─ Validates provider == "ollama"
├─ Validates endpoint is non-empty (check_endpoint_required)
├─ Validates temperature, max_tokens, top_p ranges
└─ Returns validated LLMProvider instance
4. Agent model instantiated
└─ agent_config.model: LLMProvider(provider=OLLAMA, ...)
Runtime Execution Flow¶
1. User runs `holodeck chat` or `holodeck test`
└─ CLI command loads agent config
2. AgentFactory.__init__(agent_config)
└─ Calls _create_kernel()
3. AgentFactory._create_kernel()
├─ Reads model_config.provider (OLLAMA)
├─ Creates OllamaChatCompletion service:
│ service = OllamaChatCompletion(
│ ai_model_id=config.name, # "llama3"
│ url=config.endpoint, # "http://localhost:11434"
│ api_key=config.api_key # None or provided
│ )
├─ Adds service to kernel
└─ Returns configured kernel
4. AgentFactory._create_agent()
├─ Loads instructions
├─ Creates ChatCompletionAgent with kernel
└─ Returns agent instance
5. AgentFactory.invoke(user_input)
├─ Lazy tool initialization (_ensure_tools_initialized)
├─ Calls agent.invoke() via Semantic Kernel
├─ Semantic Kernel calls Ollama endpoint
├─ Extracts response and tool calls
└─ Returns AgentExecutionResult
Error Handling Flow¶
1. Connection Failure Scenario
└─ Ollama endpoint unreachable
├─ Semantic Kernel raises ConnectionError
├─ Caught in _create_kernel()
├─ Wrapped in OllamaConnectionError
└─ Surfaced to user with actionable message
2. Model Not Found Scenario
└─ Model doesn't exist in Ollama instance
├─ Ollama API returns 404/error
├─ Semantic Kernel raises exception
├─ Caught in _create_kernel()
├─ Wrapped in OllamaModelNotFoundError
└─ Surfaced to user with `ollama pull` command
3. Invalid Configuration Scenario
└─ Missing or malformed endpoint
├─ Pydantic validation fails during config load
├─ ValidationError raised with field details
├─ Caught by ConfigLoader
└─ User sees clear field-level error messages
Validation Rules Summary¶
| Field | Required | Type | Range/Format | Default |
|---|---|---|---|---|
provider |
Yes | ProviderEnum | Must be "ollama" | N/A |
name |
Yes | str | Non-empty string | None (no default) |
endpoint |
Yes (for Ollama) | str | Valid HTTP/HTTPS URL | http://localhost:11434 |
api_key |
No | str | Any string | None |
temperature |
No | float | 0.0 ≤ t ≤ 2.0 | 0.3 |
max_tokens |
No | int | > 0 | 1000 |
top_p |
No | float | 0.0 ≤ p ≤ 1.0 | None |
Edge Cases & Constraints¶
Edge Case 1: Endpoint URL Variations¶
Scenario: User provides endpoint without http:// prefix
Handling: Pydantic validation accepts as string, Semantic Kernel may fail
Solution: Add URL format validation in LLMProvider validator to ensure http:// or https:// prefix
Edge Case 2: Environment Variable Not Set¶
Scenario: User specifies endpoint: ${OLLAMA_ENDPOINT} but env var doesn't exist
Handling: python-dotenv leaves as literal string, endpoint validation fails
Solution: ConfigLoader should validate env vars are resolved after loading
Edge Case 3: Ollama Running on Non-Standard Port¶
Scenario: User runs Ollama on port 8080 instead of 11434
Handling: User specifies endpoint: http://localhost:8080
Solution: No special handling - endpoint field supports any port
Edge Case 4: Model Name Case Sensitivity¶
Scenario: User specifies "Llama3" instead of "llama3"
Handling: Ollama API is case-sensitive, model not found error
Solution: Error message from OllamaModelNotFoundError guides user to run ollama list
Edge Case 5: Remote Ollama Without Authentication¶
Scenario: Remote endpoint doesn't require api_key but user provides it Handling: Semantic Kernel sends Authorization header, may be ignored by server Solution: No issue - extra header is harmless
File Modifications Summary¶
Existing Files (Modified)¶
src/holodeck/models/llm.py: No changes needed (Ollama already supported)src/holodeck/lib/test_runner/agent_factory.py: Add Ollama case to_create_kernel()src/holodeck/config/defaults.py: AddOLLAMA_DEFAULTSconstant
New Files¶
src/holodeck/lib/errors.py: AddOllamaConnectionErrorandOllamaModelNotFoundErrortests/unit/models/test_llm_ollama.py: Unit tests for Ollama validationtests/unit/lib/test_agent_factory_ollama.py: Unit tests for Ollama agent factorytests/integration/test_ollama_config_loading.py: Integration test for end-to-end configtests/fixtures/ollama/agent_ollama.yaml: Sample Ollama agent configuration
No Changes Required¶
src/holodeck/models/agent.py: Uses existingLLMProvider, no changessrc/holodeck/config/loader.py: Already supports provider-specific configssrc/holodeck/config/validator.py: Existing validation functions sufficient
Next Steps¶
- Create API contracts (if applicable) in
contracts/directory - Generate quickstart.md with Ollama setup and usage guide
- Update agent context with Ollama-specific technology
- Proceed to Phase 2: Write
tasks.md