Skip to content

[Needs review] Update google provider types - #453

Draft
github-actions[bot] wants to merge 1 commit into
mainfrom
update-google-provider-types-0ad57111-35126453074
Draft

github-actions[bot] wants to merge 1 commit into
mainfrom
update-google-provider-types-0ad57111-35126453074

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated update of Lingua provider types.

Provider: google

Publication mode: needs-review draft

Continuation required

This draft preserves the automation's partial work. One or more validation or advisory checks did not complete successfully; continue from this branch instead of restarting the provider update.

Feedback: comment /bt good or /bt bad to log review feedback to the Braintrust trace.

Human decisions required

The agent completed every unblocked item. These decisions require human input before the update can be finished:

  • Part.audioTranscription (AudioTranscription, WordInfo) (google-audio-transcription-output)

    • Question: What is the canonical universal representation for Google's Part.audioTranscription, which carries transcript text together with a speaker label and per-word start and end offsets, and what is the streaming accumulation contract for it? Alternatively, should Lingua declare audio transcription output non-transformable and reject it explicitly instead?
    • Evidence: crates/lingua/src/providers/google/generated.rs:223-258 adds AudioTranscription { speaker_label, text, words: Vec } and WordInfo { word, start_offset, end_offset } as google-duration strings. The Part import chain at crates/lingua/src/providers/google/convert.rs:215-296 is a silent if/else that never inspects part.audio_transcription, so the block is dropped, and convert.rs:299-302 then produces AssistantContent::Array(vec![]) when it was the only part, meaning a transcription-only response silently becomes an empty assistant message. On the streaming side crates/lingua/src/providers/google/adapter.rs:740-748 'continue's on any part without text, and UniversalStreamDelta (crates/lingua/src/processing/transform.rs:695 onward) exposes only content, tool_calls, reasoning and reasoning_signature, with no slot for a timed word array that would need to be appended across chunks. crates/lingua/src/universal/ contains no timed-word, speaker-label or diarization type; TextContentPart could hold the text but would discard speaker_label and every word offset, which is exactly the lossy coercion AGENTS.md forbids. Same-format Google to Google traffic is already safe: transform.rs:600-605 and :885-894 pass native responses and stream chunks through byte-for-byte, and crates/lingua/src/validation/google.rs::tests::test_google_response_roundtrips_discovery_20260915_fields pins the lossless typed round trip.
    • Recommended option: Ship the explicit-rejection option now and defer the universal model. Raise ConvertError::UnsupportedMapping { from: "Google Part.audioTranscription", to: } from the Part import chain in convert.rs and from adapter.rs stream_to_universal, so cross-provider transforms surface TransformError::ToUniversalFailed instead of dropping the block or emitting an empty assistant message, while Google-native passthrough stays byte-preserving. This removes the silent empty-message failure mode immediately and leaves the portable representation open. If instead a portable mapping is wanted now, prefer option B below, since it is the only one that carries speaker_label and word offsets without loss.
    • Alternatives:
      • A. Explicit rejection only (recommended): no universal change; add ConvertError::UnsupportedMapping on the Google import path. Tradeoff: cross-provider transforms of transcription responses start failing loudly rather than degrading silently, which is a behavior change for callers who were previously receiving an empty assistant message, but it is the only option that satisfies the AGENTS.md rule against silent drops without inventing a universal type.
      • B. Add a dedicated universal content variant, for example AssistantContentPart::Transcription { text, speaker_label, words: Vec<TranscriptWord { word, start, end }> }, and a matching UniversalStreamDelta field with an append-on-chunk accumulation contract. Tradeoff: fully non-lossy and future-proof for other providers that expose diarized transcripts, but it expands the universal model for a feature only Google currently emits, requires a duration parsing decision for the google-duration strings such as '0.5s', and requires every other provider adapter to decide how to reject or ignore the new variant.
      • C. Map only AudioTranscription.text onto TextContentPart and drop speaker_label and words. Tradeoff: maximizes cross-provider coverage numbers and needs no universal change, but it silently discards diarization and timing data and merges transcript text into ordinary assistant text, which AGENTS.md rejects as a lossy coercion and as a surface-shape rather than semantic match.
      • D. Carry the raw block through provider_options as an opaque round-trip payload. Tradeoff: explicitly forbidden by the AGENTS.md no-hidden-marker-fields rule and by this update's planning constraints; it would fake a lossless round trip without giving the data any meaning.
    • Likely files:
      • crates/lingua/src/providers/google/convert.rs
      • crates/lingua/src/providers/google/adapter.rs
      • crates/lingua/src/providers/google/generated.rs
      • crates/lingua/src/universal/content.rs
      • crates/lingua/src/processing/transform.rs
      • crates/lingua/src/validation/google.rs
    • Validation commands:
      • cargo test -p lingua validation::google::tests
      • cargo test -p lingua providers::google::convert::tests
      • cargo test -p lingua providers::google::adapter::tests
      • cargo test -p lingua processing::transform::tests
      • make check
      • make test-payloads
  • GenerationConfig.audioTranscriptionConfig (AudioTranscriptionConfig, LanguageHints, mode enum) (google-audio-transcription-config)

    • Question: Should GenerationConfig.audioTranscriptionConfig be modeled as portable transcription parameters on UniversalParams, or declared non-transformable and rejected explicitly? If portable, which carrier is canonical and how are the three redundant language-selection encodings (languageCodes, languageHints.languageCodes and the empty-message sentinel languageAuto) reconciled into one representation?
    • Evidence: crates/lingua/src/providers/google/generated.rs:764-830 adds AudioTranscriptionConfig, LanguageHints and AudioTranscriptionConfigMode (MODE_UNSPECIFIED, VERBATIM, SMART), with language_auto typed as Option<serde_json::Map<String, Value>> because the specification models it as an empty message sentinel. The value is parsed into the typed GenerationConfig (generated.rs:638-640) but crates/lingua/src/providers/google/adapter.rs:177-229 reads only max_output_tokens, thinking_config, temperature, top_p, top_k and stop_sequences, so it is discarded on import. It cannot fall into extras either, because generation_config is a named GoogleParams field (crates/lingua/src/providers/google/params.rs:30) rather than an unknown key. On export, adapter.rs:478-486 builds a fresh GenerationConfig { ..Default::default() }, so the config is unrecoverable on every non-passthrough path, including a Google to Google transform forced to translate by a model override (crates/lingua/src/processing/transform.rs:462). UniversalParams (adapter.rs:242-266) has no transcription slot, and the extras mechanism cannot carry it because extras are merged at the top level (adapter.rs:268-273 and :530-537) whereas this config is nested under generationConfig. Plain same-format passthrough is unaffected (transform.rs:462-472).
    • Recommended option: Treat the knobs as genuinely portable and add an explicit, typed transcription parameter block to UniversalParams covering mode (verbatim versus smart), diarization, word timestamps, language codes, custom vocabulary and adaptation phrases, canonicalizing the language selection onto a single ordered list of language codes plus an explicit automatic-detection flag derived from languageAuto. Reject with ConvertError::UnsupportedMapping only for targets that have no transcription surface, and in the same change fix the silent loss on the forced-translation Google to Google path by re-emitting the block from the universal parameters. If universal expansion is judged premature for a feature only Google exposes, fall back to alternative B.
    • Alternatives:
      • A. Typed UniversalParams transcription block (recommended): non-lossy for Google and reusable by any future provider with an ASR surface. Tradeoff: expands the universal parameter model for a single provider today, and requires a documented canonicalization for languageCodes versus languageHints.languageCodes versus languageAuto, which are three overlapping encodings of the same intent.
      • B. Declare it provider-only and reject explicitly: raise ConvertError::UnsupportedMapping when a non-Google target receives a request carrying audioTranscriptionConfig, and additionally preserve it across forced Google to Google translation by threading it through the typed Google parameter path. Tradeoff: no universal change and the smallest surface, but it classifies generic speech-recognition settings as provider-specific, which is arguably wrong since diarization and word timestamps are provider-neutral concepts.
      • C. Extend the extras mechanism to support nested provider-scoped keys so generationConfig.audioTranscriptionConfig survives round trips as an opaque blob. Tradeoff: fixes the forced-translation loss with no universal modeling, but it creates an opaque round-trip-only carrier with no meaning, which this update's constraints and the AGENTS.md typed-extras rule both forbid.
      • D. Do nothing beyond the generated types. Tradeoff: zero risk and no behavior change, but the config is then silently dropped on both cross-provider transforms and forced Google to Google translation, violating the explicit-unsupported-mapping rule.
    • Likely files:
      • crates/lingua/src/providers/google/adapter.rs
      • crates/lingua/src/providers/google/params.rs
      • crates/lingua/src/providers/google/generated.rs
      • crates/lingua/src/universal/params.rs
      • crates/lingua/src/processing/transform.rs
      • crates/lingua/src/validation/google.rs
    • Validation commands:
      • cargo test -p lingua validation::google::tests
      • cargo test -p lingua providers::google::params::tests
      • cargo test -p lingua providers::google::adapter::tests
      • cargo test -p lingua processing::transform::tests
      • make check
      • make test-payloads

Provider-only changes

These native wire features are accepted and passed through unchanged, but cross-provider transformation is intentionally unsupported:

  • Blob.displayName / FileData.displayName (google-media-display-name)
    • Native contract: The generated Google types accept and re-emit displayName on both Blob and FileData, Google-native detection and validation keep accepting the payload, an unmodified Google request or response passes through byte-for-byte, and any cross-provider transform of a payload whose Blob.displayName or FileData.displayName is set returns an explicit ConvertError::UnsupportedMapping surfaced as TransformError::ToUniversalFailed rather than silently dropping the reference handle.
    • Scope decision: The field only has meaning inside Google's own media verbalization behavior, which is provider-owned request processing with no equivalent on any other provider. Per the cross-provider transformation policy, the correct contract is full native fidelity plus an explicit unsupported-mapping error, not a universal field or a provider_options marker.
  • Part.mediaProcessing (google-part-media-processing)
    • Native contract: The generated Part type accepts and re-emits mediaProcessing, Google-native detection and validation keep accepting the payload, an unmodified Google request passes through byte-for-byte, and a cross-provider transform of a request containing a part with mediaProcessing set returns an explicit ConvertError::UnsupportedMapping instead of dropping the directive and silently changing how the media is processed.
    • Scope decision: The field directs provider-hosted media execution. Per the provider-only boundary and the AGENTS.md rule against emulating provider services, the contract is native acceptance plus byte-preserving same-format passthrough plus an explicit cross-provider unsupported-mapping error.
  • ToolCall.toolName (google-hosted-tool-call-tool-name)
    • Native contract: The generated ToolCall type accepts and re-emits toolName, native detection and validation keep accepting the payload, an unmodified Google response or stream chunk containing a hosted toolCall part passes through byte-for-byte including the echo-back round trip the API requires, and a cross-provider transform of a payload containing a hosted toolCall or toolResponse part returns an explicit ConvertError::UnsupportedMapping instead of dropping the block or misrepresenting it as a caller-executable function call.
    • Scope decision: Hosted search and retrieval with a provider-defined result block and client echo-back protocol falls squarely inside the provider-only boundary. toolName adds no portable meaning; it identifies which Google-hosted tool ran.

Validation

  • ./pipelines/generate-provider-types.sh google: failure
  • Deterministic generator retry: failure
  • Claude repair pass: failure
  • Generator after Claude repair: success
  • make generate-types PROVIDER=google: success
  • Braintrust workflow trace: success
  • Claude integration plan: success
  • Structured plan validation: success
  • Human design blockers: true
  • Claude focused implementation: success
  • Immutable plan revalidation: success
  • Initial post-implementation Rust regeneration: success
  • Initial provider update path policy: success
  • Initial provider semantic policy: success
  • Initial post-implementation TypeScript regeneration: success
  • Initial formatting: success
  • Initial focused provider tests: success
  • Initial conditional generator tests: success
  • Initial build: success
  • Initial clippy: success
  • Bounded Claude mechanical repair: skipped
  • Effective mechanical validation source: initial
  • Effective mechanical validation: success
  • make lingua-wasm: success
  • Unblocked payload capture cases: ``
  • Live capture (OpenAI): skipped
  • Live capture (Anthropic): skipped
  • Live capture (Google): skipped
  • Payload cross-provider transform capture: skipped
  • Payload fixture sync: success
  • make test-payloads: success
  • make typed-boundary-check: success
  • cargo test -p coverage-report --test cross_provider_test cross_provider_transformations_have_no_unexpected_failures: success
  • Claude read-only verification: success
  • Verification report validation: success
  • Verification verdict: fail
  • Recoverable binary patch archive: success
  • Patch artifact upload: success

Only safely scoped patches with a locally archived binary diff are published. Any incomplete AI phase or failed deterministic check produces a needs-review draft so work can continue from the PR branch.

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