Teste técnico para a First Answer
Dado um texto (resposta de IA) e uma marca_monitorada, devolve
own_brand_mentioned (bool) e other_brands (lista). São duas pipelines com a mesma
interface (spaCy (NER) e LLM) e um eval que as compara por Cohen's kappa.
Requer uv. Comandos via make (rode make help para ver todos):
make install # cria o venv + instala deps e o modelo pt do spaCy
make cases # os 3 casos oficiais nas 2 pipelines (imprime os outputs)
make eval # compara spaCy + 6 LLMs por kappa -> data/results.md (precisa de chave)
make check # lint + formatação (ruff)A pipeline LLM e o eval precisam de uma chave: cp .env.example .env e preencha
OPENROUTER_API_KEY. make cases na pipeline spaCy roda offline.
Em vez de uma solução só, duas pipelines com a mesma interface e um eval que decide
com números. Modelo comum: extrair o conjunto de marcas → own = marca ∈ conjunto,
others = conjunto − marca; a parte difícil ("é a mesma marca?") fica em normalize.py.
- spaCy (NER): baseline offline e determinístico (
pt_core_news_lg, entidadesORG/MISC, sem gazetteer). Grátis e reprodutível, mas a NER genérica erra. - LLM:
ChatOpenAIvia OpenRouter +with_structured_output(BrandResult)(Pydantic, sem parse de JSON). Preciso e robusto a vocabulário aberto, mas custa e não é determinístico.
O eval gera 50 casos sintéticos com gabarito e roda spaCy + 6 modelos (OpenAI, Google,
Anthropic, grandes e pequenos). Métrica: Cohen's kappa (θ̂), com matching fuzzy de
marcas. Como o dataset foi gerado pelo gpt-5.4, incluir modelos que não o geraram
(Gemini, Claude) é o que torna a comparação honesta.
3 casos oficiais (pipeline LLM, gpt-5.4):
// Nubank -> {"own_brand_mentioned": true, "other_brands": ["Banco Inter","C6 Bank","BTG Pactual"]}
// Nike -> {"own_brand_mentioned": false, "other_brands": ["Olympikus","Asics","Mizuno","New Balance"]}
// First Answer -> {"own_brand_mentioned": true, "other_brands": ["ChatGPT","Gemini","Claude","Perplexity","Copilot","Profound","Brandlight","Peec AI","AthenaHQ"]}Eval (50 casos) com Cohen's kappa (relatório em data/results.md):
| pipeline / modelo | kappa own | kappa brands |
|---|---|---|
| spaCy | 0.841 | 0.809 |
| openai/gpt-5.4 (gerador) | 1.000 | 0.932 |
| openai/gpt-5.4-nano | 1.000 | 0.951 |
| google/gemini-3.5-flash | 1.000 | 0.947 |
| google/gemini-3.1-flash-lite | 1.000 | 0.959 |
| anthropic/claude-sonnet-4.6 | 1.000 | 0.955 |
| anthropic/claude-haiku-4.5 | 1.000 | 0.963 |
Os LLMs zeram a tarefa binária; quem discrimina é o kappa de brands, onde o gerador ficou em último entre os LLMs (o vazamento não o favoreceu). spaCy fica atrás, mas é o único baseline sem custo.
CI em .github/workflows/ci.yml: lint (ruff) · test (pytest)
· docker (build).
