[Bug]: response_format_json_object=True is silently dropped, so JSON mode never reaches the model

オープン 初心者向け
#2,625 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 5 日以内に返信

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
72/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python
領域
backend

調査の方向性

packages/graphrag-llm/graphrag_llm/completion/lite_llm_completion.py から始めてください。ここでは _base_completion と _base_completion_async が JSON モードを有効にする前に "response_format" not in new_args を確認しています。このキーは常に値 None で存在するため、代わりに None かどうかを確認するべきです。次に、graphrag/query/structured_search/drift_search/state.py の QueryState.add_all_follow_ups と find_incomplete_actions を確認してください。これらは文字列ではないフォローアップをスキップするべきです。再現スクリプトが {'type': 'json_object'} を LiteLLM に渡しているように表示され、再現 2 で AttributeError が発生しなくなり、ユニットテストで両方のケースがカバーされていれば完了です。

索引モデルが issue の本文から書いたものです。

説明

bug triage
Do you need to file an issue?
  • I have searched the existing issues and this bug is not already filed.
  • My model is hosted on OpenAI or Azure. If not, please look at the "model providers" issue and don't file a new one here.
  • I believe this is a legitimate bug, not just a question. If this is a question, please use the Discussions area.
Describe the bug

Note on the provider checkbox: we run open-weight models via vLLM, but this bug does not depend on the provider. The reproduction below uses model_provider="openai" and never contacts a model.

Several query paths request JSON mode with response_format_json_object=True: the DRIFT local search actions (drift_search/search.py#L97), the global search map step (global_search/search.py#L97, #L240) and rate_relevancy (#L62). The flag never takes effect: the request sent to the model contains no response_format.

Root cause (graphrag_llm/completion/lite_llm_completion.py):

  • completion and completion_async pop response_format (default None) and always pass it on explicitly (#L158, #L197).
  • _base_completion and _base_completion_async only enable JSON mode if the key is absent (#L262, #L288):
    if json_object and "response_format" not in new_args:
    

Since the key is always present with the value None, this branch is never taken. LiteLLM then receives response_format=None and drops it.

Consequences in DRIFT (silent, only warnings are logged):

  1. Without JSON mode, models often answer an action in plain prose. try_parse_json_object then returns {}, and the action yields no answer and no follow-ups ("No answer found for query").
  2. Models also return non-strict JSON with single quotes. The repair in try_parse_json_object splits follow-up queries at escaped apostrophes and at the comma before a year, and the year becomes an int. Example: '...SBF\'s political dealings were mentioned in the October 7, 2023 TechCrunch digest?' becomes ["What specific details about SBF ", "s political dealings were mentioned in the October 7", 2023, "TechCrunch digest?'"].
  3. QueryState.add_all_follow_ups logs a warning for non-string items but still adds them to the graph (state.py#L45-L53). find_incomplete_actions then raises AttributeError: 'int' object has no attribute 'is_complete' (#L57), and the whole DRIFT query fails.

Observed impact (vLLM 0.30.0, default DRIFT settings, MultiHop-RAG corpus): 5 to 25 % of DRIFT actions were answered in plain prose depending on the model (Qwen3.8-27B 5 to 14 %, Mistral-Small-3.2-24B 25 %). With Qwen3.8-27B, 3 to 5 % of DRIFT queries failed with the AttributeError even after two retries.

Steps to reproduce
  1. JSON mode is dropped (no network access, no model needed):
import asyncio
import logging

import graphrag_llm.completion.lite_llm_completion as llc
from graphrag_llm.completion import create_completion
from graphrag_llm.config import ModelConfig

logging.getLogger("graphrag_llm").setLevel(logging.CRITICAL)  # hide the logged RuntimeError below

captured = {}


async def fake_acompletion(**kwargs):
    captured.update(kwargs)
    raise RuntimeError("stop before any network call")


llc.litellm.acompletion = fake_acompletion

model = create_completion(
    ModelConfig(model_provider="openai", model="gpt-4o-mini", api_key="not-used")
)


async def main():
    try:
        await model.completion_async(
            messages=[{"role": "user", "content": "Answer in JSON."}],
            response_format_json_object=True,
        )
    except RuntimeError:
        pass


asyncio.run(main())
print("response_format passed to litellm:", captured.get("response_format"))
# Actual:   None
# Expected: {'type': 'json_object'}
  1. A non-string follow-up crashes DRIFT:
from graphrag.query.structured_search.drift_search.action import DriftAction
from graphrag.query.structured_search.drift_search.state import QueryState

state = QueryState()
parent = DriftAction(query="parent question")
state.add_action(parent)
state.add_all_follow_ups(parent, ["a valid follow-up question", 2023])
state.find_incomplete_actions()
# AttributeError: 'int' object has no attribute 'is_complete'
Expected Behavior

When response_format_json_object=True is passed and no explicit response_format is given, LiteLLM receives response_format={"type": "json_object"}. A follow-up query that is not a string should be skipped instead of crashing the DRIFT search.

GraphRAG Config Used
# Excerpt of settings.yaml. All other settings are GraphRAG 3.1.0 defaults.
# The JSON-mode bug does not depend on this config: reproduction 1 runs without any settings file.
completion_models:
  default_completion_model:
    model_provider: hosted_vllm   # bug also reproduced with model_provider: openai (reproduction 1)
    model: Qwen/Qwen3.8-27B       # also observed with mistralai/Mistral-Small-3.2-24B-Instruct-2506
    api_base: ${VLLM_API_BASE}
    api_key: ${GRAPHRAG_API_KEY}
    call_args:                    # query time
      temperature: 0
      top_p: 1
      max_tokens: 4000
    retry:
      type: exponential_backoff
      max_retries: 2

embedding_models:
  default_embedding_model:
    model_provider: hosted_vllm
    model: BAAI/bge-m3
    api_base: ${VLLM_EMBED_API_BASE}
    api_key: ${GRAPHRAG_API_KEY}

drift_search:                     # GraphRAG 3.1.0 defaults
  completion_model_id: default_completion_model
  embedding_model_id: default_embedding_model
  n_depth: 3
  drift_k_followups: 20
  primer_folds: 5
  concurrency: 32
Logs and screenshots

Reproduction 1 prints: response_format passed to litellm: None
Reproduction 2 raises: AttributeError: 'int' object has no attribute 'is_complete' (state.py, line 57)

Additional Information
  • Versions: graphrag 3.1.0, graphrag-llm 3.1.0, litellm 1.86.2, Python 3.13. The same code is in v3.1.2, v3.2.0, v3.3.0 and on main (34b2b67).
  • Suggested fix 1, in both _base_completion and _base_completion_async:
    if json_object and new_args.get("response_format") is None:
    With this change, reproduction 1 prints {'type': 'json_object'}.
  • Suggested fix 2 (defensive): in QueryState.add_all_follow_ups, continue after the warning for non-string items. With fix 1 the trigger mostly disappears, but json_object mode does not constrain types, so a model can still return non-string items.
  • I am happy to open a PR with both changes and unit tests.
主要言語
Python
スター
36.6k
フォーク
3.9k
平均マージ
4日 11時間
マージ済み PR(30日)
1

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

microsoft/graphrag のほかの issue

microsoft/graphrag の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。