--- name: catcher-data-vector description: Use quando um app ou agente precisa consumir o Vector do Catcher (/v1/cloudvec/*). Busca vetorial sobre pgvector — ingestão de arquivos, busca híbrida (RRF), re-rank LLM, isolamento por tenant. homepage: https://catcher.one/produtos/vector docs: https://catcher.one/docs/vector api_base: https://data-api.catcher.one --- # Catcher — Vector Autenticação: `X-API-Key: ctc_…` (owner/admin). Base: https://data-api.catcher.one. Onboarding de key e conceitos gerais: https://catcher.one/skill. Referência navegável: https://catcher.one/docs/vector. # Vector — busca semântica gerenciada (`cloudvec`) > Rotas `/v1/cloudvec/*` · owner/admin · gated por `CLOUDVEC_ENABLED` (off → 404) ## O que é **Busca semântica gerenciada** (RAG) sobre seus textos e documentos. Você cria **coleções**, coloca documentos (ou **sobe arquivos** que o sistema converte + chunka + indexa), e busca por **significado** — não por palavra-chave. Por baixo: embeddings da OpenAI + PostgreSQL/pgvector. ## O que dá pra fazer - Criar/listar/remover **coleções** (por projeto). - Organizar em **pastas** hierárquicas (`suporte/faturamento/`) e **escopar a busca** por pasta (recursivo). - Adicionar documentos por texto (embeda + upserta em lote, até 100/request). - **Ingerir qualquer arquivo** (PDF, docx, xlsx, pptx, imagens, md, txt…): o pipeline converte para Markdown, guarda original + Markdown no [Storage](storage.md), chunka e embeda. Imagens e PDFs escaneados passam por **OCR via visão**. - **Buscar** por similaridade de cosseno, escopada por pasta; resultados de arquivos apontam de volta para a fonte. ## Como funciona por dentro (na prática) - **Coleção = control-row + tabela de vetores.** Cada coleção é uma linha no tenant DB (`vec_collections`) + uma tabela no pgvector (namespaced pelo `id` da coleção). - **Embedder por-coleção.** O modelo de embedding fica **gravado em cada coleção** (default para novas: `text-embedding-3-large`, 3072 dims; coleções antigas `3-small`/1536 continuam funcionando — modelos coexistem). A busca embeda a query com o modelo da coleção e compara com `<=>` (cosine distance) no pgvector. - **Pastas = caminho materializado.** Cada documento tem um `path`. A busca é por **prefixo, recursiva**: sem `path` busca tudo; `path:"suporte/"` busca `suporte/` + subpastas. Só documentos reais entram (placeholders de pasta vazia são excluídos). - **Ingestão (fontes).** Um arquivo → sidecar **MarkItDown** (+ passe opcional de embelezamento OpenAI) → **Storage** guarda original + `.md` espelhando a árvore de pastas (`vector//`) → o Markdown é quebrado em chunks embedados. Imagens/PDF escaneado → **OCR de visão** (modelo `gpt-5.4-mini`, `detail:high`, prompt de fidelidade que não inventa valores). Cada arquivo vira uma **fonte** (`VecSource`) rastreável; seus chunks não inflam o `document_count` nem aparecem no `GET /documents` — ficam no card da fonte, e carregam `metadata.source_filename`/`source_id` para o resultado apontar de volta. - **Busca híbrida e re-rank.** Além do cosine puro, a busca aceita fusão híbrida (semântica + lexical via RRF) para não perder termo exato, re-rank LLM opt-in, filtros por metadata/fonte/tipo e modos de retrieval com FAQ cards (`qa_first`). Detalhes na seção **Busca** abaixo. ## Fluxo típico (end-to-end) ```bash KEY="X-API-Key: ctc_…" ; B=https://data-api.catcher.one ; JS='Content-Type: application/json' PID= # 1. Cria a coleção (tenant_id é opcional — isola um cliente final; ver Tenants) CID=$(curl -sX POST $B/v1/cloudvec/projects/$PID/collections -H "$KEY" -H "$JS" \ -d '{"name":"docs"}' | jq -r .id) # 2a. Adiciona documentos por texto (embeda em lote) curl -sX POST $B/v1/cloudvec/collections/$CID/documents -H "$KEY" -H "$JS" -d '{ "documents":[{"content":"O servidor tem 12 meses de garantia.","path":"suporte/","metadata":{"src":"faq"}}]}' # 2b. …ou sobe um arquivo (converte → Storage → chunka → embeda; assíncrono) curl -sX POST $B/v1/cloudvec/collections/$CID/ingest -H "$KEY" \ -F "file=@./manual.pdf" -F "path=suporte/" # → { "id":"…","status":"processing","chunk_count":…,"filename":"manual.pdf" } # 3. Busca semântica (escopada por pasta, recursiva) curl -sX POST $B/v1/cloudvec/collections/$CID/search -H "$KEY" -H "$JS" \ -d '{"query":"quanto tempo de cobertura contra defeitos?","k":5,"path":"suporte/"}' # → { "results":[{"id","content","metadata","score"}], "count":5 } ``` ## Referência de endpoints ### Coleções | Método | Rota | O que faz | |---|---|---| | `POST` | `/v1/cloudvec/projects/{pid}/collections` | Cria (`{name}` lowercase 2-48, `tenant_id?`) → `201` (`embed_model`, `dimensions`, `tenant_id?`) | | `GET` | `/v1/cloudvec/projects/{pid}/collections` | Lista | | `GET` | `/v1/cloudvec/collections/{id}` | Detalhe + `document_count` vivo | | `DELETE` | `/v1/cloudvec/collections/{id}` | Remove (dropa tabela de vetores + control-row) → `204` | #### Isolamento por tenant (end-customers) Passe `tenant_id` ao criar a coleção para isolar um **cliente final**: a coleção passa a viver no **schema Postgres dedicado do tenant** (`t_` + role `tr_`) e a busca naquela coleção só enxerga os vetores dele. Sem `tenant_id`, a coleção é de nível projeto (schema default `public`, compartilhada). É o mesmo `tenant_id` usado no Database, Storage e Cache — ver **[Tenants](tenants.md)**. Para isolar ainda mais fino (por usuário final dentro de um tenant) sem multiplicar coleções, use o `filter` por metadata na busca (abaixo). ### Pastas | Método | Rota | O que faz | |---|---|---| | `POST` | `/v1/cloudvec/collections/{id}/folders` | Cria pasta (`{path}`) → `201 {path:"suporte/faturamento/"}` | | `GET` | `/v1/cloudvec/collections/{id}/folders?parent=` | Subpastas imediatas | | `GET` | `/v1/cloudvec/collections/{id}/folders/stats?path=` | `{document_count}` recursivo | | `DELETE` | `/v1/cloudvec/collections/{id}/folders?path=` | Apaga pasta + conteúdo recursivo → `{deleted:N}` | ### Documentos | Método | Rota | O que faz | |---|---|---| | `POST` | `/v1/cloudvec/collections/{id}/documents` | Embeda + upserta (`{documents:[{content,path?,id?,metadata?}]}`, máx. 100) → `201` | | `GET` | `/v1/cloudvec/collections/{id}/documents?path=&recursive=&limit=` | Lista (limit ≤ 1000) | | `DELETE` | `/v1/cloudvec/collections/{id}/documents/{docId}` | Remove → `204` | ### Busca `POST /v1/cloudvec/collections/{id}/search` → `{results:[{id,content,metadata,score}], count}` | Campo | Tipo | O que faz | |---|---|---| | `query` | string | A consulta (é embedada — 1 embedding por busca). | | `k` | int | Máximo de resultados (default 5). | | `path` | string | Escopo por pasta, recursivo (`""`/ausente = coleção inteira). | | `hybrid` | bool | **Fusão RRF** do braço semântico (cosine) com um braço lexical full-text ANTES do corte top-k — um doc term-exato (ID, SKU, código) que o cosine enterrou ainda aparece. Cada hit ganha `metadata.match: "semantic"\|"lexical"\|"both"`. | | `alpha` | float 0..1 | Peso do braço semântico na fusão (lexical = 1−alpha; ausente = 0.5; `0` = lexical puro). | | `rerank` | bool | Re-rank listwise com LLM após a recuperação (opt-in — custa uma chamada de modelo; use em consultas críticas). | | `mode` | string | Retrieval com FAQ cards do Refinery: default `qa_first` (cards antes de chunks; degrada para chunks se não houver cards) · `chunks` (só chunks) · `qa_only`. | | `filter` | map | Pré-filtro de igualdade por metadata (AND), ex.: `{"end_user_id":"u_9"}`. | | `source_id` | string | Só chunks de uma fonte específica. | | `type` | string | Só um tipo de fonte (`pdf\|word\|spreadsheet\|markdown\|text\|…`). | `score` = similaridade de cosseno em [0,1] (1 = idêntico). ### Ingestão de arquivos (fontes) | Método | Rota | O que faz | |---|---|---| | `POST` | `/v1/cloudvec/collections/{id}/ingest` | Multipart (`file` + `path?`), ≤ 64 MB → `{id,status:"processing",chunk_count,…}` | | `GET` | `/v1/cloudvec/collections/{id}/sources?path=` | Lista fontes em `path` | | `GET` | `/v1/cloudvec/collections/{id}/sources/{sid}` | Detalhe + **signed URLs** (TTL 30 min) do original + Markdown + os chunks | | `DELETE` | `/v1/cloudvec/collections/{id}/sources/{sid}` | Remove fonte + chunks + objetos no Storage → `204` | Ingestão de binários requer o sidecar (`CLOUDVEC_CONVERTER_URL`) + `CLOUDSTORE_ENABLED` + um GCP project READY no projeto (para o bucket `vector-sources`). `.md`/`.txt` passam direto. ## Códigos de erro do módulo `INVALID_COLLECTION_NAME` (400, inclui `path` inválido) · `COLLECTION_ALREADY_EXISTS` (409) · `COLLECTION_NOT_FOUND` (404) · `EMPTY_INPUT` (400) · `BATCH_TOO_LARGE` (400, > 100 docs) · `EMBEDDING_DIMENSION_MISMATCH` (400) · `SOURCE_NOT_FOUND` (404) · `CONVERSION_UNAVAILABLE` (422, sidecar ausente p/ binário) · `NO_READY_PROJECT` (409) · `MISSING_FIELD` (400). ## Gotchas - **Embeddings custam** (OpenAI) — `documents` cobra por embedar cada `content`; busca cobra 1 embedding por query. Agrupe inserções em lote (até 100/request). - **Não misture modelos numa coleção** — o modelo é fixado na criação; se mudar o default global, coleções novas usam o novo, as antigas mantêm o seu (sem `EMBEDDING_DIMENSION_MISMATCH`). - **Ingestão é assíncrona** — `status: processing` → `ready`|`failed` (`last_error`); faça poll via `GET .../sources/{sid}`. - **Chunks de fontes ≠ documents** — não aparecem no `GET /documents` nem contam no `document_count`; aparecem nos resultados de busca com `metadata.source_filename`. - **Para RAG:** ingira os docs em pastas por assunto, depois `search` com `path` para focar o contexto e `k` para o tamanho do contexto.