llamaIndex - ✅(Solved) Fix [Bug]: Refine does not catch `ValueError`/`TypeError` from tool-calling structured outputs [5 pull requests, 3 comments, 3 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#21089Fetched 2026-04-08 01:03:33
View on GitHub
Comments
3
Participants
3
Timeline
16
Reactions
0
Timeline (top)
cross-referenced ×5commented ×3labeled ×2mentioned ×2

Error Message

Traceback (most recent call last):

Fix Action

Fix / Workaround

from unittest.mock import patch from pydantic import BaseModel, Field from llama_index.core import SummaryIndex, Document from llama_index.core.response_synthesizers import Refine from llama_index.core.program.function_program import FunctionCallingProgram from llama_index.llms.openai import OpenAI

with patch.object(FunctionCallingProgram, "call", side_effect=ValueError("LLM did not return any tool calls")): response = query_engine.query("What is the capital of France?")

Traceback (most recent call last):
  File "D:\code\llama_index\repro_refine_bug.py", line 22, in <module>
    response = query_engine.query("What is the capital of France?")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\base\base_query_engine.py", line 44, in query
    query_result = self._query(str_or_query_bundle)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\query_engine\retriever_query_engine.py", line 197, in _query
    response = self._response_synthesizer.synthesize(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\response_synthesizers\base.py", line 235, in synthesize
    response_str = self.get_response(
                   ^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\response_synthesizers\refine.py", line 179, in get_response
    response = self._give_response_single(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\response_synthesizers\refine.py", line 241, in _give_response_single
    program(
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\response_synthesizers\refine.py", line 77, in __call__
    answer = self._llm.structured_predict(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index\llms\openai\base.py", line 1118, in structured_predict
    return super().structured_predict(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\llms\llm.py", line 360, in structured_predict
    result = program(llm_kwargs=llm_kwargs, **prompt_args)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\unittest\mock.py", line 1124, in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\unittest\mock.py", line 1128, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\unittest\mock.py", line 1183, in _execute_mock_call
    raise effect
ValueError: LLM did not return any tool calls

PR fix notes

PR #21090: fix(core): handle ValueError and TypeError from structured output failures

Description (problem / solution / changelog)

Description

Expanded Refine’s structured-response fallback handlers to catch ValueError and TypeError in addition to ValidationError, so tool-calling structured-output failures are handled gracefully instead of bubbling up and failing the query.

Fixes #21089

New Package?

Did I fill in the tool.llamahub section in the pyproject.toml and provide a detailed README.md for my new integration or package?

  • Yes
  • No

Version Bump?

Did I bump the version in the pyproject.toml file of the package I am updating? (Except for the llama-index-core package)

  • Yes
  • No

Type of Change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

Your pull-request will likely not be merged unless it is covered by some form of impactful unit testing.

  • I added new unit tests to cover this change
  • I believe this change is already covered by existing unit tests

Suggested Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added Google Colab support for the newly added notebooks.
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I ran uv run make format; uv run make lint to appease the lint gods

Changed files

  • llama-index-core/llama_index/core/response_synthesizers/refine.py (modified, +8/-16)
  • llama-index-core/tests/response_synthesizers/test_refine.py (modified, +47/-0)

PR #21138: fix(core): catch ValueError/TypeError in Refine structured output handling

Description (problem / solution / changelog)

Description

The Refine response synthesizer only catches ValidationError when structured output programs fail. After #21036 introduced explicit ValueError and TypeError raises in the function-calling structured output path, these uncaught exceptions cause queries to crash instead of falling through to the existing warning + fallback behavior.

This expands the exception handling in all four response methods (_give_response_single, _refine_response_single, _agive_response_single, _arefine_response_single) to catch ValueError and TypeError alongside ValidationError, matching the graceful degradation pattern already in place.

Related issues: #19926, #19044

Fixes #21089

Testing

Verified with the exact reproduction script from the issue:

from unittest.mock import patch
from pydantic import BaseModel, Field
from llama_index.core import SummaryIndex, Document
from llama_index.core.response_synthesizers import Refine
from llama_index.core.program.function_program import FunctionCallingProgram
from llama_index.llms.openai import OpenAI

class Answer(BaseModel):
    response: str = Field(description="Answer to the question")
    confidence: float = Field(description="Confidence score between 0 and 1")

docs = [Document(text="Paris is the capital of France.")]
index = SummaryIndex.from_documents(docs)
query_engine = index.as_query_engine(
    response_synthesizer=Refine(llm=OpenAI(api_key="sk-fake"), output_cls=Answer)
)

with patch.object(FunctionCallingProgram, "__call__", side_effect=ValueError("LLM did not return any tool calls")):
    response = query_engine.query("What is the capital of France?")

print(response)  # "Empty Response" instead of crash

Before the fix: ValueError: LLM did not return any tool calls propagates and crashes. After the fix: warning is logged and the query returns "Empty Response" gracefully.

Changed files

  • llama-index-core/llama_index/core/response_synthesizers/refine.py (modified, +4/-4)

PR #21237: fix(core): catch ValueError and TypeError in Refine structured output handlers

Description (problem / solution / changelog)

Summary

  • Broadens exception handling in Refine response synthesizer to catch ValueError and TypeError alongside ValidationError
  • Prevents query failures when the function-calling structured-output path raises these exceptions (e.g., missing tool calls, non-BaseModel outputs)
  • Applied to all four try/except blocks: _give_response_single, _refine_response_single, and their async counterparts

Root Cause

After changes in #21036, the function-calling structured-output path can raise:

  • ValueError for missing/failed tool calls
  • TypeError for non-BaseModel tool outputs

These were not caught by the except ValidationError clause, causing the query to fail instead of falling through to the existing warning/fallback path.

Test Plan

  • Verified all four except blocks updated consistently
  • No behavior change for ValidationError — still caught and logged as before
  • ValueError and TypeError now trigger the same warning path instead of propagating

Fixes #21089

Changed files

  • llama-index-core/llama_index/core/indices/document_summary/base.py (modified, +4/-2)
  • llama-index-core/llama_index/core/response_synthesizers/refine.py (modified, +4/-4)
  • llama-index-core/tests/indices/document_summary/test_index.py (modified, +22/-0)

PR #21285: Fix Refine to catch ValueError/TypeError from structured outputs

Description (problem / solution / changelog)

Fixes #21089

Description

When using output_cls or structured_answer_filtering with a function-calling LLM, the Refine response synthesizer only catches ValidationError from structured output programs. However, the function-calling path can also raise:

  • ValueError when the LLM returns no tool calls
  • TypeError when the tool output is not a BaseModel subclass

These are semantically equivalent to ValidationError (the structured output failed) and should follow the same warning-and-fallback path rather than crashing the query.

Changes

In refine.py, extend all four except ValidationError blocks to also catch ValueError and TypeError:

  1. _give_response_single (sync) -- line ~240
  2. _refine_response_single (sync) -- line ~300
  3. _arefine_response_single (async) -- line ~408
  4. _agive_response_single (async) -- line ~448

Each change is identical: except ValidationError as e: becomes except (ValidationError, ValueError, TypeError) as e:.

The log message is also generalized from "Validation error" to "Error" since it now covers more exception types.

Testing

from unittest.mock import patch
from pydantic import BaseModel, Field
from llama_index.core import SummaryIndex, Document
from llama_index.core.response_synthesizers import Refine
from llama_index.core.program.function_program import FunctionCallingProgram
from llama_index.llms.openai import OpenAI

class Answer(BaseModel):
    response: str = Field(description="Answer to the question")
    confidence: float = Field(description="Confidence score between 0 and 1")

docs = [Document(text="Paris is the capital of France.")]
index = SummaryIndex.from_documents(docs)
query_engine = index.as_query_engine(
    response_synthesizer=Refine(llm=OpenAI(api_key="sk-fake"), output_cls=Answer)
)

# Previously crashed with ValueError; now falls back to "Empty Response"
with patch.object(FunctionCallingProgram, "__call__", side_effect=ValueError("LLM did not return any tool calls")):
    response = query_engine.query("What is the capital of France?")
print(response)  # "Empty Response"

Changed files

  • llama-index-core/llama_index/core/response_synthesizers/refine.py (modified, +8/-8)

PR #21288: fix: catch ValueError and TypeError in Refine structured output handlers

Description (problem / solution / changelog)

Fixes #21089

When using output_cls with a function-calling LLM, you can get a ValueError if the model returns no tool calls, or a TypeError if the output isn't a BaseModel subclass. These were crashing the query instead of falling back gracefully like ValidationError already does. Extended all five except blocks in refine.py (the sync and async versions of _give_response_single and _refine_response_single, plus the initial model_validate_json call) to catch all three exception types.

Changed files

  • llama-index-core/llama_index/core/response_synthesizers/refine.py (modified, +9/-9)

Code Example

### Relevant Logs/Tracbacks
RAW_BUFFERClick to expand / collapse

Bug Description

When using output_cls with a function-calling LLM, Refine._give_response_single and its refine/async counterparts only catch ValidationError. On current main, the function-calling structured-output path can raise ValueError for missing/failed tool calls and TypeError for non-BaseModel tool outputs. These exceptions are uncaught in Refine, so the query fails instead of taking the existing warning path used for ValidationError.

This likely also affects structured_answer_filtering=True, since Refine supports that mode and uses structured programs there as well, but the repro below demonstrates the output_cls path specifically.

This appears to have been introduced after v0.14.18, in the changes from #21036

Version

0.14.18

Steps to Reproduce


from unittest.mock import patch
from pydantic import BaseModel, Field
from llama_index.core import SummaryIndex, Document
from llama_index.core.response_synthesizers import Refine
from llama_index.core.program.function_program import FunctionCallingProgram
from llama_index.llms.openai import OpenAI


class Answer(BaseModel):
    response: str = Field(description="Answer to the question")
    confidence: float = Field(description="Confidence score between 0 and 1")


docs = [Document(text="Paris is the capital of France.")]
index = SummaryIndex.from_documents(docs)

query_engine = index.as_query_engine(
    response_synthesizer=Refine(llm=OpenAI(api_key="sk-fake"), output_cls=Answer)
)

with patch.object(FunctionCallingProgram, "__call__", side_effect=ValueError("LLM did not return any tool calls")):
    response = query_engine.query("What is the capital of France?")

print(response)

Relevant Logs/Tracbacks

Traceback (most recent call last):
  File "D:\code\llama_index\repro_refine_bug.py", line 22, in <module>
    response = query_engine.query("What is the capital of France?")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\base\base_query_engine.py", line 44, in query
    query_result = self._query(str_or_query_bundle)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\query_engine\retriever_query_engine.py", line 197, in _query
    response = self._response_synthesizer.synthesize(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\response_synthesizers\base.py", line 235, in synthesize
    response_str = self.get_response(
                   ^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\response_synthesizers\refine.py", line 179, in get_response
    response = self._give_response_single(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\response_synthesizers\refine.py", line 241, in _give_response_single
    program(
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\response_synthesizers\refine.py", line 77, in __call__
    answer = self._llm.structured_predict(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index\llms\openai\base.py", line 1118, in structured_predict
    return super().structured_predict(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\site-packages\llama_index_instrumentation\dispatcher.py", line 335, in wrapper
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "D:\code\llama_index\llama-index-core\llama_index\core\llms\llm.py", line 360, in structured_predict
    result = program(llm_kwargs=llm_kwargs, **prompt_args)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\unittest\mock.py", line 1124, in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\unittest\mock.py", line 1128, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\gauta\miniforge3\envs\llamaindex-dev\Lib\unittest\mock.py", line 1183, in _execute_mock_call
    raise effect
ValueError: LLM did not return any tool calls

extent analysis

Fix Plan

To fix the issue, we need to catch ValueError and TypeError exceptions in the Refine._give_response_single method. Here are the steps:

  • Modify the Refine._give_response_single method to catch ValueError and TypeError exceptions.
  • Handle the exceptions by taking the existing warning path used for ValidationError.

Example code:

try:
    response = self._llm.structured_predict(
        # ... existing code ...
    )
except (ValidationError, ValueError, TypeError) as e:
    # Handle the exceptions by taking the warning path
    logger.warning(f"Error occurred during response synthesis: {e}")
    # ... existing warning path code ...

Additionally, you can also modify the Refine class to handle these exceptions in a more centralized way:

class Refine:
    # ... existing code ...

    def _give_response_single(self, query, context):
        try:
            # ... existing code ...
        except (ValidationError, ValueError, TypeError) as e:
            self._handle_synthesis_error(e)

    def _handle_synthesis_error(self, e):
        logger.warning(f"Error occurred during response synthesis: {e}")
        # ... existing warning path code ...

Verification

To verify that the fix worked, you can run the same test case that reproduced the issue and check that the ValueError exception is caught and handled correctly.

Extra Tips

  • Make sure to test the fix thoroughly to ensure that it doesn't introduce any new issues.
  • Consider adding more robust error handling mechanisms to the Refine class to handle other potential exceptions that may occur during response synthesis.

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]: Refine does not catch `ValueError`/`TypeError` from tool-calling structured outputs [5 pull requests, 3 comments, 3 participants]