ChatDeepSeek Async Paths (`Ainvoke`/`Astream`) Leak Raw `Expecting Value` Instead Of DeepSeek-Friendly Error
ChatDeepSeek only overrides sync `_generate` and `_stream` to translate `JSONDecodeError` from the OpenAI SDK into a DeepSeek-specific message. The inherited async `_agenerate` and `_astream` are not wrapped, so async callers still receive the opaque `json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)`.
The `ChatDeepSeek` class in `langchain-deepseek` subclasses `LangChainOpenAI` and defines `_generate` and `_stream` overrides to catch `JSONDecodeError` and re-raise with a friendly DeepSeek message. It does not override the corresponding async methods `_agenerate` and `_astream`, so the base class implementations run without this error translation, allowing the raw `JSONDecodeError` to propagate on async paths.
1. Install langchain-deepseek 1.1.0 (and langchain-openai 1.6.0, openai 3.2.0).
2. Create a `ChatDeepSeek` instance with a stubbed client whose `create`/`with_raw_response.create` (sync and async) raise `json.decoder.JSONDecodeError('Expecting value', '', 0)`.
3. Call `invoke`, `stream`, `ainvoke`, and `astream` with a simple message.
4. Observe that `invoke` and `stream` produce the friendly message `'DeepSeek API returned an invalid response. Please check the API status and try again.'`, while `ainvoke` and `astream` leak the raw `'Expecting value'`.
Fixing Code Block
async def _agenerate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[AsyncCallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> ChatResult:
try:
return await super()._agenerate(
messages, stop=stop, run_manager=run_manager, **kwargs
)
except JSONDecodeError:
raise JSONDecodeError(
'DeepSeek API returned an invalid response. Please check the API status and try again.',
'',
0,
) from None
async def _astream(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[AsyncCallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> AsyncIterator[ChatGenerationChunk]:
try:
async for chunk in super()._astream(
messages, stop=stop, run_manager=run_manager, **kwargs
):
yield chunk
except JSONDecodeError:
raise JSONDecodeError(
'DeepSeek API returned an invalid response. Please check the API status and try again.',
'',
0,
) from None
Added `_agenerate` and `_astream` overrides that mirror the existing sync `_generate` and `_stream`. Both methods wrap the call to the parent implementation in a `try/except` for `JSONDecodeError` and re-raise with the same DeepSeek-specific message, ensuring all four entry points (`invoke`, `stream`, `ainvoke`, `astream`) consistently translate the error.
Edge Case Audit
This change only affects async paths that already raise `JSONDecodeError` from the OpenAI SDK. It masks the original error by using `from None`, which may hide lower-level details but matches the existing sync behavior. Ensure the added methods are kept in sync with future signature changes of `BaseChatOpenAI._agenerate` / `_astream`. If the message needs to be updated, change it in all four methods. Rollback is safe by deleting the two new methods, reverting to the previous behavior.