serviceCenter = DI-Container (Resolver, Registry, Context) fuer Service-Instanziierung serviceHub = Consumer-facing Aggregation (DB-Interfaces, Runtime-State, lazy Service-Resolution via serviceCenter) - modules/serviceHub/ erstellt: ServiceHub, PublicService, getInterface() - 22 Consumer-Dateien migriert (routes, features, tests): imports von modules.services auf serviceHub bzw. serviceCenter umgestellt - resolver.py: legacy fallback auf altes services/ entfernt - modules/services/ komplett geloescht (83 Dateien inkl. dead code mainAiChat.py) - pre-extraction: progress callback durch chunk-pipeline propagiert, operationType DATA_EXTRACT->DATA_ANALYSE fuer guenstigeres Modell
505 lines
20 KiB
Python
505 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
AI Model Selection Test - Prints prioritized fallback model lists for all interface calls
|
|
|
|
Tests all main interface methods in interfaceAiObjects.py and shows which models
|
|
are selected for each type of AI operation (text generation, image analysis,
|
|
image generation, web research, etc.).
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
import base64
|
|
|
|
# Add the gateway to path (go up 2 levels from tests/functional/)
|
|
_gateway_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
if _gateway_path not in sys.path:
|
|
sys.path.insert(0, _gateway_path)
|
|
|
|
from modules.serviceHub import getInterface as getServices
|
|
from modules.datamodels.datamodelAi import (
|
|
AiCallOptions,
|
|
AiCallRequest,
|
|
AiModelCall,
|
|
OperationTypeEnum,
|
|
PriorityEnum,
|
|
ProcessingModeEnum,
|
|
)
|
|
from modules.datamodels.datamodelUam import User
|
|
from modules.aicore.aicoreModelRegistry import modelRegistry
|
|
from modules.aicore.aicoreModelSelector import modelSelector
|
|
|
|
|
|
class ModelSelectionTester:
|
|
def __init__(self) -> None:
|
|
testUser = User(
|
|
id="test_user_models",
|
|
username="test_models",
|
|
email="test@example.com",
|
|
fullName="Test Models",
|
|
language="en",
|
|
mandateId="test_mandate",
|
|
)
|
|
self.services = getServices(testUser, None)
|
|
|
|
async def initialize(self) -> None:
|
|
from modules.serviceCenter.services.serviceAi.mainServiceAi import AiService
|
|
from modules.interfaces.interfaceAiObjects import AiObjects
|
|
|
|
self.services.ai = await AiService.create(self.services)
|
|
self.aiObjects = await AiObjects.create()
|
|
|
|
async def _printFallbackListWithContext(self, title: str, prompt: str, context: str, options: AiCallOptions) -> None:
|
|
print(f"\n{'='*80}")
|
|
print(f"{title}")
|
|
print(f"{'='*80}")
|
|
print(
|
|
f"Operation={options.operationType.name}, Priority={options.priority.name}, ProcessingMode={options.processingMode.name}"
|
|
)
|
|
|
|
# Show context and prompt sizes
|
|
promptSize = len(prompt.encode("utf-8"))
|
|
contextSize = len(context.encode("utf-8"))
|
|
totalSize = promptSize + contextSize
|
|
print(f"Prompt size: {promptSize} bytes, Context size: {contextSize} bytes, Total: {totalSize} bytes")
|
|
|
|
availableModels = modelRegistry.getAvailableModels()
|
|
failoverModelList = modelSelector.getFailoverModelList(
|
|
prompt=prompt,
|
|
context=context,
|
|
options=options,
|
|
availableModels=availableModels,
|
|
)
|
|
|
|
if not failoverModelList:
|
|
print("No suitable models found (capability filter returned empty list).")
|
|
return
|
|
|
|
print("Prioritized fallback model sequence (name | quality | speed | $/1k in | ctx | score):")
|
|
for idx, m in enumerate(failoverModelList, 1):
|
|
costIn = getattr(m, "costPer1kTokensInput", 0.0)
|
|
# Calculate detailed score breakdown
|
|
promptSize = len(prompt.encode("utf-8"))
|
|
contextSize = len(context.encode("utf-8"))
|
|
totalSize = promptSize + contextSize
|
|
|
|
# Get detailed scoring
|
|
sizeRating = modelSelector._getSizeRating(m, totalSize)
|
|
processingModeRating = modelSelector._getProcessingModeRating(m.processingMode, options.processingMode)
|
|
priorityRating = modelSelector._getPriorityRating(m, options.priority)
|
|
totalScore = sizeRating + processingModeRating + priorityRating
|
|
|
|
print(
|
|
f" {idx:>2}. {m.displayName} | Q={getattr(m, 'qualityRating', 0)} | S={getattr(m, 'speedRating', 0)} | ${costIn:.4f} | ctx={getattr(m, 'contextLength', 0)} | score={totalScore:.3f}"
|
|
)
|
|
print(f" Size: {sizeRating:.3f}, ProcessingMode: {processingModeRating:.3f}, Priority: {priorityRating:.3f}")
|
|
|
|
async def _printFallbackList(self, title: str, prompt: str, options: AiCallOptions) -> None:
|
|
print(f"\n{'='*80}")
|
|
print(f"{title}")
|
|
print(f"{'='*80}")
|
|
print(
|
|
f"Operation={options.operationType.name}, Priority={options.priority.name}, ProcessingMode={options.processingMode.name}"
|
|
)
|
|
|
|
# Show context and prompt sizes
|
|
context = "" # Currently using empty context
|
|
promptSize = len(prompt.encode("utf-8"))
|
|
contextSize = len(context.encode("utf-8"))
|
|
totalSize = promptSize + contextSize
|
|
print(f"Prompt size: {promptSize} bytes, Context size: {contextSize} bytes, Total: {totalSize} bytes")
|
|
|
|
availableModels = modelRegistry.getAvailableModels()
|
|
failoverModelList = modelSelector.getFailoverModelList(
|
|
prompt=prompt,
|
|
context=context,
|
|
options=options,
|
|
availableModels=availableModels,
|
|
)
|
|
|
|
if not failoverModelList:
|
|
print("No suitable models found (capability filter returned empty list).")
|
|
return
|
|
|
|
print("Prioritized fallback model sequence (name | quality | speed | $/1k in | ctx | score):")
|
|
for idx, m in enumerate(failoverModelList, 1):
|
|
costIn = getattr(m, "costPer1kTokensInput", 0.0)
|
|
# Calculate detailed score breakdown
|
|
promptSize = len(prompt.encode("utf-8"))
|
|
contextSize = len(context.encode("utf-8"))
|
|
totalSize = promptSize + contextSize
|
|
|
|
# Get detailed scoring
|
|
sizeRating = modelSelector._getSizeRating(m, totalSize)
|
|
processingModeRating = modelSelector._getProcessingModeRating(m.processingMode, options.processingMode)
|
|
priorityRating = modelSelector._getPriorityRating(m, options.priority)
|
|
totalScore = sizeRating + processingModeRating + priorityRating
|
|
|
|
print(
|
|
f" {idx:>2}. {m.displayName} | Q={getattr(m, 'qualityRating', 0)} | S={getattr(m, 'speedRating', 0)} | ${costIn:.4f} | ctx={getattr(m, 'contextLength', 0)} | score={totalScore:.3f}"
|
|
)
|
|
print(f" Size: {sizeRating:.3f}, ProcessingMode: {processingModeRating:.3f}, Priority: {priorityRating:.3f}")
|
|
|
|
async def run(self) -> None:
|
|
"""Test model selection for all interface methods."""
|
|
print("=" * 100)
|
|
print("AI INTERFACE MODEL SELECTION TEST")
|
|
print("=" * 100)
|
|
print("Testing model selection for all interface methods in interfaceAiObjects.py")
|
|
print("=" * 100)
|
|
|
|
# Test 1: Text Generation (call method)
|
|
await self._testTextGeneration()
|
|
|
|
# Test 2: Image Analysis (callImage method)
|
|
await self._testImageAnalysis()
|
|
|
|
# Test 3: Image Generation (generateImage method)
|
|
await self._testImageGeneration()
|
|
|
|
# Test 4: Web Search (searchWebsites method)
|
|
await self._testWebSearch()
|
|
|
|
# Test 5: Web Crawling (crawlWebsites method)
|
|
await self._testWebCrawling()
|
|
|
|
# Test 6: Web Research (webQuery method)
|
|
await self._testWebResearch()
|
|
|
|
# Test 7: Content Analysis with Chunking
|
|
await self._testContentAnalysis()
|
|
|
|
# Test 8: Website Selection
|
|
await self._testWebsiteSelection()
|
|
|
|
# Test 9: Actual Interface Calls
|
|
await self._testActualInterfaceCalls()
|
|
|
|
# Show model registry summary
|
|
await self._showModelSummary()
|
|
|
|
print("\n" + "=" * 100)
|
|
print("ALL INTERFACE TESTS COMPLETED")
|
|
print("=" * 100)
|
|
|
|
async def _testTextGeneration(self) -> None:
|
|
"""Test model selection for text generation calls."""
|
|
print(f"\n{'='*80}")
|
|
print("1. TEXT GENERATION (call method)")
|
|
print(f"{'='*80}")
|
|
|
|
# Test different text generation scenarios
|
|
scenarios = [
|
|
("Text Analysis", "Write a summary about artificial intelligence trends.", OperationTypeEnum.DATA_ANALYSE),
|
|
("Planning Task", "Create a project plan for software development.", OperationTypeEnum.PLAN),
|
|
("Analysis Task", "Analyze the pros and cons of cloud computing.", OperationTypeEnum.DATA_ANALYSE),
|
|
]
|
|
|
|
for title, prompt, operation_type in scenarios:
|
|
options = AiCallOptions(
|
|
operationType=operation_type,
|
|
priority=PriorityEnum.BALANCED,
|
|
processingMode=ProcessingModeEnum.ADVANCED,
|
|
maxCost=0.05,
|
|
maxProcessingTime=30,
|
|
)
|
|
await self._printFallbackList(f" {title}", prompt, options)
|
|
|
|
async def _testImageAnalysis(self) -> None:
|
|
"""Test model selection for image analysis calls."""
|
|
print(f"\n{'='*80}")
|
|
print("2. IMAGE ANALYSIS (callImage method)")
|
|
print(f"{'='*80}")
|
|
|
|
# Create a small test image (1x1 pixel PNG)
|
|
test_image_data = base64.b64encode(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\nIDATx\x9cc```\x00\x00\x00\x04\x00\x01\xdd\x8d\xb4\x1c\x00\x00\x00\x00IEND\xaeB`\x82').decode('utf-8')
|
|
|
|
options = AiCallOptions(
|
|
operationType=OperationTypeEnum.IMAGE_ANALYSE,
|
|
priority=PriorityEnum.BALANCED,
|
|
processingMode=ProcessingModeEnum.ADVANCED,
|
|
maxCost=0.02,
|
|
maxProcessingTime=20,
|
|
)
|
|
|
|
prompt = "Describe what you see in this image."
|
|
await self._printFallbackList(" Image Analysis", prompt, options)
|
|
|
|
async def _testImageGeneration(self) -> None:
|
|
"""Test model selection for image generation calls."""
|
|
print(f"\n{'='*80}")
|
|
print("3. IMAGE GENERATION (generateImage method)")
|
|
print(f"{'='*80}")
|
|
|
|
options = AiCallOptions(
|
|
operationType=OperationTypeEnum.IMAGE_GENERATE,
|
|
priority=PriorityEnum.QUALITY,
|
|
processingMode=ProcessingModeEnum.DETAILED,
|
|
maxCost=0.10,
|
|
maxProcessingTime=60,
|
|
)
|
|
|
|
prompt = "A futuristic cityscape with flying cars and neon lights."
|
|
await self._printFallbackList(" Image Generation", prompt, options)
|
|
|
|
async def _testWebResearch(self) -> None:
|
|
"""Test model selection for web research calls."""
|
|
print(f"\n{'='*80}")
|
|
print("6. WEB RESEARCH (webQuery method)")
|
|
print(f"{'='*80}")
|
|
|
|
options = AiCallOptions(
|
|
operationType=OperationTypeEnum.WEB_SEARCH_DATA,
|
|
priority=PriorityEnum.BALANCED,
|
|
processingMode=ProcessingModeEnum.ADVANCED,
|
|
maxCost=0.05,
|
|
maxProcessingTime=30,
|
|
)
|
|
|
|
prompt = "What are the latest trends in artificial intelligence?"
|
|
await self._printFallbackList(" Web Research", prompt, options)
|
|
|
|
async def _testWebSearch(self) -> None:
|
|
"""Test model selection for web search calls."""
|
|
print(f"\n{'='*80}")
|
|
print("4. WEB SEARCH (searchWebsites method)")
|
|
print(f"{'='*80}")
|
|
|
|
options = AiCallOptions(
|
|
operationType=OperationTypeEnum.WEB_SEARCH_DATA,
|
|
priority=PriorityEnum.BALANCED,
|
|
processingMode=ProcessingModeEnum.BASIC,
|
|
maxCost=0.01,
|
|
maxProcessingTime=30,
|
|
)
|
|
|
|
prompt = "Search for artificial intelligence companies"
|
|
await self._printFallbackList(" Web Search", prompt, options)
|
|
|
|
async def _testWebCrawling(self) -> None:
|
|
"""Test model selection for web crawling calls."""
|
|
print(f"\n{'='*80}")
|
|
print("5. WEB CRAWLING (crawlWebsites method)")
|
|
print(f"{'='*80}")
|
|
|
|
options = AiCallOptions(
|
|
operationType=OperationTypeEnum.WEB_CRAWL,
|
|
priority=PriorityEnum.BALANCED,
|
|
processingMode=ProcessingModeEnum.BASIC,
|
|
maxCost=0.02,
|
|
maxProcessingTime=60,
|
|
)
|
|
|
|
prompt = "Crawl content from these URLs"
|
|
await self._printFallbackList(" Web Crawling", prompt, options)
|
|
|
|
async def _testContentAnalysis(self) -> None:
|
|
"""Test model selection for content analysis with chunking."""
|
|
print(f"\n{'='*80}")
|
|
print("7. CONTENT ANALYSIS WITH CHUNKING")
|
|
print(f"{'='*80}")
|
|
|
|
# Test with large content to trigger chunking
|
|
large_content = {
|
|
"https://example.com/page1": "This is a large document about artificial intelligence. " * 1000,
|
|
"https://example.com/page2": "This is another large document about machine learning. " * 1000,
|
|
}
|
|
|
|
options = AiCallOptions(
|
|
operationType=OperationTypeEnum.DATA_ANALYSE,
|
|
priority=PriorityEnum.BALANCED,
|
|
processingMode=ProcessingModeEnum.ADVANCED,
|
|
maxCost=0.10,
|
|
maxProcessingTime=60,
|
|
)
|
|
|
|
prompt = "Analyze this content and provide key insights."
|
|
await self._printFallbackList(" Content Analysis", prompt, options)
|
|
|
|
async def _testWebsiteSelection(self) -> None:
|
|
"""Test model selection for website selection."""
|
|
print(f"\n{'='*80}")
|
|
print("8. WEBSITE SELECTION (selectRelevantWebsites method)")
|
|
print(f"{'='*80}")
|
|
|
|
# This method uses webQuery internally, so it uses the same model selection as web research
|
|
options = AiCallOptions(
|
|
operationType=OperationTypeEnum.WEB_SEARCH_DATA,
|
|
priority=PriorityEnum.BALANCED,
|
|
processingMode=ProcessingModeEnum.ADVANCED,
|
|
maxCost=0.03,
|
|
maxProcessingTime=20,
|
|
)
|
|
|
|
prompt = "Select the most relevant websites from this list for AI research."
|
|
await self._printFallbackList(" Website Selection", prompt, options)
|
|
|
|
async def _testActualInterfaceCalls(self) -> None:
|
|
"""Test actual interface calls to show real model selection."""
|
|
print(f"\n{'='*80}")
|
|
print("9. ACTUAL INTERFACE CALLS (Real Model Selection)")
|
|
print(f"{'='*80}")
|
|
|
|
# Test 1: Text generation call
|
|
print("\n Testing: aiObjects.call() - Text Generation")
|
|
try:
|
|
request = AiCallRequest(
|
|
prompt="Write a short summary about machine learning.",
|
|
context="",
|
|
options=AiCallOptions(
|
|
operationType=OperationTypeEnum.DATA_ANALYSE,
|
|
priority=PriorityEnum.BALANCED,
|
|
processingMode=ProcessingModeEnum.ADVANCED,
|
|
maxCost=0.05,
|
|
maxProcessingTime=30,
|
|
)
|
|
)
|
|
|
|
# Get the model selection that would be used
|
|
availableModels = modelRegistry.getAvailableModels()
|
|
failoverModelList = modelSelector.getFailoverModelList(
|
|
prompt=request.prompt,
|
|
context=request.context,
|
|
options=request.options,
|
|
availableModels=availableModels,
|
|
)
|
|
|
|
if failoverModelList:
|
|
print(f" Selected model: {failoverModelList[0].displayName}")
|
|
print(f" Fallback models: {[m.displayName for m in failoverModelList[1:3]]}")
|
|
else:
|
|
print(" No suitable models found")
|
|
|
|
except Exception as e:
|
|
print(f" Error: {e}")
|
|
|
|
# Test 2: Image analysis call
|
|
print("\n Testing: aiObjects.callImage() - Image Analysis")
|
|
try:
|
|
options = AiCallOptions(
|
|
operationType=OperationTypeEnum.IMAGE_ANALYSE,
|
|
priority=PriorityEnum.BALANCED,
|
|
processingMode=ProcessingModeEnum.ADVANCED,
|
|
maxCost=0.02,
|
|
maxProcessingTime=20,
|
|
)
|
|
|
|
availableModels = modelRegistry.getAvailableModels()
|
|
failoverModelList = modelSelector.getFailoverModelList(
|
|
prompt="Describe this image",
|
|
context="",
|
|
options=options,
|
|
availableModels=availableModels,
|
|
)
|
|
|
|
if failoverModelList:
|
|
print(f" Selected model: {failoverModelList[0].displayName}")
|
|
print(f" Fallback models: {[m.displayName for m in failoverModelList[1:3]]}")
|
|
else:
|
|
print(" No suitable models found")
|
|
|
|
except Exception as e:
|
|
print(f" Error: {e}")
|
|
|
|
# Test 3: Image generation call
|
|
print("\n Testing: aiObjects.generateImage() - Image Generation")
|
|
try:
|
|
options = AiCallOptions(
|
|
operationType=OperationTypeEnum.IMAGE_GENERATE,
|
|
priority=PriorityEnum.QUALITY,
|
|
processingMode=ProcessingModeEnum.DETAILED,
|
|
maxCost=0.10,
|
|
maxProcessingTime=60,
|
|
)
|
|
|
|
availableModels = modelRegistry.getAvailableModels()
|
|
failoverModelList = modelSelector.getFailoverModelList(
|
|
prompt="A futuristic cityscape",
|
|
context="",
|
|
options=options,
|
|
availableModels=availableModels,
|
|
)
|
|
|
|
if failoverModelList:
|
|
print(f" Selected model: {failoverModelList[0].displayName}")
|
|
print(f" Fallback models: {[m.displayName for m in failoverModelList[1:3]]}")
|
|
else:
|
|
print(" No suitable models found")
|
|
|
|
except Exception as e:
|
|
print(f" Error: {e}")
|
|
|
|
# Test 4: Web research call
|
|
print("\n Testing: aiObjects.webQuery() - Web Research")
|
|
try:
|
|
options = AiCallOptions(
|
|
operationType=OperationTypeEnum.WEB_SEARCH_DATA,
|
|
priority=PriorityEnum.BALANCED,
|
|
processingMode=ProcessingModeEnum.ADVANCED,
|
|
maxCost=0.05,
|
|
maxProcessingTime=30,
|
|
)
|
|
|
|
availableModels = modelRegistry.getAvailableModels()
|
|
failoverModelList = modelSelector.getFailoverModelList(
|
|
prompt="What are AI trends?",
|
|
context="",
|
|
options=options,
|
|
availableModels=availableModels,
|
|
)
|
|
|
|
if failoverModelList:
|
|
print(f" Selected model: {failoverModelList[0].displayName}")
|
|
print(f" Fallback models: {[m.displayName for m in failoverModelList[1:3]]}")
|
|
else:
|
|
print(" No suitable models found")
|
|
|
|
except Exception as e:
|
|
print(f" Error: {e}")
|
|
|
|
async def _showModelSummary(self) -> None:
|
|
"""Show summary of all available models and their capabilities."""
|
|
print(f"\n{'='*80}")
|
|
print("MODEL REGISTRY SUMMARY")
|
|
print(f"{'='*80}")
|
|
|
|
availableModels = modelRegistry.getAvailableModels()
|
|
print(f"Total models available: {len(availableModels)}")
|
|
|
|
# Group by connector type
|
|
by_connector = {}
|
|
for model in availableModels:
|
|
connector_type = getattr(model, 'connectorType', 'unknown')
|
|
if connector_type not in by_connector:
|
|
by_connector[connector_type] = []
|
|
by_connector[connector_type].append(model)
|
|
|
|
print(f"\nModels by connector type:")
|
|
for connector_type, models in by_connector.items():
|
|
print(f" {connector_type}: {len(models)} models")
|
|
for model in models:
|
|
capabilities = getattr(model, 'capabilities', [])
|
|
print(f" - {model.displayName}: {capabilities}")
|
|
|
|
# Show operation type support
|
|
print(f"\nOperation type support:")
|
|
for op_type in OperationTypeEnum:
|
|
supported_models = [m for m in availableModels if hasattr(m, 'operationTypes') and any(ot.operationType == op_type for ot in m.operationTypes)]
|
|
print(f" {op_type.name}: {len(supported_models)} models")
|
|
if supported_models:
|
|
model_names = [m.displayName for m in supported_models[:3]] # Show first 3 models
|
|
print(f" Models: {', '.join(model_names)}")
|
|
|
|
|
|
async def main() -> None:
|
|
tester = ModelSelectionTester()
|
|
await tester.initialize()
|
|
await tester.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|
|
|