Skip to content

Commit bbe28c0

Browse files
fix(chat_model): raise clear ModelException when BaiduYiyan key is not a JSON object (infiniflow#17389)
The BaiduYiyan / Qianfan provider in rag/llm/chat_model.py:1189 does an unguarded `json.loads(key)` followed by `.get("yiyan_ak")` and `.get("yiyan_sk")`. The provider REQUIRES a JSON key (per conf/models/baidu.json) but a user pasting a plain Baidu API key like "bce-v3/ALTAK-.../..." (the most common mistake: copying from the Qianfan console into a BaiduYiyan field) would crash with `json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)` from inside rag/llm internals, with no indication of what the user did wrong. This is the BaiduYiyan equivalent of: - infiniflow#17204 / PR infiniflow#17215 (Azure-OpenAI) - infiniflow#17373 / PR infiniflow#17377 (AWS Bedrock) Add a parallel helper to rag.llm.key_utils that mirrors the Bedrock fix shape: ``` def _resolve_qianfan_credentials(key): # Accepts dict or JSON-string-encoded dict. Returns the dict. # On non-JSON input (e.g. plain "bce-v3/..."): # raises ModelException with the required schema in the message # (yiyan_ak + yiyan_sk, conf/models/baidu.json reference). # On JSON top-level non-dict (list, string, number): # raises ModelException rather than letting the caller hit # AttributeError on .get("yiyan_ak"). ``` Wire BaiduYiyanChat.__init__ (chat_model.py:1189) through the helper instead of the bare `json.loads(key)`. The downstream `key.get("yiyan_ak", "")` / `.get("yiyan_sk", "")` calls are unchanged. No public API change. No data-model change. No migration. Fixes infiniflow#17389.
1 parent c87aa3b commit bbe28c0

2 files changed

Lines changed: 69 additions & 2 deletions

File tree

rag/llm/chat_model.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@
3636
from common.llm_request_context import current_llm_user
3737
from common.token_utils import num_tokens_from_string, total_token_count_from_response, usage_from_response
3838
from rag.llm import FACTORY_DEFAULT_BASE_URL, LITELLM_PROVIDER_PREFIX, SupportedLiteLLMProvider
39+
<<<<<<< HEAD
3940
from rag.llm.key_utils import _normalize_replicate_key
4041
from rag.llm.mws_utils import mws_api_url, require_mws_token
42+
=======
43+
from rag.llm.key_utils import _normalize_replicate_key, _resolve_qianfan_credentials
44+
>>>>>>> c6d1af4b1 (fix(chat_model): raise clear ModelException when BaiduYiyan key is not a JSON object (#17389))
4145
from rag.llm.tool_decorator import FunctionToolSession, is_tool
4246
from rag.nlp import is_chinese, is_english
4347
from rag.utils.url_utils import ensure_v1
@@ -1335,7 +1339,12 @@ def __init__(self, key, model_name, base_url=None, **kwargs):
13351339

13361340
import qianfan
13371341

1338-
key = json.loads(key)
1342+
# Parse via the shared helper. On non-JSON input (e.g. a plain Baidu
1343+
# API key like "bce-v3/...") the helper raises a clear ModelException
1344+
# pointing at the required schema; without this guard a raw
1345+
# json.loads would surface a JSONDecodeError from inside rag/llm
1346+
# internals (see #17389).
1347+
key = _resolve_qianfan_credentials(key)
13391348
ak = key.get("yiyan_ak", "")
13401349
sk = key.get("yiyan_sk", "")
13411350
self.client = qianfan.ChatCompletion(ak=ak, sk=sk)

rag/llm/key_utils.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
# limitations under the License.
1515
#
1616
import json
17+
import logging
18+
19+
from common.exceptions import ModelException
1720

1821

1922
def _normalize_replicate_key(key):
@@ -31,4 +34,59 @@ def _normalize_replicate_key(key):
3134
return key
3235

3336

34-
__all__ = ["_normalize_replicate_key"]
37+
def _resolve_qianfan_credentials(key):
38+
"""Parse a BaiduYiyan (Baidu Qianfan) ``key`` and return it as a dict.
39+
40+
The BaiduYiyan / Qianfan API requires a JSON object with at least
41+
``yiyan_ak`` and ``yiyan_sk``. See ``conf/models/baidu.json`` for the
42+
model class.
43+
44+
On non-JSON input -- for example a user pasting a plain Baidu API key
45+
like ``"bce-v3/ALTAK-.../..."`` -- we raise a clear
46+
:class:`ModelException` pointing at the required schema, instead of
47+
letting ``json.loads`` bubble up as ``json.decoder.JSONDecodeError:
48+
Expecting value: line 1 column 1 (char 0)`` from inside ``rag/llm``
49+
internals. Mirrors the fix shape of ``_resolve_bedrock_credentials``
50+
for #17373 and the Azure fix for #17204 / #17215.
51+
52+
On a JSON top-level type that is not a dict (e.g. a list, string, or
53+
number), we also raise the same clear error rather than calling
54+
``.get("yiyan_ak")`` on the value and getting an ``AttributeError``.
55+
"""
56+
if isinstance(key, dict):
57+
payload = key
58+
elif isinstance(key, str):
59+
try:
60+
payload = json.loads(key)
61+
except (json.JSONDecodeError, TypeError):
62+
logging.warning(
63+
"BaiduYiyan key is not valid JSON; expected a JSON object with 'yiyan_ak' and 'yiyan_sk' (see conf/models/baidu.json).",
64+
)
65+
raise ModelException(
66+
'BaiduYiyan requires a JSON key with at least \'yiyan_ak\' and \'yiyan_sk\'. Example: {"yiyan_ak": "...", "yiyan_sk": "..."}. See conf/models/baidu.json for the model class.',
67+
retryable=False,
68+
)
69+
else:
70+
logging.warning(
71+
"BaiduYiyan key is not a string or dict (got %s); expected a JSON object with 'yiyan_ak' and 'yiyan_sk'.",
72+
type(key).__name__,
73+
)
74+
raise ModelException(
75+
f"BaiduYiyan requires a JSON key, got {type(key).__name__}. See conf/models/baidu.json for the model class.",
76+
retryable=False,
77+
)
78+
79+
if not isinstance(payload, dict):
80+
logging.warning(
81+
"BaiduYiyan key parsed as JSON but is not a dict (got %s).",
82+
type(payload).__name__,
83+
)
84+
raise ModelException(
85+
f"BaiduYiyan key must be a JSON object, got {type(payload).__name__}. See conf/models/baidu.json for the model class.",
86+
retryable=False,
87+
)
88+
89+
return payload
90+
91+
92+
__all__ = ["_normalize_replicate_key", "_resolve_qianfan_credentials"]

0 commit comments

Comments
 (0)