|
|
|
|
@ -21,6 +21,8 @@ class EntityNormalizer:
|
|
|
|
|
"buy_car": "order_create",
|
|
|
|
|
"purchase_car": "order_create",
|
|
|
|
|
"cancel_order": "order_cancel",
|
|
|
|
|
"list_orders": "order_list",
|
|
|
|
|
"show_orders": "order_list",
|
|
|
|
|
"list_inventory": "inventory_search",
|
|
|
|
|
"search_inventory": "inventory_search",
|
|
|
|
|
"clear_conversation": "conversation_reset",
|
|
|
|
|
@ -38,6 +40,44 @@ class EntityNormalizer:
|
|
|
|
|
"veiculo": "vehicle_id",
|
|
|
|
|
"carro": "vehicle_id",
|
|
|
|
|
}
|
|
|
|
|
_TOOL_ARGUMENT_ALIASES = {
|
|
|
|
|
"cancelar_pedido": {
|
|
|
|
|
"order_id": "numero_pedido",
|
|
|
|
|
"pedido_id": "numero_pedido",
|
|
|
|
|
"id_pedido": "numero_pedido",
|
|
|
|
|
"numero": "numero_pedido",
|
|
|
|
|
"order_number": "numero_pedido",
|
|
|
|
|
"reason": "motivo",
|
|
|
|
|
},
|
|
|
|
|
"realizar_pedido": {
|
|
|
|
|
"order_id": "vehicle_id",
|
|
|
|
|
"car_id": "vehicle_id",
|
|
|
|
|
"id_veiculo": "vehicle_id",
|
|
|
|
|
"customer_cpf": "cpf",
|
|
|
|
|
},
|
|
|
|
|
"listar_pedidos": {
|
|
|
|
|
"max_results": "limite",
|
|
|
|
|
"limit": "limite",
|
|
|
|
|
"customer_cpf": "cpf",
|
|
|
|
|
},
|
|
|
|
|
"cancelar_agendamento_revisao": {
|
|
|
|
|
"review_id": "protocolo",
|
|
|
|
|
"schedule_id": "protocolo",
|
|
|
|
|
"reason": "motivo",
|
|
|
|
|
},
|
|
|
|
|
"editar_data_revisao": {
|
|
|
|
|
"review_id": "protocolo",
|
|
|
|
|
"schedule_id": "protocolo",
|
|
|
|
|
"data_hora": "nova_data_hora",
|
|
|
|
|
"new_datetime": "nova_data_hora",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
_TOOL_REQUIRED_ARGUMENTS = {
|
|
|
|
|
"cancelar_pedido": ("numero_pedido", "motivo"),
|
|
|
|
|
"realizar_pedido": ("cpf", "vehicle_id"),
|
|
|
|
|
"editar_data_revisao": ("protocolo", "nova_data_hora"),
|
|
|
|
|
"cancelar_agendamento_revisao": ("protocolo",),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def empty_turn_decision(self) -> dict:
|
|
|
|
|
return TurnDecision().model_dump()
|
|
|
|
|
@ -167,11 +207,18 @@ class EntityNormalizer:
|
|
|
|
|
if isinstance(entities, dict):
|
|
|
|
|
normalized["entities"] = dict(entities)
|
|
|
|
|
|
|
|
|
|
tool_name = str(normalized.get("tool_name") or "").strip()
|
|
|
|
|
tool_arguments = normalized.get("tool_arguments")
|
|
|
|
|
if tool_name and isinstance(tool_arguments, dict):
|
|
|
|
|
normalized["tool_arguments"] = self.normalize_tool_arguments(tool_name, tool_arguments)
|
|
|
|
|
|
|
|
|
|
if self._should_route_order_alias_to_collection(normalized):
|
|
|
|
|
normalized["action"] = "collect_order_create"
|
|
|
|
|
normalized["missing_fields"] = []
|
|
|
|
|
normalized["response_to_user"] = None
|
|
|
|
|
|
|
|
|
|
normalized = self._coerce_incomplete_tool_call_to_collection(normalized)
|
|
|
|
|
|
|
|
|
|
return normalized
|
|
|
|
|
|
|
|
|
|
def _normalize_turn_missing_fields(self, missing_fields: list) -> list[str]:
|
|
|
|
|
@ -203,9 +250,123 @@ class EntityNormalizer:
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def _coerce_incomplete_tool_call_to_collection(self, payload: dict) -> dict:
|
|
|
|
|
if payload.get("action") != "call_tool":
|
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
tool_name = str(payload.get("tool_name") or "").strip()
|
|
|
|
|
if not tool_name:
|
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
required_fields = self._TOOL_REQUIRED_ARGUMENTS.get(tool_name)
|
|
|
|
|
tool_arguments = payload.get("tool_arguments")
|
|
|
|
|
if not required_fields or not isinstance(tool_arguments, dict):
|
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
missing_fields = [field for field in required_fields if tool_arguments.get(field) in (None, "", [])]
|
|
|
|
|
if not missing_fields:
|
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
entities = payload.get("entities")
|
|
|
|
|
if not isinstance(entities, dict):
|
|
|
|
|
entities = self.empty_extraction_payload()
|
|
|
|
|
payload["entities"] = entities
|
|
|
|
|
|
|
|
|
|
if tool_name == "cancelar_pedido" and payload.get("intent") == "order_cancel":
|
|
|
|
|
cancel_entities = self.normalize_cancel_order_fields(
|
|
|
|
|
{
|
|
|
|
|
**(entities.get("cancel_order_fields") or {}),
|
|
|
|
|
**tool_arguments,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
entities["cancel_order_fields"] = cancel_entities
|
|
|
|
|
payload["action"] = "collect_order_cancel"
|
|
|
|
|
payload["tool_name"] = None
|
|
|
|
|
payload["tool_arguments"] = {}
|
|
|
|
|
payload["missing_fields"] = []
|
|
|
|
|
payload["response_to_user"] = None
|
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
if tool_name == "realizar_pedido" and payload.get("intent") == "order_create":
|
|
|
|
|
order_entities = self.normalize_order_fields(
|
|
|
|
|
{
|
|
|
|
|
**(entities.get("order_fields") or {}),
|
|
|
|
|
**tool_arguments,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
entities["order_fields"] = order_entities
|
|
|
|
|
payload["action"] = "collect_order_create"
|
|
|
|
|
payload["tool_name"] = None
|
|
|
|
|
payload["tool_arguments"] = {}
|
|
|
|
|
payload["missing_fields"] = []
|
|
|
|
|
payload["response_to_user"] = None
|
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
if tool_name in {"cancelar_agendamento_revisao", "editar_data_revisao"} and str(payload.get("domain") or "") == "review":
|
|
|
|
|
review_management_entities = self.normalize_review_management_fields(
|
|
|
|
|
{
|
|
|
|
|
**(entities.get("review_management_fields") or {}),
|
|
|
|
|
**tool_arguments,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
entities["review_management_fields"] = review_management_entities
|
|
|
|
|
payload["action"] = "collect_review_management"
|
|
|
|
|
payload["tool_name"] = None
|
|
|
|
|
payload["tool_arguments"] = {}
|
|
|
|
|
payload["missing_fields"] = []
|
|
|
|
|
payload["response_to_user"] = None
|
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
def normalize_text(self, text: str) -> str:
|
|
|
|
|
return technical_normalizer.normalize_text(text)
|
|
|
|
|
|
|
|
|
|
def normalize_tool_arguments(self, tool_name: str, arguments) -> dict:
|
|
|
|
|
if not isinstance(arguments, dict):
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
normalized_tool_name = str(tool_name or "").strip()
|
|
|
|
|
aliases = self._TOOL_ARGUMENT_ALIASES.get(normalized_tool_name, {})
|
|
|
|
|
normalized_arguments: dict = {}
|
|
|
|
|
for raw_key, value in arguments.items():
|
|
|
|
|
candidate_key = self.normalize_text(str(raw_key or "")).replace("-", "_").replace(" ", "_")
|
|
|
|
|
canonical_key = aliases.get(candidate_key, candidate_key)
|
|
|
|
|
if canonical_key not in normalized_arguments:
|
|
|
|
|
normalized_arguments[canonical_key] = value
|
|
|
|
|
|
|
|
|
|
if normalized_tool_name == "cancelar_pedido":
|
|
|
|
|
coerced = self.normalize_cancel_order_fields(normalized_arguments)
|
|
|
|
|
if "motivo" not in coerced and isinstance(normalized_arguments.get("motivo"), str):
|
|
|
|
|
motivo = str(normalized_arguments.get("motivo") or "").strip()
|
|
|
|
|
if motivo:
|
|
|
|
|
coerced["motivo"] = motivo
|
|
|
|
|
return coerced
|
|
|
|
|
|
|
|
|
|
if normalized_tool_name == "realizar_pedido":
|
|
|
|
|
return self.normalize_order_fields(normalized_arguments)
|
|
|
|
|
|
|
|
|
|
if normalized_tool_name == "listar_pedidos":
|
|
|
|
|
coerced: dict = {}
|
|
|
|
|
cpf = self.normalize_cpf(normalized_arguments.get("cpf"))
|
|
|
|
|
if cpf:
|
|
|
|
|
coerced["cpf"] = cpf
|
|
|
|
|
status = str(normalized_arguments.get("status") or "").strip()
|
|
|
|
|
if status:
|
|
|
|
|
coerced["status"] = status
|
|
|
|
|
limite = self.normalize_positive_number(normalized_arguments.get("limite"))
|
|
|
|
|
if limite:
|
|
|
|
|
coerced["limite"] = max(1, min(int(round(limite)), 50))
|
|
|
|
|
return coerced
|
|
|
|
|
|
|
|
|
|
if normalized_tool_name == "cancelar_agendamento_revisao":
|
|
|
|
|
return self.normalize_review_management_fields(normalized_arguments)
|
|
|
|
|
|
|
|
|
|
if normalized_tool_name == "editar_data_revisao":
|
|
|
|
|
return self.normalize_review_management_fields(normalized_arguments)
|
|
|
|
|
|
|
|
|
|
return normalized_arguments
|
|
|
|
|
|
|
|
|
|
def normalize_plate(self, value) -> str | None:
|
|
|
|
|
return technical_normalizer.normalize_plate(value)
|
|
|
|
|
|
|
|
|
|
@ -353,6 +514,7 @@ class EntityNormalizer:
|
|
|
|
|
"review_cancel": bool(self.normalize_bool(data.get("review_cancel"))),
|
|
|
|
|
"review_reschedule": bool(self.normalize_bool(data.get("review_reschedule"))),
|
|
|
|
|
"order_create": bool(self.normalize_bool(data.get("order_create"))),
|
|
|
|
|
"order_list": bool(self.normalize_bool(data.get("order_list"))),
|
|
|
|
|
"order_cancel": bool(self.normalize_bool(data.get("order_cancel"))),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|