llamaIndex - ✅(Solved) Fix [Bug]: `parallel_tool_calls: null` sent to OpenAI API since 0.6.19, causing 400 BadRequestError [6 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
run-llama/llama_index#20814Fetched 2026-04-08 00:30:45
View on GitHub
Comments
0
Participants
1
Timeline
9
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×6labeled ×2closed ×1

Error Message

openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid type for 'parallel_tool_calls': expected a boolean, but got null instead.", 'type': 'invalid_request_error', 'param': 'parallel_tool_calls', 'code': 'invalid_type'}}

Root Cause

PR #20744 started forwarding allow_parallel_tool_calls to the API as parallel_tool_calls, but the default value of allow_parallel_tool_calls is None instead of a proper boolean. When no tools are present, None is passed directly to the OpenAI API which expects a bool.

Fix Action

Workaround

Downgrade to llama-index-llms-openai==0.6.18:

poetry add llama-index-llms-openai==0.6.18

PR fix notes

PR #20817: fix: exclude parallel_tool_calls when no tools are present

Description (problem / solution / changelog)

Summary

Fixes #20814

Since llama-index-llms-openai 0.6.19 (introduced by #20744), _prepare_chat_with_tools sends parallel_tool_calls: null to the OpenAI API when no tools are provided (tools=[]). The OpenAI API rejects this with a 400 BadRequestError:

openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid type for 'parallel_tool_calls': expected a boolean, but got null instead."}}

Root Cause

Line 1019 in base.py:

"parallel_tool_calls": allow_parallel_tool_calls if tool_specs else None,

When tool_specs is empty, this sends parallel_tool_calls: None (serialized as null) to the API.

Fix

Only include parallel_tool_calls in the request dict when tool_specs is non-empty. This matches the existing conditional pattern for tool_choice.

Test Results

73 passed, 14 skipped (2 consecutive clean runs). Updated the test_prepare_chat_with_tools_no_tools test to assert the key is absent rather than None.

Changed files

  • llama-index-integrations/llms/llama-index-llms-openai-like/llama_index/llms/openai_like/base.py (modified, +2/-2)
  • llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/base.py (modified, +6/-6)
  • llama-index-integrations/llms/llama-index-llms-openai/tests/test_llms_openai.py (modified, +3/-3)

PR #20820: fix: omit parallel_tool_calls when no tools to prevent null in API request

Description (problem / solution / changelog)

Problem

Since v0.6.19 (introduced by #20744), _prepare_chat_with_tools sends parallel_tool_calls: null to the OpenAI API when no tools are provided. The OpenAI API expects a boolean and rejects the request with:

openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid type for 'parallel_tool_calls': expected a boolean, but got null instead.", ...}}

Root Cause

The return dict unconditionally includes parallel_tool_calls:

return {
    "messages": messages,
    "tools": tool_specs or None,
    "tool_choice": resolve_tool_choice(tool_choice, tool_required) if tool_specs else None,
    "parallel_tool_calls": allow_parallel_tool_calls if tool_specs else None,  # ← sends None
    **kwargs,
}

When tool_specs is empty/falsy, all three tool-related keys are set to None and included in the payload sent to OpenAI.

Fix

Only include tools, tool_choice, and parallel_tool_calls keys in the result dict when tool_specs is non-empty. This prevents null values from being sent to the API.

result: Dict[str, Any] = {"messages": messages, **kwargs}
if tool_specs:
    result["tools"] = tool_specs
    result["tool_choice"] = resolve_tool_choice(tool_choice, tool_required)
    result["parallel_tool_calls"] = allow_parallel_tool_calls
return result

Test

Updated existing test test_prepare_chat_with_tools_no_tools to verify the keys are omitted rather than set to None. All 73 tests pass (14 skipped), 2 consecutive clean runs.

Fixes #20814

Changed files

  • llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/base.py (modified, +6/-6)
  • llama-index-integrations/llms/llama-index-llms-openai/tests/test_llms_openai.py (modified, +3/-3)

PR #20825: fix(openai): omit parallel_tool_calls when no tools are provided

Description (problem / solution / changelog)

Summary

Since llama-index-llms-openai 0.6.19 (#20744), parallel_tool_calls is sent to the OpenAI API with a null value when tools=[], causing a 400 BadRequestError:

openai.BadRequestError: Invalid type for 'parallel_tool_calls':
expected a boolean, but got null instead.

Root Cause

_prepare_chat_with_tools returns parallel_tool_calls: None when there are no tool specs:

"parallel_tool_calls": allow_parallel_tool_calls if tool_specs else None,

The None is serialized as null in JSON and sent to the API.

Fix

Only include tools, tool_choice, and parallel_tool_calls keys in the request dict when there are actual tool specs. When tools=[], these keys are omitted entirely.

Tests

9 passed, 2 skipped in 0.90s

Updated test_prepare_chat_with_tools_no_tools to verify the keys are absent (not None).

Fixes #20814

Changed files

  • llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/base.py (modified, +7/-6)
  • llama-index-integrations/llms/llama-index-llms-openai/tests/test_llms_openai.py (modified, +3/-3)

PR #20829: fix(openai): omit tool params from request when no tools provided

Description (problem / solution / changelog)

Summary

Fixes #20814

Since llama-index-llms-openai v0.6.19 (PR #20744), when _prepare_chat_with_tools() is called with no tools, the request dict includes parallel_tool_calls: None. The OpenAI API rejects this with a 400 BadRequestError because it expects a boolean.

Changes

  • base.py: Only include tools, tool_choice, and parallel_tool_calls keys in the return dict when tool_specs is non-empty. When no tools are provided, these keys are omitted entirely (matching OpenAI API expectations).
  • test_llms_openai.py: Updated test_prepare_chat_with_tools_no_tools to assert these keys are absent from the result (not just None).

Root Cause

# Before (sends parallel_tool_calls=null to API):
return {
    "messages": ...,
    "tools": tool_specs or None,
    "tool_choice": resolve_tool_choice(...) if tool_specs else None,
    "parallel_tool_calls": allow_parallel_tool_calls if tool_specs else None,
}

# After (omits tool keys entirely when no tools):
result = {"messages": ...}
if tool_specs:
    result["tools"] = tool_specs
    result["tool_choice"] = resolve_tool_choice(tool_choice)
    result["parallel_tool_calls"] = allow_parallel_tool_calls
return result

Testing

All 9 tests pass (2 skipped due to no API key):

tests/test_llms_openai.py - 9 passed, 2 skipped

Changed files

  • llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/base.py (modified, +8/-6)
  • llama-index-integrations/llms/llama-index-llms-openai/tests/test_llms_openai.py (modified, +3/-3)

PR #20830: fix(openai): exclude parallel_tool_calls from request when no tools provided

Description (problem / solution / changelog)

Summary

Fixes #20814

Since v0.6.19 (PR #20744), parallel_tool_calls is sent as null to the OpenAI API when no tools are provided, causing a 400 BadRequestError:

openai.BadRequestError: Invalid type for 'parallel_tool_calls': expected a boolean, but got null instead.

Root Cause

# Before (buggy):
return {
    ...
    "parallel_tool_calls": allow_parallel_tool_calls if tool_specs else None,
    # ← sends null when no tools, API rejects it
}

Fix

Only include parallel_tool_calls in the request dict when tool_specs is truthy:

# After (fixed):
result = { "messages": ..., "tools": ..., "tool_choice": ..., **kwargs }
if tool_specs:
    result["parallel_tool_calls"] = allow_parallel_tool_calls
return result

Tests

cd llama-index-integrations/llms/llama-index-llms-openai
pytest tests/test_llms_openai.py -v
# 9 passed, 2 skipped

Updated test_prepare_chat_with_tools_no_tools to verify the key is absent rather than None.

Changed files

  • llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/base.py (modified, +4/-2)
  • llama-index-integrations/llms/llama-index-llms-openai/tests/test_llms_openai.py (modified, +1/-1)
  • llama-index-integrations/llms/llama-index-llms-openai/uv.lock (modified, +1/-1)

PR #20831: fix openai tool calls

Description (problem / solution / changelog)

Don't pass None for the parallel arg

FIxes https://github.com/run-llama/llama_index/issues/20814

Changed files

  • llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/base.py (modified, +6/-2)
  • llama-index-integrations/llms/llama-index-llms-openai/pyproject.toml (modified, +1/-1)
  • llama-index-integrations/llms/llama-index-llms-openai/tests/test_llms_openai.py (modified, +1/-1)

Code Example

openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid type for 'parallel_tool_calls': expected a boolean, but got null instead.", 'type': 'invalid_request_error', 'param': 'parallel_tool_calls', 'code': 'invalid_type'}}

---

poetry add llama-index-llms-openai==0.6.18

---

...
  File "/Users/my-user/my-app/src/domain/chat_workflow/chat_workflow_orchestrator.py", line 405, in detect_answerability
    response = await self.answerability_detector.run(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/handler.py", line 34, in _await_result
    stop_event = await self.stop_event_result()
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/handler.py", line 95, in stop_event_result
    return await self._result_task
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/futures.py", line 289, in __await__
    yield self  # This tells Task to wait for completion.
    ^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/tasks.py", line 385, in __wakeup
    future.result()
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/futures.py", line 202, in result
    raise self._exception.with_traceback(self._exception_tb)
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/tasks.py", line 316, in __step_run_and_handle_result
    result = coro.throw(exc)
             ^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/handler.py", line 66, in _wait_for_result
    result = await self._external_adapter.get_result()
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/plugins/basic.py", line 184, in get_result
    return await self._queues.complete
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/futures.py", line 289, in __await__
    yield self  # This tells Task to wait for completion.
    ^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/tasks.py", line 385, in __wakeup
    future.result()
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/futures.py", line 202, in result
    raise self._exception.with_traceback(self._exception_tb)
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/tasks.py", line 316, in __step_run_and_handle_result
    result = coro.throw(exc)
             ^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/plugins/basic.py", line 300, in run_with_concurrency_limit
    return await registered.workflow_run_fn(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/types/step_function.py", line 218, in run_workflow
    result = await control_loop_fn(
             ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/control_loop.py", line 463, in control_loop
    return await runner.run(start_event=start_event)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/control_loop.py", line 333, in run
    result = await self._process_tick(tick)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/control_loop.py", line 439, in _process_tick
    result = await self.process_command(command)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/control_loop.py", line 247, in process_command
    raise command.exception
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/types/step_function.py", line 143, in wrapper
    result = await partial_func()
             ^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index_instrumentation/dispatcher.py", line 386, in async_wrapper
    result = await func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/core/agent/workflow/base_agent.py", line 469, in run_agent_step
    agent_output = await self.take_step(
                   ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/core/agent/workflow/function_agent.py", line 126, in take_step
    last_chat_response = await self._get_response(current_llm_input, tools)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/core/agent/workflow/function_agent.py", line 48, in _get_response
    return await self.llm.achat_with_tools(  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/core/llms/function_calling.py", line 83, in achat_with_tools
    response = await self.achat(**chat_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index_instrumentation/dispatcher.py", line 386, in async_wrapper
    result = await func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/core/llms/callbacks.py", line 76, in wrapped_async_llm_chat
    f_return_val = await f(_self, messages, **kwargs)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/llms/openai/base.py", line 713, in achat
    return await achat_fn(messages, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 193, in async_wrapped
    return await copy(fn, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 112, in __call__
    do = await self.iter(retry_state=retry_state)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 157, in iter
    result = await action(retry_state)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/_utils.py", line 111, in inner
    return call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/__init__.py", line 393, in <lambda>
    self._add_action_func(lambda rs: rs.outcome.result())
                                     ^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 116, in __call__
    result = await fn(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/llms/openai/base.py", line 768, in _achat
    response = await aclient.chat.completions.create(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/openai/resources/chat/completions/completions.py", line 2700, in create
    return await self._post(
           ^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/openai/_base_client.py", line 1884, in post
    return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/openai/_base_client.py", line 1669, in request
    raise self._make_status_error_from_response(err.response) from None
openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid type for 'parallel_tool_calls': expected a boolean, but got null instead.", 'type': 'invalid_request_error', 'param': 'parallel_tool_calls', 'code': 'invalid_type'}}
RAW_BUFFERClick to expand / collapse

Bug Description

Since llama-index-llms-openai 0.6.19 (introduced by #20744), the parallel_tool_calls parameter is being sent to the OpenAI API with a null value instead of a boolean. This causes a 400 BadRequestError even when no tools are provided (tools=[]).

Rolling back to 0.6.18 fixes the issue.

Expected Behavior

parallel_tool_calls should either not be sent at all when there are no tools, or be sent as a valid boolean.

Actual Behavior

openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid type for 'parallel_tool_calls': expected a boolean, but got null instead.", 'type': 'invalid_request_error', 'param': 'parallel_tool_calls', 'code': 'invalid_type'}}

Root Cause

PR #20744 started forwarding allow_parallel_tool_calls to the API as parallel_tool_calls, but the default value of allow_parallel_tool_calls is None instead of a proper boolean. When no tools are present, None is passed directly to the OpenAI API which expects a bool.

Workaround

Downgrade to llama-index-llms-openai==0.6.18:

poetry add llama-index-llms-openai==0.6.18

Environment

  • llama-index-llms-openai: 0.6.19
  • Python: 3.12

Version

.

Steps to Reproduce

  1. Upgrade to llama-index-llms-openai>=0.6.19
  2. Create an agent or LLM call with tools=[]
  3. Make a chat completion request

Relevant Logs/Tracbacks

...
  File "/Users/my-user/my-app/src/domain/chat_workflow/chat_workflow_orchestrator.py", line 405, in detect_answerability
    response = await self.answerability_detector.run(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/handler.py", line 34, in _await_result
    stop_event = await self.stop_event_result()
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/handler.py", line 95, in stop_event_result
    return await self._result_task
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/futures.py", line 289, in __await__
    yield self  # This tells Task to wait for completion.
    ^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/tasks.py", line 385, in __wakeup
    future.result()
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/futures.py", line 202, in result
    raise self._exception.with_traceback(self._exception_tb)
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/tasks.py", line 316, in __step_run_and_handle_result
    result = coro.throw(exc)
             ^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/handler.py", line 66, in _wait_for_result
    result = await self._external_adapter.get_result()
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/plugins/basic.py", line 184, in get_result
    return await self._queues.complete
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/futures.py", line 289, in __await__
    yield self  # This tells Task to wait for completion.
    ^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/tasks.py", line 385, in __wakeup
    future.result()
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/futures.py", line 202, in result
    raise self._exception.with_traceback(self._exception_tb)
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/asyncio/tasks.py", line 316, in __step_run_and_handle_result
    result = coro.throw(exc)
             ^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/plugins/basic.py", line 300, in run_with_concurrency_limit
    return await registered.workflow_run_fn(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/types/step_function.py", line 218, in run_workflow
    result = await control_loop_fn(
             ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/control_loop.py", line 463, in control_loop
    return await runner.run(start_event=start_event)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/control_loop.py", line 333, in run
    result = await self._process_tick(tick)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/control_loop.py", line 439, in _process_tick
    result = await self.process_command(command)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/control_loop.py", line 247, in process_command
    raise command.exception
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/workflows/runtime/types/step_function.py", line 143, in wrapper
    result = await partial_func()
             ^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index_instrumentation/dispatcher.py", line 386, in async_wrapper
    result = await func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/core/agent/workflow/base_agent.py", line 469, in run_agent_step
    agent_output = await self.take_step(
                   ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/core/agent/workflow/function_agent.py", line 126, in take_step
    last_chat_response = await self._get_response(current_llm_input, tools)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/core/agent/workflow/function_agent.py", line 48, in _get_response
    return await self.llm.achat_with_tools(  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/core/llms/function_calling.py", line 83, in achat_with_tools
    response = await self.achat(**chat_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index_instrumentation/dispatcher.py", line 386, in async_wrapper
    result = await func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/core/llms/callbacks.py", line 76, in wrapped_async_llm_chat
    f_return_val = await f(_self, messages, **kwargs)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/llms/openai/base.py", line 713, in achat
    return await achat_fn(messages, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 193, in async_wrapped
    return await copy(fn, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 112, in __call__
    do = await self.iter(retry_state=retry_state)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 157, in iter
    result = await action(retry_state)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/_utils.py", line 111, in inner
    return call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/__init__.py", line 393, in <lambda>
    self._add_action_func(lambda rs: rs.outcome.result())
                                     ^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 116, in __call__
    result = await fn(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/llama_index/llms/openai/base.py", line 768, in _achat
    response = await aclient.chat.completions.create(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/openai/resources/chat/completions/completions.py", line 2700, in create
    return await self._post(
           ^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/openai/_base_client.py", line 1884, in post
    return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/my-user/Library/Caches/pypoetry/virtualenvs/querydocs-api-IkRwPy7b-py3.12/lib/python3.12/site-packages/openai/_base_client.py", line 1669, in request
    raise self._make_status_error_from_response(err.response) from None
openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid type for 'parallel_tool_calls': expected a boolean, but got null instead.", 'type': 'invalid_request_error', 'param': 'parallel_tool_calls', 'code': 'invalid_type'}}

extent analysis

<think> Okay, let's tackle this problem. The user is facing a 400 error because the `parallel_tool_calls` parameter is being sent as null instead of a boolean when there are no tools. The root cause is that the `allow_parallel_tool_calls` parameter, which defaults to None, is being forwarded as `parallel_tool_calls` without checking if it's set.

First, I need to figure out where in the code this parameter is being set. Since the issue started with PR #20744, I should look there. The problem is that when tools=[], allow_parallel_tool_calls is None, and that's passed directly to the API, which expects a boolean.

The fix would involve ensuring that parallel_tool_calls is only sent if it's a boolean. So, in the code where the API call is made, we need to check if allow_parallel_tool_calls is not None before including it. Alternatively, set a default value of False if there are no tools.

Looking at the stack trace, the error occurs in llama_index/llms/openai/base.py line 768 when calling aclient.chat.completions.create. The parameters for this call probably include parallel_tool_calls derived from allow_parallel_tool_calls.

So, modifying the code to conditionally include parallel_tool_calls only when allow_parallel_tool_calls is not None, or defaulting it to False when there are no tools would solve the issue.

For example, in the code where the create method is called, instead of passing parallel_tool_calls=allow_parallel_tool_calls, we can do something like:

if tools: parallel_tool_calls = allow_parallel_tool_calls else: parallel_tool_calls = None

But wait, even if there are tools, if allow_parallel_tool_calls is None, maybe the API expects it to be omitted. So perhaps the correct approach is to

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

llamaIndex - ✅(Solved) Fix [Bug]: `parallel_tool_calls: null` sent to OpenAI API since 0.6.19, causing 400 BadRequestError [6 pull requests, 1 participants]