first commit

This commit is contained in:
2026-06-13 08:23:21 -03:00
commit 89c23fb0ed
439 changed files with 32801 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
import time
import logging
import json
from src.agent.state.agent_state import AgentState, update_state_metadata
from src.agent.state.steps import GraphStep
from src.agent.state.step_helpers import set_current_step
from src.components.clients.speech_analytics_client import SpeechAnalyticsClient
from src.components.clients.exceptions.speech_exceptions import SpeechClientError
from src.utils.observer import trace_node, score_current_trace
from src.utils.ics_collector import build_ic_payload, build_noc_api_metadata, build_noc_llm_metadata
from src.compat.framework_observer import event
from src.providers.llm_provider import classification_llm, chat_llm_with_usage, LLM_ENDPOINT
from src.core.config import settings
from src.core.prompt_manager import get_prompt
from src.agent.local_prompts.speech_history_analysis import speech_history_similarity_pt
logger = logging.getLogger(__name__)
_NO_SIMILAR_MESSAGE = "Não foram encontradas reclamações anteriores similares à reclamação atual"
_HISTORY_UNAVAILABLE_MESSAGE = "Indisponível SPEECH para Histórico de Reclamação - realizar consulta manualmente"
_HISTORY_DETAIL_KEYS = (
"reclamacao_resumo",
"causa_raiz",
"descortesia_cliente",
"motivo_reclamacao",
"submotivo_reclamacao",
"sentimento_cliente",
"solucao_proposta_cliente",
)
_NULL_SPEECH_DATA = {
"reclamacao_resumo": None,
"causa_raiz": None,
"descortesia_cliente": None,
"motivo_reclamacao": None,
"submotivo_reclamacao": None,
"sentimento_cliente": None,
"solucao_proposta_cliente": None,
"analise_agente": None,
}
def _map_prediction_response(response: dict) -> dict:
"""
Map the raw Prediction API response to the internal speech_analytics schema.
"""
variables = response.get("variables", {})
return {
"reclamacao_resumo": response.get("resume") or variables.get("resume"),
"causa_raiz": variables.get("causa_raiz"),
"descortesia_cliente": variables.get("descortesia_cliente"),
"motivo_reclamacao": variables.get("motivo"),
"submotivo_reclamacao": variables.get("submotivo"),
"sentimento_cliente": variables.get("sentimento_cliente"),
"solucao_proposta_cliente": variables.get("solucao_proposta_cliente"),
}
def _build_related_history(
raw_history: list,
current_complaint_id: str | None,
llm_scores: list,
threshold: int,
) -> list:
"""
Merge LLM similarity scores with raw history items and filter by threshold.
Each returned item has: protocolo, data_reclamacao, similaridade_pct,
reasoning, plus the 7 detail keys (same as reclamacao_atual).
"""
if not raw_history or not llm_scores:
return []
history_by_protocol = {str(item.get("protocolo")): item for item in raw_history}
related: list[dict] = []
for score in llm_scores:
protocolo = str(score.get("protocolo")) if score.get("protocolo") is not None else None
if not protocolo or protocolo == str(current_complaint_id or ""):
continue
try:
similaridade_pct = int(score.get("similaridade_pct", 0))
except (TypeError, ValueError):
continue
if similaridade_pct < threshold:
continue
raw_item = history_by_protocol.get(protocolo)
if not raw_item:
continue
merged = {
"protocolo": raw_item.get("protocolo"),
"data_reclamacao": raw_item.get("data_reclamacao"),
"similaridade_pct": similaridade_pct,
"reasoning": (score.get("reasoning") or "").strip(),
}
for key in _HISTORY_DETAIL_KEYS:
merged[key] = raw_item.get(key)
related.append(merged)
related.sort(key=lambda x: x["similaridade_pct"], reverse=True)
return related
def _build_analise_agente(related_items: list) -> str:
"""
Build the analise_agente string from related items: one line per item with
'Protocolo X (Y%): <reasoning>'. Empty list -> negative spec message.
"""
if not related_items:
return _NO_SIMILAR_MESSAGE
lines = []
for item in related_items:
reasoning = (item.get("reasoning") or "").strip()
if not reasoning:
reasoning = ""
lines.append(f"Protocolo {item.get('protocolo')} ({item.get('similaridade_pct')}%): {reasoning}")
return "\n".join(lines)
async def _score_history_with_llm(
current_id: str | None,
current_complaint_description: str,
history_list: list,
session_id: str = "",
state: dict | None = None,
) -> list:
"""
Calls the LLM to score similarity between the current complaint description
and each historical item. Returns the LLM-parsed JSON list. Items below
SPEECH_SIMILARITY_THRESHOLD will be dropped later by _build_related_history.
"""
if not current_complaint_description or not history_list:
return []
clean_history = [
item for item in history_list
if str(item.get("protocolo")) != str(current_id or "")
]
if not clean_history:
logger.info("No historical items left after deduplication.")
return []
llm = classification_llm
prompt_template = get_prompt("speech_history_similarity_pt", speech_history_similarity_pt)
history_payload = [
{
"protocolo": item.get("protocolo"),
"motivo_reclamacao": item.get("motivo_reclamacao"),
"submotivo_reclamacao": item.get("submotivo_reclamacao"),
"causa_raiz": item.get("causa_raiz"),
"reclamacao_resumo": item.get("reclamacao_resumo"),
}
for item in clean_history
]
message = prompt_template.format(
current_complaint_description=current_complaint_description,
history_json=json.dumps(history_payload, ensure_ascii=False),
)
logger.info(f"Calling LLM for history similarity filtering. Items: {len(clean_history)}")
# LLM generation telemetry is recorded by agent_framework.llm.providers.