LLM Input Safety¶
Limites de segurança para controlar o tamanho e custo dos inputs enviados ao LLM, aplicados em múltiplas camadas.
Visão Geral¶
O sistema implementa limites de segurança em três camadas:
- AppConfig -- configuração centralizada dos limites globais
- ChatHistoryTrimmer -- truncamento do histórico antes de enviar ao LLM
- PlaygroundChatValidator -- validação de input do usuário via FluentValidation
Esses limites protegem contra: - Tokens excessivos (custo elevado) - Context window overflow - Abuse/spam via mensagens longas - Timeouts em transcrição de áudio
Arquivos Principais¶
| Arquivo | Descrição |
|---|---|
src/Ciba.Infrastructure/Configuration/AppConfig.cs |
Limites configuráveis (Options pattern) |
src/Ciba.Infrastructure/Services/Llm/ChatHistoryTrimmer.cs |
Truncamento de histórico para LLM |
src/Ciba.Shared/Features/Agents/Playground/PlaygroundChatValidator.cs |
Validação de input do Playground |
Limites Configuráveis (AppConfig)¶
Seção "App" no appsettings.json, registrada via Options pattern.
Histórico de Mensagens¶
| Propriedade | Default | Descrição |
|---|---|---|
MaxHistoryMessages |
20 | Número máximo de mensagens no contexto do LLM |
MaxCharsPerMessage |
5.000 | Caracteres máximos por mensagem individual |
MaxHistoryTotalChars |
30.000 | Caracteres máximos do histórico inteiro |
DefaultTemperature |
0.6 | Temperatura padrão para geração de respostas |
Extração de Mídia¶
| Propriedade | Default | Descrição |
|---|---|---|
MaxMediaExtractionsPerBatch |
5 | Itens de mídia com extração AI (OCR/transcrição) por batch de agregação |
MaxAudioBytesForTranscription |
2 MB (2.097.152 bytes) | Tamanho máximo de áudio para transcrição Whisper |
Nota: Mídias além do limite de extração são salvas no storage, mas sem OCR/transcrição. Áudios maiores que 2 MB são salvos mas não transcritos. A prioridade é dada aos itens mais recentes.
Storage¶
| Propriedade | Default | Descrição |
|---|---|---|
MaxAttachmentFileSizeBytes |
16 MB | Tamanho máximo por arquivo de anexo |
MaxAttachmentStorageBytesPerAgent |
500 MB | Quota de storage por agente |
ChatHistoryTrimmer¶
Classe estática que trunca o histórico de mensagens em duas etapas antes do envio ao LLM.
Algoritmo¶
Entrada: lista de ChatMessage + maxCharsPerMessage + maxTotalChars
│
├── Step 1: Truncar cada mensagem individualmente
│ └── message.Content[..maxCharsPerMessage] se exceder limite
│
└── Step 2: Aplicar budget total
└── Percorre do mais recente ao mais antigo
└── Mantém mensagens enquanto cabem no budget
└── Remove as mais antigas primeiro (break ao estourar)
Código¶
public static List<ChatMessage> Trim(
List<ChatMessage> messages,
int maxCharsPerMessage,
int maxTotalChars)
{
// Step 1: Truncar cada mensagem individualmente
var trimmed = messages
.Select(m => TrimMessage(m, maxCharsPerMessage))
.ToList();
// Step 2: Aplicar budget total (remove mais antigas primeiro)
return ApplyTotalBudget(trimmed, maxTotalChars);
}
Comportamento¶
| Cenário | Resultado |
|---|---|
| Mensagem com 8.000 chars | Truncada para 5.000 chars |
| Histórico com 50.000 chars total | Mensagens mais antigas removidas até caber em 30.000 |
| 25 mensagens no histórico | Limitado a 20 mensagens (via MaxHistoryMessages no caller) |
| Mensagem vazia/null | Preservada sem modificação |
Importante: O ChatHistoryTrimmer não aplica o limite de MaxHistoryMessages -- este é aplicado pelo caller (ex: AiResponseService) ao montar a lista de mensagens. O trimmer recebe a lista já limitada em quantidade e aplica os limites de caracteres.
Playground Validator¶
Validação de input do usuário no Playground via FluentValidation, aplicada antes do handler.
public class PlaygroundChatValidator : AbstractValidator<PlaygroundChatRequest>
{
private const int MaxMessages = 20;
private const int MaxContentChars = 5_000;
public PlaygroundChatValidator()
{
RuleFor(x => x.Messages)
.NotEmpty().WithMessage("Mensagens são obrigatórias")
.Must(m => m.Count <= MaxMessages)
.WithMessage($"Máximo de {MaxMessages} mensagens permitidas");
RuleForEach(x => x.Messages).ChildRules(msg =>
{
msg.RuleFor(m => m.Content)
.MaximumLength(MaxContentChars)
.WithMessage($"Conteúdo da mensagem deve ter no máximo {MaxContentChars} caracteres");
});
}
}
| Regra | Limite | Mensagem de Erro |
|---|---|---|
| Mensagens obrigatórias | > 0 | "Mensagens são obrigatórias" |
| Máximo de mensagens | 20 | "Máximo de 20 mensagens permitidas" |
| Tamanho por mensagem | 5.000 chars | "Conteúdo da mensagem deve ter no máximo 5000 caracteres" |
Camadas de Proteção¶
Requisição do Usuário
│
├── [1] PlaygroundChatValidator (FluentValidation)
│ └── Rejeita antes de executar qualquer lógica
│
├── [2] MaxHistoryMessages (aplicado no handler)
│ └── Limita quantidade de mensagens carregadas do banco
│
├── [3] ChatHistoryTrimmer (preparação para LLM)
│ ├── Trunca cada mensagem a 5k chars
│ └── Remove mensagens antigas se total > 30k chars
│
├── [4] MaxMediaExtractionsPerBatch (pipeline de agregação)
│ └── Limita extrações AI por batch (prioriza mais recentes)
│
└── [5] MaxAudioBytesForTranscription (pipeline de mídia)
└── Áudios > 2MB salvos mas não transcritos
Configuração via appsettings.json¶
{
"App": {
"MaxHistoryMessages": 20,
"MaxCharsPerMessage": 5000,
"MaxHistoryTotalChars": 30000,
"DefaultTemperature": 0.6,
"MaxMediaExtractionsPerBatch": 5,
"MaxAudioBytesForTranscription": 2097152,
"MaxAttachmentFileSizeBytes": 16777216,
"MaxAttachmentStorageBytesPerAgent": 524288000
}
}
Regras¶
- Limites são aplicados apenas na camada LLM -- mensagens são salvas integralmente no banco de dados
- Budget total tem prioridade sobre mensagens individuais -- mesmo truncadas, mensagens antigas podem ser removidas
- Mensagens mais recentes são preservadas -- o trimmer remove do mais antigo para o mais recente
- Playground valida antes do handler -- rejeição rápida via FluentValidation (HTTP 400)
- Valores default são conservadores -- 30k chars total equivale a aproximadamente 7.500 tokens
- Mídias além do limite de extração são preservadas -- salvas no storage, apenas sem OCR/transcrição