Skip to content

fix(utils): batched embed drops meta.tokens and image billed units - #784

Open
arthi-arumugam-git wants to merge 1 commit into
cohere-ai:mainfrom
arthi-arumugam-git:fix/merge-meta-drops-token-fields
Open

fix(utils): batched embed drops meta.tokens and image billed units#784
arthi-arumugam-git wants to merge 1 commit into
cohere-ai:mainfrom
arthi-arumugam-git:fix/merge-meta-drops-token-fields

Conversation

@arthi-arumugam-git

@arthi-arumugam-git arthi-arumugam-git commented Jul 26, 2026

Copy link
Copy Markdown

Client.embed() goes through merge_embed_responses on every call unless you pass batching=False or pass images, and that path rebuilds ApiMeta from scratch inside merge_meta_field. The rebuild sets api_version, four of the six ApiMetaBilledUnits fields, and warnings. Everything else on the metadata is dropped on the floor: meta.tokens, meta.cached_tokens, billed_units.images and billed_units.image_tokens.

Since batching defaults to True, the default path is the lossy one:

co.embed(texts=texts).meta.tokens                  # None
co.embed(texts=texts, batching=False).meta.tokens  # populated

Same request, different metadata, and nothing in the signature suggests that a batching flag should change which usage numbers come back. Anyone reading meta.tokens for accounting gets None and no error to tell them why.

The fix sums the four missing fields exactly the way the existing four are summed. cached_tokens sits directly on ApiMeta rather than under billed_units, so it sums off the meta list instead of the billed units list.

One deliberate choice worth flagging: tokens stays None when none of the input metas carried it, rather than building an ApiMetaTokens(None, None). A merged response with no token counts then looks exactly like it does today, which is why the existing equality assertions in test_embed_utils.py did not need touching.

Why this happened, and stopping it happening again

merge_meta_field lists the fields it copies by hand. images, image_tokens, tokens and cached_tokens were added to the models and the merge was never updated, and nothing failed. The same thing will happen to the next field added.

So one of the three tests drives its assertions off the model fields rather than a hardcoded list:

billed_fields = get_fields(ApiMetaBilledUnits())
...
for field in billed_fields:
    self.assertEqual(getattr(merged.billed_units, field), 2, f"billed_units.{field} was dropped")

Add a field to ApiMetaBilledUnits or ApiMetaTokens without touching merge_meta_field and that test fails immediately. I verified it fails on main as it stands.

The other two are ordinary: test_merge_meta_field_keeps_tokens_and_image_units covers the summing and uses output_tokens=0 on both inputs so a falsy value is not confused with an absent one, and test_merge_meta_field_leaves_tokens_unset_when_absent pins the None behaviour so it does not get simplified away later.

Notes


Note

Low Risk
Localized change to embed response merging and tests; fixes incorrect metadata rather than altering auth or API contracts.

Overview
Fixes silent loss of usage metadata when Client.embed() merges multiple batch responses (default batching=True). merge_meta_field now sums billed_units.images, billed_units.image_tokens, meta.tokens (input/output), and cached_tokens alongside the fields it already merged, so batched calls return the same usage shape as a single request.

tokens stays None when no input meta had token counts (no empty ApiMetaTokens), keeping merged responses aligned with unbatched ones when token data was never present.

Tests cover image/token summing, absent-token behavior, and a model-driven check that every numeric field on ApiMetaBilledUnits / ApiMetaTokens is merged so future model fields cannot be dropped without failing CI.

Reviewed by Cursor Bugbot for commit 0878e27. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/cohere/core/client_wrapper.py Outdated
@arthi-arumugam-git
arthi-arumugam-git force-pushed the fix/merge-meta-drops-token-fields branch from afab009 to a54fb55 Compare July 26, 2026 07:20
@arthi-arumugam-git

Copy link
Copy Markdown
Author

Good catch from the bot, the User-Agent string was still on 7.0.8 while X-Fern-SDK-Version and pyproject.toml were on 7.0.9. Pushed a fix so both read cohere/7.0.9.

Note that #785 also bumps 7.0.8 -> 7.0.9, so whichever of the two lands second will need its bump rebased. Happy to rebase on request, or drop the bump commit from one of them if you would rather handle versioning at release time.

Client.embed() sends every call through merge_embed_responses unless you
pass batching=False or images, and that path rebuilds ApiMeta from
scratch in merge_meta_field. The rebuild only sets api_version, four of
the six ApiMetaBilledUnits fields, and warnings.

So meta.tokens, meta.cached_tokens, billed_units.images and
billed_units.image_tokens are discarded on the way out. The same request
returns different metadata depending on whether batching is on:

    co.embed(texts=texts).meta.tokens                  # None
    co.embed(texts=texts, batching=False).meta.tokens  # populated

Anyone reading meta.tokens for usage accounting silently gets nothing.

Sum the missing fields the same way the existing four are summed.
cached_tokens sits directly on ApiMeta so it sums off the meta list
rather than the billed_units list. tokens stays None when no input meta
carried it, so a merged response with no token counts looks exactly like
it does today and the existing equality assertions still hold.

merge_meta_field names every field it copies by hand, which is why these
four went missing when they were added to the models. One of the tests
drives its assertions off the model fields instead of a fixed list, so
the next field added to ApiMeta fails there rather than silently
disappearing from merged responses.
@arthi-arumugam-git
arthi-arumugam-git force-pushed the fix/merge-meta-drops-token-fields branch from a54fb55 to 0878e27 Compare July 26, 2026 07:29
@arthi-arumugam-git

Copy link
Copy Markdown
Author

Pushed a cleanup pass on both this and #785.

Dropped the version bump from both. They cannot both go to 7.0.9, and core/client_wrapper.py is Fern generated rather than in .fernignore, so it is not really mine to edit. That also resolves the User-Agent mismatch Bugbot flagged, since the bump is gone. Each PR now touches only src/cohere/utils.py and tests/, so they no longer conflict with each other and can merge in either order.

Also added a test here that drives its assertions off the model fields instead of a hardcoded list. merge_meta_field enumerates what it copies by hand, which is exactly why these four fields went missing when they were added, so the new test fails the moment a field is added to ApiMeta or ApiMetaBilledUnits without updating the merge. Verified it fails on main as it stands. Body updated with the details.

@arthi-arumugam-git

Copy link
Copy Markdown
Author

Some prior art I found after opening this, which is worth linking.

Issue #711 reported image_tokens missing from the embedding response model and was closed on 2026-01-16 when the model gained the field through a regeneration. But #711 had two halves. The model was one, and merge_meta_field copying it through was the other, and only the first landed. So on main today ApiMetaBilledUnits.image_tokens exists and is populated on an unbatched call, and is still dropped on a batched one. #711 reads as fixed and is not.

PR #714 spotted that and has been open since 2025-12-28. Its model change was overtaken by the regeneration; its one line adding image_tokens to merge_meta_field never merged. This PR includes that line, alongside images, tokens and cached_tokens, so if this lands then #714 can be closed. Credit to @yurekami for finding it first, seven months ago.

That is also the argument for the model-driven test rather than another hardcoded assertion. A field was added to the model, the merge was not updated, an issue was filed, the issue was closed, and the bug survived all of it because nothing failed. The test I added fails in that situation.

@arthi-arumugam-git

Copy link
Copy Markdown
Author

Bugbot reviewed afab009, which is no longer in this branch. The rebase at 0878e27 drops the version bump and the client_wrapper.py change entirely, so the User-Agent mismatch it flagged is gone.

Reason for dropping it rather than fixing it: two open PRs cannot both claim 7.0.9, and src/cohere/core/client_wrapper.py is Fern generated rather than hand maintained. Versioning is yours to do at release time.

This PR now touches src/cohere/utils.py and tests/test_embed_utils.py only, both covered by .fernignore.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0878e27. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant