Skip to content

guirab/shopmind

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ShopMind 🛍️

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.


Stack

  • 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)

Como rodar

# 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 test

O 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

Endpoint

POST /api/agent

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": [...] }
    }
  ]
}

Ferramentas disponíveis

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.


Decisões de design

Loop de function calling

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:

  1. O modelo recebe o histórico completo + respostas das tools anteriores
  2. Se retornar toolRequests → executa as tools (em paralelo via Promise.all quando o modelo as emite juntas), registra no tool_calls_log e avança o histórico
  3. Se não retornar toolRequests → resposta final, sai do loop
  4. Um limite de MAX_TOOL_ITERATIONS = 10 protege contra loops infinitos em caso de comportamento inesperado do modelo

Isso permite encadeamento arbitrário de ferramentas (ex: ver_produtoadicionar_ao_carrinho) em uma única chamada ao endpoint.

System prompt e guardrails

O system prompt define:

  • Persona: ShopMind, assistente de compras brasileiro
  • Regra de confirmação: o modelo só passa confirmado=true para fechar_pedido após confirmação explícita do usuário — reforçado tanto no prompt quanto na própria assinatura da tool (confirmado: boolean com 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_carrinho exige o campo nome_produto além do produto_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

Histórico de conversa

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.

Modelo escolhido

GPT-4o — escolhido por três razões principais:

  1. Function calling robusto: o GPT-4o tem o melhor suporte a tool use da OpenAI. Decide corretamente quando encadear ferramentas (ex: chamar ver_produto antes de adicionar_ao_carrinho) sem precisar ser explicitamente instruído a cada passo
  2. 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
  3. 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.

Repositórios de estado

O estado mutável da aplicação é gerenciado por repositórios isolados dos dados estáticos de mock:

  • cartRepository: gerencia carrinhos por session_id com getCart, setCart e clearCart
  • orderRepository: inicializado com pedidos mock e acumula pedidos gerados em runtime via fecharPedido, permitindo consulta imediata após o fechamento

Em produção, ambos seriam substituídos por Redis (carrinho) e banco de dados relacional (pedidos).

Segurança

  • session_id validado 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

Dados mockados

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ários suportados

Cenário Ferramentas chamadas
Busca simples ("tênis até R$ 400") buscar_catalogo
Detalhes + adicionar ao carrinho ver_produtoadicionar_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

Limitações conhecidas

  • 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 fecharPedido também rejeita confirmado=false na camada de código. Ainda assim, um modelo mal instruído ou com falha de raciocínio poderia passar confirmado=true indevidamente. 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.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors