From fa8cbf19d0c09c236d20eb58397f41cd07e5f5d6 Mon Sep 17 00:00:00 2001
From: Nerzouille
Date: Mon, 8 Sep 2025 17:00:33 +0200
Subject: [PATCH] [ADD] fonctionnal libertai
---
crypto-pilot-builder/package-lock.json | 105 ---
.../python/mcp_client/api_routes.py | 96 ++-
.../python/mcp_client/config.py | 4 +-
.../python/mcp_client/mcp_client.py | 1 +
.../python/mcp_serveur/agent_factory.py | 39 +
.../python/mcp_serveur/base_agent.py | 223 ++++++
.../python/mcp_serveur/libertai_agent.py | 177 +++++
.../python/mcp_serveur/mcp_server_sdk.py | 148 +---
.../python/mcp_serveur/mcp_server_sdk_old.py | 684 ++++++++++++++++++
crypto-pilot-builder/python/test_libertai.py | 90 +++
.../src/agent_building/Ai.vue | 60 +-
.../src/components/AgentConfigManager.vue | 14 +
crypto-pilot-builder/src/store/index.js | 43 +-
13 files changed, 1420 insertions(+), 264 deletions(-)
create mode 100644 crypto-pilot-builder/python/mcp_serveur/agent_factory.py
create mode 100644 crypto-pilot-builder/python/mcp_serveur/base_agent.py
create mode 100644 crypto-pilot-builder/python/mcp_serveur/libertai_agent.py
create mode 100644 crypto-pilot-builder/python/mcp_serveur/mcp_server_sdk_old.py
create mode 100644 crypto-pilot-builder/python/test_libertai.py
diff --git a/crypto-pilot-builder/package-lock.json b/crypto-pilot-builder/package-lock.json
index 962dd4b..aeae7d6 100644
--- a/crypto-pilot-builder/package-lock.json
+++ b/crypto-pilot-builder/package-lock.json
@@ -1186,72 +1186,6 @@
"node": ">= 10.0.0"
}
},
- "node_modules/@electron/windows-sign": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz",
- "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "optional": true,
- "peer": true,
- "dependencies": {
- "cross-dirname": "^0.1.0",
- "debug": "^4.3.4",
- "fs-extra": "^11.1.1",
- "minimist": "^1.2.8",
- "postject": "^1.0.0-alpha.6"
- },
- "bin": {
- "electron-windows-sign": "bin/electron-windows-sign.js"
- },
- "engines": {
- "node": ">=14.14"
- }
- },
- "node_modules/@electron/windows-sign/node_modules/fs-extra": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
- "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^2.0.0"
- },
- "engines": {
- "node": ">=14.14"
- }
- },
- "node_modules/@electron/windows-sign/node_modules/jsonfile": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
- "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "universalify": "^2.0.0"
- },
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
- }
- },
- "node_modules/@electron/windows-sign/node_modules/universalify": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
- "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">= 10.0.0"
- }
- },
"node_modules/@element-plus/icons-vue": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
@@ -4609,15 +4543,6 @@
"buffer": "^5.1.0"
}
},
- "node_modules/cross-dirname": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz",
- "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true
- },
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@@ -8519,36 +8444,6 @@
"node": ">=4"
}
},
- "node_modules/postject": {
- "version": "1.0.0-alpha.6",
- "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz",
- "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "commander": "^9.4.0"
- },
- "bin": {
- "postject": "dist/cli.js"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/postject/node_modules/commander": {
- "version": "9.5.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
- "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "engines": {
- "node": "^12.20.0 || >=14"
- }
- },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
diff --git a/crypto-pilot-builder/python/mcp_client/api_routes.py b/crypto-pilot-builder/python/mcp_client/api_routes.py
index f3b742a..c83e259 100644
--- a/crypto-pilot-builder/python/mcp_client/api_routes.py
+++ b/crypto-pilot-builder/python/mcp_client/api_routes.py
@@ -67,6 +67,7 @@ class AgentConfig(db.Model):
user_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), nullable=False)
# Configuration IA
+ provider = db.Column(db.String(50), nullable=False, default='openai')
selected_model = db.Column(db.String(100), nullable=False)
api_key = db.Column(db.Text, nullable=False) # Chiffré en production
@@ -156,6 +157,28 @@ def validate_openai_api_key(api_key):
return True
+ def validate_libertai_api_key(api_key):
+ """LibertAI API key validation"""
+ if not api_key or not isinstance(api_key, str):
+ return False
+
+ if len(api_key) < 10 or len(api_key) > 200:
+ return False
+ allowed_pattern = r'^[A-Za-z0-9._-]+$'
+ if not re.match(allowed_pattern, api_key):
+ return False
+
+ return True
+
+ def validate_api_key(api_key, provider):
+ """Validate API key based on provider"""
+ if provider.lower() == "openai":
+ return validate_openai_api_key(api_key)
+ elif provider.lower() == "libertai":
+ return validate_libertai_api_key(api_key)
+ else:
+ return False
+
with app.app_context():
try:
db.create_all()
@@ -321,6 +344,7 @@ def create_agent_config():
'message': 'Configuration sauvegardée avec succès',
'config': {
'id': config.id,
+ 'provider': config.provider,
'selectedModel': config.selected_model,
'apiKey': config.api_key[-4:] + '...' if config.api_key else '', # Masquer la clé
'modules': config.modules_config,
@@ -350,6 +374,7 @@ def get_agent_config():
return jsonify({
'config': {
'id': config.id,
+ 'provider': config.provider,
'selectedModel': config.selected_model,
'apiKey': config.api_key, # Retourner la clé complète pour l'utilisation
'modules': config.modules_config,
@@ -389,12 +414,20 @@ def update_partial_config():
db.session.add(config)
# Mettre à jour les champs fournis
+ if 'provider' in data:
+ config.provider = data['provider']
if 'selectedModel' in data:
config.selected_model = data['selectedModel']
if 'apiKey' in data:
- # Validation du format de l'API key si elle est fournie
- # if not validate_openai_api_key(data['apiKey']):
- # return jsonify({'error': 'Format de clé API OpenAI invalide. La clé doit commencer par "sk-" et contenir uniquement des caractères alphanumériques, tirets et underscores.'}), 400
+ # Validate API key based on provider
+ provider = data.get('provider', config.provider or 'openai')
+ if not validate_api_key(data['apiKey'], provider):
+ if provider.lower() == 'openai':
+ return jsonify({'error': 'Format de clé API OpenAI invalide. La clé doit commencer par "sk-" et contenir uniquement des caractères alphanumériques, tirets et underscores.'}), 400
+ elif provider.lower() == 'libertai':
+ return jsonify({'error': 'Format de clé API LibertAI invalide. Vérifiez que votre clé est correcte.'}), 400
+ else:
+ return jsonify({'error': f'Format de clé API invalide pour le provider {provider}.'}), 400
config.api_key = data['apiKey']
if 'modules' in data:
config.modules_config = data['modules']
@@ -496,6 +529,25 @@ async def do_list():
except Exception as e:
return jsonify({"error": str(e)}), 500
+ @app.route('/mcp/providers', methods=['GET'])
+ def list_available_providers():
+ """List available AI providers"""
+ try:
+ # Import here to avoid circular imports
+ import sys
+ import os
+ sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'mcp_serveur'))
+ from agent_factory import AgentFactory
+
+ providers = AgentFactory.get_available_providers()
+ return jsonify({
+ "providers": providers,
+ "current": "openai",
+ "note": "Currently only OpenAI is fully implemented"
+ })
+ except Exception as e:
+ return jsonify({"error": f"Failed to get providers: {str(e)}"}), 500
+
@app.route('/crypto/price', methods=['POST'])
def get_crypto_price():
"""Get cryptocurrency price via MCP tool"""
@@ -573,18 +625,23 @@ def chat():
message_id = session_manager.add_message(session_id, "user", user_input)
# NOUVELLE FONCTIONNALITÉ: Extraction automatique d'informations importantes
- try:
- memory_extractions = user_memory_manager.process_user_message(
- user_id=user_id,
- message=user_input,
- openai_api_key=config.api_key,
- message_id=message_id
- )
-
- if memory_extractions > 0:
- logger.info(f"💾 {memory_extractions} information(s) extraite(s) et stockée(s) pour l'utilisateur {user_id}")
- except Exception as e:
- logger.warning(f"⚠️ Erreur lors de l'extraction de mémoire (continuer sans): {e}")
+ # IMPORTANT NOTE NOA: Only usable with OpenAI for now
+ memory_extractions = 0
+ if config.provider == 'openai':
+ try:
+ memory_extractions = user_memory_manager.process_user_message(
+ user_id=user_id,
+ message=user_input,
+ openai_api_key=config.api_key,
+ message_id=message_id
+ )
+
+ if memory_extractions > 0:
+ logger.info(f"💾 {memory_extractions} information(s) extraite(s) et stockée(s) pour l'utilisateur {user_id}")
+ except Exception as e:
+ logger.warning(f"⚠️ Erreur lors de l'extraction de mémoire (continuer sans): {e}")
+ else:
+ logger.info(f"📝 Extraction de mémoire désactivée pour le provider {config.provider} (uniquement OpenAI supporté)")
# Build conversation context with user config ET mémoire utilisateur
conversation_history = session_manager.get_context(session_id)
@@ -601,6 +658,7 @@ def chat():
'user_memory': user_memory_summary, # Nouvelle clé pour la mémoire utilisateur
'wallet_address': wallet_address, # Adresse du wallet pour les swaps
'agent_config': {
+ 'provider': config.provider,
'model': config.selected_model,
'prompt': config.prompt,
'modules': config.modules_config
@@ -890,10 +948,4 @@ def get_wallet_address():
}), 200
except Exception as e:
- return jsonify({'error': f'Failed to get wallet address: {str(e)}'}), 500
-
- # ===== AUTOWALLET ROUTES =====
-
- # Importer et créer les routes d'autowallet
- from .autowallet_routes import create_autowallet_routes
- create_autowallet_routes(app)
\ No newline at end of file
+ return jsonify({'error': f'Failed to get wallet address: {str(e)}'}), 500
\ No newline at end of file
diff --git a/crypto-pilot-builder/python/mcp_client/config.py b/crypto-pilot-builder/python/mcp_client/config.py
index e1f3a34..155d461 100644
--- a/crypto-pilot-builder/python/mcp_client/config.py
+++ b/crypto-pilot-builder/python/mcp_client/config.py
@@ -17,8 +17,8 @@
DEBUG = True
# MCP configuration
-MCP_SERVER_COMMAND = "python"
-MCP_SERVER_ARGS = [str(SERVER_DIR / SERVER_SCRIPT)]
+MCP_SERVER_COMMAND = "python3"
+MCP_SERVER_ARGS = ["-u", str(SERVER_DIR / SERVER_SCRIPT)]
# OpenAI configuration (via .env)
OPENAI_MODEL = "gpt-4o-mini"
diff --git a/crypto-pilot-builder/python/mcp_client/mcp_client.py b/crypto-pilot-builder/python/mcp_client/mcp_client.py
index a28f2c8..e40a105 100644
--- a/crypto-pilot-builder/python/mcp_client/mcp_client.py
+++ b/crypto-pilot-builder/python/mcp_client/mcp_client.py
@@ -126,6 +126,7 @@ async def chat_with_config(self, message: str, context: Dict[str, Any], api_key:
"message": message,
"context": json.dumps(context),
"api_key": api_key,
+ "provider": agent_config.get('provider', 'openai'),
"model": agent_config.get('model', 'gpt-4o-mini'),
"system_prompt": system_prompt,
"modules": json.dumps(agent_config.get('modules', {}))
diff --git a/crypto-pilot-builder/python/mcp_serveur/agent_factory.py b/crypto-pilot-builder/python/mcp_serveur/agent_factory.py
new file mode 100644
index 0000000..2867a87
--- /dev/null
+++ b/crypto-pilot-builder/python/mcp_serveur/agent_factory.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+"""
+Factory for creating AI agents based on provider
+"""
+import sys
+import os
+sys.path.append(os.path.dirname(__file__))
+from base_agent import BaseAgent
+
+class AgentFactory:
+ """Factory for creating AI agents"""
+
+ @staticmethod
+ def create_agent(provider: str = "openai") -> BaseAgent:
+ """
+ Create an AI agent based on the provider
+
+ Args:
+ provider (str): The AI provider to use (default: "openai")
+
+ Returns:
+ BaseAgent: An instance of the appropriate agent
+ """
+ if provider.lower() == "openai":
+ from mcp_server_sdk import OpenAIAgent
+ return OpenAIAgent()
+ elif provider.lower() == "libertai":
+ from libertai_agent import LibertAIAgent
+ return LibertAIAgent()
+ else:
+ # Fallback to OpenAI for now
+ print(f"⚠️ Provider '{provider}' not yet implemented, falling back to OpenAI")
+ from mcp_server_sdk import OpenAIAgent
+ return OpenAIAgent()
+
+ @staticmethod
+ def get_available_providers() -> list:
+ """Get list of available providers"""
+ return ["openai", "libertai"]
diff --git a/crypto-pilot-builder/python/mcp_serveur/base_agent.py b/crypto-pilot-builder/python/mcp_serveur/base_agent.py
new file mode 100644
index 0000000..81e8867
--- /dev/null
+++ b/crypto-pilot-builder/python/mcp_serveur/base_agent.py
@@ -0,0 +1,223 @@
+#!/usr/bin/env python3
+"""
+Base interface and shared utilities for all AI agents.
+
+This module centralizes common logic used by different providers
+(e.g., OpenAI, LibertAI) to avoid duplication:
+- Default system prompt rules
+- Tool schemas
+- Intent detection for forcing tool_choice
+- Prompt/context composition helpers
+"""
+from abc import ABC, abstractmethod
+from typing import Any, Dict, List, Optional, Tuple, Union
+
+import json
+
+class BaseAgent(ABC):
+ """Base class for all AI agents with crypto capabilities"""
+
+ @abstractmethod
+ async def process_message(self, message: str, context: str = "") -> str:
+ """Process a message with default configuration"""
+ pass
+
+ @abstractmethod
+ async def process_message_with_config(self, message: str, context: str = "",
+ system_prompt: str = "", model: str = "",
+ api_key: str = "", modules: dict = None) -> str:
+ """Process a message with custom configuration"""
+ pass
+
+ # ===== Shared utilities to reduce duplication between providers =====
+ def get_shared_system_prompt(self) -> str:
+ """Return the shared system prompt containing core crypto rules.
+
+ Providers can prefix/suffix this if needed, but the rules remain identical.
+ """
+ return (
+ "You are a crypto assistant with advanced capabilities including:\n"
+ "1. **Price Information**: Get real-time cryptocurrency prices using get_crypto_price\n"
+ "2. **Transactions**: Prepare blockchain transactions using request_transaction\n"
+ "3. **Token Information**: Get available ERC-20 tokens on Sepolia using get_sepolia_tokens\n"
+ "4. **Token Swapping**: Complete token swap capabilities using Li.Fi:\n"
+ " - get_lifi_tokens: Discover available tokens for swapping\n"
+ " - get_swap_quote: Get quotes for token swaps\n"
+ " - execute_swap: Execute token swaps with transaction data\n\n"
+ "🎯 RÈGLE CRITIQUE pour request_transaction :\n\n"
+ "COPIE EXACTEMENT le mot que dit l'utilisateur dans le paramètre \"currency\". NE TRADUIS PAS, NE CONVERTIS PAS.\n\n"
+ "EXEMPLES CORRECTS :\n"
+ "- User: \"envoie 0.001 ETH à 0x123...\" → request_transaction(..., currency=\"ETH\")\n"
+ "- User: \"envoie 0.001 sepolia à 0x123...\" → request_transaction(..., currency=\"sepolia\")\n"
+ "- User: \"envoie 5 USDC à 0x123...\" → request_transaction(..., currency=\"USDC\")\n\n"
+ "❌ ERREUR FATALE (ne jamais faire) :\n"
+ "- User dit \"ETH\" mais tu mets currency=\"sepolia\" ← INTERDIT !\n\n"
+ "✅ TOKENS DISPONIBLES : ETH, SEPOLIA, USDC, USDT, DAI, WETH, LINK, UNI\n\n"
+ "RÈGLES CRITIQUES pour les swaps :\n"
+ "1. DÉTECTION : Si tu détectes l'un de ces mots-clés dans le message :\n"
+ " - \"swap\", \"échanger\", \"convertir\", \"changer\", \"exchange\"\n"
+ " - OU phrases comme \"eth en usdc\", \"bitcoin vers dai\", \"0.001 eth en usdc\"\n"
+ " - OU demandes directes de swap avec montant\n"
+ "2. EXTRACTION : Extrais ces informations du message :\n"
+ " - Le token source (ex: ETH, USDC, BTC)\n"
+ " - Le token destination (ex: USDC, DAI, WETH)\n"
+ " - Le montant numérique\n"
+ "3. ACTION IMMÉDIATE : Utilise DIRECTEMENT l'outil execute_swap avec ces paramètres.\n"
+ " - Pour ETH → USDC : execute_swap(\"ETH\", \"USDC\", montant, adresse_wallet)\n"
+ " - Pour USDC → DAI : execute_swap(\"USDC\", \"DAI\", montant, adresse_wallet)\n"
+ " - Si pas d'adresse wallet, utilise une adresse par défaut ou demande à l'utilisateur\n\n"
+ "IMPORTANT :\n"
+ "- NE DONNE JAMAIS d'explication préalable sur le swap\n"
+ "- N'INFORME PAS l'utilisateur des détails avant d'appeler l'outil\n"
+ "- NE DEMANDE JAMAIS de confirmation comme \"Souhaitez-vous continuer ?\"\n"
+ "- APPELLE execute_swap IMMÉDIATEMENT dès que tu détectes une demande de swap\n"
+ "- La modal d'interface se charge de tout afficher à l'utilisateur\n\n"
+ "Provide clear, helpful responses about crypto prices, transactions, and swaps."
+ )
+
+ def append_modules_and_context(self, system_prompt: str, context: str = "", modules: Optional[dict] = None) -> str:
+ """Append modules and context information to the system prompt consistently."""
+ if modules:
+ active_modules = [name for name, active in modules.items() if active]
+ if active_modules:
+ system_prompt += f"\nModules activés: {', '.join(active_modules)}"
+ if context:
+ # Context may be plain text or JSON string from caller; keep as-is
+ label = "Contexte de conversation" if any(c in system_prompt for c in ["RÈGLE", "DÉTECTION"]) else "Conversation context"
+ system_prompt += f"\n{label}: {context}"
+ return system_prompt
+
+ def detect_tool_choice(self, message: str) -> Union[str, Dict[str, Any]]:
+ """Detect if we should force a specific tool based on keywords, else return 'auto'."""
+ lower_msg = message.lower()
+ transaction_keywords = ["envoie", "envoyer", "send", "transfer", "transférer", "payment", "pay"]
+ has_transaction_keyword = any(keyword in lower_msg for keyword in transaction_keywords)
+ has_address = "0x" in message and len([part for part in message.split() if part.startswith("0x") and len(part) == 42]) > 0
+ has_amount = any(char.isdigit() for char in message)
+ is_likely_transaction = has_transaction_keyword and has_address and has_amount
+
+ swap_keywords = ["swap", "échanger", "convertir", "changer", "exchange", "en usdc", "en dai", "vers usdc", "vers dai"]
+ has_swap_keyword = any(keyword in lower_msg for keyword in swap_keywords)
+ has_swap_amount = any(char.isdigit() for char in message)
+ is_likely_swap = has_swap_keyword and has_swap_amount
+
+ if is_likely_transaction:
+ return {"type": "function", "function": {"name": "request_transaction"}}
+ if is_likely_swap:
+ return {"type": "function", "function": {"name": "execute_swap"}}
+ return "auto"
+
+ def get_tools_schema(self) -> List[Dict[str, Any]]:
+ """Return the shared tools schema list used for chat.completions.create."""
+ return [
+ {
+ "type": "function",
+ "function": {
+ "name": "get_crypto_price",
+ "description": "Get real-time cryptocurrency price via CoinGecko API",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "crypto_id": {
+ "type": "string",
+ "description": "Cryptocurrency identifier (e.g. bitcoin, ethereum)"
+ },
+ "currency": {
+ "type": "string",
+ "description": "Desired currency (e.g. eur, usd, gbp)",
+ "default": "usd"
+ }
+ },
+ "required": ["crypto_id"]
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "request_transaction",
+ "description": "Request a blockchain transaction",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "recipient_address": {"type": "string", "description": "Ethereum recipient address"},
+ "amount": {"type": "string", "description": "Amount to send"},
+ "currency": {"type": "string", "description": "EXACT word user said: If user says 'ETH' use 'ETH', if user says 'sepolia' use 'sepolia'. NEVER convert ETH to sepolia!"},
+ "token_address": {"type": "string", "description": "Token contract address (optional)"}
+ },
+ "required": ["recipient_address", "amount"]
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "get_lifi_tokens",
+ "description": "Get available tokens from Li.Fi for swapping",
+ "parameters": {
+ "type": "object",
+ "properties": {"chains": {"type": "string", "description": "Comma-separated chain IDs to filter tokens (optional)"}},
+ "required": []
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "get_swap_quote",
+ "description": "Get a swap quote from Li.Fi API for token swapping",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "from_token": {"type": "string", "description": "Source token symbol or address (e.g., ETH, USDC)"},
+ "to_token": {"type": "string", "description": "Destination token symbol or address (e.g., USDC, DAI)"},
+ "amount": {"type": "string", "description": "Amount to swap"},
+ "from_address": {"type": "string", "description": "User's wallet address"},
+ "from_chain": {"type": "string", "description": "Source blockchain ID (default: 1 for Ethereum)", "default": "1"},
+ "to_chain": {"type": "string", "description": "Destination blockchain ID (default: 1 for Ethereum)", "default": "1"}
+ },
+ "required": ["from_token", "to_token", "amount", "from_address"]
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "execute_swap",
+ "description": "Execute a crypto swap using Li.Fi - generates transaction data for user to sign",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "from_token": {"type": "string", "description": "Source token symbol or address (e.g., ETH, USDC)"},
+ "to_token": {"type": "string", "description": "Destination token symbol or address (e.g., USDC, DAI)"},
+ "amount": {"type": "string", "description": "Amount to swap"},
+ "from_address": {"type": "string", "description": "User's wallet address"},
+ "from_chain": {"type": "string", "description": "Source blockchain ID (default: 1 for Ethereum)", "default": "1"},
+ "to_chain": {"type": "string", "description": "Destination blockchain ID (default: 1 for Ethereum)", "default": "1"}
+ },
+ "required": ["from_token", "to_token", "amount", "from_address"]
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "get_sepolia_tokens",
+ "description": "Get available ERC-20 tokens on Sepolia testnet",
+ "parameters": {"type": "object", "properties": {}, "required": []}
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "get_all_erc20_tokens",
+ "description": "Get all ERC-20 tokens for a specific chain",
+ "parameters": {
+ "type": "object",
+ "properties": {"chain_id": {"type": "string", "description": "Chain ID (e.g., 1 for Ethereum, 11155111 for Sepolia)", "default": "11155111"}},
+ "required": []
+ }
+ }
+ }
+ ]
+
diff --git a/crypto-pilot-builder/python/mcp_serveur/libertai_agent.py b/crypto-pilot-builder/python/mcp_serveur/libertai_agent.py
new file mode 100644
index 0000000..25441fc
--- /dev/null
+++ b/crypto-pilot-builder/python/mcp_serveur/libertai_agent.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+"""
+LibertAI Agent with crypto capabilities
+"""
+import asyncio
+import sys
+import os
+import json
+from dotenv import load_dotenv
+from base_agent import BaseAgent
+sys.path.append(os.path.dirname(__file__))
+from crypto_tools import get_crypto_price, request_transaction, get_lifi_tokens, get_swap_quote, execute_swap, get_sepolia_tokens, get_all_erc20_tokens
+load_dotenv()
+
+class LibertAIAgent(BaseAgent):
+ """LibertAI Agent with crypto capabilities"""
+
+ def __init__(self):
+ self.base_url = "https://api.libertai.io/v1"
+ self.model = "gemma-3-27b"
+ self.api_key = None
+ self.client = None
+
+ def _get_client(self, api_key=None):
+ """Create LibertAI client (OpenAI-compatible)"""
+ try:
+ import openai
+ actual_api_key = api_key or os.getenv('LIBERTAI_API_KEY')
+ if not actual_api_key:
+ print("❌ Aucune clé API LibertAI fournie")
+ return None
+ print(f"🔑 Création du client LibertAI avec l'endpoint: {self.base_url}")
+ print(f"🔑 Clé API: {actual_api_key[:10]}...{actual_api_key[-4:] if len(actual_api_key) > 14 else '***'}")
+ client = openai.AsyncOpenAI(
+ api_key=actual_api_key,
+ base_url=self.base_url
+ )
+ print("✅ Client LibertAI créé avec succès")
+ return client
+ except ImportError as e:
+ print(f"❌ Erreur d'import openai: {e}")
+ return None
+ except Exception as e:
+ print(f"❌ Erreur lors de la création du client LibertAI: {e}")
+ return None
+
+ async def process_message(self, message: str, context: str = "") -> str:
+ """Process message with default configuration"""
+ try:
+ client = self._get_client()
+ if client is None:
+ return "❌ Aucune clé API LibertAI configurée. Veuillez utiliser agent_chat_configured avec votre clé API LibertAI."
+ system_prompt = self.get_shared_system_prompt()
+ if context:
+ system_prompt = self.append_modules_and_context(system_prompt, context)
+ result = await self._chat_with_libertai(message, system_prompt, self.model, client)
+ return result
+ except Exception as e:
+ return f"❌ LibertAI agent error: {str(e)}"
+
+ async def process_message_with_config(self, message: str, context: str = "",
+ system_prompt: str = "", model: str = "hermes-3-8b",
+ api_key: str = "", modules: dict = None) -> str:
+ """Process message with custom configuration"""
+ try:
+ client = self._get_client(api_key) if api_key else self._get_client()
+ if not client:
+ return "❌ LibertAI client not configured. Please provide a valid API key."
+ if not system_prompt:
+ system_prompt = self.get_shared_system_prompt()
+ system_prompt = self.append_modules_and_context(system_prompt, context, modules)
+ result = await self._chat_with_libertai(message, system_prompt, model, client)
+ return result
+ except Exception as e:
+ return f"❌ Erreur agent LibertAI avec configuration: {str(e)}"
+
+ async def _chat_with_libertai(self, message: str, system_prompt: str, model: str, client=None) -> str:
+ """Handle chat with LibertAI including tool calls"""
+ if client is None:
+ client = self._get_client()
+ if client is None:
+ return "❌ LibertAI client not configured."
+ try:
+ print(f"🔍 DEBUG LibertAI - Message: {message}")
+ tool_choice = self.detect_tool_choice(message)
+ print(f"🔍 DEBUG LibertAI - tool_choice: {tool_choice}")
+ try:
+ response = await client.chat.completions.create(
+ model=model,
+ messages=[
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": message}
+ ],
+ max_tokens=500,
+ temperature=0.1,
+ tools=self.get_tools_schema(),
+ tool_choice=tool_choice
+ )
+ if not response or not hasattr(response, 'choices') or not response.choices:
+ print(f"❌ LibertAI response invalide: {response}")
+ return "❌ Erreur: LibertAI n'a pas retourné de réponse valide. Vérifiez votre clé API et votre connexion."
+ except Exception as e:
+ print(f"❌ Erreur lors de l'appel LibertAI: {e}")
+ return f"❌ Erreur de communication avec LibertAI: {str(e)}"
+
+ response_message = response.choices[0].message
+ if response_message.tool_calls:
+ tool_calls = response_message.tool_calls
+ tool_results = []
+ for tool_call in tool_calls:
+ tool_name = tool_call.function.name
+ tool_args = json.loads(tool_call.function.arguments)
+ print(f"🔧 LibertAI Tool Call: {tool_name} with args: {tool_args}")
+ if tool_name == "get_crypto_price":
+ result = get_crypto_price(tool_args.get("crypto_id", ""), tool_args.get("currency", "usd"))
+ elif tool_name == "request_transaction":
+ result = request_transaction(
+ tool_args.get("recipient_address", ""),
+ tool_args.get("amount", ""),
+ tool_args.get("currency", "ETH"),
+ tool_args.get("token_address")
+ )
+ elif tool_name == "get_lifi_tokens":
+ result = get_lifi_tokens(tool_args.get("chains"))
+ elif tool_name == "get_swap_quote":
+ result = get_swap_quote(
+ tool_args.get("from_token", ""),
+ tool_args.get("to_token", ""),
+ tool_args.get("amount", ""),
+ tool_args.get("from_address", ""),
+ tool_args.get("from_chain", "1"),
+ tool_args.get("to_chain", "1")
+ )
+ elif tool_name == "execute_swap":
+ result = execute_swap(
+ tool_args.get("from_token", ""),
+ tool_args.get("to_token", ""),
+ tool_args.get("amount", ""),
+ tool_args.get("from_address", ""),
+ tool_args.get("from_chain", "1"),
+ tool_args.get("to_chain", "1")
+ )
+ elif tool_name == "get_sepolia_tokens":
+ result = get_sepolia_tokens()
+ elif tool_name == "get_all_erc20_tokens":
+ result = get_all_erc20_tokens(tool_args.get("chain_id", "11155111"))
+ else:
+ result = f"❌ Unknown tool: {tool_name}"
+ tool_results.append(f"Tool {tool_name} result: {result}")
+
+ messages = [
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": message},
+ response_message,
+ {"role": "tool", "content": "\n".join(tool_results)}
+ ]
+ try:
+ second_response = await client.chat.completions.create(
+ model=model,
+ messages=messages,
+ max_tokens=300,
+ temperature=0.1
+ )
+ if not second_response or not hasattr(second_response, 'choices') or not second_response.choices:
+ print(f"❌ LibertAI second response invalide: {second_response}")
+ return "❌ Erreur: LibertAI n'a pas retourné de réponse finale valide."
+ final_response = second_response.choices[0].message.content
+ return final_response
+ except Exception as e:
+ print(f"❌ Erreur lors du second appel LibertAI: {e}")
+ return f"❌ Erreur lors de la génération de la réponse finale: {str(e)}"
+
+ direct_response = response_message.content
+ return direct_response
+ except Exception as e:
+ return f"❌ Error in LibertAI chat: {str(e)}"
+
diff --git a/crypto-pilot-builder/python/mcp_serveur/mcp_server_sdk.py b/crypto-pilot-builder/python/mcp_serveur/mcp_server_sdk.py
index 1ad1694..e8938d1 100644
--- a/crypto-pilot-builder/python/mcp_serveur/mcp_server_sdk.py
+++ b/crypto-pilot-builder/python/mcp_serveur/mcp_server_sdk.py
@@ -14,9 +14,10 @@
from mcp.server.stdio import stdio_server
sys.path.append(os.path.dirname(__file__))
from crypto_tools import get_crypto_price, request_transaction, get_lifi_tokens, get_swap_quote, execute_swap, get_sepolia_tokens, get_all_erc20_tokens
+from base_agent import BaseAgent
load_dotenv()
-class OpenAIAgent:
+class OpenAIAgent(BaseAgent):
"""OpenAI Agent with crypto capabilities"""
def __init__(self):
self.default_client = None # Client async
@@ -38,53 +39,9 @@ async def process_message(self, message: str, context: str = "") -> str:
client = self._get_default_client()
if client is None:
return "❌ Aucune clé API OpenAI configurée. Veuillez utiliser agent_chat_configured avec votre clé API."
- system_prompt = """You are a crypto assistant with advanced capabilities including:
-1. **Price Information**: Get real-time cryptocurrency prices using get_crypto_price
-2. **Transactions**: Prepare blockchain transactions using request_transaction
-3. **Token Information**: Get available ERC-20 tokens on Sepolia using get_sepolia_tokens
-4. **Token Swapping**: Complete token swap capabilities using Li.Fi:
- - get_lifi_tokens: Discover available tokens for swapping
- - get_swap_quote: Get quotes for token swaps
- - execute_swap: Execute token swaps with transaction data
-
-🎯 RÈGLE CRITIQUE pour request_transaction :
-
-COPIE EXACTEMENT le mot que dit l'utilisateur dans le paramètre "currency". NE TRADUIS PAS, NE CONVERTIS PAS.
-
-EXEMPLES CORRECTS :
-- User: "envoie 0.001 ETH à 0x123..." → request_transaction(..., currency="ETH")
-- User: "envoie 0.001 sepolia à 0x123..." → request_transaction(..., currency="sepolia")
-- User: "envoie 5 USDC à 0x123..." → request_transaction(..., currency="USDC")
-
-❌ ERREUR FATALE (ne jamais faire) :
-- User dit "ETH" mais tu mets currency="sepolia" ← INTERDIT !
-
-✅ TOKENS DISPONIBLES : ETH, SEPOLIA, USDC, USDT, DAI, WETH, LINK, UNI
-
-RÈGLES CRITIQUES pour les swaps :
-1. DÉTECTION : Si tu détectes l'un de ces mots-clés dans le message :
- - "swap", "échanger", "convertir", "changer", "exchange"
- - OU phrases comme "eth en usdc", "bitcoin vers dai", "0.001 eth en usdc"
- - OU demandes directes de swap avec montant
-2. EXTRACTION : Extrais ces informations du message :
- - Le token source (ex: ETH, USDC, BTC)
- - Le token destination (ex: USDC, DAI, WETH)
- - Le montant numérique
-3. ACTION IMMÉDIATE : Utilise DIRECTEMENT l'outil execute_swap avec ces paramètres.
- - Pour ETH → USDC : execute_swap("ETH", "USDC", montant, adresse_wallet)
- - Pour USDC → DAI : execute_swap("USDC", "DAI", montant, adresse_wallet)
- - Si pas d'adresse wallet, utilise une adresse par défaut ou demande à l'utilisateur
-
-IMPORTANT :
-- NE DONNE JAMAIS d'explication préalable sur le swap
-- N'INFORME PAS l'utilisateur des détails avant d'appeler l'outil
-- NE DEMANDE JAMAIS de confirmation comme "Souhaitez-vous continuer ?"
-- APPELLE execute_swap IMMÉDIATEMENT dès que tu détectes une demande de swap
-- La modal d'interface se charge de tout afficher à l'utilisateur
-
-Provide clear, helpful responses about crypto prices, transactions, and swaps."""
+ system_prompt = self.get_shared_system_prompt()
if context:
- system_prompt += f"\nConversation context: {context}"
+ system_prompt = self.append_modules_and_context(system_prompt, context)
result = await self._chat_with_openai(message, system_prompt, self.model, client)
return result
except Exception as e:
@@ -97,57 +54,8 @@ async def process_message_with_config(self, message: str, context: str = "",
try:
client = self._get_default_client(api_key) if api_key else self._get_default_client()
if not system_prompt:
- system_prompt = """You are a crypto assistant with advanced capabilities including:
-1. **Price Information**: Get real-time cryptocurrency prices using get_crypto_price
-2. **Transactions**: Prepare blockchain transactions using request_transaction
-3. **Token Information**: Get available ERC-20 tokens on Sepolia using get_sepolia_tokens
-4. **Token Swapping**: Complete token swap capabilities using Li.Fi:
- - get_lifi_tokens: Discover available tokens for swapping
- - get_swap_quote: Get quotes for token swaps
- - execute_swap: Execute token swaps with transaction data
-
-🎯 RÈGLE CRITIQUE pour request_transaction :
-
-COPIE EXACTEMENT le mot que dit l'utilisateur dans le paramètre "currency". NE TRADUIS PAS, NE CONVERTIS PAS.
-
-EXEMPLES CORRECTS :
-- User: "envoie 0.001 ETH à 0x123..." → request_transaction(..., currency="ETH")
-- User: "envoie 0.001 sepolia à 0x123..." → request_transaction(..., currency="sepolia")
-- User: "envoie 5 USDC à 0x123..." → request_transaction(..., currency="USDC")
-
-❌ ERREUR FATALE (ne jamais faire) :
-- User dit "ETH" mais tu mets currency="sepolia" ← INTERDIT !
-
-✅ TOKENS DISPONIBLES : ETH, SEPOLIA, USDC, USDT, DAI, WETH, LINK, UNI
-
-RÈGLES CRITIQUES pour les swaps :
-1. DÉTECTION : Si tu détectes l'un de ces mots-clés dans le message :
- - "swap", "échanger", "convertir", "changer", "exchange"
- - OU phrases comme "eth en usdc", "bitcoin vers dai", "0.001 eth en usdc"
- - OU demandes directes de swap avec montant
-2. EXTRACTION : Extrais ces informations du message :
- - Le token source (ex: ETH, USDC, BTC)
- - Le token destination (ex: USDC, DAI, WETH)
- - Le montant numérique
-3. ACTION IMMÉDIATE : Utilise DIRECTEMENT l'outil execute_swap avec ces paramètres.
- - Pour ETH → USDC : execute_swap("ETH", "USDC", montant, adresse_wallet)
- - Pour USDC → DAI : execute_swap("USDC", "DAI", montant, adresse_wallet)
- - Si pas d'adresse wallet, utilise une adresse par défaut ou demande à l'utilisateur
-
-IMPORTANT :
-- NE DONNE JAMAIS d'explication préalable sur le swap
-- N'INFORME PAS l'utilisateur des détails avant d'appeler l'outil
-- NE DEMANDE JAMAIS de confirmation comme "Souhaitez-vous continuer ?"
-- APPELLE execute_swap IMMÉDIATEMENT dès que tu détectes une demande de swap
-- La modal d'interface se charge de tout afficher à l'utilisateur
-
-Provide clear, helpful responses about crypto prices, transactions, and swaps."""
- if modules:
- active_modules = [name for name, active in modules.items() if active]
- if active_modules:
- system_prompt += f"\nModules activés: {', '.join(active_modules)}"
- if context:
- system_prompt += f"\nContexte de conversation: {context}"
+ system_prompt = self.get_shared_system_prompt()
+ system_prompt = self.append_modules_and_context(system_prompt, context, modules)
result = await self._chat_with_openai(message, system_prompt, model, client)
return result
except Exception as e:
@@ -166,29 +74,21 @@ async def _chat_with_openai(self, message: str, system_prompt: str, model: str,
has_address = "0x" in message and len([part for part in message.split() if part.startswith("0x") and len(part) == 42]) > 0
has_amount = any(char.isdigit() for char in message)
is_likely_transaction = has_transaction_keyword and has_address and has_amount
-
+
# Détection des swaps
swap_keywords = ["swap", "échanger", "convertir", "changer", "exchange", "en usdc", "en dai", "vers usdc", "vers dai"]
has_swap_keyword = any(keyword in message.lower() for keyword in swap_keywords)
has_swap_amount = any(char.isdigit() for char in message)
is_likely_swap = has_swap_keyword and has_swap_amount
-
+
# Debug logs
print(f"🔍 DEBUG - Message: {message}")
print(f"🔍 DEBUG - has_swap_keyword: {has_swap_keyword}")
print(f"🔍 DEBUG - has_swap_amount: {has_swap_amount}")
print(f"🔍 DEBUG - is_likely_swap: {is_likely_swap}")
print(f"🔍 DEBUG - is_likely_transaction: {is_likely_transaction}")
-
- tool_choice = "auto"
- if is_likely_transaction:
- tool_choice = {"type": "function", "function": {"name": "request_transaction"}}
- print(f"🔍 DEBUG - Forcing request_transaction tool")
- elif is_likely_swap:
- tool_choice = {"type": "function", "function": {"name": "execute_swap"}}
- print(f"🔍 DEBUG - Forcing execute_swap tool")
- else:
- print(f"🔍 DEBUG - Using auto tool choice")
+
+ tool_choice = self.detect_tool_choice(message)
response = await client.chat.completions.create(
model=model,
messages=[
@@ -399,12 +299,12 @@ async def _chat_with_openai(self, message: str, system_prompt: str, model: str,
print(f"🔍 DEBUG - request_transaction called with args: {args}")
currency = args.get("currency", "ETH")
print(f"🔍 DEBUG - Currency extracted: {currency}")
-
+
# SÉCURITÉ : Si l'IA met "sepolia" alors que l'utilisateur a dit "ETH", forcer "ETH"
# (Cette logique sera enlevée quand l'IA apprendra)
if currency.lower() == "sepolia":
print(f"⚠️ WARNING - IA a mis 'sepolia', mais on garde comme ça pour l'instant")
-
+
result = request_transaction(
args.get("recipient_address", ""),
args.get("amount", ""),
@@ -504,8 +404,9 @@ async def _chat_with_openai(self, message: str, system_prompt: str, model: str,
except Exception as e:
return f"❌ Error in chat: {str(e)}"
-# Agent instance
-openai_agent = OpenAIAgent()
+# Agent instance - using factory for future extensibility
+from agent_factory import AgentFactory
+openai_agent = AgentFactory.create_agent("openai")
# MCP server instance
server = Server("crypto-pilot-agent-server")
@@ -659,14 +560,19 @@ async def handle_list_tools() -> list[Tool]:
"type": "string",
"description": "Custom system prompt"
},
+ "provider": {
+ "type": "string",
+ "description": "AI provider to use (openai, libertai)",
+ "default": "openai"
+ },
"model": {
"type": "string",
- "description": "OpenAI model to use",
+ "description": "AI model to use",
"default": "gpt-4o-mini"
},
"api_key": {
"type": "string",
- "description": "User's OpenAI API key"
+ "description": "User's API key for the selected provider"
},
"modules": {
"type": "string",
@@ -699,7 +605,7 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[TextConten
result = request_transaction(recipient_address, amount, currency, token_address)
return [TextContent(type="text", text=result)]
return [TextContent(type="text", text="❌ Missing required parameters: recipient_address, amount")]
-
+
if name == "get_lifi_tokens":
chains = arguments.get("chains", None)
result = get_lifi_tokens(chains)
@@ -746,6 +652,7 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[TextConten
message = arguments.get("message", "")
context = arguments.get("context", "")
system_prompt = arguments.get("system_prompt", "")
+ provider = arguments.get("provider", "openai")
model = arguments.get("model", "gpt-4o-mini")
api_key = arguments.get("api_key", "")
modules_str = arguments.get("modules", "{}")
@@ -756,7 +663,12 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[TextConten
modules = {}
if not message:
return [TextContent(type="text", text="❌ Empty message")]
- result = await openai_agent.process_message_with_config(
+
+ # Create agent based on provider
+ from agent_factory import AgentFactory
+ agent = AgentFactory.create_agent(provider)
+
+ result = await agent.process_message_with_config(
message=message,
context=context,
system_prompt=system_prompt,
diff --git a/crypto-pilot-builder/python/mcp_serveur/mcp_server_sdk_old.py b/crypto-pilot-builder/python/mcp_serveur/mcp_server_sdk_old.py
new file mode 100644
index 0000000..0a2cdfe
--- /dev/null
+++ b/crypto-pilot-builder/python/mcp_serveur/mcp_server_sdk_old.py
@@ -0,0 +1,684 @@
+#!/usr/bin/env python3
+"""
+MCP server with OpenAI agent and crypto tools
+"""
+import asyncio
+import sys
+import os
+import openai
+import json
+from dotenv import load_dotenv
+from mcp.server.models import InitializationOptions
+from mcp.server import NotificationOptions, Server
+from mcp.types import Tool, TextContent
+from mcp.server.stdio import stdio_server
+sys.path.append(os.path.dirname(__file__))
+from crypto_tools import get_crypto_price, request_transaction, get_lifi_tokens, get_swap_quote, execute_swap, get_sepolia_tokens, get_all_erc20_tokens
+from base_agent import BaseAgent
+from base_agent_impl import BaseAgentImpl
+load_dotenv()
+
+class OpenAIAgent(BaseAgent, BaseAgentImpl):
+ """OpenAI Agent with crypto capabilities"""
+ def __init__(self):
+ BaseAgentImpl.__init__(self, "OpenAI", "gpt-4o-mini")
+ self.default_client = None # Client async
+
+ def _get_client(self, api_key=None):
+ """Create OpenAI client"""
+ if self.default_client is None:
+ if api_key or os.getenv('OPENAI_API_KEY'):
+ actual_api_key = api_key or os.getenv('OPENAI_API_KEY')
+ self.default_client = openai.AsyncOpenAI(api_key=actual_api_key)
+ else:
+ return None
+ return self.default_client
+
+ # All methods are now inherited from BaseAgentImpl
+
+ # All chat logic is now inherited from BaseAgentImpl
+ try:
+ # Détection des transactions
+ transaction_keywords = ["envoie", "envoyer", "send", "transfer", "transférer", "payment", "pay"]
+ has_transaction_keyword = any(keyword in message.lower() for keyword in transaction_keywords)
+ has_address = "0x" in message and len([part for part in message.split() if part.startswith("0x") and len(part) == 42]) > 0
+ has_amount = any(char.isdigit() for char in message)
+ is_likely_transaction = has_transaction_keyword and has_address and has_amount
+
+ # Détection des swaps
+ swap_keywords = ["swap", "échanger", "convertir", "changer", "exchange", "en usdc", "en dai", "vers usdc", "vers dai"]
+ has_swap_keyword = any(keyword in message.lower() for keyword in swap_keywords)
+ has_swap_amount = any(char.isdigit() for char in message)
+ is_likely_swap = has_swap_keyword and has_swap_amount
+
+ # Debug logs
+ print(f"🔍 DEBUG - Message: {message}")
+ print(f"🔍 DEBUG - has_swap_keyword: {has_swap_keyword}")
+ print(f"🔍 DEBUG - has_swap_amount: {has_swap_amount}")
+ print(f"🔍 DEBUG - is_likely_swap: {is_likely_swap}")
+ print(f"🔍 DEBUG - is_likely_transaction: {is_likely_transaction}")
+
+ tool_choice = "auto"
+ if is_likely_transaction:
+ tool_choice = {"type": "function", "function": {"name": "request_transaction"}}
+ print(f"🔍 DEBUG - Forcing request_transaction tool")
+ elif is_likely_swap:
+ tool_choice = {"type": "function", "function": {"name": "execute_swap"}}
+ print(f"🔍 DEBUG - Forcing execute_swap tool")
+ else:
+ print(f"🔍 DEBUG - Using auto tool choice")
+ response = await client.chat.completions.create(
+ model=model,
+ messages=[
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": message}
+ ],
+ max_tokens=500,
+ temperature=0.1,
+ tools=[
+ {
+ "type": "function",
+ "function": {
+ "name": "get_crypto_price",
+ "description": "Get real-time cryptocurrency price via CoinGecko API",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "crypto_id": {
+ "type": "string",
+ "description": "Cryptocurrency identifier (e.g. bitcoin, ethereum)"
+ },
+ "currency": {
+ "type": "string",
+ "description": "Desired currency (e.g. eur, usd, gbp)",
+ "default": "usd"
+ }
+ },
+ "required": ["crypto_id"]
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "get_lifi_tokens",
+ "description": "Get available tokens from Li.Fi for swapping",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "chains": {
+ "type": "string",
+ "description": "Comma-separated chain IDs to filter tokens (optional)"
+ }
+ },
+ "required": []
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "get_swap_quote",
+ "description": "Get a swap quote from Li.Fi API for token swapping",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "from_token": {
+ "type": "string",
+ "description": "Source token symbol or address (e.g., ETH, USDC)"
+ },
+ "to_token": {
+ "type": "string",
+ "description": "Destination token symbol or address (e.g., USDC, DAI)"
+ },
+ "amount": {
+ "type": "string",
+ "description": "Amount to swap"
+ },
+ "from_address": {
+ "type": "string",
+ "description": "User's wallet address"
+ },
+ "from_chain": {
+ "type": "string",
+ "description": "Source blockchain ID (default: 1 for Ethereum)",
+ "default": "1"
+ },
+ "to_chain": {
+ "type": "string",
+ "description": "Destination blockchain ID (default: 1 for Ethereum)",
+ "default": "1"
+ }
+ },
+ "required": ["from_token", "to_token", "amount", "from_address"]
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "execute_swap",
+ "description": "Execute a crypto swap using Li.Fi - generates transaction data for user to sign",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "from_token": {
+ "type": "string",
+ "description": "Source token symbol or address (e.g., ETH, USDC)"
+ },
+ "to_token": {
+ "type": "string",
+ "description": "Destination token symbol or address (e.g., USDC, DAI)"
+ },
+ "amount": {
+ "type": "string",
+ "description": "Amount to swap"
+ },
+ "from_address": {
+ "type": "string",
+ "description": "User's wallet address"
+ },
+ "from_chain": {
+ "type": "string",
+ "description": "Source blockchain ID (default: 1 for Ethereum)",
+ "default": "1"
+ },
+ "to_chain": {
+ "type": "string",
+ "description": "Destination blockchain ID (default: 1 for Ethereum)",
+ "default": "1"
+ }
+ },
+ "required": ["from_token", "to_token", "amount", "from_address"]
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "request_transaction",
+ "description": "Request a blockchain transaction. Use this when user wants to send/transfer cryptocurrency.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "recipient_address": {
+ "type": "string",
+ "description": "Recipient Ethereum address (0x...)"
+ },
+ "amount": {
+ "type": "string",
+ "description": "Amount to send (e.g. 0.001)"
+ },
+ "currency": {
+ "type": "string",
+ "description": "EXACT word user said: If user says 'ETH' use 'ETH', if user says 'sepolia' use 'sepolia', if user says 'USDC' use 'USDC'. NEVER convert ETH to sepolia!"
+ },
+ "token_address": {
+ "type": "string",
+ "description": "ERC-20 token contract address (optional, for ERC-20 transactions)"
+ }
+ },
+ "required": ["recipient_address", "amount"]
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "get_sepolia_tokens",
+ "description": "Get list of available ERC-20 tokens on Sepolia testnet with their addresses and decimals.",
+ "parameters": {
+ "type": "object",
+ "properties": {},
+ "required": []
+ }
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "get_all_erc20_tokens",
+ "description": "Get all available ERC-20 tokens dynamically from blockchain (Sepolia or Ethereum Mainnet). Uses Etherscan API to discover popular tokens.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "chain_id": {
+ "type": "string",
+ "description": "Chain ID: '11155111' for Sepolia, '1' for Ethereum Mainnet",
+ "default": "11155111"
+ }
+ },
+ "required": []
+ }
+ }
+ }
+ ],
+ tool_choice=tool_choice
+ )
+ response_message = response.choices[0].message
+ if hasattr(response_message, "tool_calls") and response_message.tool_calls:
+ print(f"🔍 DEBUG - Tool calls detected: {len(response_message.tool_calls)}")
+ tool_responses = []
+ for tool_call in response_message.tool_calls:
+ tool_name = tool_call.function.name
+ args = json.loads(tool_call.function.arguments)
+ print(f"🔍 DEBUG - Tool called: {tool_name} with args: {args}")
+ if tool_name == "get_crypto_price":
+ result = get_crypto_price(
+ args.get("crypto_id", ""),
+ args.get("currency", "usd")
+ )
+ tool_responses.append({
+ "name": tool_name,
+ "content": result,
+ "tool_call_id": tool_call.id
+ })
+ elif tool_name == "request_transaction":
+ print(f"🔍 DEBUG - request_transaction called with args: {args}")
+ currency = args.get("currency", "ETH")
+ print(f"🔍 DEBUG - Currency extracted: {currency}")
+
+ # SÉCURITÉ : Si l'IA met "sepolia" alors que l'utilisateur a dit "ETH", forcer "ETH"
+ # (Cette logique sera enlevée quand l'IA apprendra)
+ if currency.lower() == "sepolia":
+ print(f"⚠️ WARNING - IA a mis 'sepolia', mais on garde comme ça pour l'instant")
+
+ result = request_transaction(
+ args.get("recipient_address", ""),
+ args.get("amount", ""),
+ currency,
+ args.get("token_address", None)
+ )
+ tool_responses.append({
+ "name": tool_name,
+ "content": result,
+ "tool_call_id": tool_call.id
+ })
+ elif tool_name == "get_lifi_tokens":
+ result = get_lifi_tokens(
+ args.get("chains", None)
+ )
+ tool_responses.append({
+ "name": tool_name,
+ "content": result,
+ "tool_call_id": tool_call.id
+ })
+ elif tool_name == "get_swap_quote":
+ result = get_swap_quote(
+ args.get("from_token", ""),
+ args.get("to_token", ""),
+ args.get("amount", ""),
+ args.get("from_address", ""),
+ args.get("from_chain", "1"),
+ args.get("to_chain", "1")
+ )
+ tool_responses.append({
+ "name": tool_name,
+ "content": result,
+ "tool_call_id": tool_call.id
+ })
+ elif tool_name == "get_sepolia_tokens":
+ result = get_sepolia_tokens()
+ tool_responses.append({
+ "name": tool_name,
+ "content": result,
+ "tool_call_id": tool_call.id
+ })
+ elif tool_name == "get_all_erc20_tokens":
+ chain_id = args.get("chain_id", "11155111")
+ result = get_all_erc20_tokens(chain_id)
+ tool_responses.append({
+ "name": tool_name,
+ "content": result,
+ "tool_call_id": tool_call.id
+ })
+ elif tool_name == "execute_swap":
+ print(f"🔍 DEBUG - execute_swap called with args: {args}")
+ result = execute_swap(
+ args.get("from_token", ""),
+ args.get("to_token", ""),
+ args.get("amount", ""),
+ args.get("from_address", ""),
+ args.get("from_chain", "11155111"),
+ args.get("to_chain", "11155111")
+ )
+ print(f"🔍 DEBUG - execute_swap result: {result}")
+ tool_responses.append({
+ "name": tool_name,
+ "content": result,
+ "tool_call_id": tool_call.id
+ })
+ if tool_responses:
+ print(f"🔍 DEBUG - Tool responses: {[res['name'] for res in tool_responses]}")
+ for res in tool_responses:
+ if res["name"] == "request_transaction":
+ print(f"🔍 DEBUG - Returning request_transaction result directly")
+ return res["content"]
+ elif res["name"] == "execute_swap":
+ print(f"🔍 DEBUG - Returning execute_swap result directly")
+ return res["content"]
+ second_messages = [
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": message},
+ {"role": "assistant", "content": None, "tool_calls": response_message.tool_calls},
+ ]
+ for res in tool_responses:
+ second_messages.append({
+ "role": "tool",
+ "name": res["name"],
+ "content": res["content"],
+ "tool_call_id": res["tool_call_id"]
+ })
+ second_response = await client.chat.completions.create(
+ model=model,
+ messages=second_messages,
+ max_tokens=500,
+ temperature=0.1
+ )
+ final_response = second_response.choices[0].message.content
+ return final_response
+ direct_response = response_message.content
+ return direct_response
+ except Exception as e:
+ return f"❌ Error in chat: {str(e)}"
+
+# Agent instance - using factory for future extensibility
+from agent_factory import AgentFactory
+openai_agent = AgentFactory.create_agent("openai")
+
+# MCP server instance
+server = Server("crypto-pilot-agent-server")
+
+@server.list_tools()
+async def handle_list_tools() -> list[Tool]:
+ """List available tools"""
+ return [
+ Tool(
+ name="get_crypto_price",
+ description="Get real-time cryptocurrency price via CoinGecko",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "crypto_id": {
+ "type": "string",
+ "description": "Cryptocurrency identifier"
+ },
+ "currency": {
+ "type": "string",
+ "description": "Desired currency (default: usd)",
+ "default": "usd"
+ }
+ },
+ "required": ["crypto_id"]
+ }
+ ),
+ Tool(
+ name="request_transaction",
+ description="Request a blockchain transaction",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "recipient_address": {
+ "type": "string",
+ "description": "Ethereum recipient address"
+ },
+ "amount": {
+ "type": "string",
+ "description": "Amount to send"
+ },
+ "currency": {
+ "type": "string",
+ "description": "EXACT word user said: If user says 'ETH' use 'ETH', if user says 'sepolia' use 'sepolia'. NEVER convert ETH to sepolia!"
+ }
+ },
+ "required": ["recipient_address", "amount"]
+ }
+ ),
+ Tool(
+ name="get_lifi_tokens",
+ description="Get available tokens from Li.Fi for swapping",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "chains": {
+ "type": "string",
+ "description": "Comma-separated chain IDs to filter tokens (optional)"
+ }
+ },
+ "required": []
+ }
+ ),
+ Tool(
+ name="get_swap_quote",
+ description="Get a swap quote from Li.Fi API",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "from_token": {
+ "type": "string",
+ "description": "Source token symbol or address (e.g., ETH, USDC)"
+ },
+ "to_token": {
+ "type": "string",
+ "description": "Destination token symbol or address (e.g., USDC, DAI)"
+ },
+ "amount": {
+ "type": "string",
+ "description": "Amount to swap"
+ },
+ "from_address": {
+ "type": "string",
+ "description": "User's wallet address"
+ },
+ "from_chain": {
+ "type": "string",
+ "description": "Source blockchain ID (default: 1 for Ethereum)",
+ "default": "1"
+ },
+ "to_chain": {
+ "type": "string",
+ "description": "Destination blockchain ID (default: 1 for Ethereum)",
+ "default": "1"
+ }
+ },
+ "required": ["from_token", "to_token", "amount", "from_address"]
+ }
+ ),
+ Tool(
+ name="execute_swap",
+ description="Execute a crypto swap using Li.Fi",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "from_token": {
+ "type": "string",
+ "description": "Source token symbol or address (e.g., ETH, USDC)"
+ },
+ "to_token": {
+ "type": "string",
+ "description": "Destination token symbol or address (e.g., USDC, DAI)"
+ },
+ "amount": {
+ "type": "string",
+ "description": "Amount to swap"
+ },
+ "from_address": {
+ "type": "string",
+ "description": "User's wallet address"
+ },
+ "from_chain": {
+ "type": "string",
+ "description": "Source blockchain ID (default: 1 for Ethereum)",
+ "default": "1"
+ },
+ "to_chain": {
+ "type": "string",
+ "description": "Destination blockchain ID (default: 1 for Ethereum)",
+ "default": "1"
+ }
+ },
+ "required": ["from_token", "to_token", "amount", "from_address"]
+ }
+ ),
+ Tool(
+ name="agent_chat_configured",
+ description="Chat with AI agent using custom configuration",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "message": {
+ "type": "string",
+ "description": "User message"
+ },
+ "context": {
+ "type": "string",
+ "description": "Conversation context"
+ },
+ "system_prompt": {
+ "type": "string",
+ "description": "Custom system prompt"
+ },
+ "provider": {
+ "type": "string",
+ "description": "AI provider to use (openai, libertai)",
+ "default": "openai"
+ },
+ "model": {
+ "type": "string",
+ "description": "AI model to use",
+ "default": "gpt-4o-mini"
+ },
+ "api_key": {
+ "type": "string",
+ "description": "User's API key for the selected provider"
+ },
+ "modules": {
+ "type": "string",
+ "description": "JSON string of enabled modules"
+ }
+ },
+ "required": ["message"]
+ }
+ )
+ ]
+
+@server.call_tool()
+async def handle_call_tool(name: str, arguments: dict | None) -> list[TextContent]:
+ """Handle tool calls and agent communication"""
+ if arguments is None:
+ return [TextContent(type="text", text="❌ Invalid arguments")]
+ if name == "get_crypto_price":
+ crypto_id = arguments.get("crypto_id", "")
+ currency = arguments.get("currency", "usd")
+ if crypto_id:
+ result = get_crypto_price(crypto_id, currency)
+ return [TextContent(type="text", text=result)]
+ return [TextContent(type="text", text="❌ crypto_id required")]
+ if name == "request_transaction":
+ recipient_address = arguments.get("recipient_address", "")
+ amount = arguments.get("amount", "")
+ currency = arguments.get("currency", "ETH")
+ token_address = arguments.get("token_address", None)
+ if recipient_address and amount:
+ result = request_transaction(recipient_address, amount, currency, token_address)
+ return [TextContent(type="text", text=result)]
+ return [TextContent(type="text", text="❌ Missing required parameters: recipient_address, amount")]
+
+ if name == "get_lifi_tokens":
+ chains = arguments.get("chains", None)
+ result = get_lifi_tokens(chains)
+ return [TextContent(type="text", text=result)]
+
+ if name == "get_sepolia_tokens":
+ result = get_sepolia_tokens()
+ return [TextContent(type="text", text=result)]
+
+ if name == "get_all_erc20_tokens":
+ chain_id = arguments.get("chain_id", "11155111")
+ result = get_all_erc20_tokens(chain_id)
+ return [TextContent(type="text", text=result)]
+
+ if name == "get_swap_quote":
+ from_token = arguments.get("from_token", "")
+ to_token = arguments.get("to_token", "")
+ amount = arguments.get("amount", "")
+ from_address = arguments.get("from_address", "")
+ from_chain = arguments.get("from_chain", "1")
+ to_chain = arguments.get("to_chain", "1")
+
+ if not all([from_token, to_token, amount, from_address]):
+ return [TextContent(type="text", text="❌ Missing required parameters: from_token, to_token, amount, from_address")]
+
+ result = get_swap_quote(from_token, to_token, amount, from_address, from_chain, to_chain)
+ return [TextContent(type="text", text=result)]
+
+ if name == "execute_swap":
+ from_token = arguments.get("from_token", "")
+ to_token = arguments.get("to_token", "")
+ amount = arguments.get("amount", "")
+ from_address = arguments.get("from_address", "")
+ from_chain = arguments.get("from_chain", "1")
+ to_chain = arguments.get("to_chain", "1")
+
+ if not all([from_token, to_token, amount, from_address]):
+ return [TextContent(type="text", text="❌ Missing required parameters: from_token, to_token, amount, from_address")]
+
+ result = execute_swap(from_token, to_token, amount, from_address, from_chain or "11155111", to_chain or "11155111")
+ return [TextContent(type="text", text=result)]
+
+ if name == "agent_chat_configured":
+ message = arguments.get("message", "")
+ context = arguments.get("context", "")
+ system_prompt = arguments.get("system_prompt", "")
+ provider = arguments.get("provider", "openai")
+ model = arguments.get("model", "gpt-4o-mini")
+ api_key = arguments.get("api_key", "")
+ modules_str = arguments.get("modules", "{}")
+ modules = {}
+ try:
+ modules = json.loads(modules_str) if modules_str else {}
+ except:
+ modules = {}
+ if not message:
+ return [TextContent(type="text", text="❌ Empty message")]
+
+ # Create agent based on provider
+ from agent_factory import AgentFactory
+ agent = AgentFactory.create_agent(provider)
+
+ result = await agent.process_message_with_config(
+ message=message,
+ context=context,
+ system_prompt=system_prompt,
+ model=model,
+ api_key=api_key,
+ modules=modules
+ )
+ return [TextContent(type="text", text=result)]
+ message = arguments.get("message", "")
+ context = arguments.get("context", "")
+ if not message:
+ message = name if name and name != "agent_chat" else ""
+ if not message:
+ return [TextContent(type="text", text="❌ Empty message")]
+ result = await openai_agent.process_message(message, context)
+ return [TextContent(type="text", text=result)]
+
+async def main():
+ """Launch MCP server"""
+ async with stdio_server() as (read_stream, write_stream):
+ await server.run(
+ read_stream,
+ write_stream,
+ InitializationOptions(
+ server_name="crypto-pilot-agent-server",
+ server_version="1.0.0",
+ capabilities=server.get_capabilities(
+ notification_options=NotificationOptions(),
+ experimental_capabilities={},
+ ),
+ ),
+ )
+
+if __name__ == "__main__":
+ asyncio.run(main())
\ No newline at end of file
diff --git a/crypto-pilot-builder/python/test_libertai.py b/crypto-pilot-builder/python/test_libertai.py
new file mode 100644
index 0000000..a630707
--- /dev/null
+++ b/crypto-pilot-builder/python/test_libertai.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+"""
+Script de test pour vérifier la connexion LibertAI
+"""
+import asyncio
+import os
+from dotenv import load_dotenv
+
+async def test_libertai_connection():
+ """Test de la connexion LibertAI"""
+
+ # Clé API LibertAI (à remplacer par votre vraie clé)
+ api_key = "d56d770931ba135ae02b68ed32c8dcf4"
+
+ if not api_key:
+ print("❌ Aucune clé API LibertAI trouvée")
+ return False
+
+ print(f"🔑 Clé API trouvée: {api_key[:8]}...")
+
+ try:
+ import openai
+
+ # Créer le client LibertAI
+ client = openai.AsyncOpenAI(
+ api_key=api_key,
+ base_url="https://api.libertai.io/v1"
+ )<
+
+ print("✅ Client LibertAI créé avec succès")
+
+ # Test simple
+ print("🧪 Test de connexion avec LibertAI...")
+
+ try:
+ response = await client.chat.completions.create(
+ model="hermes-3-8b",
+ messages=[
+ {"role": "user", "content": "Hello! Can you respond with just 'OK'?"}
+ ],
+ max_tokens=10,
+ temperature=0.1
+ )
+
+ print(f"📡 Réponse reçue: {response}")
+ print(f"📡 Type de réponse: {type(response)}")
+
+ if response:
+ print(f"📡 Attributs de la réponse: {dir(response)}")
+ if hasattr(response, 'choices'):
+ print(f"📡 Choices: {response.choices}")
+ if response.choices:
+ print(f"📡 Premier choice: {response.choices[0]}")
+ if hasattr(response.choices[0], 'message'):
+ print(f"📡 Message: {response.choices[0].message}")
+ content = response.choices[0].message.content
+ print(f"✅ Contenu de la réponse: '{content}'")
+ return True
+ else:
+ print("❌ Pas d'attribut 'message' dans choices[0]")
+ else:
+ print("❌ Liste choices vide")
+ else:
+ print("❌ Pas d'attribut 'choices' dans la réponse")
+ else:
+ print("❌ Réponse None ou False")
+
+ return False
+
+ except Exception as api_error:
+ print(f"❌ Erreur API LibertAI: {api_error}")
+ print(f"❌ Type d'erreur: {type(api_error)}")
+ return False
+
+ except ImportError as e:
+ print(f"❌ Module openai non installé: {e}")
+ return False
+ except Exception as e:
+ print(f"❌ Erreur lors du test: {e}")
+ return False
+
+if __name__ == "__main__":
+ print("🚀 Test de connexion LibertAI...")
+
+ success = asyncio.run(test_libertai_connection())
+
+ if success:
+ print("🎉 Test réussi ! LibertAI fonctionne correctement.")
+ else:
+ print("💥 Test échoué. Vérifiez votre configuration.")
diff --git a/crypto-pilot-builder/src/agent_building/Ai.vue b/crypto-pilot-builder/src/agent_building/Ai.vue
index 749fd9d..b78042d 100644
--- a/crypto-pilot-builder/src/agent_building/Ai.vue
+++ b/crypto-pilot-builder/src/agent_building/Ai.vue
@@ -9,6 +9,36 @@