fix: add explicit timeouts to httpx clients to prevent ReadTimeout errors (#8538)
This commit addresses the httpx.ReadTimeout error detected in production by adding explicit timeout configurations to several httpx client usages: 1. MCP SSE client: Pass mcp_connect_to_server_timeout (30s) to sse_client() 2. MCP StreamableHTTP client: Pass mcp_connect_to_server_timeout (30s) to streamablehttp_client() 3. OpenAI model list API: Add 30s timeout with 10s connect timeout 4. Google AI model list/details API: Add 30s timeout with 10s connect timeout Previously, these httpx clients were created without explicit timeouts, which could cause ReadTimeout errors when remote servers are slow to respond. Fixes #8073 🤖 Generated with [Letta Code](https://letta.com) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: datadog-official[bot] <datadog-official[bot]@users.noreply.github.com> Co-authored-by: Kian Jones <11655409+kianjones9@users.noreply.github.com>
This commit is contained in:
committed by
Sarah Wooders
parent
85c40c8154
commit
5fbf8f93e2
@@ -82,7 +82,8 @@ async def google_ai_get_model_list_async(
|
||||
# Determine if we need to close the client at the end
|
||||
close_client = False
|
||||
if client is None:
|
||||
client = httpx.AsyncClient()
|
||||
# Use explicit timeout to prevent httpx.ReadTimeout errors
|
||||
client = httpx.AsyncClient(timeout=httpx.Timeout(30.0, connect=10.0))
|
||||
close_client = True
|
||||
|
||||
try:
|
||||
@@ -129,7 +130,8 @@ async def google_ai_get_model_details_async(
|
||||
# Determine if we need to close the client at the end
|
||||
close_client = False
|
||||
if client is None:
|
||||
client = httpx.AsyncClient()
|
||||
# Use explicit timeout to prevent httpx.ReadTimeout errors
|
||||
client = httpx.AsyncClient(timeout=httpx.Timeout(30.0, connect=10.0))
|
||||
close_client = True
|
||||
|
||||
try:
|
||||
|
||||
@@ -72,7 +72,8 @@ async def openai_get_model_list_async(
|
||||
# Use provided client or create a new one
|
||||
close_client = False
|
||||
if client is None:
|
||||
client = httpx.AsyncClient()
|
||||
# Use explicit timeout to prevent httpx.ReadTimeout errors
|
||||
client = httpx.AsyncClient(timeout=httpx.Timeout(30.0, connect=10.0))
|
||||
close_client = True
|
||||
|
||||
try:
|
||||
|
||||
@@ -7,6 +7,7 @@ from mcp.client.sse import sse_client
|
||||
from letta.functions.mcp_client.types import SSEServerConfig
|
||||
from letta.log import get_logger
|
||||
from letta.services.mcp.base_client import AsyncBaseMCPClient
|
||||
from letta.settings import tool_settings
|
||||
|
||||
# see: https://modelcontextprotocol.io/quickstart/user
|
||||
MCP_CONFIG_TOPLEVEL_KEY = "mcpServers"
|
||||
@@ -33,10 +34,12 @@ class AsyncSSEMCPClient(AsyncBaseMCPClient):
|
||||
headers[self.AGENT_ID_HEADER] = self.agent_id
|
||||
|
||||
# Use OAuth provider if available, otherwise use regular headers
|
||||
# Pass timeout to prevent httpx.ReadTimeout errors on slow connections
|
||||
timeout = tool_settings.mcp_connect_to_server_timeout
|
||||
if self.oauth_provider:
|
||||
sse_cm = sse_client(url=server_config.server_url, headers=headers if headers else None, auth=self.oauth_provider)
|
||||
sse_cm = sse_client(url=server_config.server_url, headers=headers if headers else None, auth=self.oauth_provider, timeout=timeout)
|
||||
else:
|
||||
sse_cm = sse_client(url=server_config.server_url, headers=headers if headers else None)
|
||||
sse_cm = sse_client(url=server_config.server_url, headers=headers if headers else None, timeout=timeout)
|
||||
|
||||
sse_transport = await self.exit_stack.enter_async_context(sse_cm)
|
||||
self.stdio, self.write = sse_transport
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
|
||||
from mcp import ClientSession
|
||||
@@ -7,6 +8,7 @@ from mcp.client.streamable_http import streamablehttp_client
|
||||
from letta.functions.mcp_client.types import BaseServerConfig, StreamableHTTPServerConfig
|
||||
from letta.log import get_logger
|
||||
from letta.services.mcp.base_client import AsyncBaseMCPClient
|
||||
from letta.settings import tool_settings
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -38,16 +40,18 @@ class AsyncStreamableHTTPMCPClient(AsyncBaseMCPClient):
|
||||
headers[self.AGENT_ID_HEADER] = self.agent_id
|
||||
|
||||
# Use OAuth provider if available, otherwise use regular headers
|
||||
# Pass timeout to prevent httpx.ReadTimeout errors on slow connections
|
||||
timeout = timedelta(seconds=tool_settings.mcp_connect_to_server_timeout)
|
||||
if self.oauth_provider:
|
||||
streamable_http_cm = streamablehttp_client(
|
||||
server_config.server_url, headers=headers if headers else None, auth=self.oauth_provider
|
||||
server_config.server_url, headers=headers if headers else None, auth=self.oauth_provider, timeout=timeout
|
||||
)
|
||||
else:
|
||||
# Use streamablehttp_client context manager with headers if provided
|
||||
if headers:
|
||||
streamable_http_cm = streamablehttp_client(server_config.server_url, headers=headers)
|
||||
streamable_http_cm = streamablehttp_client(server_config.server_url, headers=headers, timeout=timeout)
|
||||
else:
|
||||
streamable_http_cm = streamablehttp_client(server_config.server_url)
|
||||
streamable_http_cm = streamablehttp_client(server_config.server_url, timeout=timeout)
|
||||
|
||||
read_stream, write_stream, _ = await self.exit_stack.enter_async_context(streamable_http_cm)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user