Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 47 additions & 5 deletions deploy/helm/vanity-gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,18 @@ or the pod is killed mid-drain.

## Route mapping

`vanityGateway.mappingConfig.v2config` has three sections:
`vanityGateway.mappingConfig.v2config` has four sections:

- `openai`: per-endpoint model routes, keyed by endpoint (`chatCompletions`,
`completions`, `embeddings`, `responses`, and the image endpoints). Each route
requires `modelName` and `functionID`.
`completions`, `embeddings`, `responses`, `decisions`, and the image
endpoints). Each route requires `modelName` and `functionID`. `decisions`
serves the beta `POST /v1/decisions` route.
- `anthropicMessages`: native Anthropic Messages model routes, served on
`POST /v1/messages`. Routes use the same fields as `openai.chatCompletions`
and share the API host set by `openai.host`.
- `typesafeSystemOne`: beta TypeSafe System One model routes, served on
`POST /v1/systemone`. A top-level sibling of `anthropicMessages`, with the
same fields as `openai.decisions`.
- `vanity`: host-based routes, each requiring a `host` and a `paths` map. Each
path requires `path` and `functionID`.

Expand All @@ -123,13 +127,14 @@ across all routes.

A model may set `functionType: LLM` to be served by the LLM Gateway instead of
the invocation service, supported in `openai.chatCompletions`,
`openai.responses`, `openai.embeddings`, and `anthropicMessages`. Callers still send the public `modelName`; the gateway
`openai.responses`, `openai.embeddings`, `openai.decisions`, `anthropicMessages`, and
`typesafeSystemOne`. Callers still send the public `modelName`; the gateway
rewrites the request model to `functionID/modelName` before forwarding.

The values schema enforces what the gateway checks at startup, so a values file
that would fail the container fails the render instead:

- `functionType` is rejected outside those four endpoints
- `functionType` is rejected outside those endpoints
- `usePexec`, `outgoingPathOverride`, and `sessionTimeout` are rejected on such
a model, since the LLM Gateway ignores them. An explicit `false`, `""`, or `0`
is accepted, because that is what an absent key produces
Expand Down Expand Up @@ -162,6 +167,43 @@ Messages. Upgrade Pylon workers, the Stargate router, and the LLM Gateway
before enabling Messages routes. See `docs/overview/llm-gateway.md` for the
request, accounting, and Claude Code details.

Decision models (beta) are mapped per route. A model is enabled for a route only
when listed in that route's section, and a function that serves both formats is
listed in both:

```yaml
vanityGateway:
mappingConfig:
v2config:
openai:
host: api.example.com
decisions:
example_decision-model:
modelName: example/decision-model
functionID: <function-id>
functionVersionID: <function-version-id>
example_decision-model-llm:
modelName: example/decision-model-llm
functionID: <llm-function-id>
functionType: LLM
typesafeSystemOne:
example_decision-model:
modelName: example/decision-model
functionID: <function-id>
functionVersionID: <function-version-id>
```

Request and response bodies pass through unchanged, and responses are not
streamed. With `usePexec: true` the invocation service normally sends the request
to the default invocation path registered for the function, whichever route the
client called, so a `usePexec: true` function serves only its default path.
A function that serves both routes should set `usePexec: false`. Models with
`functionType: LLM` need LLM Gateway and Stargate builds that serve these routes;
enable them only after those builds run in the environment. Request body
sampling (`promptCacheKey`, `firstMessageHash`) and `promptCacheKeyHeaders` are
rejected in the decision sections. See the service README for error shapes and
`/v1/models` behavior.

Shadow traffic is supported. Each shadow target is resolved from the same model
table and routed by its own `functionType`, so a shadow of an LLM model reaches
the LLM Gateway, and an LLM model may shadow a model served by the invocation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@
"anthropicMessages": {
"$ref": "#/definitions/openAIStickyEndpointRoutes"
},
"typesafeSystemOne": {
"$ref": "#/definitions/openAILLMEndpointRoutes"
},
"vanity": {
"$ref": "#/definitions/vanityRoutes"
}
Expand Down Expand Up @@ -273,6 +276,29 @@
"required": [
"embeddings"
]
},
{
"properties": {
"decisions": {
"not": {
"additionalProperties": {
"not": {
"properties": {
"functionType": {
"const": "LLM"
}
},
"required": [
"functionType"
]
}
}
}
}
},
"required": [
"decisions"
]
}
]
}
Expand Down Expand Up @@ -303,6 +329,29 @@
"required": [
"anthropicMessages"
]
},
{
"properties": {
"typesafeSystemOne": {
"not": {
"additionalProperties": {
"not": {
"properties": {
"functionType": {
"const": "LLM"
}
},
"required": [
"functionType"
]
}
}
}
}
},
"required": [
"typesafeSystemOne"
]
}
]
}
Expand Down Expand Up @@ -357,6 +406,9 @@
"embeddings": {
"$ref": "#/definitions/openAILLMEndpointRoutes"
},
"decisions": {
"$ref": "#/definitions/openAILLMEndpointRoutes"
},
"responses": {
"$ref": "#/definitions/openAIStickyEndpointRoutes"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,21 @@ assert_renders "a model without functionType renders with an empty endpoint"
write_values "${ENDPOINT}" chatCompletions " functionType: llmGateway"
assert_rejected "value must be 'LLM'" "an unrecognized functionType is rejected"

for section in responses embeddings; do
for section in responses embeddings decisions; do
write_values "${ENDPOINT}" "${section}" "${LLM}"
assert_renders "an LLM model renders in ${section}"
done

write_values "" decisions "${LLM}"
assert_rejected "/vanityGateway/config/llmGatewayEndpoint" "an LLM decisions model requires an endpoint"

write_values "${ENDPOINT}" decisions "${LLM}
usePexec: true"
assert_rejected "a_model/usePexec" "usePexec is rejected on an LLM decisions model"

write_values "" decisions ""
assert_renders "a decisions model without functionType renders with an empty endpoint"

for section in completions imageGenerations imageEdits imageVariations; do
write_values "${ENDPOINT}" "${section}" "${LLM}"
assert_rejected "openai/${section}/a_model" "functionType is rejected in ${section}"
Expand Down Expand Up @@ -156,6 +166,46 @@ write_messages_values "${ENDPOINT}" "${MESSAGES_LLM}
sessionTimeout: 900"
assert_rejected "anthropicMessages/a_model/sessionTimeout" "sessionTimeout is rejected on an LLM Messages model"

# typesafeSystemOne is a top-level sibling of anthropicMessages.
write_system_one_values() {
local endpoint="$1" extra="$2"
cat > "${VALUES}" <<EOF
vanityGateway:
image:
registry: example.com
repository: foo/bar
config:
llmGatewayEndpoint: "${endpoint}"
mappingConfig:
v2config:
openai:
host: api.example.com
typesafeSystemOne:
a_model:
modelName: acme/a-model
functionID: 00000000-0000-0000-0000-000000000001
${extra}
EOF
}

write_system_one_values "" "${MESSAGES_LLM}"
assert_rejected "/vanityGateway/config/llmGatewayEndpoint" "an LLM System One model requires an endpoint"

write_system_one_values "${ENDPOINT}" "${MESSAGES_LLM}"
assert_renders "an LLM System One model with an endpoint renders"

write_system_one_values "" ""
assert_renders "a System One model without functionType renders with an empty endpoint"

write_system_one_values "${ENDPOINT}" "${MESSAGES_LLM}
usePexec: true"
assert_rejected "typesafeSystemOne/a_model/usePexec" "usePexec is rejected on an LLM System One model"

write_system_one_values "${ENDPOINT}" "${MESSAGES_LLM}
customHeaders:
X-Priority: \"5\""
assert_rejected "typesafeSystemOne/a_model/customHeaders" "an X-Priority custom header is rejected on an LLM System One model"

# The proxy preserves the caller path and the transport only speaks http(s).
for bad in "ftp://llm-gateway:8080" "http://llm-gateway:8080/v1"; do
write_values "${bad}" chatCompletions "${LLM}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,14 @@ ${extra}
EOF
}

# anthropicMessages sits beside openai, so its entries are two spaces shallower.
# anthropicMessages and typesafeSystemOne sit beside openai, so their entries
# are two spaces shallower.
write_anthropic_values() {
local extra="$1"
write_toplevel_values anthropicMessages "$1"
}

write_toplevel_values() {
local section="$1" extra="$2"
cat > "${VALUES}" <<EOF
vanityGateway:
image:
Expand All @@ -59,7 +64,7 @@ vanityGateway:
llmGatewayEndpoint: "${ENDPOINT}"
mappingConfig:
v2config:
anthropicMessages:
${section}:
a_model:
modelName: acme/a-model
functionID: 00000000-0000-0000-0000-000000000001
Expand Down Expand Up @@ -247,7 +252,7 @@ write_values chatCompletions " shadowModelName: acme/a-model-next
- perBearerKey"
assert_rejected "shadowSamplingMethod" "a legacy method after random is rejected"

for section in completions embeddings imageGenerations; do
for section in completions embeddings imageGenerations decisions; do
write_values "${section}" " shadows:
- modelName: acme/a-model-next
samplingMethod: promptCacheKey"
Expand All @@ -262,6 +267,26 @@ for section in completions embeddings imageGenerations; do
assert_rejected "promptCacheKeyHeaders" "promptCacheKeyHeaders is rejected in ${section}"
done

write_toplevel_values typesafeSystemOne " shadows:
- modelName: acme/a-model-next
samplingMethod: promptCacheKey"
assert_rejected "samplingMethod" "promptCacheKey is rejected in typesafeSystemOne"

write_toplevel_values typesafeSystemOne " shadowModelName: acme/a-model-next
shadowSamplingMethod: firstMessageHash"
assert_rejected "shadowSamplingMethod" "legacy firstMessageHash is rejected in typesafeSystemOne"

write_toplevel_values typesafeSystemOne " promptCacheKeyHeaders:
- x-session-id"
assert_rejected "promptCacheKeyHeaders" "promptCacheKeyHeaders is rejected in typesafeSystemOne"

write_toplevel_values typesafeSystemOne " shadows:
- modelName: acme/a-model-next
samplingMethod:
- perBearerKey
- random"
assert_renders "ordered bearer sampling renders in typesafeSystemOne"

write_anthropic_values " promptCacheKeyHeaders:
- x-session-id
shadows:
Expand Down
Loading