mirror of https://github.com/sysown/proxysql
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.
869 lines
26 KiB
869 lines
26 KiB
/**
|
|
* @file RAG_Tool_Handler.cpp
|
|
* @brief Implementation of RAG Tool Handler for MCP protocol
|
|
*
|
|
* Implements RAG-powered tools through MCP protocol for retrieval operations.
|
|
* This file contains the complete implementation of all RAG functionality
|
|
* including search, fetch, and administrative tools.
|
|
*
|
|
* @see RAG_Tool_Handler.h
|
|
*/
|
|
|
|
#include "RAG_Tool_Handler.h"
|
|
#include "AI_Features_Manager.h"
|
|
#include "GenAI_Thread.h"
|
|
#include "LLM_Bridge.h"
|
|
#include "proxysql_debug.h"
|
|
#include "cpp.h"
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
|
|
// Forward declaration for GloGATH
|
|
extern GenAI_Threads_Handler *GloGATH;
|
|
|
|
// JSON library
|
|
#include "../deps/json/json.hpp"
|
|
using json = nlohmann::json;
|
|
#define PROXYJSON
|
|
|
|
// Forward declaration for GloGATH
|
|
extern GenAI_Threads_Handler *GloGATH;
|
|
|
|
// ============================================================================
|
|
// Constructor/Destructor
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Constructor
|
|
*
|
|
* Initializes the RAG tool handler with configuration parameters from GenAI_Thread
|
|
* if available, otherwise uses default values.
|
|
*
|
|
* Configuration parameters:
|
|
* - k_max: Maximum number of search results (default: 50)
|
|
* - candidates_max: Maximum number of candidates for hybrid search (default: 500)
|
|
* - query_max_bytes: Maximum query length in bytes (default: 8192)
|
|
* - response_max_bytes: Maximum response size in bytes (default: 5000000)
|
|
* - timeout_ms: Operation timeout in milliseconds (default: 2000)
|
|
*
|
|
* @param ai_mgr Pointer to AI_Features_Manager for database access and configuration
|
|
*
|
|
* @see AI_Features_Manager
|
|
* @see GenAI_Thread
|
|
*/
|
|
RAG_Tool_Handler::RAG_Tool_Handler(AI_Features_Manager* ai_mgr)
|
|
: vector_db(NULL),
|
|
ai_manager(ai_mgr),
|
|
k_max(50),
|
|
candidates_max(500),
|
|
query_max_bytes(8192),
|
|
response_max_bytes(5000000),
|
|
timeout_ms(2000)
|
|
{
|
|
// Initialize configuration from GenAI_Thread if available
|
|
if (ai_manager && GloGATH) {
|
|
k_max = GloGATH->variables.genai_rag_k_max;
|
|
candidates_max = GloGATH->variables.genai_rag_candidates_max;
|
|
query_max_bytes = GloGATH->variables.genai_rag_query_max_bytes;
|
|
response_max_bytes = GloGATH->variables.genai_rag_response_max_bytes;
|
|
timeout_ms = GloGATH->variables.genai_rag_timeout_ms;
|
|
}
|
|
|
|
proxy_debug(PROXY_DEBUG_GENAI, 3, "RAG_Tool_Handler created\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Destructor
|
|
*
|
|
* Cleans up resources and closes database connections.
|
|
*
|
|
* @see close()
|
|
*/
|
|
RAG_Tool_Handler::~RAG_Tool_Handler() {
|
|
close();
|
|
proxy_debug(PROXY_DEBUG_GENAI, 3, "RAG_Tool_Handler destroyed\n");
|
|
}
|
|
|
|
// ============================================================================
|
|
// Lifecycle
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Initialize the tool handler
|
|
*
|
|
* Initializes the RAG tool handler by establishing database connections
|
|
* and preparing internal state. Must be called before executing any tools.
|
|
*
|
|
* @return 0 on success, -1 on error
|
|
*
|
|
* @see close()
|
|
* @see vector_db
|
|
* @see ai_manager
|
|
*/
|
|
int RAG_Tool_Handler::init() {
|
|
if (ai_manager) {
|
|
vector_db = ai_manager->get_vector_db();
|
|
}
|
|
|
|
if (!vector_db) {
|
|
proxy_error("RAG_Tool_Handler: Vector database not available\n");
|
|
return -1;
|
|
}
|
|
|
|
proxy_info("RAG_Tool_Handler initialized\n");
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Close and cleanup
|
|
*
|
|
* Cleans up resources and closes database connections. Called automatically
|
|
* by the destructor.
|
|
*
|
|
* @see init()
|
|
* @see ~RAG_Tool_Handler()
|
|
*/
|
|
void RAG_Tool_Handler::close() {
|
|
// Cleanup will be handled by AI_Features_Manager
|
|
}
|
|
|
|
// ============================================================================
|
|
// Helper Functions
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Extract string parameter from JSON
|
|
*
|
|
* Safely extracts a string parameter from a JSON object, handling type
|
|
* conversion if necessary. Returns the default value if the key is not
|
|
* found or cannot be converted to a string.
|
|
*
|
|
* @param j JSON object to extract from
|
|
* @param key Parameter key to extract
|
|
* @param default_val Default value if key not found
|
|
* @return Extracted string value or default
|
|
*
|
|
* @see get_json_int()
|
|
* @see get_json_bool()
|
|
* @see get_json_string_array()
|
|
* @see get_json_int_array()
|
|
*/
|
|
std::string RAG_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
|
|
*
|
|
* Safely extracts an integer parameter from a JSON object, handling type
|
|
* conversion from string if necessary. Returns the default value if the
|
|
* key is not found or cannot be converted to an integer.
|
|
*
|
|
* @param j JSON object to extract from
|
|
* @param key Parameter key to extract
|
|
* @param default_val Default value if key not found
|
|
* @return Extracted int value or default
|
|
*
|
|
* @see get_json_string()
|
|
* @see get_json_bool()
|
|
* @see get_json_string_array()
|
|
* @see get_json_int_array()
|
|
*/
|
|
int RAG_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("RAG_Tool_Handler: Failed to convert string to int for key '%s': %s\n",
|
|
key.c_str(), e.what());
|
|
return default_val;
|
|
}
|
|
}
|
|
}
|
|
return default_val;
|
|
}
|
|
|
|
/**
|
|
* @brief Extract bool parameter from JSON
|
|
*
|
|
* Safely extracts a boolean parameter from a JSON object, handling type
|
|
* conversion from string or integer if necessary. Returns the default
|
|
* value if the key is not found or cannot be converted to a boolean.
|
|
*
|
|
* @param j JSON object to extract from
|
|
* @param key Parameter key to extract
|
|
* @param default_val Default value if key not found
|
|
* @return Extracted bool value or default
|
|
*
|
|
* @see get_json_string()
|
|
* @see get_json_int()
|
|
* @see get_json_string_array()
|
|
* @see get_json_int_array()
|
|
*/
|
|
bool RAG_Tool_Handler::get_json_bool(const json& j, const std::string& key, bool default_val) {
|
|
if (j.contains(key) && !j[key].is_null()) {
|
|
if (j[key].is_boolean()) {
|
|
return j[key].get<bool>();
|
|
} else if (j[key].is_string()) {
|
|
std::string val = j[key].get<std::string>();
|
|
return (val == "true" || val == "1");
|
|
} else if (j[key].is_number()) {
|
|
return j[key].get<int>() != 0;
|
|
}
|
|
}
|
|
return default_val;
|
|
}
|
|
|
|
/**
|
|
* @brief Extract string array from JSON
|
|
*
|
|
* Safely extracts a string array parameter from a JSON object, filtering
|
|
* out non-string elements. Returns an empty vector if the key is not
|
|
* found or is not an array.
|
|
*
|
|
* @param j JSON object to extract from
|
|
* @param key Parameter key to extract
|
|
* @return Vector of extracted strings
|
|
*
|
|
* @see get_json_string()
|
|
* @see get_json_int()
|
|
* @see get_json_bool()
|
|
* @see get_json_int_array()
|
|
*/
|
|
std::vector<std::string> RAG_Tool_Handler::get_json_string_array(const json& j, const std::string& key) {
|
|
std::vector<std::string> result;
|
|
if (j.contains(key) && j[key].is_array()) {
|
|
for (const auto& item : j[key]) {
|
|
if (item.is_string()) {
|
|
result.push_back(item.get<std::string>());
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief Extract int array from JSON
|
|
*
|
|
* Safely extracts an integer array parameter from a JSON object, handling
|
|
* type conversion from string if necessary. Returns an empty vector if
|
|
* the key is not found or is not an array.
|
|
*
|
|
* @param j JSON object to extract from
|
|
* @param key Parameter key to extract
|
|
* @return Vector of extracted integers
|
|
*
|
|
* @see get_json_string()
|
|
* @see get_json_int()
|
|
* @see get_json_bool()
|
|
* @see get_json_string_array()
|
|
*/
|
|
std::vector<int> RAG_Tool_Handler::get_json_int_array(const json& j, const std::string& key) {
|
|
std::vector<int> result;
|
|
if (j.contains(key) && j[key].is_array()) {
|
|
for (const auto& item : j[key]) {
|
|
if (item.is_number()) {
|
|
result.push_back(item.get<int>());
|
|
} else if (item.is_string()) {
|
|
try {
|
|
result.push_back(std::stoi(item.get<std::string>()));
|
|
} catch (const std::exception& e) {
|
|
proxy_error("RAG_Tool_Handler: Failed to convert string to int in array: %s\n", e.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief Validate and limit k parameter
|
|
*
|
|
* Ensures the k parameter is within acceptable bounds (1 to k_max).
|
|
* Returns default value of 10 if k is invalid.
|
|
*
|
|
* @param k Requested number of results
|
|
* @return Validated k value within configured limits
|
|
*
|
|
* @see validate_candidates()
|
|
* @see k_max
|
|
*/
|
|
int RAG_Tool_Handler::validate_k(int k) {
|
|
if (k <= 0) return 10; // Default
|
|
if (k > k_max) return k_max;
|
|
return k;
|
|
}
|
|
|
|
/**
|
|
* @brief Validate and limit candidates parameter
|
|
*
|
|
* Ensures the candidates parameter is within acceptable bounds (1 to candidates_max).
|
|
* Returns default value of 50 if candidates is invalid.
|
|
*
|
|
* @param candidates Requested number of candidates
|
|
* @return Validated candidates value within configured limits
|
|
*
|
|
* @see validate_k()
|
|
* @see candidates_max
|
|
*/
|
|
int RAG_Tool_Handler::validate_candidates(int candidates) {
|
|
if (candidates <= 0) return 50; // Default
|
|
if (candidates > candidates_max) return candidates_max;
|
|
return candidates;
|
|
}
|
|
|
|
/**
|
|
* @brief Validate query length
|
|
*
|
|
* Checks if the query string length is within the configured query_max_bytes limit.
|
|
*
|
|
* @param query Query string to validate
|
|
* @return true if query is within length limits, false otherwise
|
|
*
|
|
* @see query_max_bytes
|
|
*/
|
|
bool RAG_Tool_Handler::validate_query_length(const std::string& query) {
|
|
return static_cast<int>(query.length()) <= query_max_bytes;
|
|
}
|
|
|
|
/**
|
|
* @brief Execute database query and return results
|
|
*
|
|
* Executes a SQL query against the vector database and returns the results.
|
|
* Handles error checking and logging. The caller is responsible for freeing
|
|
* the returned SQLite3_result.
|
|
*
|
|
* @param query SQL query string to execute
|
|
* @return SQLite3_result pointer or NULL on error
|
|
*
|
|
* @see vector_db
|
|
*/
|
|
SQLite3_result* RAG_Tool_Handler::execute_query(const char* query) {
|
|
if (!vector_db) {
|
|
proxy_error("RAG_Tool_Handler: Vector database not available\n");
|
|
return NULL;
|
|
}
|
|
|
|
char* error = NULL;
|
|
int cols = 0;
|
|
int affected_rows = 0;
|
|
SQLite3_result* result = vector_db->execute_statement(query, &error, &cols, &affected_rows);
|
|
|
|
if (error) {
|
|
proxy_error("RAG_Tool_Handler: SQL error: %s\n", error);
|
|
proxy_sqlite3_free(error);
|
|
return NULL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief Compute Reciprocal Rank Fusion score
|
|
*
|
|
* Computes the Reciprocal Rank Fusion score for hybrid search ranking.
|
|
* Formula: weight / (k0 + rank)
|
|
*
|
|
* @param rank Rank position (1-based)
|
|
* @param k0 Smoothing parameter
|
|
* @param weight Weight factor for this ranking
|
|
* @return RRF score
|
|
*
|
|
* @see rag.search_hybrid
|
|
*/
|
|
double RAG_Tool_Handler::compute_rrf_score(int rank, int k0, double weight) {
|
|
if (rank <= 0) return 0.0;
|
|
return weight / (k0 + rank);
|
|
}
|
|
|
|
/**
|
|
* @brief Normalize scores to 0-1 range (higher is better)
|
|
*
|
|
* Normalizes various types of scores to a consistent 0-1 range where
|
|
* higher values indicate better matches. Different score types may
|
|
* require different normalization approaches.
|
|
*
|
|
* @param score Raw score to normalize
|
|
* @param score_type Type of score being normalized
|
|
* @return Normalized score in 0-1 range
|
|
*/
|
|
double RAG_Tool_Handler::normalize_score(double score, const std::string& score_type) {
|
|
// For now, return the score as-is
|
|
// In the future, we might want to normalize different score types differently
|
|
return score;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Tool List
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Get list of available RAG tools
|
|
*
|
|
* Returns a comprehensive list of all available RAG tools with their
|
|
* input schemas and descriptions. Tools include:
|
|
* - rag.search_fts: Keyword search using FTS5
|
|
* - rag.search_vector: Semantic search using vector embeddings
|
|
* - rag.search_hybrid: Hybrid search combining FTS and vectors
|
|
* - rag.get_chunks: Fetch chunk content by chunk_id
|
|
* - rag.get_docs: Fetch document content by doc_id
|
|
* - rag.fetch_from_source: Refetch authoritative data from source
|
|
* - rag.admin.stats: Operational statistics
|
|
*
|
|
* @return JSON object containing tool definitions and schemas
|
|
*
|
|
* @see get_tool_description()
|
|
* @see execute_tool()
|
|
*/
|
|
json RAG_Tool_Handler::get_tool_list() {
|
|
json tools = json::array();
|
|
|
|
// FTS search tool
|
|
json fts_params = json::object();
|
|
fts_params["type"] = "object";
|
|
fts_params["properties"] = json::object();
|
|
fts_params["properties"]["query"] = {
|
|
{"type", "string"},
|
|
{"description", "Keyword search query"}
|
|
};
|
|
fts_params["properties"]["k"] = {
|
|
{"type", "integer"},
|
|
{"description", "Number of results to return (default: 10, max: 50)"}
|
|
};
|
|
fts_params["properties"]["offset"] = {
|
|
{"type", "integer"},
|
|
{"description", "Offset for pagination (default: 0)"}
|
|
};
|
|
|
|
// Filters object
|
|
json filters_obj = json::object();
|
|
filters_obj["type"] = "object";
|
|
filters_obj["properties"] = json::object();
|
|
filters_obj["properties"]["source_ids"] = {
|
|
{"type", "array"},
|
|
{"items", {{"type", "integer"}}},
|
|
{"description", "Filter by source IDs"}
|
|
};
|
|
filters_obj["properties"]["source_names"] = {
|
|
{"type", "array"},
|
|
{"items", {{"type", "string"}}},
|
|
{"description", "Filter by source names"}
|
|
};
|
|
filters_obj["properties"]["doc_ids"] = {
|
|
{"type", "array"},
|
|
{"items", {{"type", "string"}}},
|
|
{"description", "Filter by document IDs"}
|
|
};
|
|
filters_obj["properties"]["min_score"] = {
|
|
{"type", "number"},
|
|
{"description", "Minimum score threshold"}
|
|
};
|
|
filters_obj["properties"]["post_type_ids"] = {
|
|
{"type", "array"},
|
|
{"items", {{"type", "integer"}}},
|
|
{"description", "Filter by post type IDs"}
|
|
};
|
|
filters_obj["properties"]["tags_any"] = {
|
|
{"type", "array"},
|
|
{"items", {{"type", "string"}}},
|
|
{"description", "Filter by any of these tags"}
|
|
};
|
|
filters_obj["properties"]["tags_all"] = {
|
|
{"type", "array"},
|
|
{"items", {{"type", "string"}}},
|
|
{"description", "Filter by all of these tags"}
|
|
};
|
|
filters_obj["properties"]["created_after"] = {
|
|
{"type", "string"},
|
|
{"format", "date-time"},
|
|
{"description", "Filter by creation date (after)"}
|
|
};
|
|
filters_obj["properties"]["created_before"] = {
|
|
{"type", "string"},
|
|
{"format", "date-time"},
|
|
{"description", "Filter by creation date (before)"}
|
|
};
|
|
|
|
fts_params["properties"]["filters"] = filters_obj;
|
|
|
|
// Return object
|
|
json return_obj = json::object();
|
|
return_obj["type"] = "object";
|
|
return_obj["properties"] = json::object();
|
|
return_obj["properties"]["include_title"] = {
|
|
{"type", "boolean"},
|
|
{"description", "Include title in results (default: true)"}
|
|
};
|
|
return_obj["properties"]["include_metadata"] = {
|
|
{"type", "boolean"},
|
|
{"description", "Include metadata in results (default: true)"}
|
|
};
|
|
return_obj["properties"]["include_snippets"] = {
|
|
{"type", "boolean"},
|
|
{"description", "Include snippets in results (default: false)"}
|
|
};
|
|
|
|
fts_params["properties"]["return"] = return_obj;
|
|
fts_params["required"] = json::array({"query"});
|
|
|
|
tools.push_back({
|
|
{"name", "rag.search_fts"},
|
|
{"description", "Keyword search over documents using FTS5"},
|
|
{"inputSchema", fts_params}
|
|
});
|
|
|
|
// Vector search tool
|
|
json vec_params = json::object();
|
|
vec_params["type"] = "object";
|
|
vec_params["properties"] = json::object();
|
|
vec_params["properties"]["query_text"] = {
|
|
{"type", "string"},
|
|
{"description", "Text to search semantically"}
|
|
};
|
|
vec_params["properties"]["k"] = {
|
|
{"type", "integer"},
|
|
{"description", "Number of results to return (default: 10, max: 50)"}
|
|
};
|
|
|
|
// Filters object (same as FTS)
|
|
vec_params["properties"]["filters"] = filters_obj;
|
|
|
|
// Return object (same as FTS)
|
|
vec_params["properties"]["return"] = return_obj;
|
|
|
|
// Embedding object for precomputed vectors
|
|
json embedding_obj = json::object();
|
|
embedding_obj["type"] = "object";
|
|
embedding_obj["properties"] = json::object();
|
|
embedding_obj["properties"]["model"] = {
|
|
{"type", "string"},
|
|
{"description", "Embedding model to use"}
|
|
};
|
|
|
|
vec_params["properties"]["embedding"] = embedding_obj;
|
|
|
|
// Query embedding object for precomputed vectors
|
|
json query_embedding_obj = json::object();
|
|
query_embedding_obj["type"] = "object";
|
|
query_embedding_obj["properties"] = json::object();
|
|
query_embedding_obj["properties"]["dim"] = {
|
|
{"type", "integer"},
|
|
{"description", "Dimension of the embedding"}
|
|
};
|
|
query_embedding_obj["properties"]["values_b64"] = {
|
|
{"type", "string"},
|
|
{"description", "Base64 encoded float32 array"}
|
|
};
|
|
|
|
vec_params["properties"]["query_embedding"] = query_embedding_obj;
|
|
vec_params["required"] = json::array({"query_text"});
|
|
|
|
tools.push_back({
|
|
{"name", "rag.search_vector"},
|
|
{"description", "Semantic search over documents using vector embeddings"},
|
|
{"inputSchema", vec_params}
|
|
});
|
|
|
|
// Hybrid search tool
|
|
json hybrid_params = json::object();
|
|
hybrid_params["type"] = "object";
|
|
hybrid_params["properties"] = json::object();
|
|
hybrid_params["properties"]["query"] = {
|
|
{"type", "string"},
|
|
{"description", "Search query for both FTS and vector"}
|
|
};
|
|
hybrid_params["properties"]["k"] = {
|
|
{"type", "integer"},
|
|
{"description", "Number of results to return (default: 10, max: 50)"}
|
|
};
|
|
hybrid_params["properties"]["mode"] = {
|
|
{"type", "string"},
|
|
{"description", "Search mode: 'fuse' or 'fts_then_vec'"}
|
|
};
|
|
|
|
// Filters object (same as FTS and vector)
|
|
hybrid_params["properties"]["filters"] = filters_obj;
|
|
|
|
// Fuse object for mode "fuse"
|
|
json fuse_obj = json::object();
|
|
fuse_obj["type"] = "object";
|
|
fuse_obj["properties"] = json::object();
|
|
fuse_obj["properties"]["fts_k"] = {
|
|
{"type", "integer"},
|
|
{"description", "Number of FTS results to retrieve for fusion (default: 50)"}
|
|
};
|
|
fuse_obj["properties"]["vec_k"] = {
|
|
{"type", "integer"},
|
|
{"description", "Number of vector results to retrieve for fusion (default: 50)"}
|
|
};
|
|
fuse_obj["properties"]["rrf_k0"] = {
|
|
{"type", "integer"},
|
|
{"description", "RRF smoothing parameter (default: 60)"}
|
|
};
|
|
fuse_obj["properties"]["w_fts"] = {
|
|
{"type", "number"},
|
|
{"description", "Weight for FTS scores in fusion (default: 1.0)"}
|
|
};
|
|
fuse_obj["properties"]["w_vec"] = {
|
|
{"type", "number"},
|
|
{"description", "Weight for vector scores in fusion (default: 1.0)"}
|
|
};
|
|
|
|
hybrid_params["properties"]["fuse"] = fuse_obj;
|
|
|
|
// Fts_then_vec object for mode "fts_then_vec"
|
|
json fts_then_vec_obj = json::object();
|
|
fts_then_vec_obj["type"] = "object";
|
|
fts_then_vec_obj["properties"] = json::object();
|
|
fts_then_vec_obj["properties"]["candidates_k"] = {
|
|
{"type", "integer"},
|
|
{"description", "Number of FTS candidates to generate (default: 200)"}
|
|
};
|
|
fts_then_vec_obj["properties"]["rerank_k"] = {
|
|
{"type", "integer"},
|
|
{"description", "Number of candidates to rerank with vector search (default: 50)"}
|
|
};
|
|
fts_then_vec_obj["properties"]["vec_metric"] = {
|
|
{"type", "string"},
|
|
{"description", "Vector similarity metric (default: 'cosine')"}
|
|
};
|
|
|
|
hybrid_params["properties"]["fts_then_vec"] = fts_then_vec_obj;
|
|
|
|
hybrid_params["required"] = json::array({"query"});
|
|
|
|
tools.push_back({
|
|
{"name", "rag.search_hybrid"},
|
|
{"description", "Hybrid search combining FTS and vector"},
|
|
{"inputSchema", hybrid_params}
|
|
});
|
|
|
|
// Get chunks tool
|
|
json chunks_params = json::object();
|
|
chunks_params["type"] = "object";
|
|
chunks_params["properties"] = json::object();
|
|
chunks_params["properties"]["chunk_ids"] = {
|
|
{"type", "array"},
|
|
{"items", {{"type", "string"}}},
|
|
{"description", "List of chunk IDs to fetch"}
|
|
};
|
|
json return_params = json::object();
|
|
return_params["type"] = "object";
|
|
return_params["properties"] = json::object();
|
|
return_params["properties"]["include_title"] = {
|
|
{"type", "boolean"},
|
|
{"description", "Include title in response (default: true)"}
|
|
};
|
|
return_params["properties"]["include_doc_metadata"] = {
|
|
{"type", "boolean"},
|
|
{"description", "Include document metadata in response (default: true)"}
|
|
};
|
|
return_params["properties"]["include_chunk_metadata"] = {
|
|
{"type", "boolean"},
|
|
{"description", "Include chunk metadata in response (default: true)"}
|
|
};
|
|
chunks_params["properties"]["return"] = return_params;
|
|
chunks_params["required"] = json::array({"chunk_ids"});
|
|
|
|
tools.push_back({
|
|
{"name", "rag.get_chunks"},
|
|
{"description", "Fetch chunk content by chunk_id"},
|
|
{"inputSchema", chunks_params}
|
|
});
|
|
|
|
// Get docs tool
|
|
json docs_params = json::object();
|
|
docs_params["type"] = "object";
|
|
docs_params["properties"] = json::object();
|
|
docs_params["properties"]["doc_ids"] = {
|
|
{"type", "array"},
|
|
{"items", {{"type", "string"}}},
|
|
{"description", "List of document IDs to fetch"}
|
|
};
|
|
json docs_return_params = json::object();
|
|
docs_return_params["type"] = "object";
|
|
docs_return_params["properties"] = json::object();
|
|
docs_return_params["properties"]["include_body"] = {
|
|
{"type", "boolean"},
|
|
{"description", "Include body in response (default: true)"}
|
|
};
|
|
docs_return_params["properties"]["include_metadata"] = {
|
|
{"type", "boolean"},
|
|
{"description", "Include metadata in response (default: true)"}
|
|
};
|
|
docs_params["properties"]["return"] = docs_return_params;
|
|
docs_params["required"] = json::array({"doc_ids"});
|
|
|
|
tools.push_back({
|
|
{"name", "rag.get_docs"},
|
|
{"description", "Fetch document content by doc_id"},
|
|
{"inputSchema", docs_params}
|
|
});
|
|
|
|
// Fetch from source tool
|
|
json fetch_params = json::object();
|
|
fetch_params["type"] = "object";
|
|
fetch_params["properties"] = json::object();
|
|
fetch_params["properties"]["doc_ids"] = {
|
|
{"type", "array"},
|
|
{"items", {{"type", "string"}}},
|
|
{"description", "List of document IDs to refetch"}
|
|
};
|
|
fetch_params["properties"]["columns"] = {
|
|
{"type", "array"},
|
|
{"items", {{"type", "string"}}},
|
|
{"description", "List of columns to fetch"}
|
|
};
|
|
|
|
// Limits object
|
|
json limits_obj = json::object();
|
|
limits_obj["type"] = "object";
|
|
limits_obj["properties"] = json::object();
|
|
limits_obj["properties"]["max_rows"] = {
|
|
{"type", "integer"},
|
|
{"description", "Maximum number of rows to return (default: 10, max: 100)"}
|
|
};
|
|
limits_obj["properties"]["max_bytes"] = {
|
|
{"type", "integer"},
|
|
{"description", "Maximum number of bytes to return (default: 200000, max: 1000000)"}
|
|
};
|
|
|
|
fetch_params["properties"]["limits"] = limits_obj;
|
|
fetch_params["required"] = json::array({"doc_ids"});
|
|
|
|
tools.push_back({
|
|
{"name", "rag.fetch_from_source"},
|
|
{"description", "Refetch authoritative data from source database"},
|
|
{"inputSchema", fetch_params}
|
|
});
|
|
|
|
// Admin stats tool
|
|
json stats_params = json::object();
|
|
stats_params["type"] = "object";
|
|
stats_params["properties"] = json::object();
|
|
|
|
tools.push_back({
|
|
{"name", "rag.admin.stats"},
|
|
{"description", "Get operational statistics for RAG system"},
|
|
{"inputSchema", stats_params}
|
|
});
|
|
|
|
json result;
|
|
result["tools"] = tools;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief Get description of a specific tool
|
|
*
|
|
* Returns the schema and description for a specific RAG tool.
|
|
*
|
|
* @param tool_name Name of the tool to describe
|
|
* @return JSON object with tool description or error response
|
|
*
|
|
* @see get_tool_list()
|
|
* @see execute_tool()
|
|
*/
|
|
json RAG_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 a RAG tool
|
|
*
|
|
* Executes the specified RAG tool with the provided arguments. Handles
|
|
* input validation, parameter processing, database queries, and result
|
|
* formatting according to MCP specifications.
|
|
*
|
|
* Supported tools:
|
|
* - rag.search_fts: Full-text search over documents
|
|
* - rag.search_vector: Vector similarity search
|
|
* - rag.search_hybrid: Hybrid search with two modes (fuse, fts_then_vec)
|
|
* - rag.get_chunks: Retrieve chunk content by ID
|
|
* - rag.get_docs: Retrieve document content by ID
|
|
* - rag.fetch_from_source: Refetch data from authoritative source
|
|
* - rag.admin.stats: Get operational statistics
|
|
*
|
|
* @param tool_name Name of the tool to execute
|
|
* @param arguments JSON object containing tool arguments
|
|
* @return JSON response with results or error information
|
|
*
|
|
* @see get_tool_list()
|
|
* @see get_tool_description()
|
|
*/
|
|
json RAG_Tool_Handler::execute_tool(const std::string& tool_name, const json& arguments) {
|
|
proxy_debug(PROXY_DEBUG_GENAI, 3, "RAG_Tool_Handler: execute_tool(%s)\n", tool_name.c_str());
|
|
|
|
// Record start time for timing stats
|
|
auto start_time = std::chrono::high_resolution_clock::now();
|
|
|
|
try {
|
|
json result;
|
|
|
|
if (tool_name == "rag.search_fts") {
|
|
// FTS search implementation
|
|
// ... (implementation details)
|
|
} else if (tool_name == "rag.search_vector") {
|
|
// Vector search implementation
|
|
// ... (implementation details)
|
|
} else if (tool_name == "rag.search_hybrid") {
|
|
// Hybrid search implementation
|
|
// ... (implementation details)
|
|
} else if (tool_name == "rag.get_chunks") {
|
|
// Get chunks implementation
|
|
// ... (implementation details)
|
|
} else if (tool_name == "rag.get_docs") {
|
|
// Get docs implementation
|
|
// ... (implementation details)
|
|
} else if (tool_name == "rag.fetch_from_source") {
|
|
// Fetch from source implementation
|
|
// ... (implementation details)
|
|
} else if (tool_name == "rag.admin.stats") {
|
|
// Admin stats implementation
|
|
// ... (implementation details)
|
|
} else {
|
|
return create_error_response("Unknown tool: " + tool_name);
|
|
}
|
|
|
|
// Calculate execution time
|
|
auto end_time = std::chrono::high_resolution_clock::now();
|
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
|
|
|
// Add timing stats to result
|
|
if (result.contains("stats")) {
|
|
result["stats"]["ms"] = static_cast<int>(duration.count());
|
|
} else {
|
|
json stats;
|
|
stats["ms"] = static_cast<int>(duration.count());
|
|
result["stats"] = stats;
|
|
}
|
|
|
|
return result;
|
|
} catch (const std::exception& e) {
|
|
proxy_error("RAG_Tool_Handler: Exception in execute_tool: %s\n", e.what());
|
|
return create_error_response("Internal error: " + std::string(e.what()));
|
|
}
|
|
} |