Skip to content

fix: embedding deserialization breaks with serde_json/arbitrary_precision#1518

Open
EinarasGar wants to merge 1 commit into0xPlaygrounds:mainfrom
EinarasGar:fix/embedding-arbitrary-precision
Open

fix: embedding deserialization breaks with serde_json/arbitrary_precision#1518
EinarasGar wants to merge 1 commit into0xPlaygrounds:mainfrom
EinarasGar:fix/embedding-arbitrary-precision

Conversation

@EinarasGar
Copy link

Problem

Embedding deserialization fails when serde_json's arbitrary_precision feature is enabled anywhere in the dependency tree. The error is:

JsonError: data did not match any variant of untagged enum ApiResponse

This is commonly triggered by rust_decimal with the serde-with-arbitrary-precision feature, which is widely used in financial applications.

Root cause

When serde_json/arbitrary_precision is active, all JSON numbers are internally represented as strings. The Vec<f64> fields in embedding response structs (EmbeddingValues, EmbeddingData, etc.) fail to deserialize because serde expects native floats but receives string-backed numbers.

The arbitrary_precision feature is a compile-time, crate-wide flag — it cannot be opted out of per-deserialization call. Any crate in the dependency tree that enables it (directly or transitively) affects all serde_json usage in the binary.

Reproduction

Any project that combines rig embeddings with rust_decimal:

[dependencies]
rig-core = "0.32"
rust_decimal = { version = "1", features = ["serde-with-arbitrary-precision"] }
let client = gemini::Client::new("key").unwrap();
let model = client.embedding_model_with_ndims("gemini-embedding-001", 768);
let result = model.embed_text("hello").await; // fails with JsonError

The same code works in a crate without rust_decimal in the dependency tree.

Fix

Change embedding response structs from Vec<f64> to Vec<serde_json::Number>, then convert to f64 via .as_f64() when building the Embedding struct.

serde_json::Number is the canonical number type that works correctly regardless of whether arbitrary_precision is enabled — it's the internal representation serde_json uses in both modes.

Affected providers

All 6 providers with embedding support:

  • Gemini (EmbeddingValues.values)
  • OpenAI (EmbeddingData.embedding)
  • Mistral (EmbeddingData.embedding)
  • OpenRouter (EmbeddingData.embedding)
  • Together (EmbeddingData.embedding)
  • Cohere (EmbeddingResponse.embeddings)

Change summary

Each provider's embedding response struct: Vec<f64>Vec<serde_json::Number>, with .filter_map(|n| n.as_f64()) at the point where the Embedding struct is built. No new dependencies, no API changes.

…sion

Change embedding response structs to use `Vec<serde_json::Number>`
instead of `Vec<f64>`, then convert to f64 after parsing.

When `serde_json/arbitrary_precision` is enabled (commonly pulled in
by `rust_decimal/serde-with-arbitrary-precision`), all JSON numbers
are represented as strings internally. This causes `Vec<f64>` fields
to fail deserialization with:
"data did not match any variant of untagged enum ApiResponse"

`serde_json::Number` deserializes correctly regardless of whether
`arbitrary_precision` is active, then `.as_f64()` converts to the
expected type.

Affects all providers: Gemini, OpenAI, Mistral, OpenRouter, Together,
and Cohere.
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