File size: 4,416 Bytes
cf52a55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""
Métricas de avaliação do pipeline.
==================================
Coerência L3, similaridade semântica, BLEU/ROUGE (quando disponível).
"""

from __future__ import annotations
import re
from typing import Dict, List, Optional, Any

# Resultado da L4
try:
    from l4_synthesis import SynthesisResult
except Exception:
    SynthesisResult = None  # type: ignore


def coherence_l3(truth_value: float, state: str, contradiction: float) -> Dict[str, float]:
    """
    Métricas de coerência com a camada L3.
    - truth_value alto e contradição baixa = bom.
    - state "Falso" ou "Indeterminado" com truth_value baixo = esperado coerente.
    """
    # Score de coerência: valor alto é bom quando não há trivialização
    contradiction_penalty = abs(contradiction)  # contradição extrema penaliza
    coherence = max(0.0, 1.0 - contradiction_penalty) * (0.5 + 0.5 * truth_value)
    return {
        "coherence_score": round(coherence, 4),
        "truth_value": truth_value,
        "contradiction_abs": abs(contradiction),
    }


def tokenize_pt(text: str) -> List[str]:
    """Tokenização simples para BLEU/ROUGE: palavras em minúsculo."""
    return re.findall(r"[a-záàãâéêíóôõúüç]+", text.lower())


def bleu_sentence(reference: str, hypothesis: str, max_n: int = 2) -> float:
    """
    BLEU simplificado por frase (n-gram precision até max_n).
    Retorna valor em [0, 1].
    """
    ref_tok = tokenize_pt(reference)
    hyp_tok = tokenize_pt(hypothesis)
    if not hyp_tok:
        return 0.0
    if not ref_tok:
        return 0.0
    p_n = []
    for n in range(1, max_n + 1):
        ref_ngrams = [tuple(ref_tok[i : i + n]) for i in range(len(ref_tok) - n + 1)]
        hyp_ngrams = [tuple(hyp_tok[i : i + n]) for i in range(len(hyp_tok) - n + 1)]
        if not hyp_ngrams:
            continue
        matches = sum(1 for g in hyp_ngrams if g in ref_ngrams)
        p_n.append(matches / len(hyp_ngrams))
    if not p_n:
        return 0.0
    # Média geométrica das precisions
    prod = 1.0
    for p in p_n:
        prod *= p
    return prod ** (1.0 / len(p_n))


def rouge_l_sentence(reference: str, hypothesis: str) -> float:
    """
    ROUGE-L simplificado (LCS de palavras).
    Retorna F1 em [0, 1].
    """
    ref_tok = tokenize_pt(reference)
    hyp_tok = tokenize_pt(hypothesis)
    if not ref_tok or not hyp_tok:
        return 0.0
    # LCS por palavras
    m, n = len(ref_tok), len(hyp_tok)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if ref_tok[i - 1] == hyp_tok[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    lcs = dp[m][n]
    prec = lcs / n if n else 0
    rec = lcs / m if m else 0
    if prec + rec == 0:
        return 0.0
    return 2 * prec * rec / (prec + rec)


def semantic_similarity(reference: str, hypothesis: str) -> float:
    """
    Similaridade por embeddings (se sentence-transformers disponível).
    Caso contrário, retorna -1.0 para indicar indisponível.
    """
    try:
        from sentence_transformers import SentenceTransformer
        model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
        ref_emb = model.encode(reference)
        hyp_emb = model.encode(hypothesis)
        from numpy import dot
        from numpy.linalg import norm
        return float(dot(ref_emb, hyp_emb) / (norm(ref_emb) * norm(hyp_emb) + 1e-9))
    except Exception:
        return -1.0


def evaluate_response(
    synthesis_result: "SynthesisResult",
    reference_answer: Optional[str] = None,
) -> Dict[str, Any]:
    """
    Agrega métricas: coerência L3 + BLEU/ROUGE (e opcionalmente similaridade)
    quando há resposta de referência.
    """
    out: Dict[str, Any] = {}
    out["coherence"] = coherence_l3(
        synthesis_result.truth_value,
        synthesis_result.state,
        synthesis_result.contradiction,
    )
    if reference_answer:
        out["bleu"] = round(bleu_sentence(reference_answer, synthesis_result.response), 4)
        out["rouge_l"] = round(rouge_l_sentence(reference_answer, synthesis_result.response), 4)
        sim = semantic_similarity(reference_answer, synthesis_result.response)
        if sim >= 0:
            out["semantic_similarity"] = round(sim, 4)
    return out