Schema Search

Pesquisa de esquema em linguagem natural na memória sobre esquemas de banco de dados.

Documentação

Schema Search

Nota: O projeto foi movido para um novo repositório e está sendo mantido pela SignalPilot Labs. O código no repositório atual está disponível apenas para referência.

Um Servidor MCP para Busca em Linguagem Natural sobre Esquemas de RDBMS. Encontre as tabelas exatas que você precisa, com todos os seus relacionamentos mapeados, em milissegundos. Nenhuma configuração de banco de dados vetorial é necessária.

Por quê

Você tem 200 tabelas no seu banco de dados. Alguém pergunta "onde estão armazenados os reembolsos dos usuários?"

Você poderia:

  • Procurar em arquivos SQL por 20 minutos
  • Passar o esquema completo para um LLM e vê-lo ter dificuldades com 200 tabelas

Ou construir embeddings esquemáticos das suas tabelas, armazená-los em memória e consultar em linguagem natural em um servidor MCP.

Benefícios

  • Nenhuma configuração de banco de dados vetorial é necessária
  • Pegada de memória pequena -- escala facilmente para 1000 tabelas e mais de 10.000 colunas.
  • Latência de consulta em milissegundos

Instalação

Rápido por padrão - A instalação base usa apenas busca BM25/difusa (sem PyTorch):

# Minimal install (BM25 + fuzzy only, ~10MB)
pip install "schema-search[postgres]"

# With semantic/hybrid search support (~500MB with PyTorch)
pip install "schema-search[postgres,semantic]"

# With LLM chunking
pip install "schema-search[postgres,semantic,llm]"

# With MCP server
pip install "schema-search[postgres,semantic,mcp]"

# Other databases
pip install "schema-search[mysql,semantic]"      # MySQL
pip install "schema-search[snowflake,semantic]"  # Snowflake
pip install "schema-search[bigquery,semantic]"   # BigQuery
pip install "schema-search[databricks,semantic]" # Databricks

Extras:

  • [semantic]: Habilita busca semântica/híbrida e reordenação CrossEncoder (adiciona sentence-transformers)
  • [llm]: Habilita divisão de esquema baseada em LLM (adiciona openai)
  • [mcp]: Suporte a servidor MCP (adiciona fastmcp)

Configuração

Edite config.yml:

logging:
  level: "WARNING"

embedding:
  location: "memory" # Options: "memory", "vectordb" (coming soon)
  model: "multi-qa-MiniLM-L6-cos-v1"
  metric: "cosine" # Options: "cosine", "euclidean", "manhattan", "dot"
  batch_size: 32
  show_progress: false
  cache_dir: "/tmp/.schema_search_cache"

chunking:
  strategy: "raw" # Options: "raw", "llm"
  max_tokens: 256
  overlap_tokens: 50
  model: "gpt-4o-mini"

search:
  # Search strategy: "semantic" (embeddings), "bm25" (BM25 lexical), "fuzzy" (fuzzy string matching), "hybrid" (semantic + bm25)
  strategy: "bm25"
  initial_top_k: 20
  rerank_top_k: 5
  semantic_weight: 0.67 # For hybrid search (bm25_weight = 1 - semantic_weight)
  hops: 1 # Number of foreign key hops for graph expansion (0-2 recommended)

reranker:
  # CrossEncoder model for reranking. Set to null to disable reranking
  model: null # "Alibaba-NLP/gte-reranker-modernbert-base"

schema:
  include_columns: true
  include_indices: true
  include_foreign_keys: true
  include_constraints: true

output:
  format: "markdown" # Options: "json", "markdown"
  limit: 5 # Default number of results to return

Servidor MCP

Integre com o Claude Desktop ou qualquer cliente MCP.

Configuração

Adicione à sua configuração MCP (ex.: ~/.cursor/mcp.json ou configuração do Claude Desktop):

Usando uv (Recomendado):

{
  "mcpServers": {
    "schema-search": {
      "command": "uvx",
      "args": [
        "schema-search[postgres,mcp]", 
        "postgresql://user:pass@localhost/db", 
        "optional/path/to/config.yml", 
        "optional llm_api_key", 
        "optional llm_base_url"
      ]
    }
  }
}

Usando pip:

{
  "mcpServers": {
    "schema-search": {
      // conda: /Users/<username>/opt/miniconda3/envs/<your env>/bin/schema-search",
      "command": "path/to/schema-search",
      "args": [
        "postgresql://user:pass@localhost/db", 
        "optional/path/to/config.yml", 
        "optional llm_api_key", 
        "optional llm_base_url"
      ]
    }
  }
}

A chave da API LLM e a URL base são necessárias apenas se você usar resumos de esquema gerados por LLM (config.chunking.strategy = 'llm').

Uso via CLI

schema-search "postgresql://user:pass@localhost/db" "optional/path/to/config.yml"

Argumentos opcionais: [config_path] [llm_api_key] [llm_base_url]

O servidor expõe schema_search(query, hops, limit) para consultas de esquema em linguagem natural.

Uso em Python

from sqlalchemy import create_engine
from schema_search import SchemaSearch

# PostgreSQL
engine = create_engine("postgresql://user:pass@localhost/db")


sc = SchemaSearch(
  engine=engine,
  config_path="optional/path/to/config.yml", # default: config.yml
  llm_api_key="optional llm api key",
  llm_base_url="optional llm base url"
  )

sc.index(force=False) # default is False
results = sc.search("where are user refunds stored?")

# Default output is markdown - render with str()
print(results)  # Formatted markdown with schemas, relationships, and scores

# Access underlying data as dictionary
result_dict = results.to_dict()
for result in result_dict['results']:
    print(result['table'])           # "refund_transactions"
    print(result['schema'])           # Full column info, types, constraints
    print(result['related_tables'])   # ["users", "payments", "transactions"]

# Override output format explicitly
json_results = sc.search("where are user refunds stored?", output_format="json")
print(json_results)  # JSON formatted string

# Override hops, limit, search strategy, and output format
results = sc.search("user_table", hops=1, limit=5, search_type="hybrid", output_format="markdown")

sc.index() detecta automaticamente mudanças no esquema e atualiza os metadados em cache, então raramente você precisará forçar um reindexação manualmente.

Strings de Conexão com Banco de Dados

O Schema Search usa strings de conexão SQLAlchemy:

# PostgreSQL
engine = create_engine("postgresql://postgres:mypass@localhost:5432/mydb")

# MySQL
engine = create_engine("mysql+pymysql://root:mypass@localhost:3306/mydb")

# Snowflake
engine = create_engine("snowflake://myuser:mypass@xy12345.us-east-1/MYDB/PUBLIC?warehouse=COMPUTE_WH&role=ANALYST")

# BigQuery
engine = create_engine("bigquery://my-project/my-dataset")

# Databricks
token = "dapi..."
host = "dbc-xyz.cloud.databricks.com"
http_path = "/sql/1.0/warehouses/abc123"
catalog = "main"
schema = "default"  # Optional

# Without schema (queries across all schemas in catalog)
engine = create_engine(
    f"databricks://token:{token}@{host}?http_path={http_path}&catalog={catalog}",
    connect_args={"user_agent_entry": "schema-search"}
)

# With schema (limits to specific schema)
engine = create_engine(
    f"databricks://token:{token}@{host}?http_path={http_path}&catalog={catalog}&schema={schema}",
    connect_args={"user_agent_entry": "schema-search"}
)

Estratégias de Busca

O Schema Search suporta quatro estratégias de busca:

  • bm25: Busca lexical usando o algoritmo de ranqueamento BM25 (sem dependências de ML)
  • fuzzy: Correspondência de strings em nomes de tabelas/colunas usando correspondência difusa (sem dependências de ML)
  • semantic: Busca por similaridade baseada em embeddings usando sentence transformers (requer [semantic])
  • hybrid: Combina pontuações semânticas e bm25 (padrão: 67% semântico, 33% bm25) (requer [semantic])

Cada estratégia realiza seu próprio ranqueamento inicial e, opcionalmente, aplica reordenação CrossEncoder se reranker.model estiver configurado (requer [semantic]). Defina reranker.model como null para desabilitar a reordenação.

Comparação de Desempenho

Nós fizemos benchmark no conjunto de dados Spider (1.234 consultas de treino em 18 bancos de dados) usando o config.yml padrão.

Memória: O modelo de embeddings requer ~90 MB e o reordenador opcional adiciona ~155 MB. A memória real do processo depende do seu runtime Python.

Sem Reordenador (reranker.model: null)

Without Reranker

  • Indexação: 0,22s ± 0,08s por banco de dados (18 no total).
  • Precisão: Híbrido lidera com Recall@1 62% / MRR 0,93; Semântico segue com Recall@1 58% / MRR 0,89.
  • Latência: BM25 e Fuzzy retornam em ~5ms; Semântico gasta ~15ms; Híbrido (semântico + fuzzy) tem média de 52ms.
  • Linha de base Fuzzy: Recall@1 22%, destacando a necessidade de sinais semânticos em consultas em linguagem natural.

Com Reordenador (Alibaba-NLP/gte-reranker-modernbert-base)

With Reranker

  • Indexação: 0,25s ± 0,05s por banco de dados (mesmos 18 DBs).
  • Precisão: Todas as estratégias convergem em torno de Recall@1 62% e MRR ≈ 0,92; Fuzzy salta de 51% → 92% MRR.
  • Compensação de latência: A passada extra do CrossEncoder eleva a latência por consulta para ~0,18–0,29s dependendo da estratégia.
  • Recomendação: Habilite o reordenador quando a precisão for mais importante; desabilite-o para buscas de latência ultrabaixa.

Você pode sobrescrever a estratégia de busca, saltos e limite no momento da consulta:

# Use fuzzy search instead of default
results = sc.search("user_table", search_type="fuzzy")

# Use BM25 for keyword-based search
results = sc.search("transactions payments", search_type="bm25")

# Use hybrid for best of both worlds
results = sc.search("where are user refunds?", search_type="hybrid")

# Override hops and limit
results = sc.search("user refunds", hops=2, limit=10)  # Expand 2 hops, return 10 tables

# Disable graph expansion
results = sc.search("user_table", hops=0)  # Only direct matches, no foreign key traversal

Formatos de Saída

O Schema Search retorna um objeto SearchResult que pode ser renderizado em vários formatos:

  • markdown (padrão): Markdown formatado com esquemas de tabela hierárquicos
  • json: Saída JSON estruturada

O objeto SearchResult tem:

  • Método __str__(): Renderiza usando o formato configurado (markdown ou json)
  • Método .to_dict(): Retorna dicionário bruto para acesso programático

Configure o formato padrão em config.yml:

output:
  format: "markdown"  # or "json"
  limit: 5            # Default number of results

Sobrescreva no momento da consulta:

# Default markdown output - just print the object
results = sc.search("user payments")
print(results)  # Formatted markdown

# Access underlying data as dictionary
data = results.to_dict()
print(data['results'][0]['table'])  # "users"

# Override to JSON format
json_results = sc.search("user payments", output_format="json")
print(json_results)  # JSON formatted string

A saída Markdown inclui:

  • Nome da tabela e pontuação de relevância
  • Chaves primárias e colunas com tipos/restrições
  • Relacionamentos de chaves estrangeiras
  • Índices e restrições
  • Tabelas relacionadas da expansão do grafo
  • Trechos de conteúdo correspondentes

Divisão com LLM

Use LLM para gerar resumos semânticos em vez de texto de esquema bruto (requer o extra [llm]):

  1. Instale: pip install "schema-search[postgres,llm]"
  2. Defina strategy: "llm" em config.yml
  3. Passe as credenciais da API:
sc = SchemaSearch(
    engine,
    llm_api_key="sk-...",
    llm_base_url="https://api.openai.com/v1/"  # optional
)

Como Funciona

  1. Extrai esquemas do banco de dados usando o inspetor SQLAlchemy
  2. Divide esquemas em partes digeríveis (markdown ou resumos gerados por LLM)
  3. Busca inicial usando a estratégia selecionada (semântico/BM25/difuso)
  4. Expande via chaves estrangeiras para encontrar tabelas relacionadas (saltos configuráveis)
  5. Reordenação opcional com CrossEncoder para refinar resultados
  6. Retorna as principais tabelas com esquema completo e relacionamentos

Cache armazenado em /tmp/.schema_search_cache/ (configurável em config.yml)

Licença

MIT