AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

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)`.

mediumConfidence 95%Langchain-DeepseekAffected V1.1.0

Origin Analysis

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.

Ecosystem Topology