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 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-08 15:43:22 -04:00
parent 94b428d168
commit df46046c86
2 changed files with 35 additions and 15 deletions

View File

@@ -145,11 +145,17 @@ async def post_message(
plan = await get_user_plan(current_user.account_id, db) 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: try:
ai_content, suggested_flows, chat = await assistant_chat_service.send_message( ai_content, suggested_flows, chat = await assistant_chat_service.send_message(
chat_id=chat_id, chat_id=chat_id,
user_id=current_user.id, user_id=user_id,
account_id=current_user.account_id, account_id=account_id,
message=data.message, message=data.message,
db=db, db=db,
) )
@@ -159,8 +165,8 @@ async def post_message(
logger.exception("Assistant chat message failed: %s", e) logger.exception("Assistant chat message failed: %s", e)
await db.rollback() await db.rollback()
await record_ai_usage( await record_ai_usage(
user_id=current_user.id, user_id=user_id,
account_id=current_user.account_id, account_id=account_id,
conversation_id=None, conversation_id=None,
generation_type="assistant_message", generation_type="assistant_message",
tier=plan, tier=plan,
@@ -180,8 +186,8 @@ async def post_message(
) )
await record_ai_usage( await record_ai_usage(
user_id=current_user.id, user_id=user_id,
account_id=current_user.account_id, account_id=account_id,
conversation_id=None, conversation_id=None,
generation_type="assistant_message", generation_type="assistant_message",
tier=plan, tier=plan,

View File

@@ -189,15 +189,29 @@ async def _call_anthropic_cached(
} }
] ]
response = await client.beta.messages.create( try:
model=settings.AI_MODEL_ANTHROPIC, response = await client.beta.messages.create(
max_tokens=max_tokens, model=settings.AI_MODEL_ANTHROPIC,
system=system_blocks, max_tokens=max_tokens,
messages=messages, system=system_blocks,
mcp_servers=mcp_servers, messages=messages,
tools=tools, mcp_servers=mcp_servers,
betas=["mcp-client-2025-11-20"], 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 # Extract text from response — MCP responses can have multiple block
# types (text, mcp_tool_use, mcp_tool_result). We join all text blocks. # types (text, mcp_tool_use, mcp_tool_result). We join all text blocks.