Agente de e-commerce com function calling, construído com Genkit SDK, exposto via API REST em Node.js (ESM). Suporta Google Gemini e OpenAI GPT-4o como provider — configurável via variável de ambiente, sem alteração de código.
- Runtime: Node.js ≥ 18 (ESM nativo)
- Agente / LLM: Genkit + OpenAI GPT-4o (padrão) ou Gemini 2.0 Flash (via
AI_PROVIDER=gemini) - Plugins Genkit: @genkit-ai/google-genai (Gemini) e @genkit-ai/compat-oai (OpenAI)
- API: Express.js
- Validação de schema: Zod
- Testes: Vitest
- Dados: mocks em memória (sem banco de dados)
# 1. Clone e instale dependências
npm install
# 2. Configure as variáveis de ambiente
cp .env.example .env
# Edite .env e adicione sua OPENAI_API_KEY
# Obtenha em: https://platform.openai.com/api-keys
# 3. Inicie o servidor
npm start
# Desenvolvimento (hot reload)
npm run dev
# Testes
npm testO servidor sobe em http://localhost:3000.
- Interface de chat:
http://localhost:3000 - Endpoint do agente:
POST http://localhost:3000/api/agent - Health check:
GET http://localhost:3000/health
O session_id deve ser um UUID v4 válido. A interface de chat gera um automaticamente via crypto.randomUUID().
Body:
{
"message": "Quero um tênis de corrida até R$ 400",
"session_id": "550e8400-e29b-41d4-a716-446655440000"
}Response:
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"response": "Encontrei 3 opções de tênis de corrida até R$ 400...",
"tool_calls_log": [
{
"tool": "buscar_catalogo",
"input": { "query": "tênis de corrida", "preco_max": 400 },
"output": { "encontrados": 3, "produtos": [...] }
}
]
}| Ferramenta | Descrição |
|---|---|
buscar_catalogo |
Busca por query, categoria e/ou preço máximo |
ver_produto |
Detalhes completos, SKUs, estoque e prazo de entrega |
verificar_carrinho |
Itens, subtotais e total do carrinho da sessão |
adicionar_ao_carrinho |
Adiciona produto com verificação de estoque e tamanho |
remover_do_carrinho |
Remove quantidade de um item, com suporte a tamanho |
limpar_carrinho |
Remove todos os itens e restaura estoque |
fechar_pedido |
Finaliza compra — exige confirmado: true |
consultar_pedido |
Status e histórico de um pedido por ID |
listar_pedidos |
Histórico de todos os pedidos da sessão |
As três últimas ferramentas (remover_do_carrinho, limpar_carrinho, listar_pedidos) vão além do escopo mínimo do enunciado e foram adicionadas para completar o fluxo de e-commerce de forma coerente.
O agente usa ai.defineFlow do Genkit — a forma idiomática do framework — para encapsular toda a lógica. Isso habilita observabilidade, tracing e integração com o Genkit DevUI nativamente.
Dentro do flow, um loop while gerencia o ciclo de function calling:
- O modelo recebe o histórico completo + respostas das tools anteriores
- Se retornar
toolRequests→ executa as tools (em paralelo viaPromise.allquando o modelo as emite juntas), registra notool_calls_loge avança o histórico - Se não retornar
toolRequests→ resposta final, sai do loop - Um limite de
MAX_TOOL_ITERATIONS = 10protege contra loops infinitos em caso de comportamento inesperado do modelo
Isso permite encadeamento arbitrário de ferramentas (ex: ver_produto → adicionar_ao_carrinho) em uma única chamada ao endpoint.
O system prompt define:
- Persona: ShopMind, assistente de compras brasileiro
- Regra de confirmação: o modelo só passa
confirmado=trueparafechar_pedidoapós confirmação explícita do usuário — reforçado tanto no prompt quanto na própria assinatura da tool (confirmado: booleancom descrição explícita) - Anti-alucinação de dados: instrução para nunca inventar dados além do retorno das ferramentas
- Anti-alucinação de IDs:
adicionar_ao_carrinhoexige o camponome_produtoalém doproduto_id. A tool valida que o nome corresponde ao produto encontrado pelo ID — se o modelo alucinar um ID errado, o erro retornado instrui a re-consultar o catálogo antes de tentar novamente - Session_id: injetado no system prompt a cada turno para garantir que o modelo use sempre o valor correto ao chamar tools
- Formato: preços em R$, tom amigável, sem repetições
O histórico é mantido em memória (Map<session_id, turns[]>) no processo Node. Cada sessão preserva os turnos anteriores, permitindo contexto entre mensagens (ex: "Me mostra mais detalhes do segundo item" referencia o resultado anterior). Um limite de MAX_HISTORY_TURNS = 20 evita que o contexto cresça indefinidamente, estourando o context window. Em produção, isso seria substituído por Redis ou banco de dados.
GPT-4o — escolhido por três razões principais:
- Function calling robusto: o GPT-4o tem o melhor suporte a tool use da OpenAI. Decide corretamente quando encadear ferramentas (ex: chamar
ver_produtoantes deadicionar_ao_carrinho) sem precisar ser explicitamente instruído a cada passo - Velocidade: significativamente mais rápido que o GPT-4 Turbo mantendo a mesma qualidade de raciocínio — relevante em agentes com múltiplos turnos de function calling
- Custo/qualidade: o GPT-4o-mini seria mais econômico, mas perde consistência em fluxos ambíguos (ex: inferir confirmação implícita como explícita no fechamento de pedido) — um risco direto ao critério de guardrails do desafio
O provider é configurado em src/ai.js via a variável AI_PROVIDER — trocar de Gemini para OpenAI (ou vice-versa) é uma alteração no .env, sem tocar na lógica do agente. Isso demonstra na prática a portabilidade que o Genkit oferece.
O estado mutável da aplicação é gerenciado por repositórios isolados dos dados estáticos de mock:
cartRepository: gerencia carrinhos porsession_idcomgetCart,setCarteclearCartorderRepository: inicializado com pedidos mock e acumula pedidos gerados em runtime viafecharPedido, permitindo consulta imediata após o fechamento
Em produção, ambos seriam substituídos por Redis (carrinho) e banco de dados relacional (pedidos).
session_idvalidado como UUID v4 antes de qualquer processamento- Payload limitado a 1mb via
express.json({ limit: '1mb' }) - Race condition no estoque documentada — em produção resolvida com transação ou Redis SETNX
As implementações das tools usam dados hardcoded em src/data/mock.js. A lógica de negócio (filtragem, estoque, carrinho) é funcional para demonstrar os cenários, mas não persiste entre reinicializações do servidor.
| Cenário | Ferramentas chamadas |
|---|---|
| Busca simples ("tênis até R$ 400") | buscar_catalogo |
| Detalhes + adicionar ao carrinho | ver_produto → adicionar_ao_carrinho |
| Fechar pedido com confirmação | verificar_carrinho → aguarda confirmação → fechar_pedido |
| Consulta de status de um pedido | consultar_pedido |
| Histórico de pedidos da sessão | listar_pedidos |
| Remover item do carrinho | remover_do_carrinho |
| Limpar carrinho | limpar_carrinho |
- Histórico em memória: reiniciar o servidor limpa todas as sessões
- Sem autenticação: qualquer UUID válido acessa qualquer carrinho — em produção exigiria auth
- Estoque não persiste: mudanças no estoque somem ao reiniciar
- Tool calls paralelas: o GPT-4o pode serializar tool calls que poderiam ser executadas em paralelo
- Guardrail de confirmação via LLM: a proteção contra fechar pedido sem confirmação explícita é reforçada no system prompt e na descrição da tool — a tool
fecharPedidotambém rejeitaconfirmado=falsena camada de código. Ainda assim, um modelo mal instruído ou com falha de raciocínio poderia passarconfirmado=trueindevidamente. Em produção, isso seria resolvido com uma flag de confirmação gerenciada pelo servidor (fora do controle do LLM), não apenas pelo prompt.