Files
wehub-resource-sync e04ed9c211
CF: Deploy Dev Docs / deploy (push) Has been cancelled
Sync Labels / build (push) Has been cancelled
tests / unit tests (macos-latest) (push) Has been cancelled
tests / unit tests (windows-latest) (push) Has been cancelled
tests / unit tests (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:32:45 +08:00

197 lines
7.8 KiB
Go

// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package vectorassistimprovequeryrecall
import (
"context"
"encoding/json"
"fmt"
"net/http"
yaml "github.com/goccy/go-yaml"
"github.com/googleapis/mcp-toolbox/internal/tools"
"github.com/googleapis/mcp-toolbox/internal/util"
"github.com/googleapis/mcp-toolbox/internal/util/parameters"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
const resourceType string = "vector-assist-improve-query-recall"
// Query to check if the index exists and if it is an HNSW index.
const checkIndexQuery = `
SELECT
(COUNT(1) > 0) AS index_present,
COALESCE(BOOL_OR(indexdef ILIKE '%USING hnsw%'), false) AS is_hnsw
FROM pg_indexes
WHERE schemaname = @schema_name::TEXT
AND tablename = @table_name::TEXT
AND indexname = @index_name::TEXT
AND indexdef ILIKE '%' || @vector_column_name::TEXT || '%';
`
// Query to find the optimal index parameters
const improveRecallQuery = `
SELECT output_ef_search AS ef_search
FROM vector_assist.find_ef_search_for_target_recall(
table_name => @table_name::TEXT,
schema_name => @schema_name::TEXT,
column_name => @vector_column_name::TEXT,
top_k => @top_k::INT,
target_recall => @target_recall::FLOAT,
distance_func => @distance_func::TEXT
);
`
func init() {
if !tools.Register(resourceType, newConfig) {
panic(fmt.Sprintf("tool type %q already registered", resourceType))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{ConfigBase: tools.ConfigBase{Name: name}}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type compatibleSource interface {
PostgresPool() *pgxpool.Pool
RunSQL(context.Context, string, []any) (any, error)
}
type Config struct {
tools.ConfigBase `yaml:",inline"`
Type string `yaml:"type" validate:"required"`
Source string `yaml:"source" validate:"required"`
Annotations *tools.ToolAnnotations `yaml:"annotations,omitempty"`
}
var _ tools.ToolConfig = Config{}
func (cfg Config) ToolConfigType() string {
return resourceType
}
func (cfg Config) Initialize(context.Context) (tools.Tool, error) {
allParameters := parameters.Parameters{
parameters.NewStringParameter("schema_name", "Optional parameter: Schema name of the table.", parameters.WithStringDefault("public")),
parameters.NewStringParameter("table_name", "Table name experiencing degraded vector search recall.", parameters.WithStringRequired(true)),
parameters.NewStringParameter("vector_column_name", "Column name containing the vector embeddings.", parameters.WithStringRequired(true)),
parameters.NewStringParameter("index_name", "Name of the vector index to tune.", parameters.WithStringRequired(true)),
parameters.NewIntParameter("top_k", "Optional parameter: Top k value for the vector search.", parameters.WithIntDefault(10)),
parameters.NewFloatParameter("target_recall", "Optional parameter: Target recall value for search results.", parameters.WithFloatDefault(0.95)),
parameters.NewStringParameter("distance_func", "Optional parameter: Distance function used for the vector search similarity.", parameters.WithStringDefault("cosine")),
}
paramManifest := allParameters.Manifest()
if cfg.Description == "" {
cfg.Description = "Use this tool to troubleshoot and optimize existing vector search workloads when a user reports irrelevant results, poor accuracy, or degraded recall. It determines the optimal tuning parameter (such as ef_search) for an active vector index to improve the search results. The tool outputs an actionable SQL query recommendation to be executed as a next action using the 'execute_sql' tool."
}
return Tool{
BaseTool: tools.NewBaseTool(
cfg,
tools.GetAnnotationsOrDefault(cfg.Annotations, tools.NewDestructiveAnnotations),
tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
allParameters,
),
}, nil
}
var _ tools.Tool = Tool{}
type Tool struct {
tools.BaseTool[Config]
}
func (t Tool) ToConfig() tools.ToolConfig {
return t.Cfg
}
func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, util.ToolboxError) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Cfg.Source, t.Cfg.Name, t.Cfg.Type)
if err != nil {
return nil, util.NewClientServerError("source used is not compatible with the tool", http.StatusInternalServerError, err)
}
paramsMap := params.AsMap()
namedArgs := pgx.NamedArgs{}
for key, value := range paramsMap {
namedArgs[key] = value
}
// Check if the index exists and if it is an HNSW index.
checkResp, err := source.RunSQL(ctx, checkIndexQuery, []any{namedArgs})
if err != nil {
return nil, util.ProcessGeneralError(err)
}
checkBytes, marshalErr := json.Marshal(checkResp)
if marshalErr != nil {
return nil, util.NewClientServerError("failed to process index check response", http.StatusInternalServerError, marshalErr)
}
var checkRows []map[string]interface{}
if unmarshalErr := json.Unmarshal(checkBytes, &checkRows); unmarshalErr != nil || len(checkRows) == 0 {
return nil, util.NewClientServerError("unexpected empty response from database", http.StatusInternalServerError, unmarshalErr)
}
row := checkRows[0]
indexPresent, ok := row["index_present"].(bool)
if !ok {
// If the key is missing or isn't a boolean, it's likely a server-side/query issue.
return nil, util.NewClientServerError("Internal error: 'index_present' is missing or has an invalid type.", http.StatusInternalServerError, nil)
}
if !indexPresent {
return nil, util.NewClientServerError("Index not found for the given table and vector column. If the table lacks an existing vector setup, use the 'define_spec' tool to configure the database.", http.StatusBadRequest, nil)
}
isHnsw, ok := row["is_hnsw"].(bool)
if !ok {
return nil, util.NewClientServerError("Internal error: 'is_hnsw' is missing or has an invalid type.", http.StatusInternalServerError, nil)
}
if !isHnsw {
return nil, util.NewClientServerError("Unsupported index type for recall optimization. Only HNSW index is supported.", http.StatusBadRequest, nil)
}
// Calculate the optimal index parameters to achieve the target recall.
tuningResp, err := source.RunSQL(ctx, improveRecallQuery, []any{namedArgs})
if err != nil {
return nil, util.ProcessGeneralError(err)
}
tuningBytes, marshalErr := json.Marshal(tuningResp)
if marshalErr != nil {
return nil, util.NewClientServerError("failed to process tuning response", http.StatusInternalServerError, marshalErr)
}
var tuningRows []map[string]interface{}
if unmarshalErr := json.Unmarshal(tuningBytes, &tuningRows); unmarshalErr != nil || len(tuningRows) == 0 {
return nil, util.NewClientServerError("unexpected empty tuning response from database", http.StatusInternalServerError, unmarshalErr)
}
// Extract ef_search (JSON decoder defaults numbers to float64)
efSearchVal, ok := tuningRows[0]["ef_search"].(float64)
if !ok {
return nil, util.NewClientServerError("Failed to calculate appropriate efSearch value", http.StatusInternalServerError, nil)
}
queryRecommendation := fmt.Sprintf("SET hnsw.ef_search = %d;", int(efSearchVal))
return queryRecommendation, nil
}