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

src/inspect-errors.ts
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: DeepSeekAuthenticationError is 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.
src/auth-errors.ts
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.

typescript
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, and metadata.fallbackNotice.
  • Invalid thinking suffix: Only the documented @think=0 and @think=1 suffixes are supported.
  • Unknown-model error: unknownModel: "throw" raises DeepSeekConfigurationError, not DeepSeekModelError.
  • 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: DeepSeekUpstreamError for 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.apiMessage or details.apiMessage, even when includeErrorBody is false.

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.

typescript
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: false to 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 AbortSignal to bound the full operation.
  • Unsupported tools: Passing non-empty tool definitions to stream() raises DeepSeekUnsupportedFeatureError. This behavior was tested.
typescript
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: DeepSeekConfigurationError is 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 throws BatchAggregateError after processing finishes.
  • Failure details: The tested aggregate error exposes .failures and .errors.
  • No partial results property: The tested aggregate error does not expose .results.
typescript
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 maxToolIterations for 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() raise DeepSeekUnsupportedFeatureError.
  • Client binding: bindTools() is not the supported workflow; pass tools directly to invoke().

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: DeepSeekStructuredOutputError indicates 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.
typescript
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" raises DeepSeekConfigurationError. This test passed.
  • Invalid concurrency: A value such as 0 raises DeepSeekConfigurationError. This test passed.
  • Invalid retry policy: Check attempts, delayMs, backoffFactor, and maxDelayMs when 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.

bash
./node_modules/.bin/vitest run --project deepseek tests/troubleshooting.test.ts

These 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 AbortSignal to stop long-running operations.
  • Inspect .failures and .errors when handling batch failures.
  • Validate tool arguments and structured output.
  • Redact credentials, error details, and raw response metadata before sharing diagnostics.