NALU/Prompt1.md
Ricardo Carneiro ea6cdb5395 Initial commit — NALU AI web platform
- ASP.NET Core 9 Razor Pages + Minimal API hybrid
- 14 validators (CPF, CEP, CNPJ, email, phone, name, yes-no, birthdate, handoff, cancel-intent, company-name, plate-br, postal-code, validate_reply)
- OAuth login (Google, Microsoft, GitHub) + cookie auth
- MongoDB usage tracking + CEP cache collection
- Stripe checkout with inline PriceData (no Price IDs)
- MCP server for Claude Code / Cursor integration
- Playground (10 calls/IP/day, no auth)
- Docs: Quickstart, API Reference, N8N, MCP, Créditos, Erros, Fluxos
- Credit system: 3 cr standard validators, 5 cr validate_reply
- SmartSuggestion: contextual re-ask via IA when obtained=false
- Per-IP rate limiting + daily cap + shared-IP abuse detection
- Lightbox for comic images
- Validadores page split: Brasileiros / Universais + Em breve

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 16:39:04 -03:00

15 KiB
Raw Blame History

# Prompt 1 — NALU AI: Estrutura + Pipeline + Primeiro validador

## O que é o projeto

NALU AI (Natural Language Understanding) é uma API + MCP server em ASP.NET Core 9 (C#) que recebe um trecho de diálogo entre agente e usuário e retorna a **intenção real** extraída, com validação determinística e sugestão de fala para o agente.

Resolve problemas universais de chatbots:

- Usuário responde "Bom dia" quando perguntado o nome → bot grava "Bom Dia" como nome.

- Usuário "zoa" e diz "Meu nome é Xilofone" → bot aceita sem questionar.

- Agente oferece "20x de R$100", usuário responde "Bora em 48?" → bot extrai R$48 em vez de 48 parcelas.

- Usuário digita CEP errado → bot aceita sem validar.

---

## Princípio fundamental: 1 validador = 1 endpoint = 1 MCP tool

Cada validador tem seu **próprio endpoint** e sua **própria tool MCP**. A IA consumidora vê uma lista clara de endpoints/tools e sabe exatamente qual chamar sem ler parâmetros.

Internamente todos os endpoints compartilham o mesmo pipeline de extração — a diferença é o validador .md carregado.

### Endpoints da API


POST /v1/extract/name         → validate\_full\_name.md

POST /v1/extract/cpf          → validate\_cpf.md

POST /v1/extract/cep          → validate\_cep.md

POST /v1/extract/phone        → validate\_phone\_br.md

POST /v1/extract/email        → validate\_email.md

POST /v1/extract/yes-no       → validate\_yes\_no.md

GET  /v1/validators           → lista todos os validadores com descrição

### Tools MCP (espelho dos endpoints)


nalu\_extract\_name        → "Extrai o nome completo do usuário"

nalu\_extract\_cpf         → "Extrai e valida CPF do usuário"

nalu\_extract\_cep         → "Extrai CEP e retorna endereço completo"

nalu\_extract\_phone       → "Extrai telefone brasileiro com DDD"

nalu\_extract\_email       → "Extrai email com correção de typos"

nalu\_extract\_yes\_no      → "Detecta se o usuário disse sim ou não"

Todos aceitam o **mesmo body** — sem campo validator, o endpoint já define qual usar:


{

&#x20; "agent\_input": "Bom dia! Qual seu nome?",

&#x20; "user\_input": "Bom dia!",

&#x20; "agent\_context": "Negociador de parcelamento.",

&#x20; "language": "pt-BR"

}

---

## Arquitetura

### Stack

- ASP.NET Core 9 (C#), .NET 10

- LLM principal: Groq (Llama 3.3 70B, free tier) via API compatível OpenAI

- LLM fallback: OpenRouter (modelos baratos)

- Cache: in-memory (V1)

- Banco: PostgreSQL (logs, billing, API keys) — EF Core

- Container Docker único

### Pipeline de extração (compartilhado)


Request chega no endpoint específico (ex: /v1/extract/name)

&#x20;   ↓

Auth (API key via header Authorization: Bearer {key})

&#x20;   ↓

Rate limit por plano

&#x20;   ↓

Resolve qual validador (.md) usar baseado no endpoint

&#x20;   ↓

Cache lookup (hash de: validator\_id + agent\_input + user\_input + language)

&#x20;   ↓ cache miss

Carregar validador (.md do disco, com cache em memória)

&#x20;   ↓

Camada 1 — Regras determinísticas (regex, stop words, constraints)

&#x20;   ↓ não resolveu

Camada 2 — LLM com prompt do validador (Groq → fallback OpenRouter)

&#x20;   ↓

Pós-processamento (normalização, dígito verificador, etc.)

&#x20;   ↓

Enriquecimento externo (ViaCEP etc., se o validador declarar)

&#x20;   ↓

Montar sugestão de fala (template do validador)

&#x20;   ↓

Salvar log + retornar resposta

---

## Estrutura de pastas


src/

&#x20; Nalu.Api/

&#x20;   Program.cs

&#x20;   appsettings.json

&#x20;   appsettings.Development.json



&#x20;   Endpoints/

&#x20;     ExtractEndpoints.cs         # Registra todos os POST /v1/extract/{tipo}

&#x20;     ValidatorsEndpoints.cs      # GET /v1/validators



&#x20;   Models/

&#x20;     ExtractionRequest.cs        # Body compartilhado por todos os endpoints

&#x20;     ExtractionResponse.cs       # Response compartilhada

&#x20;     ValidatorInfo.cs            # Modelo para GET /v1/validators

&#x20;     ValidatorDefinition.cs      # Validador parseado do .md



&#x20;   Services/

&#x20;     ExtractionPipeline.cs       # Orquestra o pipeline (recebe validator\_id)

&#x20;     ValidatorLoader.cs          # Parseia .md, cache em memória, FileSystemWatcher

&#x20;     DeterministicLayer.cs       # Camada 1: stop words, regex, constraints

&#x20;     LlmExtractionService.cs     # Camada 2: Groq/OpenRouter

&#x20;     PostProcessorRegistry.cs    # Registry de IPostProcessor por nome

&#x20;     EnrichmentService.cs        # Orquestra enrichers

&#x20;     SuggestionBuilder.cs        # Monta suggestion\_to\_agent

&#x20;     CacheService.cs             # IMemoryCache com hash

&#x20;     AuthService.cs              # Valida API key, retorna plano

&#x20;     RateLimitService.cs         # Rate limit por plano



&#x20;   PostProcessors/

&#x20;     IPostProcessor.cs

&#x20;     CapitalizeProperName.cs

&#x20;     RemoveTitles.cs



&#x20;   Enrichers/

&#x20;     IEnricher.cs



&#x20;   Infrastructure/

&#x20;     GroqClient.cs

&#x20;     OpenRouterClient.cs

&#x20;     NaluDbContext.cs



&#x20;   Validators/                   # ← CADA .md É UM VALIDADOR

&#x20;     validate\_full\_name.md



tests/

&#x20; Nalu.Tests/

&#x20;   ValidatorLoaderTests.cs

&#x20;   DeterministicLayerTests.cs

&#x20;   PipelineIntegrationTests.cs



Dockerfile

### Registro dos endpoints (Minimal APIs)


// ExtractEndpoints.cs

public static class ExtractEndpoints

{

&#x20;   public static void MapExtractEndpoints(this WebApplication app)

&#x20;   {

&#x20;       var group = app.MapGroup("/v1/extract")

&#x20;           .RequireAuthorization();



&#x20;       group.MapPost("/name", async (ExtractionRequest req, ExtractionPipeline pipeline) =>

&#x20;           await pipeline.ExecuteAsync("validate\_full\_name", req));



&#x20;       // Prompt 2 adiciona os demais aqui — mesma estrutura, 2 linhas cada

&#x20;   }

}

---

## Formato dos validadores (.md)

O ValidatorLoader parseia seções ## do markdown. Cada seção vira uma propriedade do ValidatorDefinition.

### Exemplo completo: validate\_full\_name.md


\# validate\_full\_name



Extrai o nome completo do usuário a partir do diálogo.



\## config



\- type: extraction

\- version: 1.0

\- languages: pt-BR, es-ES, en-US

\- endpoint: /v1/extract/name

\- mcp\_tool: nalu\_extract\_name

\- mcp\_description: Extrai o nome completo do usuário a partir da conversa. Use quando o agente perguntou o nome e o usuário respondeu. Retorna o nome extraído, nível de certeza e sugestão de fala para o agente. Se certain=true, aceite o valor. Se certain=false e suggestion\_to\_agent não é null, use a sugestão como próxima mensagem. Se obtained=false, use a sugestão para re-pedir o dado.



\## deterministic\_rules



\### stop\_words

bom dia, boa tarde, boa noite, olá, oi, tudo bem, e aí, fala, eae, opa



\### reject\_patterns

\- ^(não|nao|sei la|sei lá|tanto faz|qualquer|nenhum|nada)$

\- ^\\d+$



\### accept\_patterns

\- ^meu nome é\\s+(.+)$

\- ^me chamo\\s+(.+)$

\- ^sou o\\s+(.+)$

\- ^sou a\\s+(.+)$

\- ^pode me chamar de\\s+(.+)$



\### constraints

\- min\_length: 2

\- must\_have\_alpha: true

\- max\_length: 120



\## prompt



Você é um extrator de nomes. Dado o diálogo abaixo entre um agente e um usuário, extraia o nome completo que o usuário informou.



Regras:

1\. Se o usuário respondeu com saudação (bom dia, oi, etc.) e NÃO disse o nome, retorne extracted\_value: null.

2\. Se o usuário deu um nome que parece falso, zueira ou ofensivo (ex: "Xilofone", "Ninguém", "Seu Pai", "Não tenho"), retorne extracted\_value com o nome mas certain: false.

3\. Se o usuário deu um nome comum/plausível, retorne extracted\_value com o nome e certain: true.

4\. Nomes incomuns mas reais (ex: "Céu", "Lua", "Sol", "Índigo") devem retornar certain: false para o agente confirmar.

5\. Normalize o nome com capitalização adequada (primeira letra maiúscula de cada palavra).



Diálogo:

Agente: {{agent\_input}}

Usuário: {{user\_input}}



Contexto do agente: {{agent\_context}}



Responda SOMENTE com JSON válido, sem markdown, sem explicação:

{

&#x20; "extracted\_value": "nome extraído ou null",

&#x20; "certain": true/false,

&#x20; "reasoning": "explicação curta de 1 linha"

}



\## few\_shot\_examples



\### example 1

\- agent\_input: Bom dia! Qual seu nome completo?

\- user\_input: Bom dia!

\- output: {"extracted\_value": null, "certain": false, "reasoning": "Usuário respondeu com saudação, não informou o nome"}



\### example 2

\- agent\_input: Qual seu nome?

\- user\_input: Meu nome é xilofone

\- output: {"extracted\_value": "Xilofone", "certain": false, "reasoning": "Nome aparenta ser zueira, precisa confirmação"}



\### example 3

\- agent\_input: Para continuar, preciso do seu nome completo.

\- user\_input: Maria Silva dos Santos

\- output: {"extracted\_value": "Maria Silva Dos Santos", "certain": true, "reasoning": "Nome completo plausível informado diretamente"}



\### example 4

\- agent\_input: Qual seu nome?

\- user\_input: sei la

\- output: {"extracted\_value": null, "certain": false, "reasoning": "Usuário foi evasivo, não informou nome"}



\### example 5

\- agent\_input: Tem certeza que seu nome é Cebola?

\- user\_input: Sim, quero que me chame de Cebola.

\- output: {"extracted\_value": "Cebola", "certain": true, "reasoning": "Usuário confirmou o nome após questionamento"}



\### example 6

\- agent\_input: Qual seu nome?

\- user\_input: Céu Azul de Oliveira

\- output: {"extracted\_value": "Céu Azul De Oliveira", "certain": false, "reasoning": "Nome incomum, pode ser real mas precisa confirmação"}



\## post\_processors

\- capitalize\_proper\_name

\- remove\_titles



\## enrichment

(nenhum)



\## suggestions



\### when\_null\_greeting

{{greeting\_response}} Mas preciso do seu nome completo para continuar. Pode me dizer?



\### when\_null\_evasive

Sem problemas, mas preciso do seu nome para prosseguir. Qual seu nome completo?



\### when\_uncertain

Só confirmando: seu nome é {{extracted\_value}}? Pode confirmar?



\### when\_certain

(sem sugestão — agente segue o fluxo)

---

## Contrato da API

### Request (mesmo para todos os endpoints)


POST /v1/extract/name

Authorization: Bearer {api\_key}

Content-Type: application/json


{

&#x20; "agent\_input": "Bom dia! Qual seu nome completo?",

&#x20; "user\_input": "Bom dia!",

&#x20; "agent\_context": "Negociador de parcelamento. Precisa de nome e CPF.",

&#x20; "language": "pt-BR"

}

### Response (mesma estrutura para todos)


{

&#x20; "obtained": false,

&#x20; "extracted\_value": null,

&#x20; "confidence": 0.15,

&#x20; "certain": false,

&#x20; "reasoning": "Usuário respondeu com saudação, não informou o nome",

&#x20; "suggestion\_to\_agent": "Bom dia! Mas preciso do seu nome completo para continuar. Pode me dizer?",

&#x20; "validator\_used": "validate\_full\_name",

&#x20; "engine": "deterministic"

}

### Lógica obtained × certain × suggestion

| Cenário | obtained | certain | suggestion_to_agent |

|---|---|---|---|

| Valor extraído e confiável | true | true | null |

| Valor extraído mas duvidoso | true | false | Frase de confirmação |

| Nada extraído (saudação, evasão) | false | false | Frase re-pedindo o dado |

| Erro de formato (CPF inválido) | false | false | Frase explicando o erro |

### GET /v1/validators


{

&#x20; "validators": \[

&#x20;   {

&#x20;     "id": "validate\_full\_name",

&#x20;     "endpoint": "/v1/extract/name",

&#x20;     "mcp\_tool": "nalu\_extract\_name",

&#x20;     "description": "Extrai o nome completo do usuário",

&#x20;     "version": "1.0",

&#x20;     "languages": \["pt-BR", "es-ES", "en-US"]

&#x20;   }

&#x20; ]

}

---

## O que gerar

1. Projeto ASP.NET Core 9 com Minimal APIs e a estrutura de pastas acima

2. ExtractEndpoints.cs com POST /v1/extract/name

3. ValidatorsEndpoints.cs com GET /v1/validators

4. ExtractionPipeline.cs — orquestra o pipeline, recebe validator\_id + ExtractionRequest

5. ValidatorLoader.cs — parseia .md, ConcurrentDictionary + FileSystemWatcher

6. DeterministicLayer.cs — stop_words, reject/accept patterns, constraints

7. LlmExtractionService.cs — monta messages e chama Groq /chat/completions com response\_format: json\_object

8. PostProcessorRegistry.cs + CapitalizeProperName.cs + RemoveTitles.cs

9. SuggestionBuilder.cs — templates com {{extracted\_value}}, {{greeting\_response}}

10. CacheService.csIMemoryCache, TTL configurável

11. AuthService.cs — valida API key contra lista em appsettings (sem banco por agora)

12. RateLimitService.cs — rate limit por plano

13. GroqClient.cs + OpenRouterClient.cs — HttpClients tipados, fallback automático em 429/5xx

14. validate\_full\_name.md completo

15. Testes para ValidatorLoader, DeterministicLayer, integração do pipeline

16. appsettings.json com Groq, OpenRouter, planos, cache, API keys de teste

17. Dockerfile

18. Models (ExtractionRequest, ExtractionResponse, ValidatorInfo, ValidatorDefinition)

### appsettings.json


{

&#x20; "Groq": {

&#x20;   "ApiKey": "YOUR\_GROQ\_API\_KEY",

&#x20;   "BaseUrl": "https://api.groq.com/openai/v1",

&#x20;   "Model": "llama-3.3-70b-versatile",

&#x20;   "MaxTokens": 500,

&#x20;   "Temperature": 0.1

&#x20; },

&#x20; "OpenRouter": {

&#x20;   "ApiKey": "YOUR\_OPENROUTER\_API\_KEY",

&#x20;   "BaseUrl": "https://openrouter.ai/api/v1",

&#x20;   "Model": "mistralai/mistral-7b-instruct",

&#x20;   "MaxTokens": 500,

&#x20;   "Temperature": 0.1

&#x20; },

&#x20; "Plans": {

&#x20;   "free":  { "monthly\_limit": 2000, "daily\_limit": 100 },

&#x20;   "hobby": { "monthly\_limit": 5000, "daily\_limit": null },

&#x20;   "indie": { "monthly\_limit": 25000, "daily\_limit": null },

&#x20;   "pro":   { "monthly\_limit": 100000, "daily\_limit": null }

&#x20; },

&#x20; "Cache": { "DefaultTtlMinutes": 60 },

&#x20; "ApiKeys": \[

&#x20;   { "key": "nalu-test-key-001", "plan": "free", "owner": "test" }

&#x20; ]

}

### Notas de implementação

- ValidatorLoader: regex por seção ##. ConcurrentDictionary como cache. FileSystemWatcher invalida ao mudar .md.

- GroqClient: /chat/completions, system message = prompt + few-shot, user message = diálogo. response\_format: { type: "json\_object" }.

- Fallback: Groq 429 ou 5xx → retry no OpenRouter.

- SuggestionBuilder: detectar saudação no user\_input para {{greeting\_response}}.

- DI para tudo no Program.cs.

### NÃO incluir

- MCP server (Prompt 2)

- Validadores além do validate_full_name (Prompt 2)

- Painel admin, billing, PostgreSQL