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

