-
Notifications
You must be signed in to change notification settings - Fork 0
Gateway transformations #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Add ProviderFormat enum with OpenAI, Anthropic, Google, Mistral, Converse, Js, and Unknown variants - Implement heuristic-based format detection for Anthropic payloads (is_anthropic_format_heuristic) - Add processing/detect module with unified parse() and parse_from_str() for multi-format detection - Add processing/catalog module with configurable catalog_lookup function for format hints - Create GooglePayload and BedrockPayload wrapper types for non-serde SDK types - Add OpenAI detect module stub - Re-export key types (ProviderFormat, DetectedPayload, TypedPayload) from lib.rs
Extract system message and message flattening logic into standalone public helper functions, eliminating central provider dispatch.
Define GooglePayload, BedrockPayload, is_google_format, is_openai_format unconditionally in detect.rs instead of using fallback modules with cfg gates.
…provider detection - Add FormatDetector trait in src/processing/detector.rs - Create providers/mistral module with MistralDetector - Move Bedrock detection from detect.rs to providers/bedrock/detect.rs - Add FormatDetector impl to anthropic, google, openai modules - Replace hardcoded detection order with priority-based detector registry - Add "Adding a Provider" section to README Adding a new provider now requires 4 touchpoints instead of 5+: 1. Add ProviderFormat variant 2. Create provider detector implementing FormatDetector 3. Add TypedPayload variant (if needed) 4. Register in detectors() list
The confidence field in DetectedPayload and FormatDetector trait was never used by any downstream consumers (gateway, braintrust-llm-router).
Move payload wrapper types to their respective provider modules and add feature gates to TypedPayload enum variants. This eliminates duplicate definitions that existed in both detect.rs and the provider modules. - Remove GooglePayload/BedrockPayload from processing/detect.rs - Import these types from providers/google and providers/bedrock - Add #[cfg(feature = "...")] to TypedPayload variants and match arms - Update re-exports in processing/mod.rs and lib.rs to be feature-gated
Mistral now has its own feature flag instead of being bundled under `openai`. The new flag depends on `openai` since Mistral uses OpenAI-compatible types for parsing.
- Add integration test for detect → transform → serialize pipeline - Add tests for Anthropic heuristic detection edge cases - Add Copy derive to all zero-sized detector structs - Refactor model prefix matching to use const arrays - Remove trivial test_detector_trait tests
Catalog testing: - Remove flaky unit tests that were unreliable due to global OnceLock state - Add test_catalog.json as a mock catalog fixture - Add integration tests (tests/catalog_test.rs) that run in separate processes with fresh OnceLock state - Document testing approach in module header Doc examples: - Fix examples in detect.rs and detector.rs to be compilable - Change from `ignore` to proper runnable/no_run examples - Use correct import paths and realistic code snippets Snapshot-based transformation tests: - Add snapshot tests for structured output, PDF normalization - Add provider sanitization tests (Mistral, Azure, Databricks, Fireworks) - Add Vertex model normalization and Responses API routing tests
choochootrain
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks reasonable at a high level. i had 2 questions that can be addressed outside this PR, just thinking out loud about what end to end behavior we want
ankrgyl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just for bookkeeping purposes, undoing my quick approval
Remove the separate as_str() method since it was only used internally by the Display implementation.
Return Err(()) instead of Ok(Unknown) for unrecognized format strings, letting callers decide how to handle failures. Callers can use .unwrap_or_default() to get the previous behavior.
Changes: - Bedrock: Fix serialization to use camelCase (modelId, inferenceConfig) matching the actual AWS Bedrock API format. Change content block enums from tagged to untagged format. - Bedrock tests: Construct actual ConverseRequest structs, serialize them, and test detection on the serialized JSON. - OpenAI tests: Use CreateChatCompletionRequestClass from generated types. - Mistral tests: Use OpenAI types (Mistral is OpenAI-compatible) with helper function to reduce boilerplate. - Google tests: Add comment explaining why raw JSON is still used (protobuf types without serde support). - Update validation tests to use new camelCase Bedrock format. Now if provider types change and no longer produce valid API payloads, the detection tests will fail to compile or fail at runtime.
Replace all heuristic-based payload format detection with direct struct deserialization using serde. Format validation now uses try_parse_* functions that attempt to deserialize into provider-specific structs. Changes: - Add try_parse_* and is_*_format_value() functions for all providers - Add TryFromLLM trait implementations for Google and Bedrock types - Add unified validate_or_transform() API for pass-through or conversion - Add Serialize derives to Google structs for bidirectional conversion - Box large enum variants in TypedPayload to fix clippy warnings - Update transform.rs to use TryFromLLM consistently for all providers - Remove old heuristic detection functions
Remove `is_*_format` and `is_*_format_value` wrapper functions across all providers, keeping only the `try_parse_*` functions as the canonical API. - Remove `is_anthropic_format`, `is_anthropic_format_value`, `detect_payload_format`, `PayloadFormat` enum - Remove `is_openai_format`, `is_openai_format_value` - Remove `is_mistral_format`, `is_mistral_format_value` - Remove `is_bedrock_format_value` - Remove `is_google_format_value` - Update `is_valid_for_format` and `detect_source_format` to use `MistralDetector.detect()` directly - Remove unused `JsonParseFailed` variant from `DetectionError` enums - Update tests to use `try_parse_*` functions directly
Replace OnceLock with ArcSwap to allow the catalog lookup function to be atomically swapped at runtime. This enables periodic catalog refresh without requiring a restart. Also adds unit tests for the catalog module, using serial_test to ensure proper test isolation for global state.
- Make from_universal public for external callers to convert universal messages to any provider format - Add universal_to_responses_input function to handle 1:N expansion for Tool messages in OpenAI Responses API format - Re-export new functions at module and crate level
Add support for transforming payloads between provider formats (e.g., OpenAI to Anthropic) when the target provider doesn't match the incoming format. Changes: - Add PayloadFormat::Transformed variant to track source→target transformations - Add has_target_payload() method to check if request has ready-to-use payload - Refactor from_payload to use lingua's validate_or_transform for unified flow - Update anthropic provider to handle both native pass-through and transformed payloads - Export to_universal from lingua for message extraction during transformation
Remove dead code that was never used by the gateway: - Remove catalog.rs and catalog lookup infrastructure (set_catalog_lookup, catalog_lookup) - the gateway does format lookup at a higher level - Remove TypedPayload enum and parse()/parse_from_str() functions - format detection is handled by validate_or_transform() in transform.rs instead - Remove DetectedPayload struct and detect_format() function chain - Remove BedrockPayload and GooglePayload wrapper types (only used by TypedPayload) - Remove arc-swap and serial_test dependencies The transform.rs path (validate_or_transform, to_universal, from_universal, is_valid_for_format) remains the primary API for payload conversion. This simplifies lingua to be a pure conversion library without global state.
- Add RequestDefaults trait for provider-specific parameter defaults - Add AnthropicDefaults with DEFAULT_MAX_TOKENS = 4096 - Add transform_request() for full request transformation with defaults - Pass-through mode has zero overhead (use original payload as-is)
This stack of pull requests is managed by Graphite. Learn more about stacking. |

Summary
Add format detection, request transformation infrastructure, and OpenAI-specific preprocessing for gateway integration.
Changes
is_valid_for_format()andvalidate_or_transform()APIsmax_tokensfor Anthropic)OpenAIRequestTransformerbuilder - reasoning model handling, structured output fallback, provider-specific field sanitizationNew Public API
Testing
payloads/transformations/covering provider sanitization, reasoning models, structured output, and cross-format conversion