Skip to content

feat(openrouter): surface cost and cost_details in response_metadata#35461

Merged
Mason Daugherty (mdrxy) merged 3 commits intolangchain-ai:masterfrom
untilhamza:feat/openrouter-cost-metadata
Mar 1, 2026
Merged

feat(openrouter): surface cost and cost_details in response_metadata#35461
Mason Daugherty (mdrxy) merged 3 commits intolangchain-ai:masterfrom
untilhamza:feat/openrouter-cost-metadata

Conversation

@untilhamza
Copy link
Copy Markdown
Contributor

@untilhamza Hamza Kyamanywa (untilhamza) commented Feb 27, 2026

Description

OpenRouter returns cost and cost_details in its API response usage object, providing the actual cost of each API call. Currently, _create_usage_metadata() only extracts token counts and drops these cost fields.

This PR surfaces both cost and cost_details in response_metadata for both non-streaming and streaming paths, allowing users to access actual API costs directly from the response without manual estimation from token counts.

Example response from OpenRouter:

{
  "usage": {
    "prompt_tokens": 100,
    "completion_tokens": 50,
    "cost": 0.000075,
    "cost_details": {
      "upstream_inference_cost": 0.00007745,
      "upstream_inference_prompt_cost": 0.00000895,
      "upstream_inference_completions_cost": 0.0000685
    }
  }
}

After this change:

result = chat.invoke("hello")
result.response_metadata["cost"]          # 0.000075
result.response_metadata["cost_details"]  # {...}

Changes

  • _create_chat_result: Surface cost and cost_details from token_usage into response_metadata (non-streaming)
  • _convert_chunk_to_message_chunk: Same for streaming AIMessageChunk
  • Added PLR0912 to noqa comments (new branches pushed count over threshold)
  • Added two unit tests: one verifying cost fields are present when returned, one verifying they're absent when not in usage

Issue

N/A — discovered while integrating OpenRouter in a production pipeline. The cost data is already returned by the API but was being silently dropped.

Dependencies

None.

Twitter handle

@hamza_kyamanywa

OpenRouter returns `cost` and `cost_details` in its API response usage
object, but these fields were being dropped by `_create_usage_metadata()`
which only extracts token counts.

This change surfaces both fields in `response_metadata` for both
non-streaming (`_create_chat_result`) and streaming
(`_convert_chunk_to_message_chunk`) paths, allowing users to access
actual API costs without manual estimation from token counts.
@github-actions github-actions bot added external feature For PRs that implement a new feature; NOT A FEATURE REQUEST integration PR made that is related to a provider partner package integration openrouter `langchain-openrouter` package issues & PRs and removed feature For PRs that implement a new feature; NOT A FEATURE REQUEST labels Feb 27, 2026
Copy link
Copy Markdown
Member

@mdrxy Mason Daugherty (mdrxy) left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do see one blocker in the streaming path:

  1. In _stream and _astream, final-chunk metadata currently replaces message_chunk.response_metadata:
    • message_chunk = message_chunk.model_copy(update={"response_metadata": generation_info})
  2. Since cost fields are added earlier in _convert_chunk_to_message_chunk, they can be dropped if finish_reason metadata is set on the same chunk.

Could you merge metadata instead of replacing it (e.g. merge existing message_chunk.response_metadata with generation_info)?

Also, please add streaming regression tests (stream + astream) asserting cost/cost_details survive on the final chunk when usage includes them.

…preserve cost fields

The final streaming chunk overwrote response_metadata with generation_info,
dropping cost/cost_details set by _convert_chunk_to_message_chunk. Now merges
existing metadata with generation_info in both _stream and _astream paths.

Adds regression tests for sync and async streaming to verify cost fields
survive alongside finish_reason on the final chunk.
@untilhamza
Copy link
Copy Markdown
Contributor Author

Mason Daugherty (@mdrxy) Good catch — thanks for the review!

Fixed in c79c26b:

  1. Metadata merge: Both _stream and _astream now merge existing response_metadata with generation_info instead of replacing it:
    update={"response_metadata": {**message_chunk.response_metadata, **generation_info}}
  2. Regression tests: Added test_stream_cost_survives_final_chunk and test_astream_cost_survives_final_chunk — both assert that cost/cost_details survive alongside finish_reason on the final chunk.

All 194 unit tests pass.

@mdrxy Mason Daugherty (mdrxy) changed the title feat(openrouter): surface cost and cost_details in response_metadata feat(openrouter): surface cost and cost_details in response_metadata Mar 1, 2026
@mdrxy Mason Daugherty (mdrxy) merged commit f84b534 into langchain-ai:master Mar 1, 2026
18 of 20 checks passed
@github-actions github-actions bot added feature For PRs that implement a new feature; NOT A FEATURE REQUEST and removed feature For PRs that implement a new feature; NOT A FEATURE REQUEST labels Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external feature For PRs that implement a new feature; NOT A FEATURE REQUEST integration PR made that is related to a provider partner package integration openrouter `langchain-openrouter` package issues & PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants