Skip to content

ChatOpenAI (Responses API, streaming): response_metadata.model_name is the requested alias, not the resolved model #11466

Description

@SiKreuz

Checked other resources

  • This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
  • I added a very descriptive title to this issue.
  • I searched the LangChain.js documentation with the integrated search.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain.js rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).

Example Code

No API key needed — the converter is exported from the package root:

import { convertResponsesDeltaToChatGenerationChunk } from "@langchain/openai";
import { concat } from "@langchain/core/utils/stream";

// The two events around a streamed Responses call: `response.created` echoes
// the requested model, `response.completed` carries the resolved snapshot.
const events = [
  { type: "response.created", response: { id: "resp_1", model: "gpt-5.4" } },
  {
    type: "response.completed",
    response: {
      id: "resp_1",
      model: "gpt-5.4-2026-03-05",
      status: "completed",
      output: [],
    },
  },
];

const merged = events
  .map((e) => convertResponsesDeltaToChatGenerationChunk(e))
  .filter((c) => c != null)
  .map((c) => c.message)
  .reduce((a, b) => concat(a, b));

console.log("model_name:", merged.response_metadata.model_name);
console.log("model:     ", merged.response_metadata.model);

Actual output on @langchain/openai 1.5.10:

model_name: gpt-5.4
model:      gpt-5.4gpt-5.4-2026-03-05

Expected: both gpt-5.4-2026-03-05. The non-streaming path already does this —
convertResponsesMessageToAIMessage allowlists model: response.model and
model_name: response.model from the completed response.


### Error Message and Stack Trace (if applicable)

_No response_

### Description

When the Responses API is used with streaming, `response_metadata.model_name` is the **requested** model string rather than the resolved snapshot the API actually served, and `response_metadata.model` is a concatenation of both.

In `convertResponsesDeltaToChatGenerationChunk` (`src/converters/responses.ts`), both keys are written from the `response.created` event, which echoes back the requested model:

```typescript
} else if (event.type === "response.created") {
  id = event.response.id;
  response_metadata.id = event.response.id;
  response_metadata.model_name = event.response.model; // requested alias
  response_metadata.model = event.response.model;
}

The response.completed branch carries the resolved snapshot, but only copies the keys present on its response object:

for (const [key, value] of Object.entries(event.response)) { ... }

event.response has no model_name key, so model_name is never corrected. model is written a second time — and because neither model nor model_name appears in the last-write-wins list _mergeDicts consults (["id", "name", "output_version", "model_provider"]) nor in DEFAULT_MERGE_IGNORE_KEYS, the second write is string-appended instead of replacing:

model_name: "gpt-5.4"
model:      "gpt-5.4gpt-5.4-2026-03-05"

Expected: both "gpt-5.4-2026-03-05", matching the non-streaming Responses path (convertResponsesMessageToAIMessage, which sets model_name: response.model) and the Completions path.

Impact: anything attributing cost or usage by response_metadata.model_name records the alias, so per-snapshot pricing and model-version comparisons are wrong. Langfuse's LangChain callback handler ends every generation with this field, so all traced reasoning-enabled calls are recorded under the alias.

Note model_provider already sits in the merge's last-write-wins list, so this class of key is handled — model and model_name look simply missed. #11054 is the same merge behaviour surfacing on the Completions path.

Proposed fix

Write both keys from response.completed / response.incomplete instead of response.created:

  } else if (event.type === "response.created") {
    id = event.response.id;
    response_metadata.id = event.response.id;
-   response_metadata.model_name = event.response.model;
-   response_metadata.model = event.response.model;
  } else if (event.type === "response.completed" || event.type === "response.incomplete") {
    id = event.response.id;
+   response_metadata.model_name = event.response.model;

(model is then set once by the existing Object.entries copy, so nothing concatenates.) Adding model and model_name next to model_provider in _mergeDicts' last-write-wins list would additionally fix #11054 and make this robust for every provider.

Related

System Info

  • @langchain/openai 1.5.10, @langchain/core 1.2.9, langchain 1.5.10 (all latest at time of filing)
  • macOS (Darwin arm64)
  • Node.js 26.7.0 / Bun 1.4.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions