[DashScope] reasoning parameter on ChatOpenAI breaks subagent tool calling — use separate models as workaround

Bug: reasoning parameter causes tool_call chain error in DeepAgents subagent

Environment

Package Version
deepagents 0.4.11
langchain 1.2.12
langchain-openai 1.1.11
langgraph 1.1.3
Python 3.12+
LLM Provider Alibaba DashScope (OpenAI-compatible)
Model qwen3.5-122b-a10b

Description

When using ChatOpenAI with the reasoning parameter and create_deep_agent with subagents, the main agent works correctly (delegates task to subagent), but the subagent crashes during its internal tool call chain.

The reasoning parameter triggers the Responses API code path (_construct_lc_result_from_responses_api), which formats messages differently. When the subagent inherits this model and performs multi-step tool calling, the tool_call → tool_response message pairing breaks, and the LLM API rejects the request.

Reproduction Code

from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent

# Tool functions (simplified)
async def query_kyc(company_name: str) -> str:
    """查询企业基本工商信息"""
    return "企业名称:xxx有限公司\n法人:张三\n..."

async def query_field(company_name: str, query: str) -> str:
    """根据用户意图查询企业详细字段"""
    return "纳税信用等级:A"

model = ChatOpenAI(
    model="qwen3.5-122b-a10b",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key="sk-xxx",
    timeout=60,
    reasoning={              # ← This causes the issue
        "effort": "medium",
        "summary": "auto"
    }
)

subagent = {
    "name": "Business_agent",
    "description": "Query company information.",
    "system_prompt": "You are a business info assistant. Use tools to query data.",
    "tools": [query_kyc, query_field],
}

agent = create_deep_agent(
    model=model,
    system_prompt="You are an enterprise info expert.",
    subagents=[subagent]
)

import asyncio
async def main():
    async for chunk in agent.astream({"messages": [
        {"role": "user", "content": "查询xxx有限公司的基本信息,纳税等级,开户行,画像和标签"}
    ]}):
        print(chunk)

asyncio.run(main())

Actual Behavior

The main agent successfully delegates to the subagent via the task tool. Then the subagent crashes when it tries to call its own tools:

ValueError: ResponseError(code='server_error', message='<400> InternalError.Algo.InvalidParameter:
An assistant message with "tool_calls" must be followed by tool messages responding to each "tool_call_id".
The following tool_call_ids did not have response messages: message[4].role')

Full Stack Trace

Traceback (most recent call last):
  File "core/main_agent.py", line 63, in <module>
    asyncio.run(t1.quick_start(messages))
  File "core/main_agent.py", line 45, in quick_start
    async for chunk in agent.astream({"messages": messages}):
  File "langgraph/pregel/main.py", line 3111, in astream
    async for _ in runner.atick(
  File "langgraph/pregel/_runner.py", line 304, in atick
    await arun_with_retry(
  File "langgraph/pregel/_retry.py", line 211, in arun_with_retry
    return await task.proc.ainvoke(task.input, config)

  # --- Main agent's tools node spawns the subagent ---
  File "langgraph/prebuilt/tool_node.py", line 846, in _afunc
    outputs = await asyncio.gather(*coros)
  File "langgraph/prebuilt/tool_node.py", line 1191, in _arun_one
    return await self._awrap_tool_call(tool_request, execute)
  File "deepagents/middleware/subagents.py", line 463, in atask
    result = await subagent.ainvoke(subagent_state)

  # --- Inside subagent: model node tries to call LLM ---
  File "langgraph/pregel/main.py", line 3462, in ainvoke
    async for chunk in self.astream(
  File "langgraph/pregel/main.py", line 3111, in astream
    async for _ in runner.atick(
  File "langchain/agents/factory.py", line 1352, in amodel_node
    result = await awrap_model_call_handler(request, _execute_model_async)

  # --- Through middleware chain ---
  File "langchain/agents/middleware/todo.py", line 253, in awrap_model_call
  File "deepagents/middleware/filesystem.py", line 1126, in awrap_model_call
  File "deepagents/middleware/summarization.py", line 1024, in awrap_model_call

  # --- LLM call crashes here ---
  File "langchain/agents/factory.py", line 1321, in _execute_model_async
    output = await model_.ainvoke(messages)
  File "langchain_openai/chat_models/base.py", line 1744, in _agenerate
    return _construct_lc_result_from_responses_api(
  File "langchain_openai/chat_models/base.py", line 4423, in _construct_lc_result_from_responses_api
    raise ValueError(response.error)
ValueError: ResponseError(code='server_error', message='<400> InternalError.Algo.InvalidParameter:
An assistant message with "tool_calls" must be followed by tool messages responding to each "tool_call_id".
The following tool_call_ids did not have response messages: message[4].role')
During task with name 'model' and id '0f42eef9-ac9e-b585-321b-e9a3f65283f8'
During task with name 'tools' and id 'cb8ef8e4-8e89-0d95-ac39-68555be375f7'

Expected Behavior

The reasoning parameter should work with subagents that perform multi-step tool calling, or the subagent should be able to override the model to avoid inheriting the Responses API code path.

Workaround

Use separate model instances — subagent config supports a model field to override the main agent’s model:

model_with_reasoning = ChatOpenAI(..., reasoning={"effort": "medium", "summary": "auto"})
model_basic = ChatOpenAI(...)  # no reasoning

agent = create_deep_agent(
    model=model_with_reasoning,  # main agent: reasoning ON
    subagents=[{
        "name": "Business_agent",
        "model": model_basic,    # subagent: reasoning OFF ← fixes the issue
        "tools": [query_kyc, query_field],
        ...
    }]
)

Analysis

Root Cause

The issue is primarily in DashScope’s Responses API compatibility layer, not in langchain-openai or DeepAgents.

How it happens

  1. The reasoning parameter causes langchain-openai to switch from the Chat Completions API path to the Responses API path (see _use_responses_api at base.py:1512: if self.reasoning is not None: return True).

  2. langchain-openai constructs the request using Responses API format — messages become a flat input array with typed blocks (reasoning, function_call, function_call_output, etc.) via _construct_responses_api_input at base.py:4225.

  3. DashScope’s OpenAI-compatible endpoint receives this Responses API format and internally converts it back to Chat Completions format for processing. Evidence: the error message uses Chat Completions terminology (“assistant message with tool_calls”) rather than Responses API terminology (“function_call” / “function_call_output”).

  4. During this internal conversion, when multiple tool-calling rounds are flattened into the input array (with interleaved reasoning, function_call, and function_call_output blocks), DashScope’s conversion logic loses the tool_call → tool_response pairing, causing the validation to fail at message[4].

  5. The main agent is unaffected because it only makes a single tool call (the task tool), so there’s no multi-round accumulation. The subagent crashes because it performs multi-step tool calling internally.

Summary

  • Qwen model: Not involved — the request never reaches the model.
  • DashScope compatibility layer: Primary issue — its Responses API → Chat Completions internal conversion breaks multi-round tool calling message pairing.
  • langchain-openai: No bug — the Responses API format it generates is likely valid for OpenAI’s native API, but DashScope’s compatibility layer cannot handle it correctly.
  • DeepAgents: No bug — it simply passes messages through; it is not responsible for API format conversion.

hi @yech

have you tried reasoning_effort="medium" insread of reasoning` field?

hi, I just tried it and it ran successfully.

The difference between the two:

reasoning dict:

(venv) python -m core.main_agent

User: Hello, I just want to know the basic information of xxx Technology Co., Ltd., tax level, account opening bank, as well as portrait and label!

============================================================

[company Basic info info] is called, parameter: xxx Technology Co., Ltd

[content_detail_query] is called with parameters: company=xxx Technology Co., Ltd., query=tax credit, fields=['TaxCreditList ']

[content_detail_query] is called, parameters: company=xxx Technology Co., Ltd., query=bank account opening bank, fields=['BankInfo ']

[content_detail_query] is called, parameters: company=xxx Technology Co., Ltd., query=enterprise tag, fields=['TagList ']

Traceback (most recent call last):

File “”, line 198, in _run_module_as_main

File “”, line 88, in _run_code

raise ValueError(response.error)

ValueError: ResponseError(code=‘server_error’, message=‘<400> InternalError.Algo.InvalidParameter: An assistant message with “tool_calls” must be followed by tool messages responding to each “tool_call_id”. The following tool_call_ids did not have response messages: message[4].role’)

During task with name ‘model’ and id ‘1119fad6-fb78-64aa-77d7-5474258633e6’

During task with name ‘tools’ and id ‘c516a6c4-6fe3-6d5a-a510-6d4c05c1c137’

reasoning_effort=“medium”:

Same as above, normal tool call and successful reply.

reasoning_effort avoids this path entirely.

Could you please tell me what solution this is and what are the differences? :partying_face: