File size: 3,997 Bytes
df1da34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Tool: consulta à base de conhecimento do produto ativo.

Implementação simples por palavras-chave (TF-IDF leve em memória).
Em produção, isso seria substituído por RAG real (Aula 05).
Aqui o objetivo é ensinar o PADRÃO de tool de busca, não a engine de busca.
"""
import re
from typing import Iterable

from src.config import get_product_config


def _stem(token: str) -> str:
    """Stemming português curto para casar singular/plural.

    Casos cobertos:
      frações  → fraç     fração   → fraç
      equações → equaç    equação  → equaç
      ideias   → idei     ideia    → idei
      cliente  → client   clientes → client
    """
    if len(token) > 4 and (token.endswith("ções") or token.endswith("ões")):
        # frações → fraç ; equações → equaç ; reflexões → reflex
        return token[:-4] if token.endswith("ções") else token[:-3]
    if len(token) > 4 and (token.endswith("ção") or token.endswith("ão")):
        return token[:-3] if token.endswith("ção") else token[:-2]
    if len(token) > 4 and token.endswith("es"):
        return token[:-2]
    if len(token) > 3 and token.endswith("s"):
        return token[:-1]
    if len(token) > 3 and token.endswith("a"):
        # ideia → idei ; idéia → idéi
        return token[:-1]
    return token


def _tokenize(text: str) -> list[str]:
    """Tokeniza: minúsculo, remove pontuação, splita, stemiza por aproximação."""
    text = text.lower()
    text = re.sub(r"[^\w\sáéíóúâêôãõàç]", " ", text)
    return [_stem(t) for t in text.split() if len(t) >= 3]


def _score(query_tokens: Iterable[str], doc_tokens: Iterable[str]) -> int:
    """Score = quantidade de tokens stemizados da query presentes no doc."""
    doc_set = set(doc_tokens)
    return sum(1 for t in query_tokens if t in doc_set)


def run_knowledge_base_query(query: str, top_k: int = 2) -> str:
    """Busca os top_k documentos mais relevantes da KB do produto ativo."""
    config = get_product_config()
    kb = config.get("knowledge_base", [])
    if not kb:
        return "Base de conhecimento vazia para este produto."

    query_tokens = _tokenize(query)

    scored: list[tuple[int, dict]] = []
    for doc in kb:
        all_text = f"{doc['title']} {doc['content']}"
        doc_tokens = _tokenize(all_text)
        score = _score(query_tokens, doc_tokens)
        scored.append((score, doc))

    # Ordena por score desc, mantém só os com score > 0
    scored = sorted(scored, key=lambda x: x[0], reverse=True)
    relevant = [doc for score, doc in scored if score > 0][:top_k]

    if not relevant:
        return f"Nenhum documento relevante encontrado para a query: '{query}'"

    out_lines = [f"Encontrados {len(relevant)} documento(s):"]
    for i, doc in enumerate(relevant, 1):
        out_lines.append(f"\n--- Documento {i}: {doc['title']} ---")
        out_lines.append(doc["content"].strip())
    return "\n".join(out_lines)


knowledge_base_query_tool: dict = {
    "type": "function",
    "function": {
        "name": "knowledge_base_query",
        "description": (
            "Busca conteúdo na base de conhecimento curada do produto. "
            "Use SEMPRE que precisar de fato pedagógico (EducIAção) ou "
            "de orientação de fase de Design Thinking (DesignMind). "
            "NÃO invente conteúdo da KB — sempre busque primeiro."
        ),
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Palavras-chave da busca. Exemplo: 'fração equivalente'",
                },
                "top_k": {
                    "type": "integer",
                    "description": "Número de documentos a retornar (padrão: 2)",
                    "default": 2,
                },
            },
            "required": ["query"],
            "additionalProperties": False,
        },
    },
}