MeuAgente / src /tools /knowledge_base.py
gaiotto's picture
deploy: agente aula07 no HF Space
df1da34
Raw
History Blame Contribute Delete
4 kB
"""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,
},
},
}