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

839 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\# 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:
```json
{
&#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)
```csharp
// 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`
```markdown
\# 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
```
```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)
```json
{
&#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
```json
{
&#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.cs``IMemoryCache`, 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
```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