feat(ai): migrate from Google ADK to PydanticAI with working tool calling

Major Changes:
- Replace Google ADK with PydanticAI framework for agent orchestration
- Implement OpenAI-compatible API endpoint for Ollama integration
- Fix streaming response to send deltas instead of cumulative text
- Add /chat/completions route alias for Open-WebUI compatibility
- Enable tool calling with 5 local tools (calculate, date/time utilities)

Architecture:
- Core-AI service: Standalone Python service with PydanticAI agent
- PydanticAI: Uses OpenAI-compatible Ollama API at /v1 endpoint
- Tool Registry: Shared tool system between core-ai and core-api
- Streaming: Fixed async context issues and delta calculation

Verified Working:
✅ Chat completion (streaming & non-streaming)
✅ Tool calling with mistral-nemo and mistral-tools models
✅ Open-WebUI integration via core-ai:8086
✅ 5 tools: calculate, get_current_time, get_current_date, calculate_date_difference, add_days_to_date
✅ Proper streaming deltas (no repetition)

Technical Details:
- PydanticAI 1.25.0+ with full Ollama support
- Async context manager issue resolved via chunk collection
- Delta calculation: chunk[len(previous):] to extract new content only
- Routes: /v1/chat/completions and /chat/completions (Open-WebUI compat)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-30 10:31:14 +01:00
co-authored by Claude
parent 0558a4556c
commit 53267e1665
44 changed files with 6544 additions and 115 deletions
+19 -5
View File
@@ -9,7 +9,9 @@ try:
from src.credentials import (
PORTAINER_URL, PORTAINER_API_KEY,
NPM_URL, NPM_EMAIL, NPM_PASSWORD,
KUMA_URL, KUMA_USERNAME, KUMA_PASSWORD, KUMA_API_KEY
KUMA_URL, KUMA_USERNAME, KUMA_PASSWORD, KUMA_API_KEY,
BRAVE_SEARCH_API_KEY,
GOOGLE_SEARCH_API_KEY, GOOGLE_SEARCH_ENGINE_ID
)
except ImportError:
# Fallback to empty strings if credentials.py doesn't exist
@@ -23,6 +25,9 @@ except ImportError:
KUMA_USERNAME = ""
KUMA_PASSWORD = ""
KUMA_API_KEY = ""
BRAVE_SEARCH_API_KEY = ""
GOOGLE_SEARCH_API_KEY = ""
GOOGLE_SEARCH_ENGINE_ID = ""
class Settings(BaseSettings):
@@ -51,8 +56,8 @@ class Settings(BaseSettings):
ollama_timeout: int = 300 # 5 minutes
# Model Configuration
default_model: str = "gemma3:4b"
agent_model: str = "gemma3:4b" # Must support tool calling with ADK (~4GB VRAM)
default_model: str = "mistral-tools:7b"
agent_model: str = "gemma2:9b-instruct-q5_K_M" # Must support tool calling with ADK (~4GB VRAM)
lightweight_models: str = "gemma3-tools:1b,phi3:mini"
heavy_models: str = "mistral:7b,gemma2:9b,gemma3:12b,mixtral:8x7b"
code_models: str = "codestral:latest,codegemma:latest"
@@ -61,8 +66,8 @@ class Settings(BaseSettings):
# agent_model: str = "gemma3:12b"
# System Prompt Variant (for A/B testing)
# Options: v1_verbose, v2_concise, v3_imperative, v4_minimal, v4_gemini_suggestion, v5_adk_optimized
system_prompt_variant: str = "v7_adk_best_practice"
# Options: v1_verbose, v2_concise, v3_imperative, v4_minimal, v4_gemini_suggestion, v5_adk_optimized, v7_adk_best_practice, v8_holistic
system_prompt_variant: str = "v8_holistic"
# Agent Configuration
agent_fallback_enabled: bool = True
@@ -89,6 +94,15 @@ class Settings(BaseSettings):
embedding_dimension: int = 768 # nomic-embed-text dimension
embedding_batch_size: int = 32
# Search Configuration
search_provider: str = "google" # Options: google, brave, searxng, duckduckgo
searxng_url: str = "http://searxng:8080" # For future self-hosted SearxNG
# Search API Keys (from credentials.py)
brave_search_api_key: str = BRAVE_SEARCH_API_KEY # https://brave.com/search/api/
google_search_api_key: str = GOOGLE_SEARCH_API_KEY # https://console.cloud.google.com/
google_search_engine_id: str = GOOGLE_SEARCH_ENGINE_ID # Custom Search Engine ID
# Infrastructure Management (from credentials.py)
portainer_url: str = PORTAINER_URL
portainer_api_key: str = PORTAINER_API_KEY