Prompt Generator
Índice
O que é
O processor-prompt-generator é o segundo serviço da pipeline de processamento do Kairos. Ele recebe os dados brutos de uma vaga ou currículo, seleciona o template de prompt adequado para a fonte de dados e injeta o conteúdo real no template, produzindo um histórico de chat estruturado pronto para ser consumido pelo Skill Extractor.
Evento recebido → Identificar source → Selecionar mapper → Injetar dados → Publicar prompt
A separação entre "geração de prompt" e "extração de skills" é intencional: permite que os templates evoluam independentemente do cliente Ollama, e que cada fonte de dados tenha seu próprio formato de prompt sem alterar código compartilhado.
Como Funciona
Fluxo de Funcionamento
Eventos Consumidos e Produzidos
| Tipo | Evento | Conteúdo |
|---|---|---|
| Consumido | OpportunityScraped | Dados brutos da vaga (título, empresa, requisitos, atividades) |
| Consumido | ResumeUploaded | Texto bruto do currículo submetido pelo usuário via API |
| Consumido | ResumeRawLoaded | Texto bruto do currículo carregado de dataset (ex: Kaggle) |
| Produzido | OpportunityPromptGenerated | Lista de mensagens messages: list[dict] |
| Produzido | ResumePromptGenerated | Lista de mensagens messages: list[dict] |
O campo messages substitui o enriched_prompt: str que existia na versão anterior — é uma lista de mensagens no formato da API /api/chat do Ollama, com roles system, user e assistant.
Decisões Abordadas
Strategy Pattern com Mapper Registry (ADR-009): diferentes fontes de dados (Mackenzie, Kaggle, API) têm estruturas distintas. Em vez de um if/elif crescente por source, cada fonte tem sua própria classe mapper registrada centralmente. Adicionar suporte a uma nova fonte não exige modificar código existente (Open/Closed Principle) — basta criar um novo arquivo com a classe e registrá-la.
Estrutura de 6 mensagens com few-shot no histórico de chat (ADR-042): fornecer respostas prontas no role assistant ensina o modelo a imitar exatamente a estrutura do JSON de saída e a categorização hard/soft/knowledge. Isso reduz falhas de parsing e inconsistências de classificação em SLMs sem exigir fine-tuning. A alternativa de colocar os exemplos no system prompt foi rejeitada porque modelos menores tendem a ignorar exemplos quando misturados às instruções.
Técnicas do system.md (ADR-038): o conteúdo do prompt de sistema usa role setting, XML tags, negative constraints e template JSON nativo. Essas técnicas foram validadas como mais eficazes do que prompts em linguagem natural livre para manter consistência em SLMs de 1B de parâmetros.
Strategy Pattern + Mapper Registry
Diferentes fontes de dados (Mackenzie, Kaggle, API) têm estruturas distintas. Em vez de um if/elif crescente por source, o serviço implementa o Strategy Pattern com um Registry central (ADR-009):
Os mappers são registrados centralmente no __init__.py do pacote. Para adicionar suporte a uma nova fonte, basta criar um novo arquivo com a classe e adicioná-la ao registro — sem modificar código existente (Open/Closed Principle).
class MackenziePromptMapper(BasePromptMapper):
FIELD_MAPPING = {
"position": "titulo",
"company": "empresa",
"requirements": "requisitos",
"activities": "atividades",
}
@property
def source(self) -> str:
return "mackenzie_portal_carreiras"
def map_to_messages(self, data: dict) -> list[dict]:
fields = {
key: data.get(src) or self.DEFAULT_VALUE
for key, src in self.FIELD_MAPPING.items()
}
user_content = OPPORTUNITY_USER_MACKENZIE_TEMPLATE.format(**fields)
return [
{"role": "system", "content": OPPORTUNITY_SYSTEM_PROMPT},
*OPPORTUNITY_FEW_SHOT_EXAMPLES,
{"role": "user", "content": user_content},
]
O registro de todos os mappers é feito em processor_prompt_generator/__init__.py:
PromptMapperRegistry.register(MackenziePromptMapper())
PromptMapperRegistry.register(KaggleOpportunityPromptMapper())
PromptMapperRegistry.register(ResumeKagglePromptMapper())
PromptMapperRegistry.register(ResumeApiPromptMapper())
Estrutura Multi-Role Chat (Few-Shot)
O histórico de chat enviado ao Ollama segue uma estrutura de 6 mensagens que combina instrução, exemplos e dados reais (ADR-042):
| Posição | Role | Conteúdo | Arquivo |
|---|---|---|---|
| 1 | system | Taxonomia ESCO + regras de extração + restrições negativas | system.md |
| 2 | user | Exemplo fictício de entrada (vaga/currículo de treino) | few_shot_1_user.md |
| 3 | assistant | Resposta JSON esperada para o exemplo 1 | few_shot_1_assistant.json |
| 4 | user | Segundo exemplo fictício de entrada | few_shot_2_user.md |
| 5 | assistant | Resposta JSON esperada para o exemplo 2 | few_shot_2_assistant.json |
| 6 | user | Dados reais da vaga ou currículo atual | <source>_user.md (template) |
O aprendizado por analogia (fornecer respostas prontas no role assistant) ensina o modelo a imitar exatamente a estrutura do JSON de saída e a categorização hard/soft/knowledge. Reduz drasticamente falhas de parsing e inconsistências de classificação em SLMs.
Organização de Arquivos de Recursos
Os templates e exemplos são armazenados fora do código Python para facilitar governança e atualização sem deploy:
services/processor-prompt-generator/
└── resources/
├── opportunity/
│ ├── system.md
│ ├── few_shot_1_user.md
│ ├── few_shot_1_assistant.json
│ ├── few_shot_2_user.md
│ ├── few_shot_2_assistant.json
│ ├── mackenzie_user.md
│ └── kaggle_user.md
└── resume/
├── system.md
├── few_shot_1_user.md
├── few_shot_1_assistant.json
├── few_shot_2_user.md
├── few_shot_2_assistant.json
├── kaggle_user.md
└── api_user.md
Os arquivos são lidos em tempo de importação do serviço e mantidos em cache. Cada mapper usa .format() nativo do Python para injetar os dados reais no template <source>_user.md.
Técnicas de Prompt Engineering (ADR-038)
O conteúdo do system.md aplica as seguintes estratégias para maximizar a consistência de SLMs:
| Técnica | Implementação | Benefício |
|---|---|---|
| Role/Persona Setting | "Você é um Consultor de Carreira Especialista em Análise de Competências" | Foca os pesos atencionais do modelo no escopo correto |
| XML Tags | <instruction>, <rules>, <data>, <output_format> | Separa estruturalmente instruções de conteúdo |
| Negative Constraints | "NÃO alucine", "NÃO adicione texto fora do JSON" | Reduz tendências indesejadas sem exigir fine-tuning |
| JSON nativo | Template JSON + enforçamento via structured output | Garante schema consistente para consumo downstream |
Cada nova fonte de dados requer a criação de um novo mapper, um novo arquivo <source>_user.md e o registro do mapper em __init__.py. O system.md e os exemplos few-shot são compartilhados entre sources do mesmo domínio (opportunity ou resume).
Janela de Contexto
O histórico com 2 exemplos few-shot + dados reais pode ser extenso. O processor-skill-extractor usa num_ctx=8192 tokens para garantir que mesmo currículos longos não sejam truncados.
system (regras) ~500 tokens
few_shot_1 (user) ~200 tokens
few_shot_1 (assistant) ~100 tokens
few_shot_2 (user) ~200 tokens
few_shot_2 (assistant) ~100 tokens
user (dados reais) até ~6000 tokens
─────────────────────────────────────────
Total até ~7100 tokens (< 8192)
ADR's Relacionadas
| ADR | Data | Decisão |
|---|---|---|
| ADR-009 | 2026 | Strategy Pattern com Mapper Registry para extensibilidade por fonte de dados |
| ADR-038 | 2026 | Técnicas de prompt engineering para SLMs (role setting, XML tags, negative constraints) |
| ADR-042 | 2026 | Estrutura de 6 mensagens com few-shot no histórico de chat Multi-Role |