mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-10 21:20:20 +08:00
239 lines
19 KiB
Markdown
239 lines
19 KiB
Markdown
# Format Conversion Audit
|
|
|
|
Last audited: 2026-06-03
|
|
|
|
This audit tracks `source format -> Canonical -> target format` behavior. It is intentionally stricter than historical best-effort conversion.
|
|
|
|
Statuses:
|
|
|
|
- `native`: emitted as a target-native field without semantic change.
|
|
- `mapped`: converted through canonical/provider-specific mapping.
|
|
- `extension-preserved`: preserved in same-format canonical roundtrip or target-approved extension namespace.
|
|
- `transport-only`: audited client transport metadata intentionally omitted when the target has no compatible transport channel.
|
|
- `unaudited`: rejected because the source field is not in the audited provider schema inventory for cross-format conversion.
|
|
- `unsupported`: rejected because the request/field shape is outside the supported conversion surface, independent of schema drift.
|
|
- `lossy-blocked`: conversion fails closed.
|
|
- `invalid-enum`: conversion fails closed because a provider enum value is not valid for the target mapping.
|
|
|
|
Full schema field coverage is tracked in `docs/api/format-field-coverage-matrix.md`. That matrix is generated from the schema inventory in `docs/api/provider-interface-definitions.md` by `python3 docs/api/generate_format_field_coverage.py` and gives every documented OpenAI, Claude, and Gemini schema field a handling status. “Handled” means mapped, same-format/native preserved, extension-preserved, blocked with a structured error, or explicitly marked outside the canonical conversion surface.
|
|
|
|
Provider schema refresh is not a runtime dependency. Same-format runtime paths do not use this matrix; they bypass canonical conversion. Same-format canonical roundtrip must preserve unrecognized provider fields through provider extension namespaces. Cross-format conversion is capability-based: only explicitly mapped fields are emitted, and newly discovered or unknown provider fields fail closed with `UnauditedField` until a lossless mapping is audited.
|
|
|
|
## Implemented Boundary Changes
|
|
|
|
| Area | Current behavior |
|
|
| --- | --- |
|
|
| Pure conversion API | `convert_request_pure` and response equivalents do not apply model override or stream policy. |
|
|
| Legacy conversion API | `convert_request` / `convert_response` are retained for migration and may still use legacy context behavior. |
|
|
| Same-format provider path | Bypasses canonical conversion and copies the parsed JSON object before transport edits. |
|
|
| Cross-format same-format-provider path | Uses `convert_request_pure`, then applies model/body/stream edits in transport. |
|
|
| Conversion errors | Added `UnauditedField`, `UnsupportedField`, `InvalidEnumValue`, `LossyConversionBlocked`, and `InvalidTargetField`. |
|
|
| Reporting | Added `ConversionReport` with field statuses. Runtime reports remain conversion-operation oriented; exhaustive nested schema coverage is enforced by `format-field-coverage-matrix.md`. |
|
|
| Source schema coverage | Cross-format request conversion rejects unknown source root fields before emit. Every documented schema field is covered by the field coverage matrix. |
|
|
| Schema drift handling | Official schema changes are detected by regenerating the inventory/matrix. Runtime same-format remains passthrough; cross-format unknowns return `UnauditedField` until deliberately mapped. |
|
|
| Tool schema roundtrip | Claude `input_schema` and Gemini `functionDeclarations.parameters` preserve raw same-format schema through provider-specific extensions. |
|
|
| Tool result ids | Chat `tool_call_id`, Responses `call_id`, Claude `tool_use_id`, and Gemini `functionResponse.id` are mapped through canonical tool IDs. |
|
|
|
|
## OpenAI Chat -> OpenAI Responses
|
|
|
|
| Chat field | Canonical handling | Responses output | Status |
|
|
| --- | --- | --- | --- |
|
|
| `model` | request identity | `model` | native |
|
|
| `messages` | canonical messages/instructions | `input`, `instructions` | mapped |
|
|
| `max_tokens` | generation max tokens | `max_output_tokens` | mapped |
|
|
| `max_completion_tokens` | generation max tokens | `max_output_tokens` | mapped |
|
|
| `temperature` | generation | `temperature` | native |
|
|
| `top_p` | generation | `top_p` | native |
|
|
| `top_logprobs` | generation | `top_logprobs` | native |
|
|
| `n` | generation but no Responses equivalent | none | lossy-blocked |
|
|
| `stop` | generation but no Responses equivalent | none | lossy-blocked |
|
|
| `presence_penalty` | generation but no Responses equivalent | none | lossy-blocked |
|
|
| `frequency_penalty` | generation but no Responses equivalent | none | lossy-blocked |
|
|
| `seed` | generation but no Responses equivalent | none | lossy-blocked |
|
|
| `logprobs` | generation but no Responses equivalent | none | lossy-blocked |
|
|
| `stream` | OpenAI extension | `stream` | mapped if explicit |
|
|
| `stream_options` | Chat-specific extension | none | lossy-blocked |
|
|
| `tools[].function.name` | canonical tool | `tools[].name` | mapped |
|
|
| `tools[].function.description` | canonical tool | `tools[].description` | mapped |
|
|
| `tools[].function.parameters` | canonical tool | `tools[].parameters` | mapped |
|
|
| `tools[].function.strict` | canonical tool strict | `tools[].strict` | mapped, implemented |
|
|
| assistant `tool_calls[].id` | canonical tool use id | `function_call.call_id` | mapped, implemented |
|
|
| tool message `tool_call_id` | canonical tool result id | `function_call_output.call_id` | mapped, implemented |
|
|
| `tool_choice` | canonical tool choice | `tool_choice` | mapped |
|
|
| `parallel_tool_calls` | canonical bool | `parallel_tool_calls` | native |
|
|
| `metadata` | canonical metadata | `metadata` | native |
|
|
| `response_format` | canonical response format | `text.format` | mapped |
|
|
| `reasoning_effort` | OpenAI enum | `reasoning.effort` | mapped; invalid enum blocked |
|
|
| `verbosity` | OpenAI extension | `text.verbosity` | mapped |
|
|
| `store` | OpenAI extension | `store` | extension-preserved |
|
|
| `service_tier` | OpenAI extension | `service_tier` | extension-preserved |
|
|
| `safety_identifier` | OpenAI extension | `safety_identifier` | extension-preserved |
|
|
| `prompt_cache_key` | OpenAI extension | `prompt_cache_key` | extension-preserved |
|
|
| `user` | legacy Chat user field | none | lossy-blocked |
|
|
| unknown top-level fields | source schema guard | none | unaudited |
|
|
|
|
## OpenAI Responses -> OpenAI Chat
|
|
|
|
| Responses field | Canonical handling | Chat output | Status |
|
|
| --- | --- | --- | --- |
|
|
| `model` | request identity | `model` | native |
|
|
| `input` | canonical messages/content/tool I/O | `messages` | mapped |
|
|
| `instructions` | canonical instruction/system | `messages` system/developer | mapped |
|
|
| `max_output_tokens` | generation max tokens | `max_completion_tokens` | mapped |
|
|
| `temperature` | generation | `temperature` | native |
|
|
| `top_p` | generation | `top_p` | native |
|
|
| `top_logprobs` | generation | `top_logprobs` | native |
|
|
| `metadata` | canonical metadata | `metadata` | native |
|
|
| `client_metadata` | Responses client transport metadata | none | transport-only; omitted |
|
|
| `parallel_tool_calls` | canonical bool | `parallel_tool_calls` | native |
|
|
| `text.format` | canonical response format | `response_format` | mapped |
|
|
| `text.verbosity` | Responses extension | `verbosity` | mapped |
|
|
| `tools[].type=function` | canonical tool | `tools[].type=function` | mapped |
|
|
| `tools[].name` | canonical tool | `tools[].function.name` | mapped |
|
|
| `tools[].parameters` | canonical tool | `tools[].function.parameters` | mapped |
|
|
| `tools[].strict` | canonical tool strict | `tools[].function.strict` | mapped, implemented |
|
|
| `function_call.call_id` | canonical tool use id | `tool_calls[].id` | mapped, implemented |
|
|
| `function_call_output.call_id` | canonical tool result id | tool message `tool_call_id` | mapped, implemented |
|
|
| `tools[].type=custom` | raw Responses tool | none | lossy-blocked to Chat |
|
|
| `tools[].type=web_search*` | raw Responses tool | none | lossy-blocked to Chat |
|
|
| `tool_choice` | canonical tool choice | `tool_choice` | mapped |
|
|
| `reasoning.effort` | OpenAI enum | `reasoning_effort` | mapped; invalid enum blocked |
|
|
| `reasoning.summary` | Responses-only | none | lossy-blocked |
|
|
| `reasoning.budget_tokens` | Responses-only | none | lossy-blocked |
|
|
| `stream` | Responses request transport policy | none | lossy-blocked; target stream policy is transport-owned |
|
|
| `include` | Responses-only | none | lossy-blocked; legacy emitter no longer leaks |
|
|
| `previous_response_id` | Responses-only | none | lossy-blocked; legacy emitter no longer leaks |
|
|
| `truncation` | Responses-only | none | lossy-blocked |
|
|
| `prompt` | Responses-only | none | lossy-blocked |
|
|
| `conversation` | Responses-only | none | lossy-blocked |
|
|
| `background` | Responses-only | none | lossy-blocked |
|
|
| `max_tool_calls` | Responses-only | none | lossy-blocked |
|
|
| unknown top-level fields | source schema guard | none | unaudited |
|
|
|
|
## Claude Messages <-> OpenAI Chat / Responses
|
|
|
|
Claude to OpenAI Chat, Claude to OpenAI Responses, and the reverse directions are included in the field coverage matrix. Runtime strict guards cover request root fields, provider extension namespaces, thinking/cache/tool-result hazards, and target generation-field gaps. Fields without a lossless target equivalent fail closed instead of being dropped.
|
|
|
|
High-risk fields:
|
|
|
|
| Claude field | OpenAI target risk | Required status |
|
|
| --- | --- | --- |
|
|
| `system` with cache blocks | Chat/Responses system instructions | same-format preserved; cross-format `cache_control` loss is blocked |
|
|
| `thinking` | OpenAI reasoning | Claude request-level thinking config maps to OpenAI reasoning; message-level thinking blocks are blocked for Responses |
|
|
| `cache_control` | OpenAI content/tool extensions | same-format preserved; cross-format blocked when no target equivalent exists |
|
|
| `tools[].input_schema` | OpenAI tool parameters | mapped; raw same-format schema preservation implemented |
|
|
| `tool_choice.disable_parallel_tool_use` | OpenAI `parallel_tool_calls` | mapped, implemented |
|
|
| `tool_result` multi-block content | OpenAI tool output/content | same-format preserved; cross-format to Chat/Responses is lossy-blocked |
|
|
| `metadata` | OpenAI metadata | mapped when the target has metadata |
|
|
| `container`, `inference_geo`, `service_tier` | OpenAI target has no audited equivalent | lossy-blocked unless a target-approved mapping is added |
|
|
|
|
## Gemini GenerateContent <-> OpenAI Chat / Responses / Claude
|
|
|
|
Gemini to OpenAI Chat, Gemini to OpenAI Responses, Gemini to Claude, and reverse generation paths are included in the field coverage matrix. Gemini-only request fields are preserved same-format and blocked cross-format unless the target mapping is explicitly audited.
|
|
|
|
High-risk fields:
|
|
|
|
| Gemini field | Target risk | Required status |
|
|
| --- | --- | --- |
|
|
| `contents[].parts[].thoughtSignature` | OpenAI/Claude thinking | Chat/Claude preserve; Responses cross-format is lossy-blocked |
|
|
| `tools[].functionDeclarations` | OpenAI/Claude tool schema | mapped; raw same-format `parameters` preservation implemented |
|
|
| `toolConfig.functionCallingConfig.allowedFunctionNames` | OpenAI/Claude tool choice | single-name mapping implemented; multi-name input is lossy-blocked |
|
|
| `toolConfig.functionCallingConfig.mode` | OpenAI/Claude tool choice enum | valid enum required; invalid values fail with `InvalidEnumValue` |
|
|
| `generationConfig.thinkingConfig.thinkingLevel` | OpenAI/Claude reasoning effort | low/medium/high mapping implemented; invalid values fail closed |
|
|
| `safetySettings` | OpenAI/Claude no direct equivalent | lossy-blocked |
|
|
| `cachedContent` | OpenAI/Claude no direct equivalent | lossy-blocked |
|
|
| `codeExecution` | OpenAI/Claude tool/builtin mismatch | lossy-blocked |
|
|
| `generationConfig.responseModalities` | OpenAI/Claude modality mismatch | lossy-blocked |
|
|
| `functionResponse.id` | tool result id | conversion preserves id; Gemini upstream cleanup is transport-layer edit only |
|
|
|
|
## Embedding And Rerank
|
|
|
|
Embedding and rerank request parse/emit capability and strict target guards are implemented. Provider schema fields outside these canonical conversion surfaces are marked `not-in-conversion-surface` in the field coverage matrix instead of being left implicit.
|
|
|
|
Embedding source capability:
|
|
|
|
| Source format | Parsed request shape | Canonical fields | Status |
|
|
| --- | --- | --- | --- |
|
|
| OpenAI Embedding | `model`, `input`, `encoding_format`, `dimensions`, `user`, `parameters`, `task` | OpenAI-like embedding | mapped |
|
|
| Jina Embedding | OpenAI-like plus provider extension namespace | OpenAI-like embedding | mapped |
|
|
| Doubao Embedding | OpenAI-like `model` + text `input` | OpenAI-like embedding | mapped |
|
|
| Gemini Embedding | single `content.parts[].text` or batch `requests[]` | text input, `dimensions`, `task` | mapped |
|
|
| Aliyun Multimodal Embedding | `input.contents[]`, `parameters.dimension` | text/multimodal input, `dimensions`, `parameters` | mapped |
|
|
|
|
Embedding target guards:
|
|
|
|
| Target format | Accepted canonical fields | Blocked fields/cases | Status |
|
|
| --- | --- | --- | --- |
|
|
| OpenAI Embedding | text or token input, `encoding_format`, `dimensions`, `user` | multimodal input, `task`, generic `parameters` | lossy-blocked |
|
|
| Jina Embedding | text input, `dimensions`, `task`, `parameters` | token/multimodal input, `encoding_format`, `user` | lossy-blocked |
|
|
| Gemini Embedding | text input, `dimensions`, valid `taskType` | token/multimodal input, `encoding_format`, `user`, generic `parameters`, invalid `taskType` | lossy-blocked / invalid-enum |
|
|
| Doubao Embedding | text input, `dimensions` | token/multimodal input, `encoding_format`, `user`, `task`, generic `parameters` | lossy-blocked |
|
|
| Aliyun Multimodal Embedding | text or multimodal input, `dimensions`, `parameters` | token input, `encoding_format`, `user`, `task` | lossy-blocked |
|
|
|
|
Cross-format embedding invariants:
|
|
|
|
- Embedding formats can only convert to embedding formats.
|
|
- Unknown provider-specific embedding extension namespaces are blocked cross-format unless the namespace matches the target.
|
|
- Aliyun `parameters.dimension` maps to canonical `dimensions` and is not treated as generic `parameters`.
|
|
- Gemini batch embedding parse requires every batch item to share the same model, dimensions, and task.
|
|
|
|
Rerank first pass:
|
|
|
|
| Area | Current behavior | Status |
|
|
| --- | --- | --- |
|
|
| Source formats | OpenAI Rerank and Jina Rerank parse OpenAI-like `model`, `query`, `documents`, `top_n`, `return_documents` | mapped |
|
|
| Target formats | OpenAI Rerank and Jina Rerank emit OpenAI-like rerank bodies | mapped |
|
|
| Boundary | Rerank formats can only convert to rerank formats | lossy-blocked |
|
|
| Validation | Empty query/documents and `top_n=0` fail closed | invalid-target-field |
|
|
| Extensions | Unknown provider-specific rerank extension namespaces are blocked cross-format | unsupported |
|
|
|
|
## Sync Response Conversion
|
|
|
|
Cross-format sync response conversion now validates source stop/finish/status
|
|
enums before emitting a target body. Same-format runtime response passthrough is
|
|
still outside canonical conversion.
|
|
|
|
| Source field | Target risk | Current behavior | Status |
|
|
| --- | --- | --- | --- |
|
|
| Same-format response raw stop/status fields | canonical emitters would otherwise normalize unknown enum/status to default target stop values | raw OpenAI Chat `finish_reason`, OpenAI Responses `status`, Claude `stop_reason`/`stop_sequence`, and Gemini `finishReason` are preserved through provider extension metadata | extension-preserved |
|
|
| OpenAI Chat `choices[].finish_reason` | unknown value would otherwise emit as target normal stop | valid Chat enum required; unknown values fail with `InvalidEnumValue` | invalid-enum |
|
|
| OpenAI Responses `status` | `queued`, `in_progress`, and `cancelled` have no sync target equivalent | non-terminal valid states fail with `LossyConversionBlocked`; invalid states fail with `InvalidEnumValue` | lossy-blocked / invalid-enum |
|
|
| OpenAI Responses `incomplete_details.reason=content_filter` | previously mapped to max tokens/`length` | maps to canonical content filter and emits Chat `content_filter` / Claude `content_filtered` / Gemini `SAFETY` | mapped |
|
|
| Claude `stop_reason` | unknown value would otherwise emit as target normal stop | valid known stop enum required for cross-format conversion | invalid-enum |
|
|
| Gemini `candidates[].finishReason` | known-but-unmappable reasons would otherwise emit as target normal stop | mappable safety/max/stop reasons convert; known unmappable values such as `OTHER`, `MALFORMED_FUNCTION_CALL`, `UNEXPECTED_TOOL_CALL`, `MISSING_THOUGHT_SIGNATURE`, and `MALFORMED_RESPONSE` fail with `LossyConversionBlocked`; future unknown values fail with `InvalidEnumValue` | lossy-blocked / invalid-enum |
|
|
| Canonical `Unknown` stop reason | target emitters default to normal stop values | cross-format response conversion blocks canonical unknown stop reasons | lossy-blocked |
|
|
|
|
## Stream Conversion
|
|
|
|
Sixth batch first pass is implemented for unknown event handling and runtime
|
|
same-format boundaries. Sync response finish/status parity has a first strict
|
|
pass; stream finish-reason guardrails are implemented for unknown/unmappable
|
|
terminal reasons. Stream event schema fields are covered in the field coverage
|
|
matrix; provider-by-provider fixtures cover the runtime event behavior.
|
|
|
|
Current stream behavior:
|
|
|
|
| Area | Current behavior | Status |
|
|
| --- | --- | --- |
|
|
| Provider parsers | OpenAI Chat, OpenAI Responses, Claude, and Gemini unknown stream payloads become `CanonicalStreamEvent::UnknownEvent` | mapped |
|
|
| Cross-format stream matrix | Unknown canonical stream events emit a target-format error SSE with `unsupported_stream_event` and terminate conversion | lossy-blocked |
|
|
| Stream finish reason guard | Unknown OpenAI finish reasons, unknown Claude `stop_reason`, and Gemini known-but-unmappable `finishReason` values such as `OTHER` are preserved as raw canonical finish strings, then blocked by the matrix with `unsupported_finish_reason` | lossy-blocked |
|
|
| OpenAI Responses stream target | Canonical `length` and `content_filter` terminal reasons emit `response.incomplete` with `incomplete_details.reason=max_output_tokens` or `content_filter` instead of `response.completed` | mapped |
|
|
| Terminal observer | Unknown provider stream events increment `unknown_event_count`; OpenAI Responses failed events mark terminal error state | mapped |
|
|
| Stream -> sync aggregate | Unknown OpenAI Chat, OpenAI Responses, Claude, and Gemini stream events make the runtime finalize checked path return an error and block `body_json` fallback; legacy public aggregate helpers keep `Option` compatibility | lossy-blocked |
|
|
| Runtime strict fallback guard | `UnauditedField`, `InvalidEnumValue`, `UnsupportedField`, `LossyConversionBlocked`, and `InvalidTargetField` from registry response conversion are not allowed to fall through legacy conversion helpers | lossy-blocked |
|
|
| Runtime same-format stream | Same-format stream passthrough remains outside canonical conversion; stream policy edits are transport-layer only | native |
|
|
|
|
Stream fixture coverage:
|
|
|
|
| Provider stream | Covered fixture areas |
|
|
| --- | --- |
|
|
| OpenAI Chat | sync aggregation for text, tool call IDs/names/argument deltas, finish reason, and usage; cross-format unknown finish/event blocking |
|
|
| OpenAI Responses | text snapshot de-duplication, multi-part messages, reasoning/items, function calls, image generation calls, same-family stream sync, unknown event blocking |
|
|
| Claude Messages | thinking signatures, tool input deltas, cache/usage aggregation, media emission, unknown stop/event blocking |
|
|
| Gemini GenerateContent | text/media/signature aggregation, function calls/results, safety finish mapping, unknown parts/events, and unmappable finish reason blocking |
|
|
|
|
Matrix-level interception remains the authoritative runtime path for cross-format
|
|
unknown events; direct client emitters are covered only as provider/client
|
|
building blocks.
|