You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
138 lines
5.2 KiB
Python
138 lines
5.2 KiB
Python
from fastapi import HTTPException
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.services.llm_service import LLMService
|
|
from app.services.tool_registry import ToolRegistry
|
|
|
|
|
|
class OrquestradorService:
|
|
LOW_VALUE_RESPONSES = {
|
|
"certo.",
|
|
"certo",
|
|
"ok.",
|
|
"ok",
|
|
"entendi.",
|
|
"entendi",
|
|
"claro.",
|
|
"claro",
|
|
}
|
|
|
|
def __init__(self, db: Session):
|
|
"""Inicializa servicos de LLM e registro de tools para a sessao atual."""
|
|
self.llm = LLMService()
|
|
self.registry = ToolRegistry(db)
|
|
|
|
async def handle_message(self, message: str) -> str:
|
|
"""Processa mensagem, executa tool quando necessario e retorna resposta final."""
|
|
tools = self.registry.get_tools()
|
|
|
|
llm_result = await self.llm.generate_response(
|
|
message=self._build_router_prompt(message),
|
|
tools=tools,
|
|
)
|
|
|
|
if not llm_result["tool_call"] and self._is_operational_query(message):
|
|
llm_result = await self.llm.generate_response(
|
|
message=self._build_force_tool_prompt(message),
|
|
tools=tools,
|
|
)
|
|
|
|
if llm_result["tool_call"]:
|
|
tool_name = llm_result["tool_call"]["name"]
|
|
arguments = llm_result["tool_call"]["arguments"]
|
|
|
|
try:
|
|
tool_result = await self.registry.execute(tool_name, arguments)
|
|
except HTTPException as exc:
|
|
return self._http_exception_detail(exc)
|
|
|
|
final_response = await self.llm.generate_response(
|
|
message=self._build_result_prompt(
|
|
user_message=message,
|
|
tool_name=tool_name,
|
|
tool_result=tool_result,
|
|
),
|
|
tools=[],
|
|
)
|
|
text = (final_response.get("response") or "").strip()
|
|
if self._is_low_value_response(text):
|
|
return self._fallback_format_tool_result(tool_name, tool_result)
|
|
|
|
return text or self._fallback_format_tool_result(tool_name, tool_result)
|
|
|
|
text = (llm_result.get("response") or "").strip()
|
|
if self._is_low_value_response(text):
|
|
return "Entendi. Pode me dar mais detalhes para eu consultar corretamente?"
|
|
return text
|
|
|
|
def _is_low_value_response(self, text: str) -> bool:
|
|
return text.strip().lower() in self.LOW_VALUE_RESPONSES
|
|
|
|
def _is_operational_query(self, message: str) -> bool:
|
|
text = message.lower()
|
|
keywords = (
|
|
"estoque",
|
|
"carro",
|
|
"carros",
|
|
"suv",
|
|
"sedan",
|
|
"hatch",
|
|
"pickup",
|
|
"financi",
|
|
"cpf",
|
|
"troca",
|
|
"revis",
|
|
"placa",
|
|
"cancelar pedido",
|
|
"pedido",
|
|
)
|
|
return any(k in text for k in keywords)
|
|
|
|
def _build_router_prompt(self, user_message: str) -> str:
|
|
return (
|
|
"Voce e um assistente de concessionaria. "
|
|
"Sempre que a solicitacao depender de dados operacionais (estoque, validacao de cliente, "
|
|
"avaliacao de troca, agendamento de revisao ou cancelamento de pedido), use a tool correta. "
|
|
"Se faltar parametro obrigatorio para a tool, responda em texto pedindo apenas o que falta.\n\n"
|
|
f"Mensagem do usuario: {user_message}"
|
|
)
|
|
|
|
def _build_force_tool_prompt(self, user_message: str) -> str:
|
|
return (
|
|
"Reavalie a mensagem e priorize chamar tool se houver intencao operacional. "
|
|
"Use texto apenas quando faltar dado obrigatorio.\n\n"
|
|
f"Mensagem do usuario: {user_message}"
|
|
)
|
|
|
|
def _build_result_prompt(self, user_message: str, tool_name: str, tool_result) -> str:
|
|
return (
|
|
"Responda ao usuario de forma objetiva usando o resultado da tool abaixo. "
|
|
"Nao invente dados. Se a lista vier vazia, diga explicitamente que nao encontrou resultados.\n\n"
|
|
f"Pergunta original: {user_message}\n"
|
|
f"Tool executada: {tool_name}\n"
|
|
f"Resultado da tool: {tool_result}"
|
|
)
|
|
|
|
def _http_exception_detail(self, exc: HTTPException) -> str:
|
|
detail = exc.detail
|
|
if isinstance(detail, str):
|
|
return detail
|
|
return "Nao foi possivel concluir a operacao solicitada."
|
|
|
|
def _fallback_format_tool_result(self, tool_name: str, tool_result) -> str:
|
|
if tool_name == "consultar_estoque":
|
|
if not tool_result:
|
|
return "Nao encontrei nenhum veiculo com os criterios informados."
|
|
return f"Encontrei {len(tool_result)} veiculo(s) com os criterios informados."
|
|
|
|
if tool_name == "cancelar_pedido" and isinstance(tool_result, dict):
|
|
numero = tool_result.get("numero_pedido", "N/A")
|
|
status = tool_result.get("status", "N/A")
|
|
return f"Pedido {numero} atualizado com status {status}."
|
|
|
|
if tool_name == "validar_cliente_venda" and isinstance(tool_result, dict):
|
|
aprovado = tool_result.get("aprovado")
|
|
return "Cliente aprovado para financiamento." if aprovado else "Cliente nao aprovado para financiamento."
|
|
|
|
return "Operacao concluida com sucesso."
|