|
|
|
@ -85,6 +85,14 @@ class OrquestradorService(ReviewFlowMixin, OrderFlowMixin):
|
|
|
|
message=message,
|
|
|
|
message=message,
|
|
|
|
user_id=user_id,
|
|
|
|
user_id=user_id,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
reset_override = await self._try_handle_immediate_context_reset(
|
|
|
|
|
|
|
|
message=message,
|
|
|
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
|
|
|
turn_decision=early_turn_decision,
|
|
|
|
|
|
|
|
finish=finish,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if reset_override:
|
|
|
|
|
|
|
|
return reset_override
|
|
|
|
pending_order_selection = await self._try_resolve_pending_order_selection(
|
|
|
|
pending_order_selection = await self._try_resolve_pending_order_selection(
|
|
|
|
message=message,
|
|
|
|
message=message,
|
|
|
|
user_id=user_id,
|
|
|
|
user_id=user_id,
|
|
|
|
@ -132,10 +140,13 @@ class OrquestradorService(ReviewFlowMixin, OrderFlowMixin):
|
|
|
|
)
|
|
|
|
)
|
|
|
|
# Depois do roteamento para um unico pedido, pede a decisao
|
|
|
|
# Depois do roteamento para um unico pedido, pede a decisao
|
|
|
|
# estruturada do turno final que sera executado.
|
|
|
|
# estruturada do turno final que sera executado.
|
|
|
|
turn_decision = await self._extract_turn_decision_with_llm(
|
|
|
|
if (routing_message or "").strip() == (message or "").strip():
|
|
|
|
message=routing_message,
|
|
|
|
turn_decision = early_turn_decision
|
|
|
|
user_id=user_id,
|
|
|
|
else:
|
|
|
|
)
|
|
|
|
turn_decision = await self._extract_turn_decision_with_llm(
|
|
|
|
|
|
|
|
message=routing_message,
|
|
|
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
|
|
|
)
|
|
|
|
llm_extracted_entities = await self._extract_entities_with_llm(
|
|
|
|
llm_extracted_entities = await self._extract_entities_with_llm(
|
|
|
|
message=routing_message,
|
|
|
|
message=routing_message,
|
|
|
|
user_id=user_id,
|
|
|
|
user_id=user_id,
|
|
|
|
@ -203,13 +214,29 @@ class OrquestradorService(ReviewFlowMixin, OrderFlowMixin):
|
|
|
|
|
|
|
|
|
|
|
|
decision_action = str(turn_decision.get("action") or "")
|
|
|
|
decision_action = str(turn_decision.get("action") or "")
|
|
|
|
decision_response = str(turn_decision.get("response_to_user") or "").strip()
|
|
|
|
decision_response = str(turn_decision.get("response_to_user") or "").strip()
|
|
|
|
|
|
|
|
should_prioritize_review_flow = self._should_prioritize_review_flow(
|
|
|
|
|
|
|
|
turn_decision=turn_decision,
|
|
|
|
|
|
|
|
extracted_entities=extracted_entities,
|
|
|
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
|
|
|
)
|
|
|
|
should_prioritize_order_flow = self._should_prioritize_order_flow(
|
|
|
|
should_prioritize_order_flow = self._should_prioritize_order_flow(
|
|
|
|
turn_decision=turn_decision,
|
|
|
|
turn_decision=turn_decision,
|
|
|
|
extracted_entities=extracted_entities,
|
|
|
|
extracted_entities=extracted_entities,
|
|
|
|
|
|
|
|
user_id=user_id,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if decision_action == "ask_missing_fields" and decision_response and not should_prioritize_order_flow:
|
|
|
|
if (
|
|
|
|
|
|
|
|
decision_action == "ask_missing_fields"
|
|
|
|
|
|
|
|
and decision_response
|
|
|
|
|
|
|
|
and not should_prioritize_review_flow
|
|
|
|
|
|
|
|
and not should_prioritize_order_flow
|
|
|
|
|
|
|
|
):
|
|
|
|
return await finish(decision_response, queue_notice=queue_notice)
|
|
|
|
return await finish(decision_response, queue_notice=queue_notice)
|
|
|
|
if decision_action == "answer_user" and decision_response and not should_prioritize_order_flow:
|
|
|
|
if (
|
|
|
|
|
|
|
|
decision_action == "answer_user"
|
|
|
|
|
|
|
|
and decision_response
|
|
|
|
|
|
|
|
and not should_prioritize_review_flow
|
|
|
|
|
|
|
|
and not should_prioritize_order_flow
|
|
|
|
|
|
|
|
):
|
|
|
|
return await finish(decision_response, queue_notice=queue_notice)
|
|
|
|
return await finish(decision_response, queue_notice=queue_notice)
|
|
|
|
|
|
|
|
|
|
|
|
planned_tool_response = await self._try_execute_business_tool_from_turn_decision(
|
|
|
|
planned_tool_response = await self._try_execute_business_tool_from_turn_decision(
|
|
|
|
@ -262,6 +289,14 @@ class OrquestradorService(ReviewFlowMixin, OrderFlowMixin):
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if cancel_order_response:
|
|
|
|
if cancel_order_response:
|
|
|
|
return await finish(cancel_order_response, queue_notice=queue_notice)
|
|
|
|
return await finish(cancel_order_response, queue_notice=queue_notice)
|
|
|
|
|
|
|
|
order_listing_response = await self._try_handle_order_listing(
|
|
|
|
|
|
|
|
message=routing_message,
|
|
|
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
|
|
|
intents={},
|
|
|
|
|
|
|
|
turn_decision=turn_decision,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if order_listing_response:
|
|
|
|
|
|
|
|
return await finish(order_listing_response, queue_notice=queue_notice)
|
|
|
|
# 4) Fluxo de coleta incremental para realizacao de pedido.
|
|
|
|
# 4) Fluxo de coleta incremental para realizacao de pedido.
|
|
|
|
order_response = await self._try_collect_and_create_order(
|
|
|
|
order_response = await self._try_collect_and_create_order(
|
|
|
|
message=routing_message,
|
|
|
|
message=routing_message,
|
|
|
|
@ -857,7 +892,7 @@ class OrquestradorService(ReviewFlowMixin, OrderFlowMixin):
|
|
|
|
decision = turn_decision or {}
|
|
|
|
decision = turn_decision or {}
|
|
|
|
decision_intent = str(decision.get("intent") or "").strip().lower()
|
|
|
|
decision_intent = str(decision.get("intent") or "").strip().lower()
|
|
|
|
decision_domain = str(decision.get("domain") or "").strip().lower()
|
|
|
|
decision_domain = str(decision.get("domain") or "").strip().lower()
|
|
|
|
if decision_domain != "sales" and decision_intent not in {"order_create", "inventory_search"}:
|
|
|
|
if decision_domain != "sales" and decision_intent not in {"order_create", "order_list", "inventory_search"}:
|
|
|
|
return {}
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
generic_memory = (extracted_entities or {}).get("generic_memory")
|
|
|
|
generic_memory = (extracted_entities or {}).get("generic_memory")
|
|
|
|
@ -870,6 +905,36 @@ class OrquestradorService(ReviewFlowMixin, OrderFlowMixin):
|
|
|
|
async def _extract_turn_decision_with_llm(self, message: str, user_id: int | None) -> dict:
|
|
|
|
async def _extract_turn_decision_with_llm(self, message: str, user_id: int | None) -> dict:
|
|
|
|
return await self.planner.extract_turn_decision(message=message, user_id=user_id)
|
|
|
|
return await self.planner.extract_turn_decision(message=message, user_id=user_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _try_handle_immediate_context_reset(
|
|
|
|
|
|
|
|
self,
|
|
|
|
|
|
|
|
message: str,
|
|
|
|
|
|
|
|
user_id: int | None,
|
|
|
|
|
|
|
|
turn_decision: dict | None,
|
|
|
|
|
|
|
|
finish,
|
|
|
|
|
|
|
|
) -> str | None:
|
|
|
|
|
|
|
|
decision = turn_decision or {}
|
|
|
|
|
|
|
|
decision_action = str(decision.get("action") or "").strip().lower()
|
|
|
|
|
|
|
|
decision_intent = str(decision.get("intent") or "").strip().lower()
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
decision_action != "clear_context"
|
|
|
|
|
|
|
|
and decision_intent != "conversation_reset"
|
|
|
|
|
|
|
|
and not (
|
|
|
|
|
|
|
|
hasattr(self, "policy")
|
|
|
|
|
|
|
|
and self._is_order_selection_reset_message(message)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cleaned_message = (
|
|
|
|
|
|
|
|
self._remove_order_selection_reset_prefix(message)
|
|
|
|
|
|
|
|
if hasattr(self, "policy")
|
|
|
|
|
|
|
|
else message
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self._clear_user_conversation_state(user_id=user_id)
|
|
|
|
|
|
|
|
if not cleaned_message or cleaned_message.strip() == (message or "").strip():
|
|
|
|
|
|
|
|
return await finish("Contexto da conversa limpo. Podemos recomecar do zero.")
|
|
|
|
|
|
|
|
return await self.handle_message(cleaned_message, user_id=user_id)
|
|
|
|
|
|
|
|
|
|
|
|
def _resolve_entities_for_message_plan(self, message_plan: dict, routed_message: str) -> dict:
|
|
|
|
def _resolve_entities_for_message_plan(self, message_plan: dict, routed_message: str) -> dict:
|
|
|
|
return self.planner.resolve_entities_for_message_plan(message_plan=message_plan, routed_message=routed_message)
|
|
|
|
return self.planner.resolve_entities_for_message_plan(message_plan=message_plan, routed_message=routed_message)
|
|
|
|
|
|
|
|
|
|
|
|
@ -929,9 +994,27 @@ class OrquestradorService(ReviewFlowMixin, OrderFlowMixin):
|
|
|
|
self,
|
|
|
|
self,
|
|
|
|
turn_decision: dict | None,
|
|
|
|
turn_decision: dict | None,
|
|
|
|
extracted_entities: dict | None,
|
|
|
|
extracted_entities: dict | None,
|
|
|
|
|
|
|
|
user_id: int | None = None,
|
|
|
|
) -> bool:
|
|
|
|
) -> bool:
|
|
|
|
decision = turn_decision or {}
|
|
|
|
decision = turn_decision or {}
|
|
|
|
if str(decision.get("intent") or "").strip().lower() != "order_create":
|
|
|
|
decision_intent = str(decision.get("intent") or "").strip().lower()
|
|
|
|
|
|
|
|
has_open_cancel_order_draft = bool(
|
|
|
|
|
|
|
|
user_id is not None and self.state.get_entry("pending_cancel_order_drafts", user_id, expire=True)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if has_open_cancel_order_draft:
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
if decision_intent == "order_list":
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
if decision_intent == "order_cancel":
|
|
|
|
|
|
|
|
cancel_order_fields = (extracted_entities if isinstance(extracted_entities, dict) else {}).get("cancel_order_fields")
|
|
|
|
|
|
|
|
if not isinstance(cancel_order_fields, dict):
|
|
|
|
|
|
|
|
cancel_order_fields = {}
|
|
|
|
|
|
|
|
return bool(
|
|
|
|
|
|
|
|
cancel_order_fields.get("numero_pedido")
|
|
|
|
|
|
|
|
or cancel_order_fields.get("motivo")
|
|
|
|
|
|
|
|
or has_open_cancel_order_draft
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if decision_intent != "order_create":
|
|
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
entities = extracted_entities if isinstance(extracted_entities, dict) else {}
|
|
|
|
entities = extracted_entities if isinstance(extracted_entities, dict) else {}
|
|
|
|
@ -952,6 +1035,48 @@ class OrquestradorService(ReviewFlowMixin, OrderFlowMixin):
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _should_prioritize_review_flow(
|
|
|
|
|
|
|
|
self,
|
|
|
|
|
|
|
|
turn_decision: dict | None,
|
|
|
|
|
|
|
|
extracted_entities: dict | None,
|
|
|
|
|
|
|
|
user_id: int | None = None,
|
|
|
|
|
|
|
|
) -> bool:
|
|
|
|
|
|
|
|
has_open_review_draft = bool(
|
|
|
|
|
|
|
|
user_id is not None
|
|
|
|
|
|
|
|
and (
|
|
|
|
|
|
|
|
self.state.get_entry("pending_review_drafts", user_id, expire=True)
|
|
|
|
|
|
|
|
or self.state.get_entry("pending_review_reuse_confirmations", user_id, expire=True)
|
|
|
|
|
|
|
|
or self.state.get_entry("pending_review_confirmations", user_id, expire=True)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if has_open_review_draft:
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
decision = turn_decision or {}
|
|
|
|
|
|
|
|
decision_intent = str(decision.get("intent") or "").strip().lower()
|
|
|
|
|
|
|
|
if decision_intent != "review_schedule":
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
entities = extracted_entities if isinstance(extracted_entities, dict) else {}
|
|
|
|
|
|
|
|
review_fields = entities.get("review_fields")
|
|
|
|
|
|
|
|
generic_memory = entities.get("generic_memory")
|
|
|
|
|
|
|
|
if not isinstance(review_fields, dict):
|
|
|
|
|
|
|
|
review_fields = {}
|
|
|
|
|
|
|
|
if not isinstance(generic_memory, dict):
|
|
|
|
|
|
|
|
generic_memory = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return any(
|
|
|
|
|
|
|
|
(
|
|
|
|
|
|
|
|
review_fields.get("placa"),
|
|
|
|
|
|
|
|
review_fields.get("data_hora"),
|
|
|
|
|
|
|
|
review_fields.get("modelo"),
|
|
|
|
|
|
|
|
review_fields.get("ano"),
|
|
|
|
|
|
|
|
review_fields.get("km"),
|
|
|
|
|
|
|
|
review_fields.get("revisao_previa_concessionaria"),
|
|
|
|
|
|
|
|
generic_memory.get("placa"),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_json_object(self, text: str):
|
|
|
|
def _parse_json_object(self, text: str):
|
|
|
|
return self.normalizer.parse_json_object(text)
|
|
|
|
return self.normalizer.parse_json_object(text)
|
|
|
|
|
|
|
|
|
|
|
|
|