Import ClientFlow production v4928.1.5.132.4

This commit is contained in:
plx
2026-07-29 13:11:01 +00:00
parent 6445044ac6
commit 261d342057
405 changed files with 48373 additions and 1401 deletions

View File

@@ -13,7 +13,9 @@ from app.config import settings
from app.chatwoot_client import add_private_note
from app.message_cleaner import extract_chatwoot_content
from app.persistence import (
get_recent_chatwoot_public_context,
get_state_for_conversation,
mark_raw_event_error,
mark_raw_event_processed,
save_raw_event,
)
@@ -112,7 +114,7 @@ def extract_message_content_for_context(message: Dict[str, Any]) -> str:
def extract_recent_conversation_context(
payload: Dict[str, Any],
current_message_id: Optional[str],
max_messages: int = 4,
max_messages: int = 2,
) -> List[str]:
"""Extrai as últimas mensagens relevantes antes da mensagem atual.
@@ -174,13 +176,20 @@ def build_triage_previous_context(payload: Dict[str, Any], extracted: Dict[str,
recent_lines = extract_recent_conversation_context(
payload=payload,
current_message_id=extracted.get("source_event_id"),
max_messages=4,
max_messages=2,
)
if not recent_lines:
recent_lines = get_recent_chatwoot_public_context(
extracted.get("conversation_id"),
current_source_event_id=extracted.get("source_event_id"),
max_messages=2,
)
if recent_lines:
lines.append("Histórico recente, apenas para contexto:")
lines.append("Últimos 2 emails públicos antes da mensagem atual, apenas para contexto:")
lines.extend(recent_lines)
else:
lines.append("Histórico recente: não disponível no webhook.")
lines.append("Últimos 2 emails públicos: não disponível no webhook nem em raw_events.")
return "\n".join(lines)
@@ -341,36 +350,17 @@ def validate_chatwoot_signature(request: Request, raw_body: bytes) -> None:
if not hmac.compare_digest(expected_signature, received_signature):
raise HTTPException(status_code=401, detail="invalid chatwoot signature")
@router.post("/chatwoot")
async def chatwoot_webhook(request: Request) -> Dict[str, Any]:
async def process_saved_chatwoot_raw_event(raw_event_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
"""Processa um raw_event Chatwoot já persistido.
raw_body = await request.body()
validate_chatwoot_signature(request, raw_body)
try:
payload = json.loads(raw_body.decode('utf-8') or '{}')
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail='invalid json payload')
v4928.1.5.25: o Chatwoot email webhook coloca o sentido da mensagem
em ``payload.message_type``. Nos emails recebidos o ``sender.type`` pode
vir vazio, por isso a ingestão não pode depender de ``sender.type`` do contacto.
Esta função é partilhada pelo webhook online e pelo script de recovery para
evitar que raw_events fiquem em ``processed=false`` sem erro visível.
"""
extracted = extract_chatwoot_event(payload)
raw_event_info = save_raw_event(
source_system="chatwoot",
event_type=extracted["event_type"],
source_event_id=extracted["source_event_id"],
conversation_id=extracted["conversation_id"],
contact_id=extracted["contact_id"],
payload=payload,
)
raw_event_id = raw_event_info["id"]
if raw_event_info.get("processed"):
return {
"status": "duplicate_ignored",
"raw_event_id": raw_event_id,
"message_id": raw_event_info.get("message_id"),
"action_run_id": raw_event_info.get("action_run_id"),
}
if not extracted["content"]:
mark_raw_event_processed(
raw_event_id=raw_event_id,
@@ -381,6 +371,7 @@ async def chatwoot_webhook(request: Request) -> Dict[str, Any]:
"status": "ignored",
"reason": "empty_content",
"raw_event_id": raw_event_id,
"message_type": extracted.get("message_type"),
}
if extracted["is_outgoing"]:
@@ -396,10 +387,13 @@ async def chatwoot_webhook(request: Request) -> Dict[str, Any]:
completed = auto_complete_result.get("status") == "auto_completed"
# Mensagens enviadas manualmente no Chatwoot sem task pendente são
# normais. Devem ser ignoradas como informação operacional, não como
# processing_error, para não poluir System health.
mark_raw_event_processed(
raw_event_id=raw_event_id,
ignored=not completed,
error=None if completed else f"outgoing: {auto_complete_result.get('status')}",
error=None,
)
return {
@@ -451,9 +445,55 @@ async def chatwoot_webhook(request: Request) -> Dict[str, Any]:
"task_id": response.task_id,
"conversation_id": extracted["conversation_id"],
"contact_id": extracted["contact_id"],
"message_type": extracted.get("message_type"),
"action_decision": response.action_decision.model_dump(),
"action_result": response.action_result.model_dump(),
"needs_review": response.needs_review,
"usage": response.usage.model_dump(),
"chatwoot_note": chatwoot_note_result,
}
@router.post("/chatwoot")
async def chatwoot_webhook(request: Request) -> Dict[str, Any]:
raw_body = await request.body()
validate_chatwoot_signature(request, raw_body)
try:
payload = json.loads(raw_body.decode('utf-8') or '{}')
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail='invalid json payload')
extracted = extract_chatwoot_event(payload)
if not isinstance(payload, dict) or not payload:
raise HTTPException(status_code=422, detail="empty chatwoot payload")
if not extracted.get("conversation_id") and not extracted.get("source_event_id"):
raise HTTPException(status_code=422, detail="chatwoot payload without conversation/message id")
if not extracted.get("content"):
raise HTTPException(status_code=422, detail="chatwoot payload without message content")
raw_event_info = save_raw_event(
source_system="chatwoot",
event_type=extracted["event_type"],
source_event_id=extracted["source_event_id"],
conversation_id=extracted["conversation_id"],
contact_id=extracted["contact_id"],
payload=payload,
)
raw_event_id = raw_event_info["id"]
if raw_event_info.get("processed"):
return {
"status": "duplicate_ignored",
"raw_event_id": raw_event_id,
"message_id": raw_event_info.get("message_id"),
"action_run_id": raw_event_info.get("action_run_id"),
}
try:
return await process_saved_chatwoot_raw_event(raw_event_id, payload)
except Exception as exc:
# Não deixar eventos em pending silencioso. O recovery script pode
# reprocessar com --include-errors depois de corrigida a causa.
mark_raw_event_error(raw_event_id=raw_event_id, error=f"processing_exception: {type(exc).__name__}: {exc}")
raise