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:
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 |