fix: remove dead code duplicates, add SQL LIMIT across all services

- Remove duplicate function definitions with hardcoded "AI-оператор" strings
  (ai_voice_runtime, ai_orchestrator, voice_name_config, voice.py)
- Remove unreachable dead code after return in ai_voice_runtime
- Add SQL LIMIT to 17 unbounded queries across 12 services to prevent OOM
- Move Python-side filtering to SQL WHERE in reporting_service
- Downgrade 19 logger.warning to logger.info for normal-flow events in media_runtime

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yera All
2026-04-13 12:04:26 +05:00
co-authored by Claude Opus 4.6
parent 6b8f5d069b
commit 6798320209
17 changed files with 70 additions and 302 deletions
+4 -113
View File
@@ -1913,8 +1913,9 @@ def _last_messages(session, thread_id: str, limit: int) -> list[TelegramMessageR
select(TelegramMessageRow)
.where(TelegramMessageRow.thread_id == thread_id)
.order_by(TelegramMessageRow.id.desc())
.limit(max(limit, 1))
).scalars().all()
return list(reversed(rows[:limit]))
return list(reversed(rows))
def _last_whatsapp_messages(session, thread_id: str, limit: int) -> list[WhatsAppMessageRow]:
@@ -1922,8 +1923,9 @@ def _last_whatsapp_messages(session, thread_id: str, limit: int) -> list[WhatsAp
select(WhatsAppMessageRow)
.where(WhatsAppMessageRow.thread_id == thread_id)
.order_by(WhatsAppMessageRow.id.desc())
.limit(max(limit, 1))
).scalars().all()
return list(reversed(rows[:limit]))
return list(reversed(rows))
def _select_trigger_message(
@@ -2204,93 +2206,6 @@ def _extract_json_object(raw: str) -> dict[str, Any]:
return parsed
def _stub_decision(
*,
customer: Customer | None,
interaction: Interaction,
last_user_message: TelegramMessageRow,
kb_results: list[KBArticleRow],
language: str,
) -> dict[str, Any]:
text = last_user_message.text
if _looks_like_human_request(text):
return {
"language": language,
"intent": "handoff_request",
"reply_text": "",
"confidence": 0.2,
"needs_handoff": True,
"handoff_reason": "Клиент запросил живого оператора.",
"case_action": "keep_open",
"kb_refs": [],
}
if _is_sensitive_request(text):
return {
"language": language,
"intent": "sensitive_request",
"reply_text": "",
"confidence": 0.25,
"needs_handoff": True,
"handoff_reason": "Нужен человек: запрос затрагивает чувствительную тему или действие вне доступных tools.",
"case_action": "keep_open",
"kb_refs": [],
}
if _looks_like_resolution_confirmation(text):
reply = (
"Мен компанияның AI көмекшісімін. Рақмет, өтінішті жабамын. Қажет болса, адам операторын қоса аламын."
if language == "kz"
else "Я AI-помощник компании. Спасибо, отмечаю вопрос как решённый. Если понадобится человек, сразу передам диалог оператору."
)
return {
"language": language,
"intent": "resolution_confirmed",
"reply_text": reply,
"confidence": 0.9,
"needs_handoff": False,
"handoff_reason": None,
"case_action": "close",
"kb_refs": [],
}
if kb_results:
best = kb_results[0]
snippet = _article_snippet(best)
reply = (
"Мен компанияның AI көмекшісімін. Білім базасына сүйеніп жауап беремін: "
f"{snippet} Егер қажет болса, адам операторына бірден өткіземін."
if language == "kz"
else "Я AI-помощник компании и отвечаю по базе знаний. "
f"{snippet} Если этого недостаточно, сразу передам диалог живому оператору."
)
return {
"language": language,
"intent": "kb_answer",
"reply_text": reply,
"confidence": 0.84,
"needs_handoff": False,
"handoff_reason": None,
"case_action": "keep_open",
"kb_refs": [best.article_id],
}
name = customer.display_name if customer else (interaction.customer_id or "клиент")
reply = (
f"Мен компанияның AI көмекшісімін. {name}, сұрағыңызды түсіндім, бірақ бұл үшін қосымша тексеріс керек. "
"Қажет болса, адам операторына бірден өткіземін."
if language == "kz"
else f"Я AI-помощник компании. {name}, понял ваш вопрос, но для точного ответа мне не хватает данных "
"из доступных инструментов. Передаю диалог оператору."
)
return {
"language": language,
"intent": "handoff_missing_tool",
"reply_text": "",
"confidence": 0.3,
"needs_handoff": True,
"handoff_reason": "Нет достаточных данных в базе знаний или доступных инструментах.",
"case_action": "keep_open",
"kb_refs": [],
}
def _openai_prompt(
*,
customer: Customer | None,
@@ -2434,30 +2349,6 @@ def _sanitize_decision(raw: dict[str, Any], *, fallback_language: str) -> dict[s
return decision
def _always_reply_fallback(last_user_text: str, language: str) -> str:
normalized = str(last_user_text or "").strip().lower()
greeting_tokens = ("привет", "здравствуйте", "добрый", "салем", "сә", "сәлем", "hello", "hi")
if language == "kz":
if any(token in normalized for token in greeting_tokens):
return (
"Сәлеметсіз бе! Мен компанияның AI-көмекшісімін. "
"Сұрағыңызды жазыңыз, мен бірден көмектесуге тырысамын."
)
return (
"Мен көмектесуге дайынмын. Сұрағыңызды нақтырақ жазыңыз, "
"мен сізге бірден жауап беремін."
)
if any(token in normalized for token in greeting_tokens):
return (
"Здравствуйте! Я AI-помощник компании. "
"Напишите ваш вопрос, и я сразу постараюсь помочь."
)
return (
"Я на связи и готов помочь. "
"Напишите, пожалуйста, чуть подробнее, что именно вам нужно."
)
def _stub_decision(
*,
customer: Customer | None,
-30
View File
@@ -50,36 +50,6 @@ def _voice_max_context_segments() -> int:
return max(6, app._int_env("AI_VOICE_MAX_CONTEXT_SEGMENTS", 12))
def _voice_disclosure_prefix(language: str) -> str:
if language == "kz":
return "Men kompaniyanyn AI operatoriymyn. "
return "Я AI-оператор компании. "
def _voice_greeting(language: str) -> str:
if language == "kz":
return (
"Men kompaniyanyn AI operatoriymyn. Salemetsiz be. "
"Suragynyzdy aitanyz, men birden komektesuge tyrisamyn."
)
return (
"Я AI-оператор компании. Здравствуйте. "
"Коротко расскажите, с чем помочь, и я сразу начну разбираться."
)
def _voice_handoff_reply(language: str) -> str:
if language == "kz":
return (
"Men AI operator retinde bastapky konteksti jiyap aldyм. "
"Kazir sizdi tiry operatorga kosamyn."
)
return (
"Я как AI-оператор собрал первичный контекст. "
"Сейчас переведу вас на живого оператора."
)
def _voice_disclosure_prefix(language: str) -> str:
return persona.voice_disclosure_prefix(language)
@@ -18,38 +18,6 @@ from services.shared.sql_models import VoiceNameCollectionSettingsRow
VOICE_NAME_COLLECTION_SETTINGS_KEY = "global"
def voice_name_collection_default_config() -> VoiceNameCollectionConfig:
return VoiceNameCollectionConfig(
enabled=True,
texts=VoiceNameCollectionTextsConfig(
ru=VoiceNameCollectionLanguageTexts(
start_prompt="Здравствуйте. Назовите, пожалуйста, ваше имя.",
personalized_greeting_template=(
"Я AI-оператор компании. Здравствуйте, {name}. "
"Коротко расскажите, с чем помочь, и я сразу начну разбираться."
),
confirmation_greeting_template=(
"Я AI-оператор компании. Если я правильно расслышал, вас зовут {name}? "
"И чем помочь?"
),
inline_followup_prompt="И ещё подскажите, как мне к вам обращаться?",
),
kz=VoiceNameCollectionLanguageTexts(
start_prompt="Сәлеметсіз бе. Атыңызды атаңызшы.",
personalized_greeting_template=(
"Men kompaniyanyn AI operatoriymyn. Salemetsiz be, {name}. "
"Suragynyzdy aitanyz, men birden komektesuge tyrisamyn."
),
confirmation_greeting_template=(
"Men kompaniyanyn AI operatoriymyn. Durys estisem, atynyz {name} pa? "
"Qalai komektesemin?"
),
inline_followup_prompt="Tagy bir naqtylasam, sizge qalai qaratamyn?",
),
),
)
def voice_name_collection_default_config() -> VoiceNameCollectionConfig:
return VoiceNameCollectionConfig(
enabled=True,
-59
View File
@@ -272,20 +272,6 @@ def _voice_start_name_prompt(language: str | None) -> str:
return "Здравствуйте. Назовите, пожалуйста, ваше имя."
def _default_voice_greeting(language: str | None, *, agent_profile: str = "voice_support") -> str:
if str(agent_profile or "").strip() == "voice_start":
return _voice_start_name_prompt(language)
if str(language or "").strip() == "kz":
return (
"Мен компанияның AI оператормын. Сәлеметсіз бе. "
"Қандай сұрағыңыз бар екенін айтыңыз, мен бірден көмектесуге тырысамын."
)
return (
"Я AI-оператор компании. Здравствуйте. "
"Коротко расскажите, с чем помочь, и я сразу начну разбираться."
)
def _default_voice_greeting(language: str | None, *, agent_profile: str = "voice_support") -> str:
if str(agent_profile or "").strip() == "voice_start":
return _voice_start_name_prompt(language)
@@ -989,51 +975,6 @@ def _request_runtime_handoff(
summary=handoff_summary,
metadata=decision.metadata,
)
session = get_session()
try:
voice_session = _load_voice_session(session, session_id)
if not str(voice_session.interaction_id or "").strip():
raise RuntimeError("Voice AI session is missing interaction_id")
handoff_payload = VoiceAIHandoffRequestIn(
voice_session_id=voice_session.session_id,
ai_session_id=voice_session.ai_session_id,
interaction_id=voice_session.interaction_id,
target_queue_id=voice_session.handoff_target_queue_id,
reason=decision.handoff_reason or "AI requested human handoff.",
metadata=decision.metadata,
summary={
"customer_request_text": customer_request_text,
"ai_outcome_text": decision.summary_text or decision.reply_text or decision.handoff_reason or "",
"recommended_next_step": "Продолжить звонок вручную и проверить контекст обращения.",
},
)
LOGGER.warning(
"voice_runtime.handoff_request call_id=%s session_id=%s ai_session_id=%s interaction_id=%s target_queue_id=%s reason=%s",
voice_session.call_id,
voice_session.session_id,
voice_session.ai_session_id,
voice_session.interaction_id,
voice_session.handoff_target_queue_id,
(decision.handoff_reason or "AI requested human handoff.")[:300],
)
try:
_bridge_request(
"POST",
f"/internal/voice-ai/calls/{voice_session.call_id}/handoff",
payload=handoff_payload.model_dump(),
timeout=_handoff_timeout_seconds(),
)
except Exception as exc:
LOGGER.warning(
"voice_runtime.handoff_failed call_id=%s session_id=%s ai_session_id=%s error=%s",
voice_session.call_id,
voice_session.session_id,
voice_session.ai_session_id,
str(exc)[:500],
)
raise
finally:
session.close()
def _handle_media_error(session_id: str, message: str, metadata: dict[str, Any] | None = None) -> None:
@@ -545,7 +545,7 @@ class AudioSocketMediaRuntime:
return
actor.partial_transcript = transcript_text
actor.partial_intent = self._detect_early_intent(transcript_text)
logger.warning(
logger.info(
"audiosocket.partial_asr_ready session_id=%s generation=%s text_len=%s intent=%s",
actor.registration.voice_session_id,
utterance_generation,
@@ -685,7 +685,7 @@ class AudioSocketMediaRuntime:
phase=reply_phase,
status="delivered",
)
logger.warning(
logger.info(
"audiosocket.reply_delivered session_id=%s greeting=%s phase=%s",
actor.registration.voice_session_id,
is_greeting,
@@ -773,7 +773,7 @@ class AudioSocketMediaRuntime:
await writer.drain()
if not keepalive_logged:
keepalive_logged = True
logger.warning(
logger.info(
"audiosocket.prereg_keepalive media_uuid=%s frame_bytes=%s",
media_uuid,
len(keepalive_payload),
@@ -810,9 +810,9 @@ class AudioSocketMediaRuntime:
close_as_error = False
peer = writer.get_extra_info("peername")
try:
logger.warning("audiosocket.accept peer=%s", peer)
logger.info("audiosocket.accept peer=%s", peer)
packet_type, payload = await read_packet(reader, timeout=self._idle_timeout_seconds)
logger.warning(
logger.info(
"audiosocket.initial_packet peer=%s packet_type=%s payload_len=%s",
peer,
packet_type,
@@ -821,7 +821,7 @@ class AudioSocketMediaRuntime:
if packet_type != AUDIO_SOCKET_PACKET_UUID:
raise RuntimeError("AudioSocket UUID handshake is required")
media_uuid = normalize_media_uuid(payload)
logger.warning("audiosocket.handshake peer=%s media_uuid=%s", peer, media_uuid)
logger.info("audiosocket.handshake peer=%s media_uuid=%s", peer, media_uuid)
registration = await self._await_registration(
media_uuid,
writer=writer,
@@ -829,7 +829,7 @@ class AudioSocketMediaRuntime:
)
if registration is None:
raise RuntimeError(f"Unknown AudioSocket media_uuid: {media_uuid}")
logger.warning(
logger.info(
"audiosocket.registered peer=%s session_id=%s media_uuid=%s",
peer,
registration.voice_session_id,
@@ -864,7 +864,7 @@ class AudioSocketMediaRuntime:
await self._handle_pcm(actor, payload)
continue
if packet_type == AUDIO_SOCKET_PACKET_DTMF:
logger.warning(
logger.info(
"audiosocket.dtmf session_id=%s payload_len=%s",
actor.registration.voice_session_id,
len(payload),
@@ -874,7 +874,7 @@ class AudioSocketMediaRuntime:
close_reason = "audiosocket_hangup"
break
if packet_type == AUDIO_SOCKET_PACKET_UUID:
logger.warning(
logger.info(
"audiosocket.extra_uuid session_id=%s payload_len=%s",
actor.registration.voice_session_id,
len(payload),
@@ -887,7 +887,7 @@ class AudioSocketMediaRuntime:
logger.warning("audiosocket.timeout peer=%s", peer)
except asyncio.IncompleteReadError:
close_reason = "connection_closed"
logger.warning("audiosocket.peer_closed peer=%s", peer)
logger.info("audiosocket.peer_closed peer=%s", peer)
except Exception as exc:
close_reason = str(exc)[:1000] or "media_runtime_error"
close_as_error = True
@@ -905,7 +905,7 @@ class AudioSocketMediaRuntime:
return
if not actor.first_pcm_logged:
actor.first_pcm_logged = True
logger.warning(
logger.info(
"audiosocket.first_pcm session_id=%s state=%s frame_bytes=%s",
actor.registration.voice_session_id,
actor.state,
@@ -978,7 +978,7 @@ class AudioSocketMediaRuntime:
actor.registration.voice_session_id,
)
if greeting_text:
logger.warning(
logger.info(
"audiosocket.greeting session_id=%s text_len=%s",
actor.registration.voice_session_id,
len(greeting_text),
@@ -1059,7 +1059,7 @@ class AudioSocketMediaRuntime:
return
if self._is_low_signal_transcript(transcript_text):
actor.finalized_utterance_generation = max(actor.finalized_utterance_generation, utterance_generation)
logger.warning(
logger.info(
"audiosocket.low_signal_ignored session_id=%s transcript=%s",
actor.registration.voice_session_id,
transcript_text[:120],
@@ -1246,7 +1246,7 @@ class AudioSocketMediaRuntime:
continue
total_audio_bytes += len(synthesis.audio_bytes)
if not first_frame_sent:
logger.warning(
logger.info(
"audiosocket.tts_ready session_id=%s greeting=%s synth_ms=%s audio_bytes=%s",
actor.registration.voice_session_id,
is_greeting,
@@ -1270,7 +1270,7 @@ class AudioSocketMediaRuntime:
await self._write_audio_packet(actor, frame)
if not first_frame_sent:
first_frame_sent = True
logger.warning(
logger.info(
"audiosocket.first_frame session_id=%s greeting=%s frame_bytes=%s",
actor.registration.voice_session_id,
is_greeting,
@@ -1304,7 +1304,7 @@ class AudioSocketMediaRuntime:
phase=reply_phase,
status="delivered",
)
logger.warning(
logger.info(
"audiosocket.reply_delivered session_id=%s greeting=%s phase=%s",
actor.registration.voice_session_id,
is_greeting,
@@ -1331,7 +1331,7 @@ class AudioSocketMediaRuntime:
continue
if not actor.keepalive_loop_logged:
actor.keepalive_loop_logged = True
logger.warning(
logger.info(
"audiosocket.keepalive_loop session_id=%s state=%s frame_bytes=%s",
actor.registration.voice_session_id,
actor.state,
@@ -1349,7 +1349,7 @@ class AudioSocketMediaRuntime:
return
if actor.state == state and (handoff_reason or None) is None:
return
logger.warning(
logger.info(
"audiosocket.state session_id=%s from_state=%s to_state=%s handoff_reason=%s",
actor.registration.voice_session_id,
actor.state,
@@ -1377,7 +1377,7 @@ class AudioSocketMediaRuntime:
if actor.closed:
return
actor.closed = True
logger.warning(
logger.info(
"audiosocket.cleanup session_id=%s reason=%s error=%s first_pcm=%s",
actor.registration.voice_session_id,
reason,
+3 -4
View File
@@ -66,14 +66,13 @@ def create_event(payload: AuditEventIn) -> AuditEvent:
def list_events(actor: str | None = None, action: str | None = None, limit: int = 100) -> list[AuditEvent]:
session = get_session()
try:
stmt = select(AuditEventRow).order_by(AuditEventRow.id.asc())
stmt = select(AuditEventRow).order_by(AuditEventRow.id.desc())
if actor:
stmt = stmt.where(AuditEventRow.actor == actor)
if action:
stmt = stmt.where(AuditEventRow.action == action)
rows = session.execute(stmt).scalars().all()
rows = rows[-limit:]
return [_to_out(r) for r in rows]
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [_to_out(r) for r in reversed(rows)]
finally:
session.close()
+3 -4
View File
@@ -101,11 +101,10 @@ def create_message(payload: EmailMessageIn) -> EmailMessageOut:
def list_messages(from_email: str | None = None, limit: int = 100) -> list[EmailMessageOut]:
session = get_session()
try:
stmt = select(EmailMessageRow).order_by(EmailMessageRow.id.asc())
stmt = select(EmailMessageRow).order_by(EmailMessageRow.id.desc())
if from_email:
stmt = stmt.where(EmailMessageRow.from_email == from_email)
rows = session.execute(stmt).scalars().all()
rows = rows[-limit:]
return [_to_out(row) for row in rows]
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [_to_out(row) for row in reversed(rows)]
finally:
session.close()
+2 -2
View File
@@ -41,8 +41,8 @@ def list_outbox(
stmt = select(EventOutboxRow).order_by(EventOutboxRow.id.desc())
if status:
stmt = stmt.where(EventOutboxRow.status == status)
rows = session.execute(stmt).scalars().all()
return [outbox_item_from_row(row) for row in rows[:limit]]
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [outbox_item_from_row(row) for row in rows]
finally:
session.close()
+2 -2
View File
@@ -295,8 +295,8 @@ def list_interactions(
if assigned_to:
stmt = stmt.where(Interaction.assigned_to == assigned_to)
rows = session.execute(stmt).scalars().all()
return [_to_out(r) for r in rows[:limit]]
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [_to_out(r) for r in rows]
finally:
session.close()
+2 -2
View File
@@ -635,8 +635,8 @@ def list_sessions(
stmt = stmt.where(IvrSessionRow.interaction_id == interaction_id)
if status:
stmt = stmt.where(IvrSessionRow.status == status)
rows = session.execute(stmt).scalars().all()
return [_to_session_out(row) for row in rows[:limit]]
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [_to_session_out(row) for row in rows]
finally:
session.close()
+2 -2
View File
@@ -364,8 +364,8 @@ def list_recordings(
stmt = stmt.where(CallRecordingRow.interaction_id == interaction_id)
if status:
stmt = stmt.where(CallRecordingRow.status == status)
rows = session.execute(stmt).scalars().all()
return [_to_out(row) for row in rows[:limit]]
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [_to_out(row) for row in rows]
finally:
session.close()
+9 -9
View File
@@ -244,17 +244,17 @@ def _load_fact_records(
queue_id: str | None,
channel: str | None,
) -> list[dict[str, Any]]:
rows = session.execute(
select(ReportingInteractionFactRow).order_by(ReportingInteractionFactRow.id.asc())
).scalars().all()
records = [_serialize_fact_row(row) for row in rows]
stmt = select(ReportingInteractionFactRow).order_by(ReportingInteractionFactRow.id.asc())
if queue_id:
records = [item for item in records if item["queue_id"] == queue_id]
stmt = stmt.where(ReportingInteractionFactRow.queue_id == queue_id)
if channel:
records = [item for item in records if item["channel"] == channel]
if dt_from or dt_to:
records = [item for item in records if _in_range(item["created_at"], dt_from, dt_to)]
return records
stmt = stmt.where(ReportingInteractionFactRow.channel == channel)
if dt_from:
stmt = stmt.where(ReportingInteractionFactRow.created_at >= dt_from.isoformat())
if dt_to:
stmt = stmt.where(ReportingInteractionFactRow.created_at < dt_to.isoformat())
rows = session.execute(stmt).scalars().all()
return [_serialize_fact_row(row) for row in rows]
def _load_interactions_map(session, interaction_ids: list[str]) -> dict[str, Interaction]:
+1 -1
View File
@@ -188,7 +188,7 @@ def live_calls(
session = get_session()
try:
event_rows = session.execute(
select(VoiceEventRow).order_by(VoiceEventRow.id.desc())
select(VoiceEventRow).order_by(VoiceEventRow.id.desc()).limit(500)
).scalars().all()
interaction_rows = session.execute(
select(Interaction).order_by(Interaction.id.desc())
+8 -8
View File
@@ -1631,12 +1631,11 @@ def list_messages(
) -> list[TelegramWebhookOut]:
session = get_session()
try:
stmt = select(TelegramMessageRow).order_by(TelegramMessageRow.id.asc())
stmt = select(TelegramMessageRow).order_by(TelegramMessageRow.id.desc())
if chat_id:
stmt = stmt.where(TelegramMessageRow.chat_id == chat_id)
rows = session.execute(stmt).scalars().all()
rows = rows[-limit:]
return [_to_webhook_out(r) for r in rows]
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [_to_webhook_out(r) for r in reversed(rows)]
finally:
session.close()
@@ -1655,8 +1654,8 @@ def list_threads(
stmt = select(TelegramThreadRow).order_by(TelegramThreadRow.last_message_at.desc())
if status:
stmt = stmt.where(TelegramThreadRow.status == status)
rows = session.execute(stmt).scalars().all()
return [_to_thread_out(row) for row in rows[:limit]]
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [_to_thread_out(row) for row in rows]
finally:
session.close()
@@ -1709,9 +1708,10 @@ def get_thread_messages(
rows = session.execute(
select(TelegramMessageRow)
.where(TelegramMessageRow.thread_id == thread.thread_id)
.order_by(TelegramMessageRow.id.asc())
.order_by(TelegramMessageRow.id.desc())
.limit(max(limit, 1))
).scalars().all()
return [_to_message_out(row) for row in rows[-limit:]]
return [_to_message_out(row) for row in reversed(rows)]
finally:
session.close()
+4 -3
View File
@@ -120,8 +120,9 @@ def list_voice_events(
) -> list[VoiceEventOut]:
session = get_session()
try:
rows = session.execute(select(VoiceEventRow).order_by(VoiceEventRow.id.asc())).scalars().all()
rows = rows[-limit:]
return [_to_out(r) for r in rows]
rows = session.execute(
select(VoiceEventRow).order_by(VoiceEventRow.id.desc()).limit(max(limit, 1))
).scalars().all()
return [_to_out(r) for r in reversed(rows)]
finally:
session.close()
+3 -4
View File
@@ -111,11 +111,10 @@ def create_message(payload: WebchatMessageIn) -> WebchatMessageOut:
def list_messages(session_id: str | None = None, limit: int = 100) -> list[WebchatMessageOut]:
session = get_session()
try:
stmt = select(WebchatMessageRow).order_by(WebchatMessageRow.id.asc())
stmt = select(WebchatMessageRow).order_by(WebchatMessageRow.id.desc())
if session_id:
stmt = stmt.where(WebchatMessageRow.session_id == session_id)
rows = session.execute(stmt).scalars().all()
rows = rows[-limit:]
return [_to_out(row) for row in rows]
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [_to_out(row) for row in reversed(rows)]
finally:
session.close()
+8 -8
View File
@@ -1883,12 +1883,11 @@ def list_messages(
) -> list[WhatsAppWebhookOut]:
session = get_session()
try:
stmt = select(WhatsAppMessageRow).order_by(WhatsAppMessageRow.id.asc())
stmt = select(WhatsAppMessageRow).order_by(WhatsAppMessageRow.id.desc())
if chat_id:
stmt = stmt.where(WhatsAppMessageRow.chat_id == chat_id)
rows = session.execute(stmt).scalars().all()
rows = rows[-limit:]
return [_to_webhook_out(r) for r in rows]
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [_to_webhook_out(r) for r in reversed(rows)]
finally:
session.close()
@@ -1907,10 +1906,10 @@ def list_threads(
stmt = select(WhatsAppThreadRow).order_by(WhatsAppThreadRow.last_message_at.desc())
if status:
stmt = stmt.where(WhatsAppThreadRow.status == status)
rows = session.execute(stmt).scalars().all()
rows = session.execute(stmt.limit(max(limit, 1))).scalars().all()
return [
_to_thread_out(row, unread_count=_thread_unread_count(session, row.thread_id))
for row in rows[:limit]
for row in rows
]
finally:
session.close()
@@ -1965,9 +1964,10 @@ def get_thread_messages(
rows = session.execute(
select(WhatsAppMessageRow)
.where(WhatsAppMessageRow.thread_id == thread.thread_id)
.order_by(WhatsAppMessageRow.id.asc())
.order_by(WhatsAppMessageRow.id.desc())
.limit(max(limit, 1))
).scalars().all()
return [_to_message_out(row) for row in rows[-limit:]]
return [_to_message_out(row) for row in reversed(rows)]
finally:
session.close()