Skip to content

bug(orchestration): getDeltaContent() returns empty string for Anthropic Claude streaming chunks #1963

Description

@madankumarpichamuthu

Describe the Bug

When using Anthropic Claude models via the Orchestration API in streaming mode, OrchestrationStreamChunkResponse.getDeltaContent() returns undefined (treated as '') for every chunk, causing handleLLMNewToken to fire with empty strings. No text is ever surfaced to the LangChain streaming caller.

Root Cause

ChatDelta.content is typed as string in the generated schema (packages/orchestration/src/client/api/schema/chat-delta.ts):

export type ChatDelta = {
  content: string;
  // ...
};

getDeltaContent() returns it directly:

getDeltaContent(choiceIndex = 0): string | undefined {
  return this.findChoiceByIndex(choiceIndex)?.delta.content;
}

However, Anthropic Claude models return delta.content as Array<{type: string, text: string}> (content blocks) in streaming chunks — not a plain string. The TypeScript type is wrong, and the method has no handling for the array case. The result is that every streaming chunk from a Claude model is silently treated as an empty token.

Steps to Reproduce

  1. Configure OrchestrationClient with a Claude model (e.g. claude-3-5-sonnet)
  2. Enable streaming ({ streaming: true } in langchainOptions)
  3. Call streamEvents({ version: "v3" }) or iterate _streamResponseChunks
  4. Observe that all handleLLMNewToken callbacks fire with content = ""

Expected Behavior

getDeltaContent() should extract and concatenate text from content-block arrays when delta.content is an array:

getDeltaContent(choiceIndex = 0): string | undefined {
  const content = this.findChoiceByIndex(choiceIndex)?.delta.content;
  if (Array.isArray(content)) {
    return content
      .filter(b => b.type === 'text')
      .map(b => b.text ?? '')
      .join('');
  }
  return content;
}

The ChatDelta type should also be widened:

export type ChatDelta = {
  content: string | Array<{ type: string; text?: string; [key: string]: any }>;
  // ...
};

Actual Behavior

Every streaming chunk from Claude fires handleLLMNewToken(""). The full response is only available after stream completion via the non-streaming path.

Confirmed Affected Version

@sap-ai-sdk/langchain 2.11.0 (latest at time of filing)

Workaround

Downstream consumers can subclass OrchestrationClient and override _streamResponseChunks to extract text manually from chunk._data:

const rawContent =
  chunk._data?.intermediate_results?.llm?.choices?.[0]?.delta?.content ??
  chunk._data?.final_result?.choices?.[0]?.delta?.content;

const text = Array.isArray(rawContent)
  ? rawContent.filter(b => b.type === 'text').map(b => b.text ?? '').join('')
  : '';

This is the workaround we currently carry in our application. We'd like to see it fixed natively in the SDK.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions