Skip to content

Request Rewrite Rules

GPROXY lets you rewrite or delete fields in the request body before it leaves for the upstream provider. This is the escape hatch you reach for when a client won’t stop sending temperature: 1.0 to a model that rejects it, when you need to force stream_options.include_usage = true, or when a specific provider needs a non-standard field injected.

Rewrite rules are defined per provider and live in the provider’s settings JSON. They are applied in the handler layer before alias resolution, using the model name the client sent — so filters that match on the alias name still work.

Implementation: sdk/gproxy-channel/src/utils/rewrite.rs.

{
"path": "temperature", // dot-notation into the JSON body
"action": { "type": "set", "value": 0.7 },
"filter": { // optional — AND logic
"model_pattern": "gpt-4*",
"operations": ["generate_content", "stream_generate_content"],
"protocols": ["openai_chat_completions"]
}
}
  • path — dot-notation address into the JSON body. temperature, stream_options.include_usage, metadata.tenant, and so on. Missing intermediate keys are auto-created for set.
  • action.type — either set (with a value) or remove.
  • filter (optional) — restricts which requests the rule fires on. All specified dimensions must match (AND logic). An omitted dimension matches everything.

The rule list is applied in order, so a later rule can overwrite an earlier one on the same path.

Walks the path, creating intermediate objects if they don’t exist, and writes value at the leaf. value is any JSON value — scalar, object, or array. If an intermediate key exists but is not an object (e.g. the client sent "a": "string" and you rewrite a.b.c), GPROXY overwrites it with a fresh object.

// Force include_usage = true on every OpenAI stream.
{
"path": "stream_options.include_usage",
"action": { "type": "set", "value": true },
"filter": { "protocols": ["openai_chat_completions"] }
}

Walks to the parent and deletes the leaf key. Missing paths are a silent no-op.

// Strip temperature for clients that always send 1.0.
{
"path": "temperature",
"action": { "type": "remove" }
}
DimensionMeaningMatching
model_patternGlob against the model name the client sent.* (any run of chars), ? (exactly one char). See the glob tests for exact behavior.
operationsAllowlist of OperationFamily values.Rule fires only if the current operation is in the list.
protocolsAllowlist of ProtocolKind values.Rule fires only if the current protocol is in the list.

The model_pattern is matched against the original model name the client sent — before alias resolution and before any provider prefix stripping. That means a filter on chat-default matches requests that asked for the alias chat-default, not requests that asked for the underlying real model.

The canonical order is:

permission → rewrite → alias → execute

Rewrite runs after permission check (so denied requests don’t mutate anything) and before alias resolution (so filters see the alias name the client typed).

1. Force a sampling cap on a specific model family

Section titled “1. Force a sampling cap on a specific model family”
{
"path": "temperature",
"action": { "type": "set", "value": 0.7 },
"filter": { "model_pattern": "o3*" }
}

2. Inject a required metadata.tenant on every call

Section titled “2. Inject a required metadata.tenant on every call”
{
"path": "metadata.tenant",
"action": { "type": "set", "value": "acme-prod" }
}

Intermediate objects are auto-created, so you don’t need to pre-set metadata separately.

{
"path": "thinking",
"action": { "type": "remove" },
"filter": { "protocols": ["claude"] }
}

4. Rewrite stream_options only on streaming OpenAI calls

Section titled “4. Rewrite stream_options only on streaming OpenAI calls”
{
"path": "stream_options",
"action": {
"type": "set",
"value": { "include_usage": true }
},
"filter": {
"operations": ["stream_generate_content"],
"protocols": ["openai_chat_completions"]
}
}
  • Rules are applied in order. If two rules touch the same path, the later one wins.
  • Non-object bodies (empty GETs, arrays, null) are skipped silently — the rules are only meaningful for JSON-object request bodies.
  • Regex is not supported; model_pattern is a simple glob.
  • There is no way to read an existing value and compute a new one — this is a set/remove system, not a scripting language. If you find yourself wanting conditionals on the value, consider handling it client-side or proposing a dedicated channel extension.

Two places:

  • Seed TOML — provider settings.rewrite_rules is a JSON array on the provider entry. It’s easiest to assemble the rules in the console and export the TOML, rather than hand-writing the JSON.
  • Embedded consoleProviders → {your provider} → Settings exposes a typed editor for the rewrite rule list. Edits take effect on the next request; no reload needed.

Channels that currently expose rewrite_rules in their settings include OpenAI-compatible, Anthropic, Claude Code, and most of the other channel types.