aicorePluginMistral erstellt und bereit für API key und testing
This commit is contained in:
parent
8bb1611efb
commit
667c432485
4 changed files with 285 additions and 0 deletions
|
|
@ -47,6 +47,7 @@ Connector_AiAnthropic_API_SECRET = DEV_ENC:Z0FBQUFBQm8xSUpENmFBWG16STFQUVZxNzZZR
|
|||
Connector_AiPerplexity_API_SECRET = DEV_ENC:Z0FBQUFBQm82Mzk2Q1MwZ0dNcUVBcUtuRDJIcTZkMXVvYnpjM3JEMzJiT1NKSHljX282ZDIyZTJYc09VSTdVNXAtOWU2UXp5S193NTk5dHJsWlFjRjhWektFOG1DVGY4ZUhHTXMzS0RPN1lNcF9nSlVWbW5BZ1hkZDVTejl6bVZNRFVvX29xamJidWRFMmtjQmkyRUQ2RUh6UTN1aWNPSUJBPT0=
|
||||
Connector_AiTavily_API_SECRET = DEV_ENC:Z0FBQUFBQm8xSUpEQTdnUHMwd2pIaXNtMmtCTFREd0pyQXRKb1F5eGtHSnkyOGZiUnlBOFc0b3Vzcndrc3ViRm1nMDJIOEZKYWxqdWNkZGh5N0Z4R0JlQmxXSG5pVnJUR2VYckZhMWNMZ1FNeXJ3enJLVlpiblhOZTNleUg3ZzZyUzRZanFSeDlVMkI=
|
||||
Connector_AiPrivateLlm_API_SECRET = jL4vyNfh_tv4rxoRaHKW88sVWNHbj32GsxuKE2A8bf0
|
||||
Connector_AiMistral_API_SECRET = your_mistral_api_key_here
|
||||
|
||||
# Microsoft Service Configuration
|
||||
Service_MSFT_CLIENT_ID = c7e7112d-61dc-4f3a-8cd3-08cc4cd7504c
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ Connector_AiAnthropic_API_SECRET = INT_ENC:Z0FBQUFBQm8xSVRjT1ZlRWVJdVZMT3ljSFJDc
|
|||
Connector_AiPerplexity_API_SECRET = INT_ENC:Z0FBQUFBQm82Mzk2UWZJdUFhSW8yc3RKc0tKRXphd0xWMkZOVlFpSGZ4SGhFWnk0cTF5VjlKQVZjdS1QSWdkS0pUSWw4OFU5MjUxdTVQel9aeWVIZTZ5TXRuVmFkZG0zWEdTOGdHMHpsTzI0TGlWYURKU1Q0VVpKTlhxUk5FTmN6SUJScDZ3ZldIaUJZcWpaQVRiSEpyQm9tRTNDWk9KTnZBPT0=
|
||||
Connector_AiTavily_API_SECRET = INT_ENC:Z0FBQUFBQm8xSVRkdkJMTDY0akhXNzZDWHVYSEt1cDZoOWEzSktneHZEV2JndTNmWlNSMV9KbFNIZmQzeVlrNE5qUEIwcUlBSGM1a0hOZ3J6djIyOVhnZzI3M1dIUkdicl9FVXF3RGktMmlEYmhnaHJfWTdGUkktSXVUSGdQMC1vSEV6VE8zR2F1SVk=
|
||||
Connector_AiPrivateLlm_API_SECRET = jL4vyNfh_tv4rxoRaHKW88sVWNHbj32GsxuKE2A8bf0
|
||||
Connector_AiMistral_API_SECRET = your_mistral_api_key_here
|
||||
|
||||
# Microsoft Service Configuration
|
||||
Service_MSFT_CLIENT_ID = c7e7112d-61dc-4f3a-8cd3-08cc4cd7504c
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ Connector_AiAnthropic_API_SECRET = PROD_ENC:Z0FBQUFBQnBDM1Z3TnhYdlhSLW5RbXJyMHFX
|
|||
Connector_AiPerplexity_API_SECRET = PROD_ENC:Z0FBQUFBQm82Mzk2Q1FGRkJEUkI4LXlQbHYzT2RkdVJEcmM4WGdZTWpJTEhoeUF1NW5LUVpJdDBYN3k1WFN4a2FQSWJSQmd0U0xJbzZDTmFFN05FcXl0Z3V1OEpsZjYydV94TXVjVjVXRTRYSWdLMkd5XzZIbFV6emRCZHpuOUpQeThadE5xcDNDVGV1RHJrUEN0c1BBYXctZFNWcFRuVXhRPT0=
|
||||
Connector_AiTavily_API_SECRET = PROD_ENC:Z0FBQUFBQnBDM1Z3NmItcDh6V0JpcE5Jc0NlUWZqcmllRHB5eDlNZmVnUlNVenhNTm5xWExzbjJqdE1GZ0hTSUYtb2dvdWNhTnlQNmVWQ2NGVDgwZ0MwMWZBMlNKWEhzdlF3TlZzTXhCZWM4Z1Uwb18tSTRoU1JBVTVkSkJHOTJwX291b3dPaVphVFg=
|
||||
Connector_AiPrivateLlm_API_SECRET = jL4vyNfh_tv4rxoRaHKW88sVWNHbj32GsxuKE2A8bf0
|
||||
Connector_AiMistral_API_SECRET = your_mistral_api_key_here
|
||||
|
||||
# Microsoft Service Configuration
|
||||
Service_MSFT_CLIENT_ID = c7e7112d-61dc-4f3a-8cd3-08cc4cd7504c
|
||||
|
|
|
|||
282
modules/aicore/aicorePluginMistral.py
Normal file
282
modules/aicore/aicorePluginMistral.py
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
import logging
|
||||
import httpx
|
||||
from typing import List
|
||||
from fastapi import HTTPException
|
||||
from modules.shared.configuration import APP_CONFIG
|
||||
from .aicoreBase import BaseConnectorAi
|
||||
from modules.datamodels.datamodelAi import AiModel, PriorityEnum, ProcessingModeEnum, OperationTypeEnum, AiModelCall, AiModelResponse, createOperationTypeRatings
|
||||
|
||||
# Configure logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ContextLengthExceededException(Exception):
|
||||
"""Exception raised when the context length exceeds the model's limit"""
|
||||
pass
|
||||
|
||||
class RateLimitExceededException(Exception):
|
||||
"""Exception raised when the provider's rate limit (TPM) is exceeded"""
|
||||
pass
|
||||
|
||||
def loadConfigData():
|
||||
"""Load configuration data for Mistral connector"""
|
||||
return {
|
||||
"apiKey": APP_CONFIG.get('Connector_AiMistral_API_SECRET'),
|
||||
}
|
||||
|
||||
class AiMistral(BaseConnectorAi):
|
||||
"""Connector for communication with the Mistral AI API (Le Chat Mistral)."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# Load configuration
|
||||
self.config = loadConfigData()
|
||||
self.apiKey = self.config["apiKey"]
|
||||
|
||||
# HttpClient for API calls
|
||||
# Timeout set to 600 seconds (10 minutes) for complex requests that may take longer
|
||||
# AiService calls can take significantly longer due to prompt building and processing overhead
|
||||
self.httpClient = httpx.AsyncClient(
|
||||
timeout=600.0,
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.apiKey}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
)
|
||||
logger.info("Mistral Connector initialized")
|
||||
|
||||
def getConnectorType(self) -> str:
|
||||
"""Get the connector type identifier."""
|
||||
return "mistral"
|
||||
|
||||
def getModels(self) -> List[AiModel]:
|
||||
"""Get all available Mistral models."""
|
||||
return [
|
||||
AiModel(
|
||||
name="mistral-large-latest",
|
||||
displayName="Mistral Large 3",
|
||||
connectorType="mistral",
|
||||
apiUrl="https://api.mistral.ai/v1/chat/completions",
|
||||
temperature=0.2,
|
||||
maxTokens=16384,
|
||||
contextLength=256000,
|
||||
costPer1kTokensInput=0.0005, # $0.50/M tokens (updated 2026-02)
|
||||
costPer1kTokensOutput=0.0015, # $1.50/M tokens (updated 2026-02)
|
||||
speedRating=8, # Good speed for complex tasks
|
||||
qualityRating=9, # High quality
|
||||
functionCall=self.callAiBasic,
|
||||
priority=PriorityEnum.BALANCED,
|
||||
processingMode=ProcessingModeEnum.ADVANCED,
|
||||
operationTypes=createOperationTypeRatings(
|
||||
(OperationTypeEnum.PLAN, 9),
|
||||
(OperationTypeEnum.DATA_ANALYSE, 9),
|
||||
(OperationTypeEnum.DATA_GENERATE, 9),
|
||||
(OperationTypeEnum.DATA_EXTRACT, 8)
|
||||
),
|
||||
version="mistral-large-latest",
|
||||
calculatepriceCHF=lambda processingTime, bytesSent, bytesReceived: (bytesSent / 4 / 1000) * 0.0005 + (bytesReceived / 4 / 1000) * 0.0015
|
||||
),
|
||||
AiModel(
|
||||
name="mistral-small-latest",
|
||||
displayName="Mistral Small 3.2",
|
||||
connectorType="mistral",
|
||||
apiUrl="https://api.mistral.ai/v1/chat/completions",
|
||||
temperature=0.2,
|
||||
maxTokens=16384,
|
||||
contextLength=128000,
|
||||
costPer1kTokensInput=0.00006, # $0.06/M tokens (updated 2026-02)
|
||||
costPer1kTokensOutput=0.00018, # $0.18/M tokens (updated 2026-02)
|
||||
speedRating=9, # Very fast, lightweight model
|
||||
qualityRating=7, # Good quality, cost-efficient
|
||||
functionCall=self.callAiBasic,
|
||||
priority=PriorityEnum.SPEED,
|
||||
processingMode=ProcessingModeEnum.BASIC,
|
||||
operationTypes=createOperationTypeRatings(
|
||||
(OperationTypeEnum.PLAN, 7),
|
||||
(OperationTypeEnum.DATA_ANALYSE, 7),
|
||||
(OperationTypeEnum.DATA_GENERATE, 8),
|
||||
(OperationTypeEnum.DATA_EXTRACT, 7)
|
||||
),
|
||||
version="mistral-small-latest",
|
||||
calculatepriceCHF=lambda processingTime, bytesSent, bytesReceived: (bytesSent / 4 / 1000) * 0.00006 + (bytesReceived / 4 / 1000) * 0.00018
|
||||
),
|
||||
AiModel(
|
||||
name="mistral-large-latest",
|
||||
displayName="Mistral Large 3 Vision",
|
||||
connectorType="mistral",
|
||||
apiUrl="https://api.mistral.ai/v1/chat/completions",
|
||||
temperature=0.2,
|
||||
maxTokens=16384,
|
||||
contextLength=256000,
|
||||
costPer1kTokensInput=0.0005, # $0.50/M tokens (updated 2026-02)
|
||||
costPer1kTokensOutput=0.0015, # $1.50/M tokens (updated 2026-02)
|
||||
speedRating=6, # Slower for vision tasks
|
||||
qualityRating=8, # Good quality vision
|
||||
functionCall=self.callAiImage,
|
||||
priority=PriorityEnum.QUALITY,
|
||||
processingMode=ProcessingModeEnum.DETAILED,
|
||||
operationTypes=createOperationTypeRatings(
|
||||
(OperationTypeEnum.IMAGE_ANALYSE, 8)
|
||||
),
|
||||
version="mistral-large-latest",
|
||||
calculatepriceCHF=lambda processingTime, bytesSent, bytesReceived: (bytesSent / 4 / 1000) * 0.0005 + (bytesReceived / 4 / 1000) * 0.0015
|
||||
)
|
||||
]
|
||||
|
||||
async def callAiBasic(self, modelCall: AiModelCall) -> AiModelResponse:
|
||||
"""
|
||||
Calls the Mistral AI API with the given messages using standardized pattern.
|
||||
|
||||
Mistral's chat completions API is OpenAI-compatible: it accepts the same
|
||||
message format (role/content) including system messages, and returns
|
||||
responses in the same choices[0].message.content structure.
|
||||
|
||||
Args:
|
||||
modelCall: AiModelCall with messages and options
|
||||
|
||||
Returns:
|
||||
AiModelResponse with content and metadata
|
||||
|
||||
Raises:
|
||||
HTTPException: For errors in API communication
|
||||
"""
|
||||
try:
|
||||
# Extract parameters from modelCall
|
||||
messages = modelCall.messages
|
||||
model = modelCall.model
|
||||
options = modelCall.options
|
||||
temperature = getattr(options, "temperature", None)
|
||||
if temperature is None:
|
||||
temperature = model.temperature
|
||||
maxTokens = model.maxTokens
|
||||
|
||||
payload = {
|
||||
"model": model.name,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
"max_tokens": maxTokens
|
||||
}
|
||||
|
||||
response = await self.httpClient.post(
|
||||
model.apiUrl,
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
error_message = f"Mistral API error: {response.status_code} - {response.text}"
|
||||
logger.error(error_message)
|
||||
|
||||
# Check for rate limit exceeded (429 TPM)
|
||||
if response.status_code == 429:
|
||||
try:
|
||||
error_data = response.json()
|
||||
error_msg = error_data.get("error", {}).get("message", "Rate limit exceeded")
|
||||
raise RateLimitExceededException(
|
||||
f"Rate limit exceeded for {model.name}: {error_msg}"
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
raise RateLimitExceededException(
|
||||
f"Rate limit exceeded for {model.name}"
|
||||
)
|
||||
|
||||
# Check for context length exceeded error
|
||||
if response.status_code == 400:
|
||||
try:
|
||||
error_data = response.json()
|
||||
if (error_data.get("error", {}).get("code") == "context_length_exceeded" or
|
||||
"context length" in error_data.get("error", {}).get("message", "").lower() or
|
||||
"too many tokens" in error_data.get("error", {}).get("message", "").lower()):
|
||||
raise ContextLengthExceededException(
|
||||
f"Context length exceeded: {error_data.get('error', {}).get('message', 'Unknown error')}"
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
pass # If we can't parse the error, fall through to generic error
|
||||
|
||||
# Include the actual error details in the exception
|
||||
raise HTTPException(status_code=500, detail=error_message)
|
||||
|
||||
responseJson = response.json()
|
||||
content = responseJson["choices"][0]["message"]["content"]
|
||||
|
||||
return AiModelResponse(
|
||||
content=content,
|
||||
success=True,
|
||||
modelId=model.name,
|
||||
metadata={"response_id": responseJson.get("id", "")}
|
||||
)
|
||||
|
||||
except ContextLengthExceededException:
|
||||
# Re-raise context length exceptions without wrapping
|
||||
raise
|
||||
except RateLimitExceededException:
|
||||
# Re-raise rate limit exceptions without wrapping
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error calling Mistral API: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Error calling Mistral API: {str(e)}")
|
||||
|
||||
async def callAiImage(self, modelCall: AiModelCall) -> AiModelResponse:
|
||||
"""
|
||||
Analyzes an image with the Mistral Vision API using standardized pattern.
|
||||
|
||||
Mistral Large 3 is multimodal and accepts image inputs in OpenAI-compatible
|
||||
format: {"type": "image_url", "image_url": {"url": "data:...base64,..."}}
|
||||
|
||||
Args:
|
||||
modelCall: AiModelCall with messages and image data in options
|
||||
|
||||
Returns:
|
||||
AiModelResponse with analysis content
|
||||
"""
|
||||
try:
|
||||
# Extract parameters from modelCall
|
||||
messages = modelCall.messages
|
||||
model = modelCall.model
|
||||
|
||||
# Messages should already be in the correct format with image data embedded
|
||||
# Just verify they contain image data
|
||||
if not messages or not messages[0].get("content"):
|
||||
raise ValueError("No messages provided for image analysis")
|
||||
|
||||
logger.debug(f"Starting image analysis with {len(messages)} message(s)...")
|
||||
|
||||
# Use the messages directly - they should already contain the image data
|
||||
# in the format: {"type": "image_url", "image_url": {"url": "data:...base64,..."}}
|
||||
# Mistral Large 3 supports this OpenAI-compatible vision format natively
|
||||
|
||||
# Use parameters from model
|
||||
temperature = model.temperature
|
||||
|
||||
payload = {
|
||||
"model": model.name,
|
||||
"messages": messages,
|
||||
"temperature": temperature
|
||||
}
|
||||
|
||||
response = await self.httpClient.post(
|
||||
model.apiUrl,
|
||||
json=payload
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Mistral API error: {response.status_code} - {response.text}")
|
||||
raise HTTPException(status_code=500, detail="Error communicating with Mistral API")
|
||||
|
||||
responseJson = response.json()
|
||||
content = responseJson["choices"][0]["message"]["content"]
|
||||
|
||||
return AiModelResponse(
|
||||
content=content,
|
||||
success=True,
|
||||
modelId=model.name,
|
||||
metadata={"response_id": responseJson.get("id", "")}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during image analysis: {str(e)}", exc_info=True)
|
||||
return AiModelResponse(
|
||||
content="",
|
||||
success=False,
|
||||
error=f"Error during image analysis: {str(e)}"
|
||||
)
|
||||
Loading…
Reference in a new issue