You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/lib/AI_Tool_Handler.cpp

228 lines
6.5 KiB

#ifdef PROXYSQLGENAI
#include "proxysql.h"
/**
* @file AI_Tool_Handler.cpp
* @brief Implementation of AI Tool Handler for MCP protocol
*
* Implements AI-powered tools through MCP protocol, primarily
* the ai_nl2sql_convert tool for natural language to SQL conversion.
*
* @see AI_Tool_Handler.h
*/
#include "AI_Tool_Handler.h"
#include "LLM_Bridge.h"
#include "Anomaly_Detector.h"
#include "AI_Features_Manager.h"
#include "proxysql_debug.h"
#include "cpp.h"
#include <sstream>
#include <algorithm>
// JSON library
#include "../deps/json/json.hpp"
using json = nlohmann::json;
#define PROXYJSON
// ============================================================================
// Constructor/Destructor
// ============================================================================
/**
* @brief Constructor using existing AI components
*/
AI_Tool_Handler::AI_Tool_Handler(LLM_Bridge* llm, Anomaly_Detector* anomaly)
: llm_bridge(llm),
anomaly_detector(anomaly),
owns_components(false)
{
proxy_debug(PROXY_DEBUG_GENAI, 3, "AI_Tool_Handler created (wrapping existing components)\n");
}
/**
* @brief Constructor - creates own components
* Note: This implementation uses global instances
*/
AI_Tool_Handler::AI_Tool_Handler()
: llm_bridge(NULL),
anomaly_detector(NULL),
owns_components(false)
{
// Use global instances from AI_Features_Manager
if (GloAI) {
llm_bridge = GloAI->get_llm_bridge();
anomaly_detector = GloAI->get_anomaly_detector();
}
proxy_debug(PROXY_DEBUG_GENAI, 3, "AI_Tool_Handler created (using global instances)\n");
}
/**
* @brief Destructor
*/
AI_Tool_Handler::~AI_Tool_Handler() {
close();
proxy_debug(PROXY_DEBUG_GENAI, 3, "AI_Tool_Handler destroyed\n");
}
// ============================================================================
// Lifecycle
// ============================================================================
/**
* @brief Initialize the tool handler
*/
int AI_Tool_Handler::init() {
if (!llm_bridge) {
proxy_error("AI_Tool_Handler: LLM bridge not available\n");
return -1;
}
proxy_info("AI_Tool_Handler initialized\n");
return 0;
}
/**
* @brief Close and cleanup
*/
void AI_Tool_Handler::close() {
if (owns_components) {
// Components would be cleaned up here
// For now, we use global instances managed by AI_Features_Manager
}
}
// ============================================================================
// Helper Functions
// ============================================================================
/**
* @brief Extract string parameter from JSON
*/
std::string AI_Tool_Handler::get_json_string(const json& j, const std::string& key,
const std::string& default_val) {
if (j.contains(key) && !j[key].is_null()) {
if (j[key].is_string()) {
return j[key].get<std::string>();
} else {
// Convert to string if not already
return j[key].dump();
}
}
return default_val;
}
/**
* @brief Extract int parameter from JSON
*/
int AI_Tool_Handler::get_json_int(const json& j, const std::string& key, int default_val) {
if (j.contains(key) && !j[key].is_null()) {
if (j[key].is_number()) {
return j[key].get<int>();
} else if (j[key].is_string()) {
try {
return std::stoi(j[key].get<std::string>());
} catch (const std::exception& e) {
proxy_error("AI_Tool_Handler: Failed to convert string to int for key '%s': %s\n",
key.c_str(), e.what());
return default_val;
}
}
}
return default_val;
}
// ============================================================================
// Tool List
// ============================================================================
/**
* @brief Get list of available AI tools
*/
json AI_Tool_Handler::get_tool_list() {
json tools = json::array();
// NL2SQL tool
json nl2sql_params = json::object();
nl2sql_params["type"] = "object";
nl2sql_params["properties"] = json::object();
nl2sql_params["properties"]["natural_language"] = {
{"type", "string"},
{"description", "Natural language query to convert to SQL"}
};
nl2sql_params["properties"]["schema"] = {
{"type", "string"},
{"description", "Database/schema name for context"}
};
nl2sql_params["properties"]["context_tables"] = {
{"type", "string"},
{"description", "Comma-separated list of relevant tables (optional)"}
};
nl2sql_params["properties"]["max_latency_ms"] = {
{"type", "integer"},
{"description", "Maximum acceptable latency in milliseconds (optional)"}
};
nl2sql_params["properties"]["allow_cache"] = {
{"type", "boolean"},
{"description", "Whether to check semantic cache (default: true)"}
};
nl2sql_params["required"] = json::array({"natural_language"});
tools.push_back({
{"name", "ai_nl2sql_convert"},
{"description", "Convert natural language query to SQL using LLM"},
{"inputSchema", nl2sql_params}
});
json result;
result["tools"] = tools;
return result;
}
/**
* @brief Get description of a specific tool
*/
json AI_Tool_Handler::get_tool_description(const std::string& tool_name) {
json tools_list = get_tool_list();
for (const auto& tool : tools_list["tools"]) {
if (tool["name"] == tool_name) {
return tool;
}
}
return create_error_response("Tool not found: " + tool_name);
}
// ============================================================================
// Tool Execution
// ============================================================================
/**
* @brief Execute an AI tool
*/
json AI_Tool_Handler::execute_tool(const std::string& tool_name, const json& arguments) {
proxy_debug(PROXY_DEBUG_GENAI, 3, "AI_Tool_Handler: execute_tool(%s)\n", tool_name.c_str());
try {
// LLM processing tool (generic, replaces NL2SQL)
if (tool_name == "ai_nl2sql_convert") {
// NOTE: The ai_nl2sql_convert tool is deprecated.
// NL2SQL functionality has been replaced with a generic LLM bridge.
// Future NL2SQL will be implemented as a Web UI using external agents (Claude Code + MCP server).
return create_error_response("The ai_nl2sql_convert tool is deprecated. "
"Use the generic LLM: queries via MySQL protocol instead.");
}
// Unknown tool
return create_error_response("Unknown tool: " + tool_name);
} catch (const std::exception& e) {
proxy_error("AI_Tool_Handler: Exception in execute_tool: %s\n", e.what());
return create_error_response(std::string("Exception: ") + e.what());
} catch (...) {
proxy_error("AI_Tool_Handler: Unknown exception in execute_tool\n");
return create_error_response("Unknown exception");
}
}
#endif /* PROXYSQLGENAI */