diff --git a/services/core-ai/diagnostics/test_adk_direct.py b/services/core-ai/diagnostics/test_adk_direct.py deleted file mode 100644 index c1b7957..0000000 --- a/services/core-ai/diagnostics/test_adk_direct.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python3 -""" -Diagnostic tool to test ADK agent directly (without HTTP layer). -This tests ADK initialization and basic completion without tools. - -Usage: - python diagnostics/test_adk_direct.py -""" -import asyncio -import sys -import os -from pathlib import Path - -# Add parent directory to path to import from src -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from src.config import get_settings -from src.agents import ADK_AVAILABLE - -if not ADK_AVAILABLE: - print("✗ Google ADK not available") - print(" Install with: pip install google-adk") - sys.exit(1) - -from src.agents import ADKAgent - - -async def test_adk_direct(): - """Test ADK agent without HTTP layer""" - settings = get_settings() - - print("=" * 70) - print("ADK DIRECT TEST (No Tools)") - print("=" * 70) - - print(f"\n1. Configuration") - print(f" Model: {settings.agent_model}") - print(f" Ollama URL: {settings.ollama_base_url}") - print(f" ADK Prompt Variant: {settings.adk_system_prompt_variant}") - - # Test cases without tools - test_cases = [ - { - "name": "Simple question", - "messages": [ - {"role": "user", "content": "What is the capital of France? Answer in one sentence."} - ] - }, - { - "name": "Math problem", - "messages": [ - {"role": "user", "content": "What is 15 + 27? Just give me the number."} - ] - }, - { - "name": "Multi-step reasoning", - "messages": [ - {"role": "user", "content": "If I have 3 apples and buy 2 more, then eat 1, how many do I have left?"} - ] - } - ] - - # Run tests - for i, test_case in enumerate(test_cases, 1): - print(f"\n{'-' * 70}") - print(f"Test {i}/{len(test_cases)}: {test_case['name']}") - print(f"{'-' * 70}") - - try: - # Initialize ADK agent (no tools) - print("\n→ Initializing ADK agent (no tools)...") - agent = ADKAgent(tools=[]) - print("✓ ADK agent initialized") - - # Test non-streaming - print(f"\n→ Testing non-streaming completion...") - print(f" Question: {test_case['messages'][0]['content']}") - - response = await agent.chat_completion( - messages=test_case['messages'] - ) - - print(f"\n✓ Response received:") - print(f" {response}") - - # Test streaming - print(f"\n→ Testing streaming completion...") - chunks = [] - event_count = 0 - - async for chunk in agent.chat( - messages=test_case['messages'], - stream=True - ): - event_count += 1 - chunk_type = chunk.get("type") - - if chunk_type == "content" and chunk.get("content"): - chunks.append(chunk["content"]) - elif chunk_type == "tool_call": - print(f" 🔧 Tool call: {chunk.get('tool')}") - elif chunk_type == "tool_result": - print(f" ✅ Tool result") - elif chunk_type == "error": - print(f" ❌ Error: {chunk.get('content')}") - - full_content = "".join(chunks) - print(f"\n✓ Streaming response received:") - print(f" Events: {event_count}") - print(f" Content: {full_content}") - - print(f"\n✓ Test {i} PASSED") - - except ImportError as e: - print(f"\n✗ Test {i} FAILED: ADK import error") - print(f" Error: {e}") - print(f" Install: pip install google-adk") - return False - - except Exception as e: - print(f"\n✗ Test {i} FAILED") - print(f" Error: {type(e).__name__}: {e}") - import traceback - traceback.print_exc() - return False - - print("\n" + "=" * 70) - print("✓ ALL ADK TESTS PASSED (No Tools)!") - print("=" * 70) - print("\nNext steps:") - print(" 1. ADK initialization works") - print(" 2. ADK can generate responses without tools") - print(" 3. Ready to add tool integration (Phase 2)") - return True - - -if __name__ == "__main__": - result = asyncio.run(test_adk_direct()) - sys.exit(0 if result else 1) diff --git a/services/core-ai/diagnostics/test_adk_tools.py b/services/core-ai/diagnostics/test_adk_tools.py deleted file mode 100644 index 1aefb60..0000000 --- a/services/core-ai/diagnostics/test_adk_tools.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python3 -""" -Diagnostic tool to test ADK agent with tools. - -This tests: -1. Local tool registration -2. Tool execution -3. ADK agent with tools -4. (Optional) REST tool discovery from core-api - -Usage: - python diagnostics/test_adk_tools.py -""" -import asyncio -import sys -import os -from pathlib import Path - -# Add parent directory to path to import from src -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from src.config import get_settings -from src.tools import get_all_tools, get_agent_tools, discover_and_register_tools -from src.agents import ADK_AVAILABLE - -if not ADK_AVAILABLE: - print("✗ Google ADK not available") - print(" Install with: pip install google-adk") - sys.exit(1) - -from src.agents import ADKAgent - - -async def test_tools_diagnostic(): - """Test ADK agent with tools""" - settings = get_settings() - - print("=" * 70) - print("ADK TOOLS DIAGNOSTIC") - print("=" * 70) - - # ======================================================================== - # Part 1: Local Tools - # ======================================================================== - print("\n" + "=" * 70) - print("PART 1: LOCAL TOOLS") - print("=" * 70) - - print("\n1. Local Tool Registration") - tools = get_all_tools() - print(f" Registered tools: {len(tools)}") - for tool_name in tools.keys(): - print(f" - {tool_name}") - - # Test local tools directly - print("\n2. Testing Local Tools") - - print("\n → Testing get_current_time...") - from src.tools.local import get_current_time - time_result = await get_current_time() - print(f" Result: {time_result}") - - print("\n → Testing get_current_date...") - from src.tools.local import get_current_date - date_result = await get_current_date() - print(f" Result: {date_result}") - - print("\n → Testing calculate...") - from src.tools.local import calculate - calc_result = await calculate("15 + 27") - print(f" Result: 15 + 27 = {calc_result}") - - print("\n → Testing date operations...") - from src.tools.local import add_days_to_date, calculate_date_difference - future_date = await add_days_to_date(date_result, 30) - print(f" {date_result} + 30 days = {future_date}") - - diff = await calculate_date_difference(date_result, future_date) - print(f" Difference: {diff}") - - print("\n✓ All local tools working") - - # ======================================================================== - # Part 2: ADK Integration - # ======================================================================== - print("\n" + "=" * 70) - print("PART 2: ADK INTEGRATION") - print("=" * 70) - - print("\n3. Converting Tools to ADK Format") - adk_tools = get_agent_tools() - print(f" ADK tools created: {len(adk_tools)}") - - # ======================================================================== - # Part 3: ADK Agent with Tools - # ======================================================================== - print("\n" + "=" * 70) - print("PART 3: ADK AGENT WITH TOOLS") - print("=" * 70) - - test_cases = [ - { - "name": "Simple calculation with tool", - "messages": [ - {"role": "user", "content": "What is 123 + 456? Use the calculate tool to find the answer."} - ] - }, - { - "name": "Current time query", - "messages": [ - {"role": "user", "content": "What is the current time and date? Use the appropriate tools."} - ] - }, - { - "name": "Date calculation", - "messages": [ - {"role": "user", "content": "What will the date be 45 days from now? Use the date tools."} - ] - }, - ] - - # Run tests - for i, test_case in enumerate(test_cases, 1): - print(f"\n{'-' * 70}") - print(f"Test {i}/{len(test_cases)}: {test_case['name']}") - print(f"{'-' * 70}") - - try: - # Initialize ADK agent with tools - print("\n→ Initializing ADK agent with tools...") - agent = ADKAgent(discover_tools=True) - print(f"✓ ADK agent initialized with {len(agent.tools)} tools") - - # Test non-streaming - print(f"\n→ Query: {test_case['messages'][0]['content']}") - - response = await agent.chat_completion( - messages=test_case['messages'] - ) - - print(f"\n✓ Response:") - print(f" {response}") - - print(f"\n✓ Test {i} PASSED") - - except Exception as e: - print(f"\n✗ Test {i} FAILED") - print(f" Error: {type(e).__name__}: {e}") - import traceback - traceback.print_exc() - return False - - # ======================================================================== - # Part 4: REST Tool Discovery (Optional) - # ======================================================================== - print("\n" + "=" * 70) - print("PART 4: REST TOOL DISCOVERY (OPTIONAL)") - print("=" * 70) - - print(f"\n4. Attempting to discover tools from core-api") - print(f" Core-API URL: {settings.core_api_base_url}") - - try: - print("\n→ Fetching OpenAPI spec from core-api...") - rest_tools_count = await discover_and_register_tools() - print(f"✓ Discovered and registered {rest_tools_count} REST tools from core-api") - - # Show all tools now - all_tools = get_all_tools() - print(f"\n Total tools registered: {len(all_tools)}") - for tool_name in all_tools.keys(): - print(f" - {tool_name}") - - except Exception as e: - print(f"\n⚠️ Could not discover REST tools from core-api") - print(f" Reason: {type(e).__name__}: {e}") - print(f" This is expected if core-api is not running or doesn't have OpenAPI docs yet") - - # ======================================================================== - # Summary - # ======================================================================== - print("\n" + "=" * 70) - print("✓ ADK TOOLS DIAGNOSTIC COMPLETE!") - print("=" * 70) - print("\nResults:") - print(f" ✓ Local tools: {len([t for t in get_all_tools().keys() if 'calculate' in t or 'date' in t or 'time' in t])}") - print(f" ✓ ADK integration: Working") - print(f" ✓ Tool calling: Working") - print("\nNext steps:") - print(" 1. Local tools are working") - print(" 2. ADK agent can use tools") - print(" 3. Ready to add REST tools from core-api") - print(" 4. Ready for Phase 3: Full tool integration") - return True - - -if __name__ == "__main__": - result = asyncio.run(test_tools_diagnostic()) - sys.exit(0 if result else 1) diff --git a/services/core-ai/src/agents/adk_agent.py b/services/core-ai/src/agents/adk_agent.py deleted file mode 100644 index dbf92d5..0000000 --- a/services/core-ai/src/agents/adk_agent.py +++ /dev/null @@ -1,286 +0,0 @@ -""" -ADK Agent - Google ADK with LiteLLM backend and tool calling support. - -Based on official documentation: -- https://google.github.io/adk-docs/get-started/python/ -- https://docs.litellm.ai/docs/tutorials/google_adk -- https://medium.com/@viplav.fauzdar/building-a-local-ai-agent-with-google-adk-litellm-and-ollama-6e907e2db268 -""" -import logging -import uuid -from typing import AsyncIterator, Dict, Any, List, Optional -from functools import lru_cache - -# Google ADK imports (official API) -try: - from google.adk.agents import Agent - from google.adk.models.lite_llm import LiteLlm - from google.adk.sessions import InMemorySessionService - from google.adk.runners import Runner - from google.genai import types - ADK_AVAILABLE = True -except ImportError: - ADK_AVAILABLE = False - Agent = None - LiteLlm = None - InMemorySessionService = None - Runner = None - types = None - -from src.config import get_settings -from src.prompts import get_prompt - -logger = logging.getLogger(__name__) - - -class ADKAgent: - """ - Agent using Google ADK with LiteLLM backend for Ollama. - Supports tool calling and complex orchestration. - - Example: - agent = ADKAgent(tools=[my_tool]) - response = await agent.chat_completion(messages=[{"role": "user", "content": "Hello"}]) - """ - - def __init__(self, tools: List = None, discover_tools: bool = False): - if not ADK_AVAILABLE: - raise ImportError("Google ADK not available. Install with: pip install google-adk") - - logger.info("ADKAgent: Initializing Google ADK agent...") - - self.settings = get_settings() - - # Tools can be provided explicitly or discovered - if tools is not None: - # Explicit tools provided - self.tools = tools - logger.info(f"ADKAgent: Using {len(tools)} explicitly provided tools") - elif discover_tools: - # Discover tools from registry (includes local + core-api) - logger.info("ADKAgent: Discovering tools from registry...") - from src.tools import get_agent_tools - self.tools = get_agent_tools() - logger.info(f"ADKAgent: Discovered {len(self.tools)} tools") - else: - # No tools - self.tools = [] - logger.info("ADKAgent: No tools enabled") - - # Load system prompt for ADK mode - adk_prompt_variant = getattr(self.settings, 'adk_system_prompt_variant', 'adk_agent') - self.system_prompt = get_prompt(adk_prompt_variant) - logger.info(f"ADKAgent: System prompt variant: {adk_prompt_variant}") - logger.info(f"ADKAgent: System prompt: {self.system_prompt[:100]}...") - - # Initialize LiteLlm for Ollama - # Note: ollama_chat/ doesn't execute tools, so using ollama/ for tool calling - # Testing with mistral-nemo which has better tool support than gemma2 - model_name = self.settings.agent_model - litellm_model = f"ollama/{model_name}" - - logger.info(f"ADKAgent: Initializing LiteLlm model: {litellm_model}") - logger.info(f"ADKAgent: Ollama API base: {self.settings.ollama_base_url}") - logger.info(f"ADKAgent: Tools registered: {len(self.tools)}") - - # Create LiteLlm model instance - self.model = LiteLlm( - model=litellm_model, - api_base=self.settings.ollama_base_url, - stream=True, - temperature=0.1, - ) - - # Create ADK Agent with the model - self.agent = Agent( - name="core_ai_agent", - model=self.model, - description="AI assistant for system management and Q&A", - instruction=self.system_prompt, - tools=self.tools, - ) - - # Create session service and runner - self.session_service = InMemorySessionService() - self.runner = Runner( - agent=self.agent, - app_name="core-ai", - session_service=self.session_service - ) - - logger.info("✓ ADKAgent: Initialization complete") - - async def chat( - self, - messages: List[Dict[str, str]], - conversation_id: str = None, - stream: bool = True, - prompt_variant: Optional[str] = None - ) -> AsyncIterator[Dict[str, Any]]: - """ - Process a chat message using ADK agent. - - Args: - messages: List of message dicts with 'role' and 'content' - conversation_id: Optional conversation ID for session tracking - stream: Whether to stream responses - prompt_variant: Optional prompt variant (not used, set in __init__) - - Yields: - Dict with 'type' and content. Types: - - {"type": "content", "content": "text chunk"} - - {"type": "content", "content": "", "finish_reason": "stop"} - - {"type": "error", "content": "error message"} - """ - logger.info(f"🚀 ADKAgent: Starting completion for message: {messages[-1]['content'][:50]}...") - - try: - # Extract user message (ADK handles system prompt internally) - user_messages = [m for m in messages if m["role"] != "system"] - if not user_messages: - raise ValueError("No user messages provided") - - # Use the last user message - user_query = user_messages[-1]["content"] - logger.info(f"📤 ADKAgent: User query: {user_query[:100]}...") - - # Create unique user and session IDs - user_id = "core-ai-user" - session_id = conversation_id or str(uuid.uuid4()) - - # Always create a new session for each request (simple approach) - # TODO: Implement session reuse for conversation continuity - try: - await self.session_service.create_session( - app_name="core-ai", - user_id=user_id, - session_id=session_id - ) - logger.info(f"✓ Created session: {session_id}") - except Exception as e: - logger.warning(f"Session creation warning: {e} - attempting to use existing session") - - # Create content for ADK - content = types.Content( - role='user', - parts=[types.Part(text=user_query)] - ) - - # Run agent and collect events - final_response_text = "" - event_count = 0 - - async for event in self.runner.run_async( - user_id=user_id, - session_id=session_id, - new_message=content - ): - event_count += 1 - - # Check for tool calls (official ADK method) - calls = event.get_function_calls() - if calls: - for call in calls: - tool_name = call.name if hasattr(call, 'name') else 'unknown' - logger.info(f"🔧 Tool call: {tool_name}") - continue - - # Check for tool responses (official ADK method) - responses = event.get_function_responses() - if responses: - for response in responses: - # FunctionResponse has 'response' dict, not 'content' - result = getattr(response, 'response', {}) - logger.info(f"✅ Tool response: {result}") - continue - - # Check for intermediate content (thinking/reasoning) - if event.content and event.content.parts and not event.is_final_response(): - part = event.content.parts[0] - intermediate_text = getattr(part, 'text', None) - if intermediate_text: - logger.debug(f"💭 Intermediate: {intermediate_text[:100]}...") - continue - - # Check if this is the final response - if event.is_final_response(): - if event.content and event.content.parts: - final_response_text = event.content.parts[0].text - logger.info(f"📥 ADKAgent: Final response after {event_count} events") - - # Yield content - if stream: - # Simulate streaming by yielding in chunks - chunk_size = 50 - for i in range(0, len(final_response_text), chunk_size): - chunk = final_response_text[i:i+chunk_size] - yield {"type": "content", "content": chunk} - - # Final chunk with finish reason - yield {"type": "content", "content": "", "finish_reason": "stop"} - else: - # Non-streaming: yield full response - yield {"type": "content", "content": final_response_text, "finish_reason": "stop"} - # Don't break - let loop complete for callbacks (official recommendation) - - # If no final response was received - if not final_response_text: - logger.warning(f"ADKAgent: No final response after {event_count} events") - yield { - "type": "error", - "content": "Agent did not produce a final response.", - "finish_reason": "error" - } - - except Exception as e: - logger.error(f"ADKAgent: Error during chat: {e}", exc_info=True) - yield { - "type": "error", - "content": f"Sorry, an error occurred: {str(e)}", - "finish_reason": "error" - } - - async def chat_completion( - self, - messages: List[Dict[str, str]], - conversation_id: str = None, - prompt_variant: Optional[str] = None - ) -> str: - """ - Get a non-streaming response from the ADK agent. - - Args: - messages: List of message dicts - conversation_id: Optional conversation ID - prompt_variant: Optional prompt variant - - Returns: - Complete response string - """ - final_content = "" - async for chunk in self.chat(messages=messages, conversation_id=conversation_id, stream=False, prompt_variant=prompt_variant): - if chunk["type"] == "content": - final_content += chunk["content"] - if chunk.get("finish_reason"): - break - - return final_content if final_content else "I couldn't generate a response." - - -@lru_cache() -def get_adk_agent(tools: tuple = None, discover_tools: bool = False) -> ADKAgent: - """ - Get cached ADK agent instance. - - Note: tools must be a tuple for caching to work. - Convert list to tuple before calling: get_adk_agent(tuple(tools)) - - Args: - tools: Tuple of tool functions (None to use discovery) - discover_tools: Whether to discover tools from registry - - Returns: - Cached ADKAgent instance - """ - tools_list = list(tools) if tools is not None else None - return ADKAgent(tools=tools_list, discover_tools=discover_tools) diff --git a/services/core-ai/src/config.py b/services/core-ai/src/config.py index b651cb2..7dc28f7 100644 --- a/services/core-ai/src/config.py +++ b/services/core-ai/src/config.py @@ -25,18 +25,18 @@ class Settings(BaseSettings): ollama_timeout: int = 300 # 5 minutes # Model Configuration - agent_model: str = "gemma2:9b-instruct-q5_K_M" # Optimized for ADK tool calling + agent_model: str = "gemma2:9b-instruct-q5_K_M" # Optimized for PydanticAI tool calling # System Prompt Variants system_prompt_variant: str = "minimal_agent" # For simple mode - adk_system_prompt_variant: str = "adk_agent" # For ADK mode + pydantic_system_prompt_variant: str = "pydantic_agent" # For PydanticAI mode # Base URL for Core API tools (e.g., system status, services) core_api_base_url: str = "http://core-api:8083/v1" # Feature Flags simple_enabled: bool = True # Enable simple endpoint - adk_enabled: bool = True # Enable ADK endpoint + pydantic_enabled: bool = True # Enable PydanticAI endpoint # Memory System Configuration memory_enabled: bool = True diff --git a/services/core-ai/src/prompts.py b/services/core-ai/src/prompts.py index 33186c2..561f3ba 100644 --- a/services/core-ai/src/prompts.py +++ b/services/core-ai/src/prompts.py @@ -7,7 +7,7 @@ This file contains minimal, clean prompts for the Core AI service. PROMPTS = { "minimal_agent": """You are a helpful assistant. You can answer questions. If you need information, use the available tools.""", - "adk_agent": """You are a system management assistant with access to powerful tools. + "pydantic_agent": """You are a system management assistant with access to powerful tools. Your capabilities: - System status monitoring diff --git a/services/core-ai/src/tools.py b/services/core-ai/src/tools.py index 950807b..ccf13fa 100644 --- a/services/core-ai/src/tools.py +++ b/services/core-ai/src/tools.py @@ -1,5 +1,5 @@ """ -Agent Tools - Google ADK-compatible tools for the Core AI agent +Agent Tools - Tools for the Core AI agent These tools make REST API calls to the Core API service. """ @@ -83,9 +83,9 @@ async def response(answer: str) -> None: logger.info("`response` tool called. Returning None to terminate agent loop.") return None -# ============================================================================ -# Tool Registry - ADK Format -# ============================================================================ +# ============================================================================ +# Tool Registry - Legacy (deprecated, use src/tools/registry.py instead) +# ============================================================================ try: from google.adk.tools import FunctionTool @@ -95,7 +95,7 @@ except ImportError: FunctionTool = None -def get_agent_tools() -> List[FunctionTool]: - """Get all tools available to the agent for the current test phase""" +def get_agent_tools() -> List: + """DEPRECATED: Get all tools available to the agent. Use src/tools/registry.py instead.""" logger.info("--- DIAGNOSTIC MODE (Phase 1): Agent has NO tools. ---") return [] \ No newline at end of file diff --git a/services/core-ai/src/tools/registry.py b/services/core-ai/src/tools/registry.py index 719b225..0628fbf 100644 --- a/services/core-ai/src/tools/registry.py +++ b/services/core-ai/src/tools/registry.py @@ -1,8 +1,8 @@ """ -Tool Registry - Manages tool registration and discovery for ADK agent. +Tool Registry - Manages tool registration and discovery for AI agents. -This module provides a central registry for ADK-compatible tools. -Tools can be registered, discovered, and provided to the ADK agent. +This module provides a central registry for tools. +Tools can be registered, discovered, and provided to AI agents. """ import logging import functools @@ -23,7 +23,7 @@ http_client = httpx.AsyncClient() # ============================================================================ -# Google ADK Integration +# Legacy ADK Integration (deprecated - kept for backwards compatibility) # ============================================================================ try: @@ -32,7 +32,6 @@ try: except ImportError: ADK_AVAILABLE = False FunctionTool = None - logger.warning("Google ADK not available - tools will not be registered") # ============================================================================ @@ -74,7 +73,7 @@ def log_tool_call(func): def register_tool(func: Callable) -> Callable: """ - Register a tool function for use with the ADK agent. + Register a tool function for use with AI agents. Usage: @register_tool @@ -105,10 +104,13 @@ def get_all_tools() -> Dict[str, Callable]: def get_agent_tools() -> List: """ - Get all tools as ADK FunctionTool objects. + DEPRECATED: Get all tools as ADK FunctionTool objects. + + This function is kept for backwards compatibility but is no longer used. + Use get_all_tools() instead for PydanticAI agents. Returns: - List of FunctionTool objects for ADK agent + List of FunctionTool objects for legacy ADK agent """ if not ADK_AVAILABLE: logger.warning("ADK not available - returning empty tool list") diff --git a/services/core-ai/tests/test_06_adk_setup.py b/services/core-ai/tests/test_06_adk_setup.py deleted file mode 100644 index 1c9eddc..0000000 --- a/services/core-ai/tests/test_06_adk_setup.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 -""" -Layer 6: ADK Setup Tests -Tests that Google ADK initializes correctly and can handle basic completions. -""" -import pytest -import sys -from pathlib import Path - -# Add parent directory to path -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from src.config import get_settings -from src.prompts import get_prompt -from src.agents import ADK_AVAILABLE - -if not ADK_AVAILABLE: - pytest.skip("Google ADK not available", allow_module_level=True) - -from src.agents import ADKAgent - - -def test_adk_import(): - """Test that ADK can be imported""" - assert ADK_AVAILABLE, "ADK should be available" - print("✓ ADK imports successful") - - -def test_adk_prompt_exists(): - """Test that ADK prompt variant exists""" - settings = get_settings() - prompt = get_prompt(settings.adk_system_prompt_variant) - - assert prompt is not None, "ADK prompt should exist" - assert len(prompt) > 0, "ADK prompt should not be empty" - assert "assistant" in prompt.lower() or "tools" in prompt.lower(), "ADK prompt should mention tools/assistant" - - print(f"✓ ADK prompt variant '{settings.adk_system_prompt_variant}' exists") - print(f" Prompt: {prompt[:100]}...") - - -def test_adk_agent_initialization(): - """Test that ADK agent can be initialized without tools""" - try: - agent = ADKAgent(tools=[]) - assert agent is not None, "Agent should be initialized" - assert agent.llm is not None, "LLM should be initialized" - assert agent.agent is not None, "ADK agent should be initialized" - assert agent.tools == [], "Tools should be empty" - - print("✓ ADK agent initialized successfully") - print(f" LLM: {agent.llm}") - print(f" Tools: {len(agent.tools)}") - - except Exception as e: - pytest.fail(f"ADK agent initialization failed: {e}") - - -@pytest.mark.asyncio -async def test_adk_simple_completion(): - """Test ADK agent with a simple question (no tools needed)""" - agent = ADKAgent(tools=[]) - - messages = [{"role": "user", "content": "What is 2+2? Answer with just the number."}] - - print(f"\n→ Testing ADK completion...") - response = await agent.chat_completion(messages=messages) - - assert response is not None, "Response should not be None" - assert len(response) > 0, "Response should not be empty" - assert "4" in response, f"Expected '4' in response, got: {response}" - - print(f"✓ ADK response: {response}") - - -@pytest.mark.asyncio -async def test_adk_streaming(): - """Test ADK agent streaming mode""" - agent = ADKAgent(tools=[]) - - messages = [{"role": "user", "content": "Count from 1 to 3. Just the numbers."}] - - chunks = [] - event_count = 0 - - print(f"\n→ Testing ADK streaming...") - async for chunk in agent.chat(messages=messages, stream=True): - event_count += 1 - if chunk.get("type") == "content" and chunk.get("content"): - chunks.append(chunk["content"]) - - full_content = "".join(chunks) - - assert event_count > 0, "Should receive events" - assert len(full_content) > 0, "Should receive content" - - print(f"✓ Received {event_count} events") - print(f"✓ Content: {full_content}") - - -@pytest.mark.asyncio -async def test_adk_capital_of_france(): - """Test ADK with the standard 'capital of France' question""" - agent = ADKAgent(tools=[]) - - messages = [{"role": "user", "content": "What is the capital of France?"}] - - print(f"\n→ Testing 'What is the capital of France?' with ADK...") - response = await agent.chat_completion(messages=messages) - - assert response is not None, "Response should not be None" - assert len(response) > 0, "Response should not be empty" - assert "paris" in response.lower(), f"Expected 'Paris' in answer, got: {response}" - - print(f"✓ Correct answer: {response}") - - -def test_adk_system_prompt_loading(): - """Test that ADK agent loads correct system prompt""" - agent = ADKAgent(tools=[]) - - settings = get_settings() - expected_prompt = get_prompt(settings.adk_system_prompt_variant) - - assert agent.system_prompt == expected_prompt, "System prompt should match config" - print(f"✓ System prompt loaded correctly") - print(f" Variant: {settings.adk_system_prompt_variant}") - - -if __name__ == "__main__": - pytest.main([__file__, "-v", "-s"]) diff --git a/services/core-ai/tests/test_06_pydantic_setup.py b/services/core-ai/tests/test_06_pydantic_setup.py new file mode 100644 index 0000000..3a9dd9c --- /dev/null +++ b/services/core-ai/tests/test_06_pydantic_setup.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +""" +Layer 6: PydanticAI Setup Tests +Tests that PydanticAI initializes correctly and can handle basic completions. +""" +import pytest +import sys +from pathlib import Path + +# Add parent directory to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.config import get_settings +from src.prompts import get_prompt +from src.agents import PYDANTIC_AI_AVAILABLE + +if not PYDANTIC_AI_AVAILABLE: + pytest.skip("PydanticAI not available", allow_module_level=True) + +from src.agents import PydanticAgent + + +def test_pydantic_import(): + """Test that PydanticAI can be imported""" + assert PYDANTIC_AI_AVAILABLE, "PydanticAI should be available" + print("✓ PydanticAI imports successful") + + +def test_pydantic_prompt_exists(): + """Test that PydanticAI prompt variant exists""" + settings = get_settings() + prompt = get_prompt(settings.pydantic_system_prompt_variant) + + assert prompt is not None, "PydanticAI prompt should exist" + assert len(prompt) > 0, "PydanticAI prompt should not be empty" + assert "assistant" in prompt.lower() or "tools" in prompt.lower(), "PydanticAI prompt should mention tools/assistant" + + print(f"✓ PydanticAI prompt variant '{settings.pydantic_system_prompt_variant}' exists") + print(f" Prompt: {prompt[:100]}...") + + +def test_pydantic_agent_initialization(): + """Test that PydanticAI agent can be initialized without tools""" + try: + agent = PydanticAgent(tools=[], enable_memory=False) + assert agent is not None, "Agent should be initialized" + assert agent.model is not None, "Model should be initialized" + assert agent.agent is not None, "PydanticAI agent should be initialized" + assert agent.tools == [], "Tools should be empty" + + print("✓ PydanticAI agent initialized successfully") + print(f" Model: {agent.model}") + print(f" Tools: {len(agent.tools)}") + + except Exception as e: + pytest.fail(f"PydanticAI agent initialization failed: {e}") + + +@pytest.mark.asyncio +async def test_pydantic_simple_completion(): + """Test PydanticAI agent with a simple question (no tools needed)""" + agent = PydanticAgent(tools=[], enable_memory=False) + + messages = [{"role": "user", "content": "What is 2+2? Answer with just the number."}] + + print(f"\n→ Testing PydanticAI completion...") + response = await agent.chat_completion(messages=messages) + + assert response is not None, "Response should not be None" + assert len(response) > 0, "Response should not be empty" + assert "4" in response, f"Expected '4' in response, got: {response}" + + print(f"✓ PydanticAI response: {response}") + + +@pytest.mark.asyncio +async def test_pydantic_streaming(): + """Test PydanticAI agent streaming mode""" + agent = PydanticAgent(tools=[], enable_memory=False) + + messages = [{"role": "user", "content": "Count from 1 to 3. Just the numbers."}] + + chunks = [] + event_count = 0 + + print(f"\n→ Testing PydanticAI streaming...") + async for chunk in agent.chat(messages=messages, stream=True): + event_count += 1 + if chunk.get("type") == "content" and chunk.get("content"): + chunks.append(chunk["content"]) + + full_content = "".join(chunks) + + assert event_count > 0, "Should receive events" + assert len(full_content) > 0, "Should receive content" + + print(f"✓ Received {event_count} events") + print(f"✓ Content: {full_content}") + + +@pytest.mark.asyncio +async def test_pydantic_capital_of_france(): + """Test PydanticAI with the standard 'capital of France' question""" + agent = PydanticAgent(tools=[], enable_memory=False) + + messages = [{"role": "user", "content": "What is the capital of France?"}] + + print(f"\n→ Testing 'What is the capital of France?' with PydanticAI...") + response = await agent.chat_completion(messages=messages) + + assert response is not None, "Response should not be None" + assert len(response) > 0, "Response should not be empty" + assert "paris" in response.lower(), f"Expected 'Paris' in answer, got: {response}" + + print(f"✓ Correct answer: {response}") + + +def test_pydantic_system_prompt_loading(): + """Test that PydanticAI agent loads correct system prompt""" + agent = PydanticAgent(tools=[], enable_memory=False) + + settings = get_settings() + expected_prompt = get_prompt(settings.pydantic_system_prompt_variant) + + assert agent.system_prompt == expected_prompt, "System prompt should match config" + print(f"✓ System prompt loaded correctly") + print(f" Variant: {settings.pydantic_system_prompt_variant}") + + +if __name__ == "__main__": + pytest.main([__file__, "-v", "-s"]) diff --git a/services/core-ai/tests/test_07_adk_tools.py b/services/core-ai/tests/test_07_pydantic_tools.py similarity index 75% rename from services/core-ai/tests/test_07_adk_tools.py rename to services/core-ai/tests/test_07_pydantic_tools.py index 057dfce..028b894 100644 --- a/services/core-ai/tests/test_07_adk_tools.py +++ b/services/core-ai/tests/test_07_pydantic_tools.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ -Layer 7: ADK Tools Tests -Tests that local tools are registered and work with the ADK agent. +Layer 7: PydanticAI Tools Tests +Tests that local tools are registered and work with the PydanticAI agent. """ import pytest import sys @@ -11,13 +11,13 @@ from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) from src.config import get_settings -from src.tools import get_all_tools, get_agent_tools, clear_registry -from src.agents import ADK_AVAILABLE +from src.tools.registry import get_all_tools, clear_registry +from src.agents import PYDANTIC_AI_AVAILABLE -if not ADK_AVAILABLE: - pytest.skip("Google ADK not available", allow_module_level=True) +if not PYDANTIC_AI_AVAILABLE: + pytest.skip("PydanticAI not available", allow_module_level=True) -from src.agents import ADKAgent +from src.agents import PydanticAgent def test_local_tools_registered(): @@ -82,37 +82,22 @@ async def test_calculator_security(): print(f"✓ Blocked dangerous expression: {expr}") -def test_adk_tool_conversion(): - """Test that tools can be converted to ADK format""" - adk_tools = get_agent_tools() - - assert len(adk_tools) > 0, "Should have at least some tools" - print(f"✓ Converted {len(adk_tools)} tools to ADK format") - - # All tools should be FunctionTool instances - from google.adk.tools import FunctionTool - for tool in adk_tools: - assert isinstance(tool, FunctionTool), f"Tool should be FunctionTool, got {type(tool)}" - - print(f"✓ All tools are valid FunctionTool instances") - - @pytest.mark.asyncio -async def test_adk_agent_with_tools(): - """Test ADK agent initialization with tools""" - agent = ADKAgent(discover_tools=True) +async def test_pydantic_agent_with_tools(): + """Test PydanticAI agent initialization with tools""" + agent = PydanticAgent(discover_tools=True, enable_memory=False) assert len(agent.tools) > 0, "Agent should have tools" - print(f"✓ ADK agent initialized with {len(agent.tools)} tools") + print(f"✓ PydanticAI agent initialized with {len(agent.tools)} tools") @pytest.mark.asyncio async def test_tool_calling_integration(): - """Test that ADK agent can use tools to answer questions""" - agent = ADKAgent(discover_tools=True) + """Test that PydanticAI agent can use tools to answer questions""" + agent = PydanticAgent(discover_tools=True, enable_memory=False) # Ask a question that requires the calculator tool - messages = [{"role": "user", "content": "What is 15 + 27? Use the calculator tool."}] + messages = [{"role": "user", "content": "What is 15 + 27? Use the calculate tool."}] print(f"\n→ Testing tool calling with: {messages[0]['content']}") response = await agent.chat_completion(messages=messages) diff --git a/services/core-ai/tests/test_10_adk_api.py b/services/core-ai/tests/test_10_pydantic_api.py similarity index 77% rename from services/core-ai/tests/test_10_adk_api.py rename to services/core-ai/tests/test_10_pydantic_api.py index c9830fb..e6fc29c 100644 --- a/services/core-ai/tests/test_10_adk_api.py +++ b/services/core-ai/tests/test_10_pydantic_api.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ -Layer 10: ADK API Tests -Tests HTTP endpoints for both simple and ADK agents. +Layer 10: PydanticAI API Tests +Tests HTTP endpoints for both simple and PydanticAI agents. """ import pytest import sys @@ -11,7 +11,7 @@ from pathlib import Path # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent)) -from src.agents import ADK_AVAILABLE +from src.agents import PYDANTIC_AI_AVAILABLE # Base URL for the service BASE_URL = "http://localhost:8086" @@ -54,8 +54,8 @@ async def test_list_tools(): @pytest.mark.asyncio -async def test_chat_completions_simple(): - """Test /v1/chat/completions endpoint (default)""" +async def test_chat_completions_default(): + """Test /v1/chat/completions endpoint (default - should use PydanticAI)""" async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( f"{BASE_URL}/v1/chat/completions", @@ -104,14 +104,14 @@ async def test_chat_simple_endpoint(): @pytest.mark.asyncio -async def test_chat_adk_endpoint(): - """Test /v1/chat/adk endpoint""" - if not ADK_AVAILABLE: - pytest.skip("ADK not available") +async def test_chat_pydantic_endpoint(): + """Test /v1/chat/pydantic endpoint""" + if not PYDANTIC_AI_AVAILABLE: + pytest.skip("PydanticAI not available") async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( - f"{BASE_URL}/v1/chat/adk", + f"{BASE_URL}/v1/chat/pydantic", json={ "messages": [ {"role": "user", "content": "What is the current date?"} @@ -124,26 +124,26 @@ async def test_chat_adk_endpoint(): assert response.status_code == 200 data = response.json() - assert data["model"] == "adk" + assert data["model"] == "pydantic" assert "choices" in data assert "tools_enabled" in data assert "tools_count" in data content = data["choices"][0]["message"]["content"] - print(f"✓ /v1/chat/adk response: {content[:200]}...") + print(f"✓ /v1/chat/pydantic response: {content[:200]}...") print(f" Tools enabled: {data['tools_enabled']}") print(f" Tools count: {data['tools_count']}") @pytest.mark.asyncio -async def test_chat_adk_with_calculator(): - """Test ADK endpoint using calculator tool""" - if not ADK_AVAILABLE: - pytest.skip("ADK not available") +async def test_chat_pydantic_with_calculator(): + """Test PydanticAI endpoint using calculator tool""" + if not PYDANTIC_AI_AVAILABLE: + pytest.skip("PydanticAI not available") async with httpx.AsyncClient(timeout=60.0) as client: response = await client.post( - f"{BASE_URL}/v1/chat/adk", + f"{BASE_URL}/v1/chat/pydantic", json={ "messages": [ {"role": "user", "content": "What is 123 + 456? Use the calculate tool."} @@ -157,7 +157,7 @@ async def test_chat_adk_with_calculator(): data = response.json() content = data["choices"][0]["message"]["content"] - print(f"✓ ADK with calculator: {content}") + print(f"✓ PydanticAI with calculator: {content}") @pytest.mark.asyncio @@ -198,14 +198,14 @@ async def test_streaming_simple(): @pytest.mark.asyncio -async def test_adk_without_tools(): - """Test ADK endpoint with tools disabled""" - if not ADK_AVAILABLE: - pytest.skip("ADK not available") +async def test_pydantic_without_tools(): + """Test PydanticAI endpoint with tools disabled""" + if not PYDANTIC_AI_AVAILABLE: + pytest.skip("PydanticAI not available") async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( - f"{BASE_URL}/v1/chat/adk", + f"{BASE_URL}/v1/chat/pydantic", json={ "messages": [ {"role": "user", "content": "Hello!"} @@ -221,7 +221,26 @@ async def test_adk_without_tools(): assert data["tools_enabled"] is False assert data["tools_count"] == 0 - print(f"✓ ADK without tools works") + print(f"✓ PydanticAI without tools works") + + +@pytest.mark.asyncio +async def test_list_models(): + """Test the /v1/models endpoint""" + async with httpx.AsyncClient() as client: + response = await client.get(f"{BASE_URL}/v1/models") + assert response.status_code == 200 + + data = response.json() + assert "object" in data + assert data["object"] == "list" + assert "data" in data + assert len(data["data"]) > 0 + + print(f"✓ Models endpoint OK") + print(f" Available models:") + for model in data["data"]: + print(f" - {model['id']}") if __name__ == "__main__": diff --git a/test-headers.html b/test-headers.html deleted file mode 100644 index 9d5c943..0000000 --- a/test-headers.html +++ /dev/null @@ -1,62 +0,0 @@ - - -
-This page will request headers from your current session
-