From df46046c86865fcaa3cf66b5b91d81ffaf606f9b Mon Sep 17 00:00:00 2001 From: chihlasm Date: Sun, 8 Mar 2026 15:43:22 -0400 Subject: [PATCH] fix: resolve MissingGreenlet crash and add MCP fallback in AI Assistant Capture user_id/account_id before try block so error handler survives db.rollback() without triggering lazy loads in async context. Add retry-without-MCP fallback when Anthropic MCP server returns rate limit or connection errors. Fixes PYTHON-FASTAPI-3, PYTHON-FASTAPI-4 Co-Authored-By: Claude Opus 4.6 --- backend/app/api/endpoints/assistant_chat.py | 18 +++++++---- .../app/services/assistant_chat_service.py | 32 +++++++++++++------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/backend/app/api/endpoints/assistant_chat.py b/backend/app/api/endpoints/assistant_chat.py index 83422367..3bcc1f5e 100644 --- a/backend/app/api/endpoints/assistant_chat.py +++ b/backend/app/api/endpoints/assistant_chat.py @@ -145,11 +145,17 @@ async def post_message( plan = await get_user_plan(current_user.account_id, db) + # Capture scalar fields before the try block — after db.rollback() + # the ORM objects are expired and accessing attributes triggers a + # lazy load, which crashes in async context (MissingGreenlet). + user_id = current_user.id + account_id = current_user.account_id + try: ai_content, suggested_flows, chat = await assistant_chat_service.send_message( chat_id=chat_id, - user_id=current_user.id, - account_id=current_user.account_id, + user_id=user_id, + account_id=account_id, message=data.message, db=db, ) @@ -159,8 +165,8 @@ async def post_message( logger.exception("Assistant chat message failed: %s", e) await db.rollback() await record_ai_usage( - user_id=current_user.id, - account_id=current_user.account_id, + user_id=user_id, + account_id=account_id, conversation_id=None, generation_type="assistant_message", tier=plan, @@ -180,8 +186,8 @@ async def post_message( ) await record_ai_usage( - user_id=current_user.id, - account_id=current_user.account_id, + user_id=user_id, + account_id=account_id, conversation_id=None, generation_type="assistant_message", tier=plan, diff --git a/backend/app/services/assistant_chat_service.py b/backend/app/services/assistant_chat_service.py index 86390608..9508fe56 100644 --- a/backend/app/services/assistant_chat_service.py +++ b/backend/app/services/assistant_chat_service.py @@ -189,15 +189,29 @@ async def _call_anthropic_cached( } ] - response = await client.beta.messages.create( - model=settings.AI_MODEL_ANTHROPIC, - max_tokens=max_tokens, - system=system_blocks, - messages=messages, - mcp_servers=mcp_servers, - tools=tools, - betas=["mcp-client-2025-11-20"], - ) + try: + response = await client.beta.messages.create( + model=settings.AI_MODEL_ANTHROPIC, + max_tokens=max_tokens, + system=system_blocks, + messages=messages, + mcp_servers=mcp_servers, + tools=tools, + betas=["mcp-client-2025-11-20"], + ) + except anthropic.BadRequestError as e: + # MCP server failures (rate limits, connection errors) should not + # block the assistant entirely — retry without MCP tools. + if "MCP server" in str(e) and mcp_servers is not anthropic.NOT_GIVEN: + logger.warning("MCP server error, retrying without MCP: %s", e) + response = await client.beta.messages.create( + model=settings.AI_MODEL_ANTHROPIC, + max_tokens=max_tokens, + system=system_blocks, + messages=messages, + ) + else: + raise # Extract text from response — MCP responses can have multiple block # types (text, mcp_tool_use, mcp_tool_result). We join all text blocks.