\# 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 { "agent\_input": "Bom dia! Qual seu nome?", "user\_input": "Bom dia!", "agent\_context": "Negociador de parcelamento.", "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) ↓ Auth (API key via header Authorization: Bearer {key}) ↓ Rate limit por plano ↓ Resolve qual validador (.md) usar baseado no endpoint ↓ Cache lookup (hash de: validator\_id + agent\_input + user\_input + language) ↓ cache miss Carregar validador (.md do disco, com cache em memória) ↓ Camada 1 — Regras determinísticas (regex, stop words, constraints) ↓ não resolveu Camada 2 — LLM com prompt do validador (Groq → fallback OpenRouter) ↓ Pós-processamento (normalização, dígito verificador, etc.) ↓ Enriquecimento externo (ViaCEP etc., se o validador declarar) ↓ Montar sugestão de fala (template do validador) ↓ Salvar log + retornar resposta ``` \--- \## Estrutura de pastas ``` src/ Nalu.Api/ Program.cs appsettings.json appsettings.Development.json Endpoints/ ExtractEndpoints.cs # Registra todos os POST /v1/extract/{tipo} ValidatorsEndpoints.cs # GET /v1/validators Models/ ExtractionRequest.cs # Body compartilhado por todos os endpoints ExtractionResponse.cs # Response compartilhada ValidatorInfo.cs # Modelo para GET /v1/validators ValidatorDefinition.cs # Validador parseado do .md Services/ ExtractionPipeline.cs # Orquestra o pipeline (recebe validator\_id) ValidatorLoader.cs # Parseia .md, cache em memória, FileSystemWatcher DeterministicLayer.cs # Camada 1: stop words, regex, constraints LlmExtractionService.cs # Camada 2: Groq/OpenRouter PostProcessorRegistry.cs # Registry de IPostProcessor por nome EnrichmentService.cs # Orquestra enrichers SuggestionBuilder.cs # Monta suggestion\_to\_agent CacheService.cs # IMemoryCache com hash AuthService.cs # Valida API key, retorna plano RateLimitService.cs # Rate limit por plano PostProcessors/ IPostProcessor.cs CapitalizeProperName.cs RemoveTitles.cs Enrichers/ IEnricher.cs Infrastructure/ GroqClient.cs OpenRouterClient.cs NaluDbContext.cs Validators/ # ← CADA .md É UM VALIDADOR validate\_full\_name.md tests/ Nalu.Tests/ ValidatorLoaderTests.cs DeterministicLayerTests.cs PipelineIntegrationTests.cs Dockerfile ``` \### Registro dos endpoints (Minimal APIs) ```csharp // ExtractEndpoints.cs public static class ExtractEndpoints { public static void MapExtractEndpoints(this WebApplication app) { var group = app.MapGroup("/v1/extract") .RequireAuthorization(); group.MapPost("/name", async (ExtractionRequest req, ExtractionPipeline pipeline) => await pipeline.ExecuteAsync("validate\_full\_name", req)); // Prompt 2 adiciona os demais aqui — mesma estrutura, 2 linhas cada } } ``` \--- \## 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: { "extracted\_value": "nome extraído ou null", "certain": true/false, "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 { "agent\_input": "Bom dia! Qual seu nome completo?", "user\_input": "Bom dia!", "agent\_context": "Negociador de parcelamento. Precisa de nome e CPF.", "language": "pt-BR" } ``` \### Response (mesma estrutura para todos) ```json { "obtained": false, "extracted\_value": null, "confidence": 0.15, "certain": false, "reasoning": "Usuário respondeu com saudação, não informou o nome", "suggestion\_to\_agent": "Bom dia! Mas preciso do seu nome completo para continuar. Pode me dizer?", "validator\_used": "validate\_full\_name", "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 { "validators": \[ { "id": "validate\_full\_name", "endpoint": "/v1/extract/name", "mcp\_tool": "nalu\_extract\_name", "description": "Extrai o nome completo do usuário", "version": "1.0", "languages": \["pt-BR", "es-ES", "en-US"] } ] } ``` \--- \## 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 { "Groq": { "ApiKey": "YOUR\_GROQ\_API\_KEY", "BaseUrl": "https://api.groq.com/openai/v1", "Model": "llama-3.3-70b-versatile", "MaxTokens": 500, "Temperature": 0.1 }, "OpenRouter": { "ApiKey": "YOUR\_OPENROUTER\_API\_KEY", "BaseUrl": "https://openrouter.ai/api/v1", "Model": "mistralai/mistral-7b-instruct", "MaxTokens": 500, "Temperature": 0.1 }, "Plans": { "free": { "monthly\_limit": 2000, "daily\_limit": 100 }, "hobby": { "monthly\_limit": 5000, "daily\_limit": null }, "indie": { "monthly\_limit": 25000, "daily\_limit": null }, "pro": { "monthly\_limit": 100000, "daily\_limit": null } }, "Cache": { "DefaultTtlMinutes": 60 }, "ApiKeys": \[ { "key": "nalu-test-key-001", "plan": "free", "owner": "test" } ] } ``` \### 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