---
title: "ADR-226: Gemini correlates native function-call IDs"
manual: "LLM Foundation"
version: "main"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-226@main"
source: "Adr/Adr226GeminiCorrelatesNativeFunctionCallIds.rst"
rendered: "2026-10-22T14:31:51+00:00"
---

# ADR-226: Gemini correlates native function-call IDs {#adr-226}

-   *Status:* Accepted
-   *Date:* 2026-10-10
-   *Authors:* Netresearch DTT GmbH
-   *Amends:* [ADR-222](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-222@main) (private native-call correlation metadata)

## Context {#context}

Google's [Generate Content contract](https://ai.google.dev/api/generate-content?hl=en) defines an optional
`FunctionCall.id` and requires its matching `FunctionResponse.id` when
populated. Controlled actual response-to-request probes retain the native call
ID on replay but omit it from the result: three assertion failures, two ID-less
positive controls, zero framework errors. This is no live API acceptance claim.

The public adapter synthesizes conversation-unique ToolCall IDs. A positional
join to native parts cannot preserve association when two identical parallel
calls have different native IDs and the generic list is reordered. Rejecting
those fresh parallel calls would reject a supported response.

A repeated actual HTTP capture-to-next-request probe also shows three valid
numeric replies refused by strict PHP integer/float equality: `1.0`, `-3.0`
and negative zero normalize to integers in generic argument serialization,
while their freshly captured native parts retain floats. A fractional-float
control stays accepted. These are three assertion failures with zero framework
errors on the proposed implementation, not live API failures.

## Decision {#decision}

Keep public synthesized ToolCall IDs and capture their association with
populated native IDs at the actual parser. Extend only the existing owned
opaque capsule with optional `tool_call_bindings` entries containing
`tool_call_id` and `native_id`. Preserve native parts. Validate the owning
turn, one-to-one IDs, complete coverage and matching decoded call
names/arguments before consuming the association. The existing private
transcript prescan can carry name and native ID together; an additional public
scanner is unnecessary.

Compare existing decoded argument maps without inventing lost JSON kinds.
Treat integer/float pairs as the same numeric value only when their conversion
is exact and reversible; preserve strict separation from strings, booleans and
other scalar kinds. This permits the inherited `1.0` to `1` and negative-zero
to zero normalization without accepting rounded large-integer mismatches. Apply
that comparison recursively to decoded arguments for captured and legacy
associations.

Echo only that native ID in the matching Gemini result. An absent native ID
stays absent; a present ID on an accepted named call must be a nonempty string.
Malformed or duplicate native IDs in the tool parser or a correlated replay
turn are refused with an existing typed provider exception. IDs may recur in
later turns without replacing the conversation-unique public IDs. A public ID
involved in native association must not identify another call in the
transcript.

Older capsules without bindings may recover only an unambiguous name/decoded
argument association. Never infer by position; identical parallel signatures
with populated native IDs must fail before contact. Entirely ID-less and
unambiguous older owned turns remain compatible. Native-parts-only and wholly
ID-less replay adds no generic call-list requirement; plain signed capture does
not manufacture generic calls. Always structurally validate a present binding
field: those compatible turns permit only a valid empty list. A nonempty or
malformed field is refused with a typed exception before contact. Extra
association checks concern owned generic turns requiring native ID correlation.
Foreign/generic turns provide no native binding, and other provider payloads
contain no capsule or binding.

Use the same association on closing and streaming requests carrying existing
state. Real encrypted SQL suspension/resume must preserve captured bindings,
signatures, known Struct objects and approval identity. No public field,
signature or schema change is introduced. Correlation does not establish trust,
authentication or authorization, and validation must not expose native content.

## Consequences {#consequences}

Fresh same-name/same-argument parallel calls can be answered unambiguously even
when their generic list is reordered. Ambiguous older state cannot be repaired
by guessing and is refused. Current decoded equality does not restore nested
JSON kinds lost earlier; that finding remains open.

Actual HTTP wire and durable SQLite/MariaDB oracles, selected assertion faults,
independent whole-diff review and complete gates are required. The separate
decision depends on the published Struct implementation; its implementation
must start from the published decision. Native text extraction, new streamed
capture and live availability remain separate.
