From 765e390b9b2e322eb4f7b8328d27b7114ee2c457 Mon Sep 17 00:00:00 2001 From: shokollm <270575765+shokollm@users.noreply.github.com> Date: Fri, 10 Apr 2026 05:00:22 +0000 Subject: [PATCH] feat: implement conversational AI agent with tool-calling Prototype implementation that allows: 1. Normal conversation with the AI 2. Tool-calling to update trading strategies Created new ConversationalAgent that uses CrewAI with tools: - get_current_strategy: Check current bot strategy - update_trading_strategy: Update bot's trading configuration The agent can now respond to questions like 'What is this?' without forcing JSON output, and can update strategies when user provides specific parameters. Refs #51 --- src/backend/app/api/bots.py | 83 ++++----- .../app/services/ai_agent/conversational.py | 167 ++++++++++++++++++ 2 files changed, 197 insertions(+), 53 deletions(-) create mode 100644 src/backend/app/services/ai_agent/conversational.py diff --git a/src/backend/app/api/bots.py b/src/backend/app/api/bots.py index ae87897..e18ff8e 100644 --- a/src/backend/app/api/bots.py +++ b/src/backend/app/api/bots.py @@ -16,6 +16,7 @@ from ..db.schemas import ( ) from ..db.models import Bot, BotConversation, User from ..services.ai_agent.crew import get_trading_crew +from ..services.ai_agent.conversational import get_conversational_agent router = APIRouter() MAX_BOTS_PER_USER = 3 @@ -183,69 +184,45 @@ def chat( .order_by(BotConversation.created_at) .all() ) - history_for_crew = [ + history_for_agent = [ {"role": conv.role, "content": conv.content} for conv in conversation_history[-10:] ] user_message = request.message - if request.strategy_config: - crew = get_trading_crew() - result = crew.chat(user_message, history_for_crew) + + # Use ConversationalAgent for natural chat with tool-calling + agent = get_conversational_agent(bot_id=bot_id) + result = agent.chat(user_message, history_for_agent) - assistant_content = result.get("response", "I couldn't process your request.") - if result.get("success") and result.get("strategy_config"): - bot.strategy_config = result["strategy_config"] - db.commit() + assistant_content = result.get("response", "I couldn't process your request.") - db_conversation = BotConversation( - bot_id=bot_id, - role="user", - content=user_message, - ) - db.add(db_conversation) + # Save conversation + db_conversation = BotConversation( + bot_id=bot_id, + role="user", + content=user_message, + ) + db.add(db_conversation) - db_assistant = BotConversation( - bot_id=bot_id, - role="assistant", - content=assistant_content, - ) - db.add(db_assistant) - db.commit() - db.refresh(db_assistant) + db_assistant = BotConversation( + bot_id=bot_id, + role="assistant", + content=assistant_content, + ) + db.add(db_assistant) + db.commit() + db.refresh(db_assistant) - return BotChatResponse( - response=assistant_content, - strategy_config=result.get("strategy_config"), - success=result.get("success", False), - ) - else: - crew = get_trading_crew() - result = crew.chat(user_message, history_for_crew) + # If strategy was updated via tool, refresh bot data + if result.get("strategy_updated"): + db.refresh(bot) - assistant_content = result.get("response", "I couldn't process your request.") - - db_conversation = BotConversation( - bot_id=bot_id, - role="user", - content=user_message, - ) - db.add(db_conversation) - - db_assistant = BotConversation( - bot_id=bot_id, - role="assistant", - content=assistant_content, - ) - db.add(db_assistant) - db.commit() - db.refresh(db_assistant) - - return BotChatResponse( - response=assistant_content, - strategy_config=result.get("strategy_config"), - success=result.get("success", False), - ) + return BotChatResponse( + response=assistant_content, + strategy_config=bot.strategy_config if result.get("strategy_updated") else None, + success=result.get("success", False), + ) @router.get("/{bot_id}/history", response_model=List[BotConversationResponse]) diff --git a/src/backend/app/services/ai_agent/conversational.py b/src/backend/app/services/ai_agent/conversational.py new file mode 100644 index 0000000..d8b3b63 --- /dev/null +++ b/src/backend/app/services/ai_agent/conversational.py @@ -0,0 +1,167 @@ +""" +Conversational Trading Agent + +This agent can: +1. Have normal conversations with users +2. Update trading strategies when user provides specific instructions + +Uses CrewAI's tool-calling capabilities for structured updates. +""" + +from typing import List, Optional, Dict, Any +from crewai import Agent, LLM +from crewai.tools import tool +from sqlalchemy.orm import Session + +from ...core.config import get_settings +from ...db.models import Bot + + +# Tool definitions +@tool +def get_current_strategy(bot_id: str) -> str: + """Get the current trading strategy configuration for a bot. + + Use this tool to check the current strategy before making changes. + + Args: + bot_id: The ID of the bot to get strategy for + + Returns: + JSON string with current strategy configuration + """ + from ...core.database import get_db + from ...db.models import Bot + + db = next(get_db()) + bot = db.query(Bot).filter(Bot.id == bot_id).first() + if not bot: + return '{"error": "Bot not found"}' + return str(bot.strategy_config) + + +@tool +def update_trading_strategy( + bot_id: str, + conditions: List[Dict], + actions: List[Dict], + risk_management: Optional[Dict] = None +) -> str: + """Update the trading strategy configuration for a bot. + + Call this tool when the user provides specific trading parameters like: + - Buy/sell conditions (price drops, price rises, etc.) + - Take profit percentages + - Stop loss percentages + + Args: + bot_id: The ID of the bot to update + conditions: List of trigger conditions (e.g., [{"type": "price_drop", "token": "PEPE", "threshold": 5}]) + actions: List of actions to take (e.g., [{"type": "buy", "amount_percent": 50}]) + risk_management: Optional risk settings (e.g., {"stop_loss_percent": 10, "take_profit_percent": 50}) + + Returns: + Confirmation message with updated strategy + """ + from ...core.database import get_db + from ...db.models import Bot + + db = next(get_db()) + bot = db.query(Bot).filter(Bot.id == bot_id).first() + if not bot: + return '{"error": "Bot not found"}' + + new_config = { + "conditions": conditions, + "actions": actions, + } + if risk_management: + new_config["risk_management"] = risk_management + + bot.strategy_config = new_config + db.commit() + + return f'Successfully updated trading strategy. New config: {new_config}' + + +SYSTEM_PROMPT = """You are a helpful AI trading assistant. You can: + +1. Have normal conversations - answer questions about trading, tokens, strategies, etc. +2. Help users configure their trading bots when they provide specific parameters + +When a user asks general questions, just answer conversationally. +When a user provides specific trading parameters (like percentages, tokens, conditions), +use the update_trading_strategy tool to save their configuration. + +Example conversations: +- User: "What is this?" → Answer conversationally about the trading bot platform +- User: "I want take profit at 200%" → Use update_trading_strategy with that parameter +- User: "Alert me when PEPE drops 5%" → Use update_trading_strategy with that condition + +Be friendly, helpful, and clear in your responses.""" + + +class ConversationalAgent: + def __init__(self, api_key: str, model: str = "MiniMax-M2.7", bot_id: str = None): + self.api_key = api_key + self.model = model + self.bot_id = bot_id + self.llm = LLM( + model=model, + api_key=api_key, + api_base="https://api.minimax.io/v1" + ) + + # Create agent with tools + self.agent = Agent( + role="Trading Assistant", + goal="Help users with trading strategies and general questions", + backstory=SYSTEM_PROMPT, + tools=[get_current_strategy, update_trading_strategy], + llm=self.llm, + verbose=True, + allow_delegation=False, + ) + + def chat(self, user_message: str, conversation_history: List[Dict] = None) -> Dict[str, Any]: + """Process a user message and return a response. + + Args: + user_message: The user's message + conversation_history: Optional list of previous messages + + Returns: + Dict with 'response' (the assistant's reply) and 'strategy_updated' (bool) + """ + # Execute agent using kickoff + try: + result = self.agent.kickoff(user_message) + + # Check if strategy was updated + result_str = str(result) + strategy_updated = "update_trading_strategy" in result_str or \ + "Successfully updated" in result_str + + return { + "response": result_str, + "strategy_updated": strategy_updated, + "success": True + } + except Exception as e: + return { + "response": f"I encountered an error: {str(e)}. Please try again.", + "strategy_updated": False, + "success": False + } + + +def get_conversational_agent(api_key: str = None, model: str = None, bot_id: str = None) -> ConversationalAgent: + """Get or create a ConversationalAgent instance.""" + if api_key is None: + settings = get_settings() + api_key = settings.MINIMAX_API_KEY + if model is None: + settings = get_settings() + model = settings.MINIMAX_MODEL + + return ConversationalAgent(api_key=api_key, model=model, bot_id=bot_id)