mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-21 17:25:26 -04:00
fix(code): offload create_model in server graph factory to unblock Codex (#4324)
Fix `Blocking call to os.mkdir` server startup failure when using `openai_codex` (ChatGPT) models. --- The async langgraph dev server graph factory (`server_graph._make_graph`) called `create_model` synchronously on the event loop. For the `openai_codex` provider, `create_model` → `build_chat_model` → `provider.get_token()` acquires a cross-process file lock that *unconditionally* calls `os.mkdir` (on every call, regardless of token freshness). The dev server runs `blockbuster`, which rejects blocking IO on the loop, so graph readiness fails with a 500: `Blocking call to os.mkdir`. This made the server unstartable for ChatGPT/Codex users, and re-running `/auth` did not help because the `mkdir` is unconditional. Fix: offload `create_model` via `asyncio.to_thread`, matching the existing pattern in `configurable_model._apply_overrides_async`. Made by [Open SWE](https://openswe.vercel.app/agents/2b7c33d3-d7a8-d82b-c254-affd26a14334) ## References - Plan: https://openswe.vercel.app/agents/2b7c33d3-d7a8-d82b-c254-affd26a14334/plan --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
@@ -163,7 +163,13 @@ async def _make_graph() -> Any: # noqa: ANN401
|
||||
if project_context is not None:
|
||||
settings.reload_from_environment(start_path=project_context.user_cwd)
|
||||
|
||||
result = create_model(config.model, extra_kwargs=config.model_params)
|
||||
# Offload to a worker thread: `create_model` does blocking disk IO for some
|
||||
# providers (e.g. the `openai_codex` token store currently acquires a file
|
||||
# lock via `langchain-openai` that calls `os.mkdir`), which `blockbuster`
|
||||
# rejects on the server event loop.
|
||||
result = await asyncio.to_thread(
|
||||
create_model, config.model, extra_kwargs=config.model_params
|
||||
)
|
||||
result.apply_to_settings()
|
||||
|
||||
tools, mcp_server_info = await _build_tools(config, project_context)
|
||||
|
||||
@@ -77,12 +77,17 @@ class TestServerGraph:
|
||||
mcp_server_info = [SimpleNamespace(name="docs")]
|
||||
loop_thread_id = threading.get_ident()
|
||||
create_cli_agent_thread_ids: list[int] = []
|
||||
create_model_thread_ids: list[int] = []
|
||||
warm_import_thread_ids: list[int] = []
|
||||
|
||||
def create_cli_agent_side_effect(**_: object) -> tuple[object, object]:
|
||||
create_cli_agent_thread_ids.append(threading.get_ident())
|
||||
return graph_obj, object()
|
||||
|
||||
def create_model_side_effect(*_: object, **__: object) -> object:
|
||||
create_model_thread_ids.append(threading.get_ident())
|
||||
return model_result
|
||||
|
||||
def warm_import_side_effect() -> None:
|
||||
warm_import_thread_ids.append(threading.get_ident())
|
||||
|
||||
@@ -100,7 +105,7 @@ class TestServerGraph:
|
||||
)
|
||||
config_module = _module_with_attrs(
|
||||
"deepagents_code.config",
|
||||
create_model=MagicMock(return_value=model_result),
|
||||
create_model=MagicMock(side_effect=create_model_side_effect),
|
||||
settings=SimpleNamespace(
|
||||
has_tavily=False,
|
||||
reload_from_environment=MagicMock(),
|
||||
@@ -169,6 +174,11 @@ class TestServerGraph:
|
||||
assert warm_import_thread_ids[0] != loop_thread_id
|
||||
assert create_cli_agent_thread_ids
|
||||
assert create_cli_agent_thread_ids[0] != loop_thread_id
|
||||
# `create_model` must run off the loop thread: it does blocking disk IO
|
||||
# for some providers (e.g. the `openai_codex` token store calls
|
||||
# `os.mkdir`), which `blockbuster` rejects on the server event loop.
|
||||
assert create_model_thread_ids
|
||||
assert create_model_thread_ids[0] != loop_thread_id
|
||||
kwargs = resolve_mcp_tools.await_args_list[0].kwargs
|
||||
assert kwargs["explicit_config_path"] is None
|
||||
assert kwargs["no_mcp"] is False
|
||||
|
||||
Reference in New Issue
Block a user