iflytek/astron-agent · critical · CustomException
SPARK_REQUEST_ERROR
SPARK_REQUEST_ERROR
Error message
WebSocket connection closed
What it means
SPARK_REQUEST_ERROR raised inside _recv_messages when websockets.ConnectionClosed escapes the recv loop — the Spark WebSocket dropped before a final (status==2) frame with the function_call was received. The client never got a complete answer, so no function name/arguments can be returned.
Solutions
- Retry async_call_spark_fc once or twice with backoff — a dropped connection is often transient
- Inspect the span's error events and Spark response frames logged before the close for a server-side close code/reason
- Verify network path (proxy, firewall, LB idle timeout) allows long-lived outbound WSS connections
- Check Spark service status / your appid quota and concurrency limits that may cause the server to drop connections
Example fix
// before
name, usage, args = await spark_fc.async_call_spark_fc(user_input, span)
// after
for attempt in range(3):
try:
name, usage, args = await spark_fc.async_call_spark_fc(user_input, span)
break
except CustomException as e:
if 'WebSocket connection closed' in str(e.err_msg) and attempt < 2:
await asyncio.sleep(2 ** attempt)
continue
raise Defensive patterns
Strategy: retry
Validate before calling
# preflight: check endpoint reachability import socket host = urlparse(model_url).hostname socket.getaddrinfo(host, 443) # raises early on DNS/network issues
Try / catch
for attempt in range(3):
try:
return await spark_fc.async_call_spark_fc(user_input, span)
except CustomException as e:
if 'WebSocket connection closed' in str(e.err_msg) and attempt < 2:
await asyncio.sleep(2 ** attempt)
continue
raise Prevention
- Implement automatic retry with exponential backoff on transient WS drops
- Ensure proxies/LB idle timeouts exceed the expected request duration
- Monitor Spark endpoint availability and alert on elevated drop rates
- Keep requests short; chunk very long conversations
When it happens
Trigger: During await ws_handle.recv() in the receive loop, the WebSocket protocol transitions to CLOSED (server closed the connection, network drop, or idle-connection teardown) before the function-call result frame arrives.
Common situations: Spark gateway closing long-idle connections; corporate proxy/firewall killing WebSockets; server restart or load-balancer timeout mid-request; unstable network between service and Spark endpoint.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- SPARK_REQUEST_ERROR
- RESPONSE_FAILED
- WebSocketClientNotConnectedError
- e.status
- errorMessage (dynamic; error.message or fallback 'Failed to…
AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12).
Data as JSON: /api/errors/9d0c567172f8867a.
Report an issue: GitHub.
Appendix: source
Thrown at core/workflow/infra/providers/llm/iflytek_spark/spark_fc_llm.py:137
cause_error="Sensitive content detected, LLM did not find function_call field",
)
if status != 2:
continue
token_usage = msg["payload"]["usage"]["text"]
if "function_call" not in msg["payload"]["choices"]["text"][0]:
raise CustomException(
err_code=CodeEnum.SPARK_FUNCTION_NOT_CHOICE_ERROR,
err_msg="Cannot find function_call field in LLM response",
cause_error="Cannot find function_call field in LLM response",
)
name = msg["payload"]["choices"]["text"][0]["function_call"]["name"]
arguments = msg["payload"]["choices"]["text"][0]["function_call"][
"arguments"
]
return name, token_usage, arguments
except websockets.ConnectionClosed:
raise CustomException(
err_code=CodeEnum.SPARK_REQUEST_ERROR,
err_msg="WebSocket connection closed",
cause_error="WebSocket connection closed",
)
except Exception as e:
raise CustomException(
err_code=CodeEnum.SPARK_REQUEST_ERROR,
err_msg=f"{e}",
cause_error=f"{e}",
)
async def _process_message(
self, msg: dict, span: Span
) -> tuple[str | None, dict | None, str | None]:
"""
Process a single function call message from the API response.
:param msg: Message dictionary from Spark APIView on GitHub (pinned to 5e758547a8)