mirror of
https://github.com/hoshikawa2/nemo_guardrails_configuration.git
synced 2026-07-09 17:04:20 +00:00
107 lines
5.6 KiB
Python
107 lines
5.6 KiB
Python
import os, json
|
|
from openai import OpenAI
|
|
from company_nemo_guardrails.prompts.revprec import build_revprec_prompt
|
|
from company_nemo_guardrails.prompts.csi import build_csi_prompt
|
|
from company_nemo_guardrails.prompts.vctn import build_vctn_prompt
|
|
from company_nemo_guardrails.prompts.tox import build_tox_prompt
|
|
from company_nemo_guardrails.prompts.oos import build_oos_prompt
|
|
from company_nemo_guardrails.prompts.gnd import build_gnd_prompt
|
|
from company_nemo_guardrails.prompts.aluc import build_aluc_prompt
|
|
from company_nemo_guardrails.prompts.rqlt import build_rqlt_prompt
|
|
from company_nemo_guardrails.prompts.supervisor import build_supervisor_prompt
|
|
|
|
class LLMClient:
|
|
def __init__(self):
|
|
self.use_mock=os.getenv('USE_MOCK_LLM','true').lower()=='true'
|
|
self.model=os.getenv('OPENAI_MODEL','gpt-5')
|
|
self.client=None if self.use_mock else OpenAI(base_url=os.getenv('OPENAI_BASE_URL','http://localhost:8051/v1'), api_key=os.getenv('OPENAI_API_KEY','dummy'))
|
|
|
|
def classify(self, task, payload):
|
|
|
|
if self.use_mock:
|
|
return self._mock_classify(task, payload)
|
|
|
|
# ========================
|
|
# ROUTING DE PROMPTS
|
|
# ========================
|
|
if task == "REVPREC":
|
|
prompt = build_revprec_prompt(payload["text"], payload.get("context", {}))
|
|
|
|
elif task == "CSI":
|
|
prompt = build_csi_prompt(payload["text"])
|
|
|
|
elif task == "VCTN":
|
|
prompt = build_vctn_prompt(payload["text"])
|
|
|
|
elif task == "TOX":
|
|
prompt = build_tox_prompt(payload["text"])
|
|
|
|
elif task == "OOS":
|
|
prompt = build_oos_prompt(payload["text"])
|
|
|
|
elif task == "GND":
|
|
prompt = build_gnd_prompt(payload["resposta"], payload.get("context", {}))
|
|
|
|
# ========================
|
|
# 🔥 NOVOS (faltavam)
|
|
# ========================
|
|
elif task == "ALUC":
|
|
prompt = build_aluc_prompt(payload["resposta"], payload["dados_reais"])
|
|
|
|
elif task == "RQLT":
|
|
prompt = build_rqlt_prompt(payload["pergunta"], payload["resposta"])
|
|
|
|
elif task == "SUPERVISOR_VAS":
|
|
prompt = build_supervisor_prompt(payload)
|
|
|
|
else:
|
|
raise ValueError(f"Task não suportada: {task}")
|
|
|
|
# ========================
|
|
# CALL LLM
|
|
# ========================
|
|
response = self.client.chat.completions.create(
|
|
model=self.model,
|
|
messages=[{"role": "user", "content": prompt}],
|
|
temperature=0
|
|
)
|
|
|
|
import json
|
|
text = response.choices[0].message.content
|
|
|
|
try:
|
|
return json.loads(text)
|
|
except:
|
|
return {
|
|
"allowed": False,
|
|
"label": "ERROR",
|
|
"reason": text
|
|
}
|
|
|
|
def _mock(self, task, payload):
|
|
text=(payload.get('text') or payload.get('resposta') or payload.get('answer') or '').lower()
|
|
if task=='TOX':
|
|
bad=any(w in text for w in ['idiota','burro','lixo','inútil','ofensivo']); return {'allowed':not bad,'label':'TOXICO' if bad else 'NORMAL','reason':'mock TOX','score':0 if bad else 10}
|
|
if task=='OOS':
|
|
bad=any(w in text for w in ['política','religião','presidente','concorrente','vivo','claro']); return {'allowed':not bad,'label':'OUT_OF_SCOPE' if bad else 'IN_SCOPE','reason':'mock OOS','score':0 if bad else 10}
|
|
if task=='REVPREC':
|
|
validated=payload.get('context',{}).get('ajuste_validado',False); premature=any(w in text for w in ['já fiz','já realizei','foi realizado','ajuste aplicado','cancelamento realizado'])
|
|
return {'allowed':not(premature and not validated),'label':'PREMATURA' if premature and not validated else 'OK','reason':'mock REVPREC','score':0 if premature and not validated else 10}
|
|
if task=='GND':
|
|
chunks=' '.join(payload.get('context',{}).get('chunks_rag',[])).lower(); overlap=len(set(text.split()) & set(chunks.split())); ok=overlap>=3
|
|
return {'allowed':ok,'label':'GROUNDED' if ok else 'UNGROUNDED','reason':f'mock GND overlap={overlap}','score':min(10,overlap)}
|
|
if task=='CSI':
|
|
if any(w in text for w in ['insatisfeito','raiva','péssimo','cancelar']): return {'allowed':True,'label':'Negativo','reason':'mock CSI','score':3}
|
|
if any(w in text for w in ['obrigado','ótimo','resolvido','satisfeito']): return {'allowed':True,'label':'Positivo','reason':'mock CSI','score':9}
|
|
return {'allowed':True,'label':'Neutro','reason':'mock CSI','score':6}
|
|
if task=='ALUC':
|
|
overlap=len(set(payload.get('resposta','').lower().split()) & set(payload.get('dados_reais','').lower().split())); hallucinated=overlap<2
|
|
return {'allowed':not hallucinated,'label':'ALUCINACAO' if hallucinated else 'OK','reason':f'mock ALUC overlap={overlap}','score':0 if hallucinated else 8}
|
|
if task=='RQLT':
|
|
resposta=payload.get('resposta',''); score=8 if len(resposta)>30 else 3; return {'allowed':True,'label':'QUALIDADE','reason':'mock RQLT','score':score}
|
|
if task=='VCTN':
|
|
bad=any(w in text for w in ['se vira','problema seu','não posso fazer nada']); return {'allowed':not bad,'label':'TOM_INADEQUADO' if bad else 'TOM_OK','reason':'mock VCTN','score':0 if bad else 9}
|
|
if task=='SUPERVISOR_VAS':
|
|
ok=payload.get('cancelamento_correto',False) and payload.get('servico_cancelado')==payload.get('servico_solicitado'); return {'allowed':ok,'label':'CONFORME' if ok else 'PROBLEMA','reason':'mock supervisor','score':10 if ok else 0}
|
|
return {'allowed':True,'label':'OK','reason':'mock default','score':5}
|