Repository navigation
v1.7.0 — Native Claude on Bedrock and Vertex AI
🚀 Get started
New to AISIX? Get the gateway running and route your first LLM request in minutes:
- 📖 Documentation — https://docs.api7.ai/ai-gateway/
- ⚡ Gateway quickstart (one container, declarative
resources.yaml) — https://docs.api7.ai/ai-gateway/getting-started/gateway-quickstart
📦 Download
Pull the container image from the GitHub Container Registry:
docker pull ghcr.io/api7/aisix:1.7.0The rolling latest tag and the docker.io/api7/aisix mirror for private/offline deployments are listed on the package page.
AISIX 1.7.0 serves Claude on Amazon Bedrock and Vertex AI natively, stops dropping media on native-protocol upstreams, and brings exported telemetry onto the released OpenTelemetry GenAI conventions. /v1/messages and /v1/messages/count_tokens go to each platform's own Anthropic Messages route, and prompt caching, including auto_prompt_caching, now works there. Images, audio and files in chat requests reach Vertex Gemini, Anthropic and Bedrock instead of being dropped. Bedrock gains API-key, default-credential-chain and assumed-role authentication. API keys, teams and members can carry cost-attribution tags that flow to every usage event and can drive rate limits. Two decision endpoints, /v1/systemone and /v1/decisions, are new.
- Claude on Bedrock and Vertex AI, natively (#1318, #1316, #1323, #1325): signed thinking, native stop reasons and cache counters on
/v1/messages, a workingcount_tokens, and prompt caching on every entry point. - Media reaches native-protocol upstreams (#1314, #1327): image, audio and file parts are translated for Vertex Gemini, Anthropic, Claude on Vertex AI and Bedrock, and URLs the upstream cannot read are downloaded by the gateway behind one SSRF setting.
- Bedrock, broadened (#1319, #1322, #1328): API-key, default-chain and AssumeRole authentication, GPT 5.6+ and Grok on Bedrock's Chat Completions endpoint, Converse requests that follow what the caller sent, and the proxy environment honoured.
- Telemetry on the GenAI semantic conventions (#1288, #1299, #1300): OTLP spans follow v1.41.1, and the OTLP, Datadog and SLS exporters carry structured messages with reasoning and tool calls. Each exporter chooses its own headers to record.
- Cost-attribution tags (#1301) on API keys, teams and members, stamped on every usage event and usable as
tag:<key>rate-limit dimensions. - Decision endpoints (#1293, #1294):
/v1/systemoneand/v1/decisions, with conversion between the two formats.
⚠️ Breaking changes
- OTLP spans follow the OpenTelemetry GenAI semantic conventions v1.41.1 (#1288, #1299). Saved queries and dashboards keyed on the old names must be updated:
gen_ai.systemis removed; usegen_ai.provider.name(a hosting platform wins, so Claude on Bedrock isaws.bedrock).gen_ai.request.modelis now the model name sent upstream. The model or Model Group name the caller sent moves toaisix.requested_model.gen_ai.usage.input_tokensnow includes cached tokens for Anthropic-shape usage (Anthropic, Bedrock Converse, and passthrough routes reportingcache_*_input_tokens). OpenAI-shape usage is unchanged. The Datadog exporter'sgen_ai.usage.input_tokenschanges the same way; itsaisix.*counters do not.- Span names change: the operation span is
{operation} {upstream model}(for examplechat gpt-4o) instead ofchat.completions, the SERVER span isPOST /v1/chat/completionsand the like, and MCP spans aretools/call {tool}. - Successful spans leave status unset instead of
OK. A4xxno longer marks the SERVER span as an error, and a caller that hung up (499) is not an error on any span. gen_ai.prompt/gen_ai.completionare removed. Withcontent_mode: full, content is exported asgen_ai.input.messages,gen_ai.output.messages,gen_ai.system_instructionsandgen_ai.tool.definitions; content that is not a message list, or was cut by the capture cap, stays underaisix.content.prompt/aisix.content.response.aisix.client_source_ip/aisix.client_user_agentbecomeclient.address/user_agent.original, and they andhttp.request.header.*move to the SERVER span.- Only the attempt span carries
gen_ai.operation.name/gen_ai.request.model, so a backend that counts GenAI spans counts each attempt once instead of three times. - The resource
service.nameisobservability.service_name(defaultaisix) instead of the fixedaisix-dp.
- Datadog and Aliyun SLS exporters carry structured content under
content_mode: full(#1300). On SLS,promptis replaced byinput_messages(plussystem_instructionsandtool_definitionswhen present) andresponsebyoutput_messages, each a JSON string. On Datadog,gen_ai.promptis replaced bygen_ai.input.messages(plusgen_ai.system_instructionsandgen_ai.tool.definitions) andgen_ai.completionbygen_ai.output.messages. Output messages hold orderedtext,reasoningandtool_callparts, andfinish_reasonuses the conventions' values (stop,length,tool_call,content_filter,error). A side that was truncated or is not a message list (embeddings, images, audio, jobs, A2A, MCP) stays in the old flat field withcontent_truncated, so one record can mix both. The object-store exporter is unchanged. observability.usage_event.request_headersno longer does anything (#1299). Request-header capture moved to each observability exporter (see Observability). A gateway that still sets the key starts, logs one warning, and records none of those headers until its exporters list them.- Guardrail log lines name the guardrail as
guardrail_name(#1313), which replacesrow(andmemberon the mask-mismatch warning,nameon the invalid-config warning) with no alias. On Amazon Bedrock guardrail lines the AWS guardrail identifier is logged asbedrock_guardrail_idinstead ofguardrail_id. Usage events, metrics and spans are unchanged. - Renamed failure tags and error classes.
- A semantic guardrail whose embedding model answers with vectors that do not fit the request (wrong length, base64 instead of floats, fewer vectors than inputs) now reports
semantic_embed_upstreaminstead ofsemantic_embed_unresolvedin the fail-closed422message, guardrail hit records,guardrail_bypassed_reasonandaisix_guardrail_bypasses_total{reason};aisix.embedin acustomguardrail script throwsUpstreaminstead ofUnresolved(#1309). - The Amazon Bedrock guardrail tags a non-
4294xxfrom ApplyGuardrail (AccessDeniedException,ValidationException,ResourceNotFoundException, ...) and credentials that are missing or refused before the call asbedrock_config_errorinstead ofbedrock_5xx; every429isbedrock_throttled. The tag appears inguardrail_bypassed_reason,aisix_guardrail_bypasses_total{reason}, theerror_typelabel ofaisix_guardrail_latency_seconds, and the fail-closed refusal message.5xxand transport failures staybedrock_5xx(#1328). - When the gateway cannot translate a request for the target provider, usage events, the access log and exporters (OTLP
aisix.error_class, Datadog, SLS) recorderror_class/error_kindinvalid_request_errorinstead ofinvalid_config. The error the caller receives is unchanged (#1328).
- A semantic guardrail whose embedding model answers with vectors that do not fit the request (wrong length, base64 instead of floats, fewer vectors than inputs) now reports
No resources.yaml field was removed, renamed or retyped in this release; a configuration file that loads on 1.6.0 loads unchanged.
🧭 Claude on Amazon Bedrock and Vertex AI
/v1/messagesis served natively (#1318)./v1/messagesaddressed to a Claude model on Bedrock (ananthropic.model id, with or without a region or inference-profile prefix, or an ARN naming one) or Vertex AI (claude-*) is sent to the platform's own Anthropic Messages route (Bedrock InvokeModel / InvokeModelWithResponseStream, Vertex AI:rawPredict/:streamRawPredict) instead of being translated, and the caller receives the platform's answer: signed thinking andsignature_delta,redacted_thinking, the upstream message id, native stop reasons, streamedmessage_startusage with cache counters, and on Bedrockamazon-bedrock-invocationMetricsonmessage_stop.metadata.user_id,context_management, images insidetool_resultand supportedanthropic-betavalues now reach the model. An exception Bedrock raises mid-stream becomes an Anthropicerrorevent with an Anthropic error type (throttlingException→rate_limit_error, and so on). Streamed requests to Bedrock now use InvokeModelWithResponseStream instead of ConverseStream; both need thebedrock:InvokeModelWithResponseStreampermission./v1/chat/completionsand/v1/responsesare unchanged, as is a Bedrock application inference profile ARN, and a structured-output request to a Claude family before 4.5 keeps the translated path./v1/messages/count_tokensworks on both platforms (#1318). It used to answer400. On Bedrock it uses CountTokens (permissionbedrock:CountTokens), falling back to Bedrock Mantle for models CountTokens refuses, such as cross-region-only models; on Vertex AI it uses the platform's count-tokens route. The new startup keyupstream.bedrock.mantle_endpoint(default: the public regional Mantle host) sets the Mantle URL for VPC-endpoint or GovCloud deployments.- Prompt caching works on every entry point (#1316).
cache_controlmarkers are kept through the chat translation for these targets and becomecachePointblocks on Converse (Claude in system, messages and tools; Amazon Nova in system and messages, withoutttl). Toward Bedrock a marker keeps onlytypeandttl; toward Vertex AIscopeis dropped.auto_prompt_cachingcan now be set on Claude models on Bedrock (ananthropic.model id, including region-prefixed ids and ARNs) and Vertex AI (claude-*) and applies there as on the Anthropic API; other models still refuse it with400. Some Claude models on Bedrock accept only the 5-minute cache, and Bedrock refuses a request that asks for1h. anthropic-betais filtered to what the platform runs (#1323, #1325). On Bedrock, betas are always sent in the request body'santhropic_beta, never as a header, including values from a provider key'sdefault_headersor a headerforward_client_headersadmits; on Vertex AI they stay in the header. Values the platform does not run are dropped, andadvanced-tool-use-2025-11-20is renamed totool-search-tool-2025-10-19. On/v1/chat/completionsand/v1/responsesto Claude on Bedrock, a bodyanthropic_betavalue outside that set is now removed instead of forwarded. On Bedrock,interleaved-thinking-2025-05-14,claude-code-20250219,output-128k-2025-02-19,structured-outputs-2025-11-13,token-efficient-tools-2025-02-19,tool-examples-2025-10-29andthinking-token-count-2026-05-13are forwarded, so Claude 4.x on Bedrock now thinks between tool calls for Claude Code. On/v1/messages, a provider key'sdefault_headersbetas are merged with the caller's and with the betas the request body implies before filtering; on/v1/chat/completionsand/v1/responses,default_headerswins over a forwarded value as for any other header.
🔌 Providers and endpoints
-
Images, audio and files reach native-protocol upstreams (#1314, #1327, #1333).
image_url,input_audio,fileand other media parts of/v1/chat/completions(and of/v1/messages//v1/responsesconverted to chat) are now translated into the upstream's own media blocks for Vertex Gemini, Anthropic, Claude on Vertex AI and Bedrock (Claude InvokeModel and Converse), streamed or not; they used to be dropped, and the caller got200from a model that never saw them. Media an upstream cannot take (for example audio to Claude) is now a400namingmessages[i].content[j]. Anthropic (https://) and Vertex Gemini (https:///gs://with a known media type) still receive the URL itself. Any otherhttp(s)media URL is downloaded by the gateway and sent inline; another scheme, or a URL that cannot be downloaded, is a400naming the part, with the URL's query string and fragment removed from the message. Downloads:- refuse any host that resolves to a non-public address (loopback, private and CGNAT ranges, link-local including
169.254.169.254, IPv6 unique-local and similar), checked on every redirect hop, unless the host is listed inoutbound.ssrf_protection.allowed_hostsor the check is turned off withoutbound.ssrf_protection.enabled: false. With the check on, the host must resolve from the gateway even when a proxy carries the download; - go through
HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXYlike every other upstream call, verify certificates underupstream.tls(verify,ca_file), and never forward the caller's headers; - are capped at
upstream.media_fetch.max_bytesper URL (default 50 MiB), share the request's upstream timeout, and count together with the request body againstproxy.request_body_limit_bytes(exceeding it is the same413 request_too_large).
Two new startup blocks govern this.
upstream.media_fetch(enabled, defaulttrue;max_bytes) turns downloading on or off; withenabled: false, URL parts toward an upstream that cannot read the URL are a400.outbound.ssrf_protection(enabled, defaulttrue;allowed_hosts, default empty, eachhost,host:portor[v6]:port; envAISIX_OUTBOUND__SSRF_PROTECTION__ENABLED/AISIX_OUTBOUND__SSRF_PROTECTION__ALLOWED_HOSTS) governs every URL a caller supplies that the gateway fetches, today only media downloads; URLs operators configure are unaffected. Both are declared in the 1.7.0aisixHelm chart values. Providers on OpenAI-compatible endpoints forward media parts unchanged, as before. - refuse any host that resolves to a non-public address (loopback, private and CGNAT ranges, link-local including
-
Upstream error messages reach the caller (#1319). Errors from every Amazon Bedrock operation, Azure OpenAI and Vertex AI, and
5xxerrors from every provider, now carry the upstream's own message instead of a fixed status phrase orupstream returned <status>. That text can include account ids, ARNs, deployment or project names. An error without a message is still named by its status. -
Bedrock authentication modes (#1322). A Bedrock provider key's secret takes
auth_type:access_key(default, as before, with optionalsession_token),api_key(a Bedrock API key, sent as a bearer token) ordefault_chain(the gateway's own AWS environment: environment variables, web identity / IRSA, the ECS task role or the instance profile). Withaccess_keyordefault_chain,role_arn(optionalexternal_id,role_session_name) assumes an IAM role. The Bedrock guardrail'saws_credentialsgainskind: api_keyandkind: default_chain, andstaticgainssession_tokenand the role fields. Every Bedrock call uses the chosen mode. Missing or refused credentials are a non-retryable401 authentication_errornaming the mode; a failing STS or metadata endpoint is retryable. -
GPT 5.6 and newer, and Grok, on Amazon Bedrock use Bedrock's Chat Completions endpoint (#1319). A
openai.gpt-5.6-or-newer (gpt-oss excluded) orxai.grok-*model addressed through a cross-region inference profile (global.,us., …) is sent to Bedrock's OpenAI-compatible/openai/v1/chat/completionsinstead of Converse, signed with the same provider key. Parameters AWS refuses there are dropped instead of failing (n,stop; penalties and Grok'sreasoning_effort: "none"on Grok;temperature,top_p, penalties and logprobs on GPT while it reasons). A request needing a Converse-only feature (Bedrock guardrail, performance or service tier config,top_k, aresponse_formatother thantextorjson_schemawith a schema, function tools on a reasoning GPT model) stays on Converse. Remote image URLs are inlined and positional tool-call ids are replaced with uniquecall_…ids. -
Bedrock Converse requests follow what the caller sent (#1319). On every Bedrock publisher: consecutive turns of one role are merged (tool results followed by a user message used to be refused by Bedrock);
stopbecomesstopSequences;max_completion_tokenswins overmax_tokens; aresponse_formatwith a schema becomes a tool for every publisher, including Titan, AI21, gpt-oss and unclassified ones, which used to ignore it;reasoning_effortis mapped per publisher (gpt-oss and DeepSeek V3, GPT 5.x / 6, Nova 2 — where a value other than low, medium or high is a400— and dropped for others);top_kreaches Claude and Nova;parallel_tool_callsreaches Claude 4.5+;guardrailConfig,performanceConfig,serviceTierandrequestMetadatareach the request, and an OpenAIservice_tiermaps toserviceTier. Top-level fields that are not Chat Completions parameters now reach non-Claude Converse models asadditionalModelRequestFieldsinstead of being dropped. Tool results can carry images, documents and video. With no tools to send, tool-call history is sent as text instead of being refused. -
Bedrock errors are classified like other upstreams' (#1319). A Bedrock model id the gateway cannot recognise is a non-retryable configuration error, so a Model Group stops on it instead of failing over to its next target.
ModelErrorExceptionis a retryable upstream error (502to the caller),ModelTimeoutExceptiona timeout, and a context-window overflow carriescode: context_length_exceeded. -
Bedrock chat returns reasoning in every mode (#1285). Streamed Bedrock chat now returns reasoning (
delta.reasoning_content, areasoningitem on/v1/responses, and an unsignedthinkingblock on/v1/messagesto non-Claude publishers; Claude on/v1/messagesreturns Bedrock's own signed thinking, see above), and so does non-streamed chat to non-Anthropic Bedrock publishers. -
System-only requests to Bedrock are refused up front (#1285, #1311). A request to any Bedrock model that holds only system or developer messages is now refused by the gateway with
400 invalid_request_erroron/v1/chat/completions,/v1/messagesand/v1/responses, streamed or not, and nothing is sent to Bedrock. It used to fail with500 config_error, or, for a non-streamed request to Claude on Bedrock, reach Bedrock and come back as400with the messageupstream returned 400(upstream_erroron chat and responses). The old500was retryable, so a routing group failed over to its next target; the new400ends the fallback chain, even when the group'sfallback_on_statuseslists400, and the request no longer counts as a health failure. A Vertex Gemini system-only request now succeeds. -
A tool message without
tool_call_id(#1285). On the Anthropic, Bedrock/invokeand Vertex Claude bridges the400message loses itsinvalid upstream configuration:prefix. Bedrock Converse forwards such a message as a tool result instead of silently dropping it. -
Cohere embeddings on Amazon Bedrock (#1319) take the caller's
input_type,embedding_types(orencoding_format) and, on Embed v4,dimensions, and report usage from Cohere's billed units. -
Tool-calling turns that the upstream ends with
stopreporttool_calls(#1285). On every OpenAI-wire upstream (OpenAI, Azure OpenAI, OpenAI-compatible providers, the Vertex OpenAI endpoint), a turn that emitted at least one tool call and ended withstopis now reported astool_callson/v1/chat/completionsandtool_useon/v1/messages, streamed or not. This includes OpenAI's own replies whentool_choicenames a function. Other finish reasons pass through unchanged. -
Decision endpoints
/v1/systemoneand/v1/decisions(#1293, #1294)./v1/systemonetakes TypeSafe's System One format and/v1/decisionsOpenAI's Decisions format. Either can be served by atypesafe,azure(Microsoft Foundry),openrouteroropenaiprovider key, forwarding verbatim when formats match and converting questions and answers when they differ; a converted answer that lacks a required answer field fails with502, and a routing group fails over to its next target. Direct and routing models and wildcard aliases are served, with failover, rate limits, model access control, usage events (operationsystemone/decisions) and metrics. Guardrails do not run on these endpoints. -
Responses conversations stay on the Model Group target that produced them (#1332). On
/v1/responsesthrough a routing model group, a follow-up turn that carries encrypted reasoning (encrypted_content) or aprevious_response_idis sent first to the target that produced it, then to other targets on the same provider key, then to the rest. Before, it could fail with400 invalid_encrypted_contentor404when the group's strategy picked another deployment or key. On any other target, the reasoning item is sent without its encrypted content, and its readable summary is kept. Responses served through a routing model group carry a different responseidfrom the upstream's, in non-streamed bodies and in streamed events; clients must echo values back exactly as received. Usage events, the access log and OTLP still record the upstream's own response id. Direct models are not affected. It is always on. -
service_tierin responses (#1305)./v1/chat/completionsserved by an OpenAI-shape upstream now passes the upstream'sservice_tierthrough to the client (it used to be dropped)./v1/responsesbridged onto a non-Responses upstream now reports the tier the upstream served on, or"default", instead of echoing the tier the caller requested. -
Realtime sessions close cleanly (#1333, #1339, #1340).
/v1/realtimenow completes the WebSocket close handshake. A client that closes normally gets its own code and reason back instead of1006, and the upstream receives the client's code and reason. When the upstream closes, the client gets the upstream's code and reason, with the reason relayed unredacted and cut to 123 bytes. A close code that cannot be sent in a Close frame becomes1011, in either direction. A close frame of exactly1002 "Protocol violation"also becomes1011, because the gateway cannot tell it apart from an invalid code;1002with any other reason is relayed. Any end other than a normal close (1000) is preceded by a Realtimeerrorevent of typeserver_errorthat says what happened: the upstream's close code and reason, a close frame the gateway could not relay, a close without a status code, a failed connection and its cause, or a connection that ended without a close frame. Usage that arrives while the close completes is still counted. The access-log status is unchanged.
💬 Chat completions
-
Chat usage carries the upstream's token breakdown as reported (#1341). In
/v1/chat/completionsresponses, streamed and not,usage.prompt_tokens_detailsandusage.completion_tokens_detailsnow carry every counter the upstream reported, with its value. A reported0is included. These includecached_tokens,reasoning_tokens,audio_tokens,image_tokens,text_tokens,accepted_prediction_tokensandrejected_prediction_tokens. A counter the upstream did not report is omitted, and so is a details object with nothing reported. Before, a counter of0was dropped, an object whose counters were all0was absent, andtext_tokens,image_tokensand the prediction counters were never relayed. A response served from the response cache still reports only non-zerocached_tokensandreasoning_tokens. Usage events and metrics are unchanged. -
Streamed chat usage arrives on its own chunk (#1343). With
stream_options.include_usage: true, a streamed/v1/chat/completionsnow ends with one usage-only chunk after the finish chunk. Itschoicesis[]and it is the only chunk that carriesusage, as OpenAI sends it. Before:- from OpenAI-protocol upstreams and Bedrock Converse, that chunk carried
[{"index":0,"delta":{}}]; - from Anthropic, Claude on Vertex AI and Gemini on Vertex AI, there was no such chunk.
usagerode on the finish chunk, and Gemini could also report running counts on earlier chunks.
A client that reads
choices[0]on every chunk, or that reads usage from the finish chunk, must change. Withoutinclude_usage, nothing changes. Usage events and rate-limit accounting are unchanged, including for streams that end early. - from OpenAI-protocol upstreams and Bedrock Converse, that chunk carried
🏷️ Cost-attribution tags
- Tags on API keys, teams and members (#1301).
api_keys,teamsandusersentries accepttags, a map of tag key to value (keys match^[a-z][a-z0-9_]{0,62}$, values 1 to 128 characters, at most 32 entries). The gateway resolves a request's tags per key — the API key's own value, else its team's, else its member's — and stamps them, with the key'steam_id, on every usage event. Exporters ship them: OTLPaisix.tags.<key>andaisix.team_id, Datadogaisix.tags, SLS onetags.<key>field per tag, object storetags. Tags are never Prometheus labels and are never read from the request. - Tag rate limits (#1301). Rate-limit policies accept
tag:<key>as a condition dimension (==,~=,in,~~,~*) and ingroup_by, for example one counter per department. A request without the tag does not match the condition or its negation, and is not subject to agroup_byon it.
🛡️ Guardrails
- Guardrail log lines are easier to trace (#1313). Every gateway log line about one guardrail carries its configured name under
guardrail_name(see Breaking changes). "guardrail blocked …" warnings on every endpoint now carryguardrail_nametoo, a semantic guardrail's embedding failure names the guardrail and the embedding model's display name instead ofguardrail="semantic"and the model id, and a streamed output released unscanned underon_buffer_exceeded: fail_openis now logged. - Passthrough replies with two envelopes are refused (#1289). A buffered passthrough reply carrying both a Chat and an Anthropic envelope is now refused by a fail-closed output guardrail.
📊 Observability and usage events
- Per-exporter header capture (#1299). Every observability exporter accepts
request_headersandupstream_response_headers(lowercase header names; credential headers andset-cookieare refused). Each exporter records only its own lists: OTLP ashttp.request.header.<name>and, per attempt,http.response.header.<name>; Datadog, SLS and object store asrequest_headers/upstream_response_headersmaps. - More request telemetry (#1299, #1303, #1305, #1321). Usage events and exporters gain
requested_model_id(the id of the model entry the caller addressed, beside the servingmodel_id),api_key_name,gateway_overhead_ms(time spent in the gateway, excluding upstream waits),upstream_tpot_ms(time per output token on streams), upstream-reported audio and image token counts (gen_ai.usage.{audio,image}.{input,output}_tokenson OTLP), andservice_tier, the tier the upstream actually served the request on (OTLP / Datadogaisix.service_tier, SLSservice_tier): OpenAI and compatibleservice_tier, Anthropicusage.speed/service_tier, BedrockserviceTier, GeminiusageMetadata.serviceTier. Prompt-cache writes are split by cache lifetime ascache_creation_5m_tokensandcache_creation_1h_tokens(OTLPaisix.cache_creation_5m_tokens/aisix.cache_creation_1h_tokens) when the upstream reports the split;cache_creation_tokensstays the total.gen_ai.tool.definitionsnow carries each tool's description and parameters. - A usage event's
model_idnames only a model that dispatches itself (#1304). For a Model Group (routing or semantic) or an ensemble that fails before a target is selected — guardrail block, entry-level rate limit, model forbidden, semantic resolve failure — and for a routing or semantic group's response-cache hit,model_idis now empty on/v1/chat/completionsand/v1/messagesinstead of the group's id;requested_model_idstill names the group. A direct model's pre-dispatch failure on/v1/responses,/v1/embeddingsand the other single-shot endpoints now carries the model's own id instead of empty. Queries that select a group's events bymodel_idshould userequested_model_id. total_tokensis recorded on every endpoint family that reports it (#1335). Usage events for embeddings, rerank, audio transcription and translation, image generation and edits, Realtime, batch-completion attribution and passthrough routes metered as these families now carry the upstream's reportedtotal_tokens. Before, it wasnull: gateways 1.5.0 through 1.6.x recorded it only on chat completions, completions,/v1/messages,/v1/responsesand passthrough-route events, and gateways before 1.5.0 never recorded it. Realtime-session andbatch_completionevents record the sum of the reported totals. The field isnullif any call that counted tokens reported no total. Client responses, rate limits and Prometheus counters are unchanged.- Passthrough routes meter usage by the protocol they carry (#1289). A passthrough exchange identified as chat, Anthropic Messages, Responses, Completions, embeddings, images or rerank is read with that endpoint's own usage parser, and is now counted once in the LLM request tier (
aisix_llm_requests_total,aisix_llm_request_duration_seconds, theaisix_llm_*token families and TTFT / end-to-end latency) withendpoint="/passthrough_route"andmodel= the body's model (unregistered names fold tounresolved). Usage objects on an unidentified stream are no longer read. An agent backend'stoken_usageframe no longer counts its cache read twice. The passthrough access-logtotal_tokensnow includes cache tokens, like the typed endpoints. /v1/responsesusage events carry afinish_reason(#1299) (stop,tool_calls,length,content_filter, or empty for failed / cancelled responses), derived from the result the caller received.provider_request_idis recorded wherever the provider sent a request id (#1324). Failed upstream attempts,/v1/messages/count_tokenscalls and successful calls whose response has no id of its own (Bedrock Converse and InvokeModel, embeddings, audio, images, rerank) now record the provider's request-id header (x-amzn-requestid,x-request-id,request-id,x-ms-request-id,apim-request-id,x-goog-request-idorcf-ray) in usage events and the access log; a response object's own id still wins. On a failed attempt it can therefore appear as OTLP / Datadoggen_ai.response.id. Each retried attempt carries its own id. A Bedrock Converse response'sid, which used to be empty, is the request id. Access-log lines of failed requests name the provider the request was sent to instead ofunknown(or no provider on/v1/chat/completions), and the multipart image-edit and audio routes name the requested model.- Ensemble usage is recorded whole (#1298, #1308). When a caller abandons an ensemble request, the panel members and judge that already returned are recorded as usage events ahead of the
499event, and the tokens of those that answered are charged to the key's, team's and member's token limits. A panel member or judge call that fails now records a zero-token usage event (attempt_kindpanelorjudge, with its model, status,error_classanderror_message); it used to leave none. Events are numbered by panel slot, so when a member fails, every event after it moves up oneattempt_indexper failed member. - Prompt estimates are realistic for images and opaque payloads (#1287). When an upstream reports no usage, images are estimated with OpenAI's vision tile formula, and encrypted reasoning, signatures, files and audio are no longer tokenized as text. Abandoned requests carrying screenshots or reasoning items no longer record hundreds of thousands of phantom prompt tokens.
⚙️ Deployment and configuration
- Amazon Bedrock calls honour the proxy environment (#1328). Bedrock runtime calls, the Bedrock guardrail and STS calls now go through
HTTP_PROXY/HTTPS_PROXY/ALL_PROXYunlessNO_PROXYcovers the host, the same way as every other upstream; they used to connect directly. Requests to the AWS credential endpoints (instance metadata, ECS and EKS Pod Identity, a loopback credential agent, a configured metadata endpoint) always stay direct. - Realtime sessions honour the proxy environment (#1330).
/v1/realtimenow reaches awss://provider throughHTTPS_PROXY(aws://one throughHTTP_PROXY), falling back toALL_PROXY, unlessNO_PROXYcovers the host; it used to connect directly. A SOCKS proxy is not supported: the session fails with anerrorframe and close code1011(502in the access log). - An open Realtime session holds a draining gateway (#1317). On shutdown the gateway now waits for open
/v1/realtimesessions to close, as it does for streams, so their usage is recorded. - Requests arriving late in a drain are served once (#1317). A request that arrives after the drain ends, on a connection accepted before it, is now answered with
Connection: closeand sent upstream exactly once; it could be sent two or three times (thread-per-core mode) or closed without a response. A TLS connection that completed its handshake but has not sent a request now holds the drain until it sends one,downstream.idle_timeout_secsexpires, or the client closes it. - Configuration errors name the key (#1333). A configuration value the gateway cannot read now stops startup with the full dotted key, for example
upstream.media_fetch.max_bytes. If anAISIX_*environment variable set the key, the error names that variable too. Before, the error was the bare parser message.
🐛 Fixes
- Chat requests to Claude accept
stream_options,max_completion_tokensandstop(#1343, #1344, #1345). These used to fail with400("… Extra inputs are not permitted") on Anthropic and Anthropic-compatible providers, Claude on Vertex AI, and non-streamed Claude on Amazon Bedrock.stream_optionsis no longer sent to the upstream.max_completion_tokensis the output cap and wins overmax_tokenswhen both are sent, and areasoning_effortthinking budget is sized against it.stopbecomesstop_sequences, with a string sent as a one-element array. Astop_sequencesthe caller sends itself wins overstopon every Claude path, streamed Claude on Bedrock (Converse) included. Non-streamed Claude on Bedrock sends exactly what the Anthropic path sends, and invalid values reach the upstream, which rejects them. - Streamed chat completions from audio-output models (for example
modalities: ["text","audio"]) now relaydelta.audio, end with the usage chunk and[DONE], and report audio tokens (#1333, #1341). Before, the stream dropped the audio and ended inupstream_decode_error. This affects OpenAI and OpenAI-compatible upstreams, Azure OpenAI, and the OpenAI-compatible routes of Bedrock and Vertex AI. Chat output guardrails do not inspect the generated audio or its transcript. - When an upstream reports
"cached_tokens": nullor"reasoning_tokens": null, the request now succeeds and the counter is treated as unreported (#1341). Before, the chat completion failed with502, and on a passthrough route no tokens were recorded. - On every Claude upstream (the Anthropic API, Claude on Vertex AI, Claude on Bedrock), a
cache_controlbeside a chat message'scontent, on arole: "tool"message or on atool_calls[]entry is now applied to that message's last block, the tool result or the tool call; it used to be dropped (#1316). - Claude on Bedrock no longer refuses
thinking.display: "updates"(sent by Claude Code, whose first request in a session used to fail with400); a display other thansummarizedoromittedis dropped. Requests carryingdangerous-tool-use-2026-09-03,thinking-binding-controls-2026-08-01orthinking-display-updates-2026-08-18, which Bedrock refused with400, now succeed without them (#1323). - Chat to Claude on Vertex AI with a provider key whose
anthropic-betaheader carries a value Vertex AI does not run no longer fails with400; the value is dropped (#1325). - Structured-output requests with thinking on Claude models without native structured output (Anthropic, Bedrock
/invoke, Vertex Claude) now stream the reasoning they return when not streamed (#1286). - A Responses stream that fails with a nested
errorevent now records the upstream's message in the usage event and access log instead of an all-nullenvelope (#1297). - Every request yields exactly one usage event with
attempt_index0: a cancel during retry backoff, a/v1/videosfailure after a winning attempt, and ensemble failure exits no longer write a second one, and the duplicate span that went with it is gone (#1296).
⬆️ Upgrade notes
- Update trace queries and dashboards for the new OTLP span shape, and Datadog queries on
gen_ai.usage.input_tokensfor Anthropic-shape traffic (see Breaking changes). - Move SLS indexes and Datadog facets to the structured content fields. On SLS, create field indexes for
input_messages,system_instructions,tool_definitionsandoutput_messagesand move saved queries offprompt/response; on Datadog, move facets and monitors fromgen_ai.prompt/gen_ai.completiontogen_ai.*.messages. - Reconfigure request-header capture on your exporters. If your gateway sets
observability.usage_event.request_headers, list the same headers in each observability exporter'srequest_headersafter upgrading, and remove the key. The 1.7.0aisixchart no longer carries the value. - Update log and metric queries on renamed values:
row=,member=andname=on guardrail log lines becomeguardrail_name=, and a Bedrock guardrail line'sguardrail_id=becomesbedrock_guardrail_id=;bedrock_5xxfor Bedrock guardrail configuration errors becomesbedrock_config_error;invalid_configfor request-shape refusals becomesinvalid_request_error;semantic_embed_unresolvedfor unusable embedding responses becomessemantic_embed_upstream. - Error messages now carry the upstream's text. If your gateway serves callers who must not see provider account details, note that errors from Amazon Bedrock, Azure OpenAI and Vertex AI, and
5xxerrors from every provider, now include the provider's own message, which can name account ids, ARNs, deployment or project names. - List internal media hosts in
outbound.ssrf_protection.allowed_hosts, or setoutbound.ssrf_protection.enabled: false. Chat media URLs toward Bedrock, Claude on Vertex AI, andhttp://URLs toward Anthropic or Gemini, are now downloaded by the gateway, which refuses non-public addresses. If callers send media URLs served from internal hosts, list them (Helm:config.outbound.ssrf_protection.allowed_hosts). Public names that a fake-ip or transparent proxy resolves into198.18.0.0/15are refused too; list them or turn the check off. Inline media (base64,data:URLs) and URLs the upstream reads itself are unaffected. Withproxy.request_body_limit_bytesset, downloaded media counts toward it, so a request that passed because its media was dropped can now get413. - Bedrock and Realtime traffic now goes through the proxy environment. To keep it direct (for example a Bedrock VPC endpoint), add the host to
NO_PROXY. Realtime cannot use a SOCKS proxy. - Size the termination grace period for the longest Realtime session, as well as the longest stream, since an open session now holds a draining gateway.
- Responses conversations through a routing model group across gateway versions. During a rolling upgrade or a gateway rollback, a
/v1/responsesfollow-up through a routing model group that continues a conversation started on a 1.7.0 gateway fails if it lands on an older gateway: OpenAI answers400("string too long", because the response id exceeds its 64-character limit), and other upstreams may answer404. Start a new conversation to recover. Direct models are not affected.
What's Changed
- fix(bridge): Bedrock streamed reasoning, tool-call finish reason, request-shape errors by @jarvis9443 in #1285
- fix(structured-output): carry reasoning through the tool-route simulated stream by @jarvis9443 in #1286
- fix(usage): price images and skip opaque payloads in local prompt estimates by @jarvis9443 in #1287
- feat(otlp): align exported spans with the released GenAI semantic conventions by @jarvis9443 in #1288
- fix(passthrough): meter an identified protocol with the typed endpoint's parser by @jarvis9443 in #1289
- build(deps): consolidate open dependabot bumps and security fixes by @jarvis9443 in #1291
- docs(releasing): drop the downstream offline-package section by @jarvis9443 in #1292
- docs: define AISIX OSS and shared gateway terminology by @kayx23 in #1290
- feat(proxy): serve /v1/systemone and /v1/decisions with cross-format conversion by @jarvis9443 in #1293
- fix(proxy): fail a converted decision answer that lacks a required field by @jarvis9443 in #1294
- fix(telemetry): give every request exactly one attempt_index 0 usage event by @jarvis9443 in #1296
- fix(proxy): read the nested error object of a Responses stream error event by @jarvis9443 in #1297
- fix(telemetry): keep ensemble usage rows and charges whole when the caller leaves by @jarvis9443 in #1298
- feat(telemetry): structured output content, per-exporter header capture, latency breakdown and modality tokens by @jarvis9443 in #1299
- feat(telemetry)!: structured content on the Datadog and SLS exporters by @jarvis9443 in #1300
- feat(attribution): cost-attribution tags on api keys, teams and members by @jarvis9443 in #1301
- docs(admin-api): clarify the AISIX OSS scope by @kayx23 in #1302
- feat(obs): export the addressed model entry's id as requested_model_id by @jarvis9443 in #1303
- fix(obs): a usage event's model_id names only a dispatchable model by @jarvis9443 in #1304
- feat(obs): record the service tier the upstream served on by @jarvis9443 in #1305
- fix: error messages read correctly for vowel-initial kinds by @jarvis9443 in #1306
- fix(guardrails): report an unusable embedding response as an upstream failure by @jarvis9443 in #1309
- fix(telemetry): give every failed ensemble sub-call its own usage row by @jarvis9443 in #1308
- fix(chat): translate image, audio and file parts on native bridges instead of dropping them by @jarvis9443 in #1307
- Revert "fix(chat): translate image, audio and file parts on native bridges instead of dropping them (#1307)" by @jarvis9443 in #1310
- fix(bedrock): refuse a system-only request on the Claude /invoke path too by @jarvis9443 in #1311
- test(e2e): keep every rate-limit burst inside one fixed window by @jarvis9443 in #1312
- docs(agents.md): define AISIX as the product suite by @kayx23 in #1295
- fix(guardrails): name the guardrail on every guardrail log line by @jarvis9443 in #1313
- fix(chat): translate media parts on native bridges and download URLs the upstream cannot read by @jarvis9443 in #1314
- test: fix four flaky tests at their cause (scrape handover, sentinel TILT, etcd slow-watcher gauge, shared wiremock pool) by @jarvis9443 in #1315
- fix(bedrock,vertex): make Claude prompt caching work on Bedrock and Vertex AI by @jarvis9443 in #1316
- fix(bedrock): serve GPT 5.6+/Grok on Chat Completions, fix Converse requests, pass upstream error text by @jarvis9443 in #1319
- feat(bedrock,vertex): serve /v1/messages to Claude natively on Bedrock and Vertex AI by @jarvis9443 in #1318
- feat(telemetry): split prompt-cache writes by cache lifetime on the UsageEvent by @jarvis9443 in #1321
- fix(bedrock): send Claude only the betas Bedrock runs, drop a refused thinking display, count thinking on string bodies by @jarvis9443 in #1323
- feat(bedrock): Bedrock API key, default credential chain and AssumeRole for provider keys and guardrails by @jarvis9443 in #1322
- fix(telemetry): record the provider's request id wherever the response has no id of its own; name the provider on failed requests' access-log lines by @jarvis9443 in #1324
- fix(bedrock,vertex): filter a provider key's anthropic-beta for Claude; on Bedrock send it in the body, never as a header by @jarvis9443 in #1325
- fix(server): no duplicate upstream calls or dropped late requests at the end of a drain by @jarvis9443 in #1317
- ci: shard e2e across jobs and cancel superseded PR runs by @jarvis9443 in #1326
- feat(outbound): one product-wide SSRF setting for caller-supplied URLs by @jarvis9443 in #1327
- fix: Bedrock proxy env, Bedrock guardrail 4xx tag, caller-refusal error class, chat audio tokens by @jarvis9443 in #1328
- chore(deps): bump hono to 4.13.13 in tools/mcp-conformance by @jarvis9443 in #1329
- fix(realtime): dial the upstream WebSocket through the proxy environment by @jarvis9443 in #1330
- ci: stop running docker-image on PRs, smoke every pushed image, clippy --locked by @jarvis9443 in #1331
- feat(responses): keep Responses context on the routing target that produced it by @jarvis9443 in #1332
- ci: pull etcd from the ghcr.io/api7/etcd mirror instead of quay.io by @jarvis9443 in #1334
- fix: rc.6 QA findings — media-fetch diagnostics, config key paths, streamed audio, realtime close by @jarvis9443 in #1333
- fix(usage): record the upstream's reported total_tokens on every endpoint family by @jarvis9443 in #1335
- test(admin): drop the dead testcontainers etcd test and fold its checks into CI by @jarvis9443 in #1337
- fix(media-fetch): log redirected_to the way url is logged by @jarvis9443 in #1338
- fix(realtime): stop reporting the gateway's own 1002 as the upstream's close code by @jarvis9443 in #1339
- fix(realtime): stop passing the library's 1002 upstream as the client's close code by @jarvis9443 in #1340
- fix(chat): relay the upstream's usage details as reported, empty choices on the usage frame by @jarvis9443 in #1341
- fix(chat): Claude bridge drops stream_options, maps max_completion_tokens and stop; include_usage usage on its own chunk by @jarvis9443 in #1343
- fix(bedrock): forward stop and max_completion_tokens as sent on Claude InvokeModel by @jarvis9443 in #1344
- fix(bedrock): let a Claude caller's native stop_sequences win over stop on Converse by @jarvis9443 in #1345
Full Changelog: v1.6.0...v1.7.0