diff --git a/services/ai_orchestrator_service/app.py b/services/ai_orchestrator_service/app.py index a46572e..55a8c0b 100644 --- a/services/ai_orchestrator_service/app.py +++ b/services/ai_orchestrator_service/app.py @@ -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, diff --git a/services/ai_orchestrator_service/voice.py b/services/ai_orchestrator_service/voice.py index 482a7ed..0d8b8e1 100644 --- a/services/ai_orchestrator_service/voice.py +++ b/services/ai_orchestrator_service/voice.py @@ -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) diff --git a/services/ai_orchestrator_service/voice_name_config.py b/services/ai_orchestrator_service/voice_name_config.py index dfc13fb..dce2ebc 100644 --- a/services/ai_orchestrator_service/voice_name_config.py +++ b/services/ai_orchestrator_service/voice_name_config.py @@ -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, diff --git a/services/ai_voice_runtime_service/app.py b/services/ai_voice_runtime_service/app.py index 2ed049b..97dc547 100644 --- a/services/ai_voice_runtime_service/app.py +++ b/services/ai_voice_runtime_service/app.py @@ -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: diff --git a/services/ai_voice_runtime_service/media_runtime.py b/services/ai_voice_runtime_service/media_runtime.py index ba93f5d..0dcc10e 100644 --- a/services/ai_voice_runtime_service/media_runtime.py +++ b/services/ai_voice_runtime_service/media_runtime.py @@ -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, diff --git a/services/audit_service/app.py b/services/audit_service/app.py index 0430771..1048d40 100644 --- a/services/audit_service/app.py +++ b/services/audit_service/app.py @@ -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() diff --git a/services/email_adapter_service/app.py b/services/email_adapter_service/app.py index 08e5f38..22cec85 100644 --- a/services/email_adapter_service/app.py +++ b/services/email_adapter_service/app.py @@ -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() diff --git a/services/event_bus_service/app.py b/services/event_bus_service/app.py index 997149c..e079a3a 100644 --- a/services/event_bus_service/app.py +++ b/services/event_bus_service/app.py @@ -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() diff --git a/services/interaction_service/app.py b/services/interaction_service/app.py index 57eb780..3945308 100644 --- a/services/interaction_service/app.py +++ b/services/interaction_service/app.py @@ -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() diff --git a/services/ivr_service/app.py b/services/ivr_service/app.py index 0015aef..cdcdb64 100644 --- a/services/ivr_service/app.py +++ b/services/ivr_service/app.py @@ -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() diff --git a/services/recording_service/app.py b/services/recording_service/app.py index b2d4071..4620acd 100644 --- a/services/recording_service/app.py +++ b/services/recording_service/app.py @@ -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() diff --git a/services/reporting_service/app.py b/services/reporting_service/app.py index 541e873..3524ef6 100644 --- a/services/reporting_service/app.py +++ b/services/reporting_service/app.py @@ -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]: diff --git a/services/supervisor_service/app.py b/services/supervisor_service/app.py index dcc66b8..4b28f47 100644 --- a/services/supervisor_service/app.py +++ b/services/supervisor_service/app.py @@ -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()) diff --git a/services/telegram_adapter_service/app.py b/services/telegram_adapter_service/app.py index 8f4da1e..e6bf2b4 100644 --- a/services/telegram_adapter_service/app.py +++ b/services/telegram_adapter_service/app.py @@ -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() diff --git a/services/voice_adapter_service/app.py b/services/voice_adapter_service/app.py index 08de4f4..53ca73f 100644 --- a/services/voice_adapter_service/app.py +++ b/services/voice_adapter_service/app.py @@ -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() diff --git a/services/webchat_adapter_service/app.py b/services/webchat_adapter_service/app.py index bc500b4..f5821b4 100644 --- a/services/webchat_adapter_service/app.py +++ b/services/webchat_adapter_service/app.py @@ -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() diff --git a/services/whatsapp_adapter_service/app.py b/services/whatsapp_adapter_service/app.py index 458509c..106f74f 100644 --- a/services/whatsapp_adapter_service/app.py +++ b/services/whatsapp_adapter_service/app.py @@ -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()