Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions bases/lif/advisor_restapi/core.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import os
import uuid
from typing import Any, Dict, List
Expand Down Expand Up @@ -285,6 +286,17 @@ async def continue_conversation(question: Question, username: str = Depends(get_
)


async def _safe_summarize(agent, task, username):
"""Run summarization in the background; swallows errors."""
try:
response = await agent.ask_agent(
task, "Summarize our conversation extracting metadata about the conversation and then save it"
)
logger.info(f"Summarization response for {username}: {response}")
except Exception:
logger.exception(f"Background summarization failed for {username}")


@app.post("/logout", response_model=LogoutResponse)
async def logout(username: str = Depends(get_current_user)) -> LogoutResponse:
"""
Expand All @@ -297,12 +309,7 @@ async def logout(username: str = Depends(get_current_user)) -> LogoutResponse:
if state and state.get("lif_ai_agent"):
agent = state["lif_ai_agent"]
task = "save_interaction_summary"

# TODO(#986): move this hard-coded query prompt to env/config
response = await agent.ask_agent(
task, "Summarize our conversation extracting metadata about the conversation and then save it"
)
logger.info(f"Summarization response: {response}")
asyncio.create_task(_safe_summarize(agent, task, username))

conversation_states.pop(username, None)
refresh_tokens_store.pop(username, None)
Expand Down
31 changes: 30 additions & 1 deletion test/bases/lif/advisor_restapi/test_core.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
from unittest.mock import AsyncMock, patch

import pytest
Expand Down Expand Up @@ -397,8 +398,10 @@ async def test_logout():
client=client, access_token=response_access_token, expected_response=logout_expected_response
)

# Verify the agent
# Wait for background summarization task to complete
await asyncio.sleep(0.1)

# Verify the agent was called (background task ran)
mocked_ai_agent.ask_agent.assert_awaited_with(
"save_interaction_summary",
"Summarize our conversation extracting metadata about the conversation and then save it",
Expand Down Expand Up @@ -535,3 +538,29 @@ async def test_logout_succeeds_when_session_state_already_cleared():
assert logout_response.status_code == 200, logout_response.text
assert logout_response.json() == {"success": True}
mocked_ai_agent.ask_agent.assert_not_awaited()


@pytest.mark.asyncio
async def test_logout_succeeds_even_when_summarization_fails():
"""Logout must succeed even if background summarization raises an exception."""
mocked_ai_agent = MockAgent()
mocked_ai_agent.ask_agent = AsyncMock(side_effect=RuntimeError("LLM unavailable"))

async with get_client() as client:
with patch.object(LIFAIAgent, "setup", new=AsyncMock(return_value=mocked_ai_agent)):
login_response_json = await login_user_to_lif_advisor(
client=client, username=USER_DETAILS_ALEX["username"], password=USER_DETAILS_ALEX["password"]
)
access_token = login_response_json.get("access_token")

# Logout — should return success immediately even though summarization will fail
logout_response = await client.post("/logout", headers={"Authorization": f"Bearer {access_token}"})

assert logout_response.status_code == 200, logout_response.text
assert logout_response.json() == {"success": True}

# Wait for background task to attempt and fail
await asyncio.sleep(0.1)

# Verify the agent was called (background task ran) but failed silently
mocked_ai_agent.ask_agent.assert_awaited()
Loading