initial mcp server setup
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# AI Client implementations
|
||||
from .base_client import BaseAIClient
|
||||
from .openai_client import OpenAIClient
|
||||
from .claude_client import ClaudeClient
|
||||
from .grok_client import GrokClient
|
||||
from .client_factory import AIClientFactory
|
||||
|
||||
__all__ = [
|
||||
'BaseAIClient',
|
||||
'OpenAIClient',
|
||||
'ClaudeClient',
|
||||
'GrokClient',
|
||||
'AIClientFactory'
|
||||
]
|
||||
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
Base AI Client with common MCP integration functionality
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional
|
||||
from abc import ABC
|
||||
|
||||
# Add the project root to the path to import config
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
try:
|
||||
from config import Config
|
||||
CONFIG_AVAILABLE = True
|
||||
except ImportError:
|
||||
CONFIG_AVAILABLE = False
|
||||
|
||||
from ..core.interfaces import IAIClient, IMCPClient
|
||||
|
||||
|
||||
class BaseAIClient(IAIClient, ABC):
|
||||
"""Base class for AI clients with MCP integration"""
|
||||
|
||||
def __init__(self, model_name: str, provider: str, api_key: Optional[str] = None, **kwargs):
|
||||
self._model_name = model_name
|
||||
self._provider = provider
|
||||
self._client = None
|
||||
self._initialized = False
|
||||
self._extra_config = kwargs
|
||||
|
||||
# Get API key from config if not provided
|
||||
if api_key is None and CONFIG_AVAILABLE:
|
||||
api_key = self._get_api_key_from_config()
|
||||
|
||||
if not api_key:
|
||||
raise ValueError(f"API key not provided and could not be loaded from config for provider: {provider}")
|
||||
|
||||
self._api_key = api_key
|
||||
|
||||
def _get_api_key_from_config(self) -> Optional[str]:
|
||||
"""Get API key from config based on provider"""
|
||||
if not CONFIG_AVAILABLE:
|
||||
return None
|
||||
|
||||
provider_key_map = {
|
||||
"openai": Config.OPENAI_API_KEY,
|
||||
"claude": Config.CLAUDE_API_KEY,
|
||||
"grok": Config.GROK_API_KEY
|
||||
}
|
||||
|
||||
return provider_key_map.get(self._provider)
|
||||
|
||||
@property
|
||||
def model_name(self) -> str:
|
||||
"""Get the AI model name"""
|
||||
return self._model_name
|
||||
|
||||
async def initialize(self) -> None:
|
||||
"""Initialize the AI client - to be implemented by subclasses"""
|
||||
if self._initialized:
|
||||
return
|
||||
await self._initialize_client()
|
||||
self._initialized = True
|
||||
|
||||
async def _initialize_client(self) -> None:
|
||||
"""Initialize the specific AI client - to be implemented by subclasses"""
|
||||
pass
|
||||
|
||||
async def process_with_tools(
|
||||
self,
|
||||
query: str,
|
||||
available_tools: List[Dict[str, Any]],
|
||||
mcp_client: IMCPClient
|
||||
) -> str:
|
||||
"""Process a query with MCP tools using a common pattern"""
|
||||
|
||||
# Format tools for the specific AI provider
|
||||
formatted_tools = self._format_tools_for_provider(available_tools)
|
||||
|
||||
# Create initial messages
|
||||
messages = [{"role": "user", "content": query}]
|
||||
|
||||
# Get AI response with tool calling
|
||||
response = await self.chat_completion(
|
||||
messages=messages,
|
||||
tools=formatted_tools,
|
||||
tool_choice="auto"
|
||||
)
|
||||
|
||||
# Extract assistant message
|
||||
assistant_message = response["choices"][0]["message"]
|
||||
|
||||
# Check if tools were called
|
||||
if "tool_calls" in assistant_message and assistant_message["tool_calls"]:
|
||||
# Add assistant message to conversation
|
||||
messages.append(assistant_message)
|
||||
|
||||
# Process each tool call
|
||||
for tool_call in assistant_message["tool_calls"]:
|
||||
try:
|
||||
# Extract tool call details
|
||||
tool_name = tool_call["function"]["name"]
|
||||
tool_args = json.loads(tool_call["function"]["arguments"])
|
||||
|
||||
# Call the tool via MCP client
|
||||
tool_result = await mcp_client.call_tool(tool_name, tool_args)
|
||||
|
||||
# Add tool response to conversation
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call["id"],
|
||||
"content": str(tool_result),
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
# Handle tool call errors
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call["id"],
|
||||
"content": f"Error calling tool: {str(e)}",
|
||||
})
|
||||
|
||||
# Get final response from AI with tool results
|
||||
final_response = await self.chat_completion(
|
||||
messages=messages,
|
||||
tools=formatted_tools,
|
||||
tool_choice="none" # Don't allow more tool calls
|
||||
)
|
||||
|
||||
return final_response["choices"][0]["message"]["content"]
|
||||
|
||||
# No tools called, return direct response
|
||||
return assistant_message["content"]
|
||||
|
||||
def _format_tools_for_provider(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""Format tools for the specific AI provider - to be implemented by subclasses"""
|
||||
return tools
|
||||
|
||||
async def cleanup(self) -> None:
|
||||
"""Clean up resources"""
|
||||
if self._client:
|
||||
await self._cleanup_client()
|
||||
self._initialized = False
|
||||
|
||||
async def _cleanup_client(self) -> None:
|
||||
"""Clean up the specific AI client - to be implemented by subclasses"""
|
||||
pass
|
||||
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Claude Client Implementation (Placeholder)
|
||||
"""
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
# Placeholder for Claude/Anthropic client
|
||||
# This would need the actual Anthropic SDK when implemented
|
||||
|
||||
from .base_client import BaseAIClient
|
||||
|
||||
|
||||
class ClaudeClient(BaseAIClient):
|
||||
"""Claude client with MCP integration (Placeholder Implementation)"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str = "claude-3-opus-20240229",
|
||||
api_key: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
# Note: This is a placeholder. You'll need to install the Anthropic SDK
|
||||
# pip install anthropic
|
||||
super().__init__(model_name, "claude", api_key, **kwargs)
|
||||
|
||||
# Claude specific configuration
|
||||
self._temperature = kwargs.get("temperature", 0.7)
|
||||
self._max_tokens = kwargs.get("max_tokens", 1000)
|
||||
|
||||
async def _initialize_client(self) -> None:
|
||||
"""Initialize the Claude client"""
|
||||
# TODO: Implement with actual Anthropic SDK
|
||||
# self._client = Anthropic(api_key=self._api_key)
|
||||
raise NotImplementedError("Claude client not yet implemented. Install Anthropic SDK and implement.")
|
||||
|
||||
async def chat_completion(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
tools: Optional[List[Dict[str, Any]]] = None,
|
||||
**kwargs
|
||||
) -> Dict[str, Any]:
|
||||
"""Perform Claude chat completion"""
|
||||
if not self._initialized:
|
||||
await self.initialize()
|
||||
|
||||
# TODO: Implement Claude API call
|
||||
raise NotImplementedError("Claude chat completion not yet implemented")
|
||||
|
||||
def _format_tools_for_provider(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""Format tools for Claude's expected format"""
|
||||
# TODO: Implement Claude tool formatting
|
||||
return tools
|
||||
|
||||
async def _cleanup_client(self) -> None:
|
||||
"""Clean up Claude client"""
|
||||
# TODO: Implement cleanup
|
||||
pass
|
||||
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
AI Client Factory for easy client creation and management
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
# Add the project root to the path to import config
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
try:
|
||||
from config import Config
|
||||
CONFIG_AVAILABLE = True
|
||||
except ImportError:
|
||||
CONFIG_AVAILABLE = False
|
||||
|
||||
from .base_client import BaseAIClient
|
||||
from .openai_client import OpenAIClient
|
||||
from .claude_client import ClaudeClient
|
||||
from .grok_client import GrokClient
|
||||
|
||||
|
||||
class AIClientFactory:
|
||||
"""Factory class for creating AI clients with different providers"""
|
||||
|
||||
@staticmethod
|
||||
def create_client(
|
||||
provider: str,
|
||||
model_name: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> BaseAIClient:
|
||||
"""Create an AI client for the specified provider"""
|
||||
|
||||
# Set default model names if not provided
|
||||
if model_name is None:
|
||||
if provider.lower() == "openai":
|
||||
model_name = "gpt-4o"
|
||||
elif provider.lower() == "claude":
|
||||
model_name = "claude-3-opus-20240229"
|
||||
elif provider.lower() == "grok":
|
||||
model_name = "grok-1"
|
||||
|
||||
# Get API key from config if not provided
|
||||
if api_key is None and CONFIG_AVAILABLE:
|
||||
provider_key_map = {
|
||||
"openai": Config.OPENAI_API_KEY,
|
||||
"claude": Config.CLAUDE_API_KEY,
|
||||
"grok": Config.GROK_API_KEY
|
||||
}
|
||||
api_key = provider_key_map.get(provider.lower())
|
||||
|
||||
if not api_key:
|
||||
raise ValueError(f"API key not provided and could not be loaded from config for provider: {provider}")
|
||||
|
||||
# Create the appropriate client
|
||||
provider_lower = provider.lower()
|
||||
if provider_lower == "openai":
|
||||
return OpenAIClient(model_name, api_key, **kwargs)
|
||||
elif provider_lower == "claude":
|
||||
return ClaudeClient(model_name, api_key, **kwargs)
|
||||
elif provider_lower == "grok":
|
||||
return GrokClient(model_name, api_key, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unsupported AI provider: {provider}")
|
||||
|
||||
@staticmethod
|
||||
def create_openai_client(
|
||||
model_name: str = "gpt-4o",
|
||||
api_key: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> OpenAIClient:
|
||||
"""Create an OpenAI client"""
|
||||
return AIClientFactory.create_client("openai", model_name, api_key, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def create_claude_client(
|
||||
model_name: str = "claude-3-opus-20240229",
|
||||
api_key: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> ClaudeClient:
|
||||
"""Create a Claude client"""
|
||||
return AIClientFactory.create_client("claude", model_name, api_key, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def create_grok_client(
|
||||
model_name: str = "grok-1",
|
||||
api_key: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> GrokClient:
|
||||
"""Create a Grok client"""
|
||||
return AIClientFactory.create_client("grok", model_name, api_key, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_available_providers() -> list[str]:
|
||||
"""Get list of available AI providers"""
|
||||
return ["openai", "claude", "grok"]
|
||||
|
||||
@staticmethod
|
||||
def validate_provider(provider: str) -> bool:
|
||||
"""Validate if a provider is supported"""
|
||||
return provider.lower() in AIClientFactory.get_available_providers()
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Grok Client Implementation (Placeholder)
|
||||
"""
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
# Placeholder for Grok/xAI client
|
||||
# This would need the xAI SDK or direct API integration when implemented
|
||||
|
||||
from .base_client import BaseAIClient
|
||||
|
||||
|
||||
class GrokClient(BaseAIClient):
|
||||
"""Grok client with MCP integration (Placeholder Implementation)"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str = "grok-1",
|
||||
api_key: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
# Note: This is a placeholder. You'll need xAI API integration
|
||||
super().__init__(model_name, "grok", api_key, **kwargs)
|
||||
|
||||
# Grok specific configuration
|
||||
self._temperature = kwargs.get("temperature", 0.7)
|
||||
self._max_tokens = kwargs.get("max_tokens", 1000)
|
||||
|
||||
async def _initialize_client(self) -> None:
|
||||
"""Initialize the Grok client"""
|
||||
# TODO: Implement with xAI API or SDK
|
||||
# This might require direct HTTP calls to xAI API
|
||||
raise NotImplementedError("Grok client not yet implemented. Implement xAI API integration.")
|
||||
|
||||
async def chat_completion(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
tools: Optional[List[Dict[str, Any]]] = None,
|
||||
**kwargs
|
||||
) -> Dict[str, Any]:
|
||||
"""Perform Grok chat completion"""
|
||||
if not self._initialized:
|
||||
await self.initialize()
|
||||
|
||||
# TODO: Implement Grok API call
|
||||
raise NotImplementedError("Grok chat completion not yet implemented")
|
||||
|
||||
def _format_tools_for_provider(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""Format tools for Grok's expected format"""
|
||||
# TODO: Implement Grok tool formatting
|
||||
return tools
|
||||
|
||||
async def _cleanup_client(self) -> None:
|
||||
"""Clean up Grok client"""
|
||||
# TODO: Implement cleanup
|
||||
pass
|
||||
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
OpenAI Client Implementation
|
||||
"""
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
try:
|
||||
from openai import AsyncOpenAI
|
||||
OPENAI_AVAILABLE = True
|
||||
except ImportError:
|
||||
OPENAI_AVAILABLE = False
|
||||
|
||||
from .base_client import BaseAIClient
|
||||
from config import Config
|
||||
|
||||
class OpenAIClient(BaseAIClient):
|
||||
"""OpenAI client with MCP integration"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str = "gpt-4o",
|
||||
api_key: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
if not OPENAI_AVAILABLE:
|
||||
raise ImportError("OpenAI package not installed. Install with: pip install openai")
|
||||
|
||||
super().__init__(model_name, "openai", api_key, **kwargs)
|
||||
|
||||
# OpenAI specific configuration
|
||||
self._temperature = kwargs.get("temperature", 0.7)
|
||||
self._max_tokens = kwargs.get("max_tokens", 1000)
|
||||
self._api_key = api_key or Config.OPENAI_API_KEY
|
||||
|
||||
async def _initialize_client(self) -> None:
|
||||
"""Initialize the OpenAI client"""
|
||||
self._client = AsyncOpenAI(api_key=self._api_key)
|
||||
|
||||
async def chat_completion(
|
||||
self,
|
||||
messages: List[Dict[str, Any]],
|
||||
tools: Optional[List[Dict[str, Any]]] = None,
|
||||
**kwargs
|
||||
) -> Dict[str, Any]:
|
||||
"""Perform OpenAI chat completion"""
|
||||
if not self._initialized:
|
||||
await self.initialize()
|
||||
|
||||
# Prepare request parameters
|
||||
request_params = {
|
||||
"model": self._model_name,
|
||||
"messages": messages,
|
||||
"temperature": self._temperature,
|
||||
"max_tokens": self._max_tokens,
|
||||
}
|
||||
|
||||
# Add tools if provided
|
||||
if tools:
|
||||
request_params["tools"] = tools
|
||||
request_params["tool_choice"] = kwargs.get("tool_choice", "auto")
|
||||
|
||||
# Make the API call
|
||||
response = await self._client.chat.completions.create(**request_params)
|
||||
|
||||
# Convert to standard format
|
||||
return {
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"role": choice.message.role,
|
||||
"content": choice.message.content,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": tool_call.id,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool_call.function.name,
|
||||
"arguments": tool_call.function.arguments,
|
||||
}
|
||||
}
|
||||
for tool_call in (choice.message.tool_calls or [])
|
||||
] if choice.message.tool_calls else None,
|
||||
}
|
||||
}
|
||||
for choice in response.choices
|
||||
]
|
||||
}
|
||||
|
||||
def _format_tools_for_provider(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""Format tools for OpenAI's expected format"""
|
||||
formatted_tools = []
|
||||
for tool in tools:
|
||||
formatted_tool = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool["name"],
|
||||
"description": tool["description"],
|
||||
"parameters": tool["inputSchema"],
|
||||
}
|
||||
}
|
||||
formatted_tools.append(formatted_tool)
|
||||
return formatted_tools
|
||||
|
||||
async def _cleanup_client(self) -> None:
|
||||
"""Clean up OpenAI client"""
|
||||
if self._client:
|
||||
await self._client.close()
|
||||
Reference in New Issue
Block a user