Troubleshooting
Diagnose authentication failures, model selection issues, HTTP errors, retries, streaming problems, batch failures, and structured-output errors in `@orcushq/deepseek`.
When a request fails, inspect the error class, message, code, and HTTP status. The SDK provides typed errors for common failures, although some unexpected response formats may produce native JavaScript errors. The test cases below verify SDK behavior using controlled mock responses; live upstream behavior can require separate integration tests.
Inspect errors and response metadata
try { const response = await model.invoke( 'Explain closures briefly.', { retry: false }, ) console.log('Response:', response.content) console.log('Finish reason:', response.finishReason) console.log('Model fallback:', response.metadata.modelFellBack) console.log('Fallback notice:', response.metadata.fallbackNotice)} catch (error) { if (isDeepSeekError(error)) { console.error('Error:', error.name, error.code) console.error('Message:', error.message) console.error('Status:', error.status) } else { throw error }}The successful-response test passed and returned finishReason: "stop" with modelFellBack: false. Avoid logging credentials, complete error details, or raw response transcripts without redaction. See Errors.
Authentication and authorization
- Missing credentials:
DeepSeekAuthenticationErroris thrown before the request is sent when credentials are missing or blank. - HTTP 401: Maps to
DeepSeekAuthenticationError. Check whether the session credentials are valid. - HTTP 403: Maps to
DeepSeekAuthorizationError. Check session validity and account permissions. - HTTP 404 or 405: Maps to
DeepSeekUpstreamError. The upstream endpoint or method may have changed.
import { DeepSeekWeb, DeepSeekAuthenticationError,} from '@orcushq/deepseek' const unauthenticatedModel = new DeepSeekWeb({ model: 'deepseek-v3', dsSessionId: '', authorization: '',}) try { await unauthenticatedModel.invoke('Hello', { retry: false, })} catch (error) { if (error instanceof DeepSeekAuthenticationError) { console.log('PASS: Missing credentials were rejected.') } else { throw error }}The missing-credentials test passed. The SDK does not automatically sign in, refresh credentials, or recover an invalid session. Local validation confirms only that the values are present, not that DeepSeek accepts them. See Authentication.
Model selection and fallback
The SDK registers deepseek-v3, deepseek-r1, deepseek-v4, and deepseek-r4. Unknown identifiers fall back to deepseek-v3 by default. Configure unknownModel: "throw" to reject unrecognized identifiers instead.
const strictModel = new DeepSeekWeb({ model: '__unknown_test_model__', unknownModel: 'throw', dsSessionId, authorization,}) try { await strictModel.invoke('Hello', { retry: false, })} catch (error) { if (error instanceof DeepSeekConfigurationError) { console.log('PASS: Unknown model was rejected.') } else { throw error }}- Unexpected fallback: Inspect
metadata.modelFellBack,metadata.fallbackReason, andmetadata.fallbackNotice. - Invalid thinking suffix: Only the documented
@think=0and@think=1suffixes are supported. - Unknown-model error:
unknownModel: "throw"raisesDeepSeekConfigurationError, notDeepSeekModelError. - Backend model identity: Fallback metadata describes the SDK’s local model resolution. It does not prove which exact model the upstream service served.
The strict unknown-model test passed. See Models for model selection and thinking suffixes.
HTTP and network errors
The HTTP error-mapping test verified the following response classes using mocked plain-text error bodies:
- HTTP 400:
DeepSeekInvalidRequestError. - HTTP 401:
DeepSeekAuthenticationError. - HTTP 403:
DeepSeekAuthorizationError. - HTTP 404, 405 and 408:
DeepSeekUpstreamError. - HTTP 429:
DeepSeekRateLimitError. - HTTP 503:
DeepSeekUpstreamErrorfor the tested non-envelope response. - Network failure:
DeepSeekNetworkError.
The tests also verified that the mapped errors satisfy isDeepSeekError() and that disabling retries results in one completion attempt. Other response formats, including upstream error envelopes and HTTP 5xx responses containing structured JSON, should be tested separately before assuming identical mappings.
HTTP 408 is not automatically retryable
The current implementation maps HTTP 408 to an upstream error. Do not assume every timeout-related HTTP status triggers a retry.
Protocol and response-parsing errors
- `DeepSeekProtocolError`: The response or streaming data is malformed or unexpected. A mocked malformed SSE response was correctly mapped to this error.
- Raw `SyntaxError`: An unexpected non-JSON response during setup may fail during JSON parsing without being wrapped in a DeepSeek-specific error.
- Unexpected error body: Upstream messages may be available through
DeepSeekApiError.apiMessageordetails.apiMessage, even whenincludeErrorBodyisfalse.
When investigating an unexpected response, capture the error class and status and sanitize relevant diagnostic details. Never publish credentials or complete raw response transcripts.
Retry behavior
The default turn-level retry policy allows three attempts, with a 2-second initial delay, a backoff factor of 2, and a maximum delay of 30 seconds. Retryable errors may be retried; authentication, configuration, protocol, and caller-abort errors generally are not.
const response = await model.invoke( 'Explain retry behavior.', { retry: { attempts: 2, delayMs: 25, backoffFactor: 2, maxDelayMs: 100, }, },)A mocked retry test returned HTTP 503 on the first completion attempt and a successful response on the next attempt. The test passed with two completion attempts and a recorded 25 ms delay.
Retry configuration by method
- `invoke()`: The constructor-level retry policy is not forwarded to turn-level retries. Use per-call retry settings.
- `stream()`: Constructor-level and per-call retry settings are honored. Use
retry: falseto disable stream retries. - `batch()`: The constructor-level retry policy is not forwarded to turn-level retries. Use per-call retry settings.
- Structured-output methods: Retry behavior follows the underlying invocation, streaming, or batch implementation.
The tests verified that a per-call retry policy can retry a transient 503. They did not verify every retry option or constructor-level behavior. See Reliability.
Streaming issues
- Malformed stream: Invalid SSE data can cause
DeepSeekProtocolError. This behavior was tested. - Repeated visible text: A retry can start a new attempt, but chunks already delivered to the application cannot be retracted.
- Stream stalls after headers: The configured timeout covers waiting for response headers, not the entire stream body. Use an
AbortSignalto bound the full operation. - Unsupported tools: Passing non-empty tool definitions to
stream()raisesDeepSeekUnsupportedFeatureError. This behavior was tested.
const controller = new AbortController() const stream = await model.stream( 'Explain how streaming works.', { retry: false, signal: controller.signal, },) for await (const chunk of stream) { if (chunk.content) { process.stdout.write(chunk.content) } if (chunk.done) { console.log('\nStreaming complete.') }}If repeated output is unacceptable, disable automatic retries or buffer chunks until a successful completion. Previously displayed text cannot be removed by the SDK.
Batch errors
- Invalid concurrency:
DeepSeekConfigurationErroris raised when concurrency is not a positive integer. The invalid-concurrency test passed. - Fail-fast behavior: The original request failure is propagated when the batch uses its default error policy.
- Continue-on-error: With
continueOnError: true, a failed batch throwsBatchAggregateErrorafter processing finishes. - Failure details: The tested aggregate error exposes
.failuresand.errors. - No partial results property: The tested aggregate error does not expose
.results.
try { const results = await model.batch( ['First prompt', 'Second prompt', 'Third prompt'], { concurrency: 2, continueOnError: true, retry: false, }, ) console.log(results)} catch (error) { if (error instanceof Error && error.name === 'BatchAggregateError') { const aggregate = error as { failures?: readonly unknown[] errors?: readonly unknown[] } console.error('Failures:', aggregate.failures) console.error('Errors:', aggregate.errors) } else { throw error }}The batch test confirmed that BatchAggregateError is a standard JavaScript error with failures and errors, but no results property. Use each failure entry’s input index to identify which prompt failed. See Batch.
Tool calling errors
Tool calling uses prompt-based orchestration through invoke(). Tool definitions use a Zod schema and a local func. Non-empty tool definitions passed to stream() or batch() are unsupported.
- Invalid tool arguments: Validate model-generated arguments before executing application code.
- Iteration limit: The default maximum is 8 tool iterations. Configure
maxToolIterationsfor the invocation. - Function failure: Sanitize tool errors because their messages can be returned to the model.
- Unsupported methods: The streaming test confirmed that non-empty tools passed to
stream()raiseDeepSeekUnsupportedFeatureError. - Client binding:
bindTools()is not the supported workflow; pass tools directly toinvoke().
Tool calls are orchestrated internally
The current implementation handles tool execution within `invoke()` and returns the final response. Do not rely on older examples that expect `response.toolCalls` or `finishReason: "tool_calls"`. See [Tool calling](/docs/deepseek/tools).
Structured-output errors
- JSON extraction failure:
DeepSeekStructuredOutputErrorindicates that the response could not be extracted or parsed as valid JSON. - Schema mismatch: The extracted value failed local Zod validation. Check the schema and prompt instructions.
- Custom parser failure: A custom parse function may throw its own error instead of
DeepSeekStructuredOutputError. - Batch failures: Structured batch operations can fail when an item cannot be parsed or validated.
const structured = model.withStructuredOutput( z.object({ name: z.string(), age: z.number(), }),) try { const result = await structured.invoke( 'Return a person as JSON.', ) console.log(result.value)} catch (error) { if (error instanceof DeepSeekStructuredOutputError) { console.error('Structured output failed:', error.message) console.error('Error details:', error.details) } else { throw error }}The test supplied non-JSON output and confirmed that DeepSeekStructuredOutputError was raised. Structured output uses prompt instructions, JSON extraction, and local validation; it is not native upstream JSON Schema enforcement. See Structured output.
Configuration errors
- Unknown model:
unknownModel: "throw"raisesDeepSeekConfigurationError. This test passed. - Invalid concurrency: A value such as
0raisesDeepSeekConfigurationError. This test passed. - Invalid retry policy: Check
attempts,delayMs,backoffFactor, andmaxDelayMswhen a retry configuration is rejected. - Invalid thinking suffix: Use only the supported thinking suffixes documented in Models.
- Derived client state:
withConfig()does not copy conversation state; supply the identifiers needed to continue the conversation.
Verified troubleshooting tests
The troubleshooting test suite passed all 12 tests using mocked responses. It verified successful invocation metadata, strict unknown-model handling, invalid batch concurrency, HTTP error mapping, retry after HTTP 503, network failure mapping, malformed streaming data, batch aggregation, unsupported streaming tools, structured-output parsing failure, timeout handling, and an already-aborted signal.
./node_modules/.bin/vitest run --project deepseek tests/troubleshooting.test.tsThese tests validate SDK behavior under controlled conditions. They do not independently prove live account permissions, current upstream model availability, real rate-limit responses, or every custom-header and transport scenario.
Debugging checklist
- Confirm that both authentication values are present and correctly configured.
- Inspect the actual error class, code, and HTTP status.
- Check the model identifier and fallback metadata.
- Configure retries at the appropriate method level.
- Use an
AbortSignalto stop long-running operations. - Inspect
.failuresand.errorswhen handling batch failures. - Validate tool arguments and structured output.
- Redact credentials, error details, and raw response metadata before sharing diagnostics.