AI Agent
Atendente automatizado embarcado por phone, usando OpenAI Chat Completions com knowledge base (RAG via pgvector). Cada phone pode ter exatamente 1 AI Agent.
BYO-key: o gateway não inclui créditos OpenAI. Configure sua key via
PATCH /v1/account/integrations/openai.
Base path: /v1/phones/:phoneId/ai-agent
Conceitos
- Agent: configuração (system prompt, modelo, regras de handoff). 1 por phone.
- Knowledge: documentos (URL, S3 file, texto inline) processados em chunks com embedding (
text-embedding-3-small, 1536 dim). RAG retrieval top-K por turn. - Conversation: sessão por
contactKey(chave canônica do contato). Persiste turnos para janela de contexto LLM. - Handoff: quando o agent transfere para humano (manual via API, ou automático por regra).
Configuração do agent
PUT /v1/phones/:phoneId/ai-agent
Cria ou atualiza (upsert) o agent.
Body
| Campo | Tipo | Default | Descrição |
|---|---|---|---|
systemPrompt | string | — | 20-20.000 chars. Instruções, personalidade, escopo |
model | string | gpt-4o-mini | Qualquer modelo OpenAI compatível com Chat Completions |
enabled | boolean | false | Liga/desliga sem deletar |
handoffRules.keywords | string[] | — | Frases que dispararam handoff manual (max 20) |
handoffRules.afterTurns | int | — | Handoff automático após N turnos |
contextWindowTurns | int | 10 | Histórico máximo em prompt (1-50) |
ragTopK | int | 4 | Chunks RAG por turn (0-20; 0 = desabilita RAG) |
conversationTtlDays | int | 30 | Auto-CLOSED após N dias sem turno |
greeting | string | — | Primeiro contato envia esta mensagem antes do LLM |
curl -X PUT https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-.../ai-agent \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{
"systemPrompt": "Você é a Vera, atendente da Acme Saúde...",
"model": "gpt-4o-mini",
"enabled": true,
"handoffRules": { "keywords": ["falar com humano", "atendente"], "afterTurns": 20 },
"contextWindowTurns": 10,
"ragTopK": 4,
"greeting": "Oi! Sou a Vera. Como posso ajudar?"
}'
Response 200 — objeto agent.
GET /v1/phones/:phoneId/ai-agent
Retorna configuração atual.
Errors
| HTTP | Code | Quando |
|---|---|---|
| 404 | AGENT_NOT_FOUND | Phone sem agent configurado |
DELETE /v1/phones/:phoneId/ai-agent
Remove agent + knowledge + conversations (cascade).
Response 204
POST /v1/phones/:phoneId/ai-agent/enable
Atalho para enabled=true sem precisar repassar todos os campos.
POST /v1/phones/:phoneId/ai-agent/disable
Atalho para enabled=false. Conversas em andamento continuam respondendo até a janela de contexto fechar.
POST /v1/phones/:phoneId/ai-agent/test
Sandbox para testar o prompt sem persistir conversa nem usar RAG.
Body
| Campo | Tipo | Obrigatório |
|---|---|---|
message | string | sim (1-4000 chars) |
systemPromptOverride | string | não (max 20k chars) |
curl -X POST https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-.../ai-agent/test \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{ "message": "Quanto custa uma consulta?" }'
Response 200
{
"reply": "Nossas consultas variam entre...",
"model": "gpt-4o-mini",
"usage": { "promptTokens": 89, "completionTokens": 124 }
}
Knowledge base
Base path: /v1/phones/:phoneId/ai-agent/knowledge
POST /v1/phones/:phoneId/ai-agent/knowledge
Ingere um documento (assíncrono — Worker baixa, extrai texto, chunka, gera embeddings).
Body (discriminated union por source)
{ "source": "URL", "url": "https://acme.com/sobre", "title": "Sobre a Acme" }
{ "source": "TEXT_INLINE", "text": "Conteúdo do FAQ ...", "title": "FAQ" }
{ "source": "S3_FILE", "s3Key": "knowledge/acme/manual.pdf", "title": "Manual" }
| Campo | Tipo | Obrigatório | Limite |
|---|---|---|---|
source | enum | sim | URL, TEXT_INLINE, S3_FILE |
url | URL | só URL | HTTPS público |
text | string | só TEXT_INLINE | 20-200.000 chars |
s3Key | string | só S3_FILE | 3-500 chars |
title | string | não | 255 chars |
Response 202
{
"id": "doc-1234-...",
"source": "URL",
"title": "Sobre a Acme",
"status": "PENDING",
"createdAt": "..."
}
Status: PENDING → PROCESSING → READY ou FAILED (com errorReason).
GET /v1/phones/:phoneId/ai-agent/knowledge
Lista documentos.
[
{
"id": "doc-1234-...",
"source": "URL",
"sourceRef": "https://acme.com/sobre",
"title": "Sobre a Acme",
"status": "READY",
"chunkCount": 23,
"errorReason": null,
"createdAt": "...",
"updatedAt": "..."
}
]
GET /v1/phones/:phoneId/ai-agent/knowledge/:docId
Detalhe.
Errors
| HTTP | Code | Quando |
|---|---|---|
| 404 | DOC_NOT_FOUND | — |
DELETE /v1/phones/:phoneId/ai-agent/knowledge/:docId
Remove doc + chunks (cascade).
Response 204
Conversations
Conversas geradas automaticamente quando o agent responde uma mensagem inbound (1 por (phoneId, contactKey)).
Base path: /v1/phones/:phoneId/ai-agent/conversations
GET /v1/phones/:phoneId/ai-agent/conversations
Lista paginada.
Query
| Param | Tipo | Default |
|---|---|---|
status | AI, HUMAN_HANDOFF, CLOSED | — |
limit | int | 50 (max 200) |
cursor | UUID | — |
Response 200
{
"items": [
{
"id": "conv-1234-...",
"contactKey": "+5511988887777",
"status": "AI",
"turnCount": 3,
"handoffReason": null,
"handoffAt": null,
"lastTurnAt": "2026-05-21T14:33:00.000Z",
"closedAt": null,
"createdAt": "..."
}
],
"nextCursor": null
}
GET /v1/phones/:phoneId/ai-agent/conversations/:contactKey
Detalhe da conversa. :contactKey URL-encoded (ex.: %2B5511988887777).
Errors
| HTTP | Code | Quando |
|---|---|---|
| 404 | CONVERSATION_NOT_FOUND | — |
GET /v1/phones/:phoneId/ai-agent/conversations/:contactKey/messages
Histórico de turnos.
Query
| Param | Default | Max |
|---|---|---|
limit | 50 | 200 |
Response 200
[
{
"id": "msg-1234-...",
"conversationId": "conv-1234-...",
"role": "USER",
"content": "Quanto custa uma consulta?",
"tokensEstimate": 8,
"whatsappMessageId": "ABCD1234..."
},
{
"role": "ASSISTANT",
"content": "Nossas consultas variam entre...",
"tokensEstimate": 124,
"sentMessageId": "msg-out-...",
"retrievedChunkIds": ["chunk-1", "chunk-2"]
}
]
POST /v1/phones/:phoneId/ai-agent/conversations/:contactKey/handoff
Força transferência para humano. Agent para de responder.
Body
| Campo | Tipo | Default |
|---|---|---|
reason | string | "manual" |
curl -X POST "https://wpp.ogmma.com.br/v1/phones/5f7b2e1c-.../ai-agent/conversations/%2B5511988887777/handoff" \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{ "reason": "cliente_pediu" }'
Response 200 — conversa atualizada. Também publica evento agent:handoff (ver Webhooks).
POST /v1/phones/:phoneId/ai-agent/conversations/:contactKey/release
Devolve para a IA (volta de HUMAN_HANDOFF para AI). Não funciona se já está CLOSED.
Response 200 — conversa atualizada.
Errors
| HTTP | Code | Quando |
|---|---|---|
| 409 | CONVERSATION_CLOSED | — |
POST /v1/phones/:phoneId/ai-agent/conversations/:contactKey/close
Fecha a conversa (status: CLOSED + closedAt). Idempotente.
Response 200
Auto-handoff
O agent decide automaticamente quando transferir, baseado em:
- Keywords: se o último user message contém qualquer palavra de
handoffRules.keywords(match case-insensitive substring). - afterTurns: se
turnCount >= afterTurns.
Quando dispara, publica agent:handoff com manual: false.
Stateless endpoints (sem agent persistente)
Para casos onde você quer LLM mas não quer instalar agent:
POST /v1/ai/summary
Sumariza uma lista de mensagens.
Body
{
"messages": [
{ "from": "user", "text": "Oi, quero saber sobre o produto" },
{ "from": "assistant", "text": "Claro! Temos várias opções..." }
],
"format": "bullet",
"language": "pt_BR"
}
| Campo | Tipo | Default |
|---|---|---|
messages | array | 1-200 itens |
messages[].from | user | assistant | sim |
messages[].text | string | 1-8000 chars |
format | bullet | paragraph | bullet |
language | string | pt_BR |
model | string | gpt-4o-mini |
Response 200
{
"summary": "- Cliente perguntou sobre produto X\n- Atendente apresentou 3 opções...",
"keyTopics": ["produto X", "preço", "garantia"],
"openQuestions": ["forma de pagamento"],
"model": "gpt-4o-mini",
"usage": { "promptTokens": 134, "completionTokens": 87 }
}
POST /v1/ai/classify
Classifica um texto em uma de N categorias.
Body
{
"text": "Quero saber o status do meu pedido 12345",
"categories": ["vendas", "suporte_tecnico", "rastreio_pedido", "reclamacao"],
"language": "pt_BR"
}
| Campo | Tipo | Limite |
|---|---|---|
text | string | 1-8000 chars |
categories | string[] | 2-20 itens, cada item 1-60 chars |
Response 200
{
"category": "rastreio_pedido",
"confidence": 0.95,
"alternatives": [],
"rationale": "Cliente mencionou número de pedido explicitamente",
"model": "gpt-4o-mini",
"usage": { "promptTokens": 67, "completionTokens": 38 }
}
Errors
| HTTP | Code | Quando |
|---|---|---|
| 502 | AI_BAD_OUTPUT | OpenAI retornou JSON inválido |
Custos
Todas as chamadas (test, agent turns, summary, classify, knowledge embedding) consomem da sua key OpenAI direto. O gateway registra em ai_usage_events para você consultar via admin endpoints, mas não cobra markup.
Estimativa típica (gpt-4o-mini):
- Agent turn: ~200-800 tokens prompt + ~100-300 completion = ~$0.0002-0.0008 por turn.
- Embedding (knowledge): ~$0.00002 por chunk.
- Whisper: ~$0.006/min.