Pular para conteúdo

RFC-001: Sistema de Embeddings para Agent Matching

Status: PROPOSTA (aguardando validação) Hipótese: HYP-018 Data: 2026-01-17 Autor: Claude + Founder


Problema

O detectAgentTrigger atual usa matching por keywords com ~60% de acurácia:

Query Esperado Atual Causa
"testar com playwright" Charles (QA) Thomas (Contrarian) "testar" duplicado
"vendas B2B" Ernesto Partnership Architect "b2b" só em PARCERIA
"estressado" Founder Coach + Design/CXO "ui" substring match

Proposta

Matching semântico via embeddings vetoriais com fallback para keywords.

Stack

Componente Tecnologia
Modelo text-embedding-3-small (OpenAI)
Storage Supabase pgvector
Cache LRU em memória
Fallback Keywords existentes

Arquitetura

Query do Usuário
┌─────────────────┐
│ Gerar Embedding │ ◄── OpenAI API
└────────┬────────┘
┌─────────────────┐
│ Buscar Top 5    │ ◄── Supabase pgvector
│ Agentes Similar │
└────────┬────────┘
┌─────────────────┐
│ Keyword Match   │ ◄── ACTIVATION_MATRIX (fallback)
└────────┬────────┘
┌─────────────────┐
│ Combinar Scores │ ◄── 70% embed + 30% keyword
└────────┬────────┘
    Resultado

Fases de Implementação

FASE 1: Infraestrutura (Dia 1-2)

Tabela Supabase:

CREATE TABLE apex_agent_embeddings (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id TEXT NOT NULL DEFAULT 'sellsync',
  agent_id TEXT NOT NULL,
  agent_name TEXT NOT NULL,
  embedding_type TEXT NOT NULL,
  embedding VECTOR(1536) NOT NULL,
  source_text TEXT NOT NULL,
  model TEXT NOT NULL DEFAULT 'text-embedding-3-small',
  created_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(tenant_id, agent_id, embedding_type)
);

CREATE INDEX ON apex_agent_embeddings
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 10);

Function de busca:

CREATE OR REPLACE FUNCTION match_agents(
  query_embedding VECTOR(1536),
  match_count INT DEFAULT 5,
  match_threshold FLOAT DEFAULT 0.7
)
RETURNS TABLE (agent_id TEXT, agent_name TEXT, similarity FLOAT)
LANGUAGE plpgsql AS $$
BEGIN
  RETURN QUERY
  SELECT e.agent_id, e.agent_name,
         1 - (e.embedding <=> query_embedding) AS similarity
  FROM apex_agent_embeddings e
  WHERE 1 - (e.embedding <=> query_embedding) > match_threshold
  ORDER BY e.embedding <=> query_embedding
  LIMIT match_count;
END;
$$;

Arquivos a criar: - shared/mcp-server/src/embeddings.ts (~200 linhas) - shared/mcp-server/src/agent-matcher.ts (~150 linhas)

FASE 2: Geração de Embeddings (Dia 2-3)

3 embeddings por agente:

Tipo Fonte Peso
description AGENTS_MANIFEST.yaml 40%
keywords ACTIVATION_MATRIX 30%
use_cases Novo arquivo YAML 30%

Estrutura use-cases:

# core/agents/use-cases/ernesto.yaml
agent_id: ernesto
use_cases:
  - "como montar um time de vendas B2B"
  - "preciso de ajuda para prospectar clientes"
  - "qual a melhor estratégia de vendas para SaaS"
  - "como estruturar um funil de vendas"
  - "quero aumentar minha conversão de leads"

Script gerador: - core/tools/generate-agent-embeddings.ts (~100 linhas)

FASE 3: Matching Híbrido (Dia 3-4)

interface MatchResult {
  agentId: string;
  confidence: number;
  method: 'embedding' | 'keyword' | 'hybrid';
  scores: {
    embeddingScore: number;
    keywordScore: number;
    finalScore: number;
  };
  needsConfirmation: boolean; // true se confidence < 0.6
}

async function matchAgent(query: string): Promise<MatchResult[]> {
  const queryEmbedding = await generateEmbedding(query);

  const embeddingMatches = await supabase.rpc('match_agents', {
    query_embedding: queryEmbedding,
    match_count: 5,
    match_threshold: 0.7
  });

  const keywordMatches = keywordMatch(query);

  return combineScores(embeddingMatches, keywordMatches, {
    embeddingWeight: 0.7,
    keywordWeight: 0.3
  });
}

Cache LRU:

const queryCache = new LRUCache<string, MatchResult[]>({
  max: 500,
  ttl: 1000 * 60 * 60  // 1 hora
});

FASE 4: Integração MCP (Dia 4-5)

export async function detectAgentTrigger(input: {
  userQuery: string;
  useEmbeddings?: boolean;
}) {
  const { userQuery, useEmbeddings = true } = input;

  if (useEmbeddings && FEATURE_FLAGS.USE_EMBEDDINGS) {
    return await hybridMatch(userQuery);
  } else {
    return keywordMatch(userQuery);
  }
}

Feature flags:

const FEATURE_FLAGS = {
  USE_EMBEDDINGS: process.env.USE_EMBEDDINGS === 'true',
  EMBEDDINGS_WEIGHT: parseFloat(process.env.EMBEDDINGS_WEIGHT || '0.7'),
};

FASE 5: Testes (Dia 5-6)

Dataset: core/tests/agent-matching-dataset.yaml (100 casos)

tests:
  - query: "preciso testar minha aplicação com playwright"
    expected_agent: charles
    expected_confidence: ">0.8"
  - query: "como montar estratégia de vendas B2B"
    expected_agent: ernesto
    expected_confidence: ">0.8"

Critérios de aceite:

Métrica Mínimo Alvo
Acurácia 90% 95%
Latência p50 <200ms <100ms
Latência p99 <500ms <300ms

FASE 6: Rollout (Dia 6-7)

Dia EMBEDDINGS_WEIGHT Descrição
7 0.3 30% embed, 70% keyword
8 0.5 50/50
9 0.7 70% embed, 30% keyword
10 1.0 100% embed

Rollback instantâneo:

export USE_EMBEDDINGS=false


Custos

Item Custo
Embeddings 39 agentes (one-time) ~$0.01
Por query ~$0.00002
10.000 queries/mês ~$0.20/mês
Total mensal < $1/mês

Riscos e Mitigações

Risco Mitigação
OpenAI API down Fallback automático para keywords
Embeddings desatualizados Re-gerar mensalmente
Latência alta Cache LRU + pgvector index

Arquivos Finais

apex/
├── shared/mcp-server/src/
│   ├── embeddings.ts          # NOVO
│   ├── agent-matcher.ts       # NOVO
│   ├── agents.ts              # MODIFICAR
│   └── index.ts               # MODIFICAR
├── core/
│   ├── agents/use-cases/      # NOVO (39 arquivos)
│   ├── tools/
│   │   └── generate-embeddings.ts
│   └── tests/
│       ├── agent-matching-dataset.yaml
│       └── benchmark-agent-matching.ts
└── supabase/migrations/
    └── 20260117_agent_embeddings.sql

Critérios para Virar ADR

Este RFC vira ADR-015 quando: 1. [ ] Implementado 2. [ ] Testado com 100 casos 3. [ ] Acurácia >= 90% comprovada 4. [ ] Rollout completo sem incidentes 5. [ ] Aprovado pelo founder


Changelog

Data Ação
2026-01-17 RFC criado, vinculado a HYP-018