From 9b24a69b40d6ad38bf4cc69b29ad29d9631ecd18 Mon Sep 17 00:00:00 2001 From: David Breitgand Date: Fri, 12 Dec 2025 16:32:44 +0200 Subject: [PATCH] Pluggable BBR framework proposal --- .../1964-pluggable-bbr-framework/README.md | 251 ++++++++++++++++++ ...luggable-framework-architecture-sketch.png | Bin 0 -> 31847 bytes 2 files changed, 251 insertions(+) create mode 100644 docs/proposals/1964-pluggable-bbr-framework/README.md create mode 100644 docs/proposals/1964-pluggable-bbr-framework/images/pluggable-framework-architecture-sketch.png diff --git a/docs/proposals/1964-pluggable-bbr-framework/README.md b/docs/proposals/1964-pluggable-bbr-framework/README.md new file mode 100644 index 0000000000..48822e8010 --- /dev/null +++ b/docs/proposals/1964-pluggable-bbr-framework/README.md @@ -0,0 +1,251 @@ +# Pluggable Body-Based Routing (BBR) Framework + +Author(s): @davidbreitgand @srampal + +## Proposal Status + +***Draft*** + +## Summary + +The Gateway API Inference Extension (v1.2.1) includes an initial implementation of Body-Based Routing (BBR). Currently, BBR provides a single capability: it extracts the model name from the request body and adds it to the `X-Gateway-Model-Name` header. This header is then used to route the request to the appropriate InferencePool and its associated Endpoint Picker Extension (EPP) instances. + +The current BBR implementation is limited and lacks extensibility. Similar to the [pluggability introduced in the scheduling subsystem](../0845-scheduler-architecture-proposal/README.md), BBR should support custom extensions without requiring modifications to the GIE code base. + +This proposal introduces a plugin architecture for BBR that allows developers to implement custom logic. Plugins could be organized into a chain or DAG for ordered and concurrent execution. + +See [this document](https://docs.google.com/document/d/1So9uRjZrLUHf7Rjv13xy_ip3_5HSI1cn1stS3EsXLWg/edit?tab=t.0#heading=h.55jwocr94axs) for additional context amd reference. + +## Goals + +The pluggable BBR Framework aims at addressing the following goals + +- Avoid monolithic architecture +- Mimic pluggability and configurability of the scheduling subsystem without coupling between the two +- Enable organizing plugins into a topology for ordered and concurrent execution +- Avoid redundant recurrent body parsing across plugins in a topology for the sake of performance +- Limit changes to the BBR feature to avoid any changes in the rest of the code base +- Follow best practices and experience from the Scheduling subsystem + pluggability effort. For example, extending the system to support the above + should be through implementing well defined `Plugin` interfaces and registering + them in the BBR subsystem; any configuration would be done in the + same way (e.g., code and/or configuration file) +- Reuse common code from EPP, such as `TypedName`, wherever make sense, but avoid reusing specialized code with non-BBR functionality to avoid abuse +- Enable extensible collection and registration of metrics using lessons from the pluggable scheduling sub-system +- Provide reference plugin implementations. + +## Non-Goals + +- Modify existing GIE abstractions +- Fully align plugins, registries, and factories across BBR and EPP +- Dynamically reconfigure plugins and plugin topologies at runtime + +## Proposal + +### Overview + +There is an embedded `BBRPlugin` interface building on the `Plugin` interface adopted from EPP. This interface should be implemented by any BBR plugin. Each pluigin is identified by its `TypedName` (adopted from EPP), where `TypedName().Type` gives the string representing the type of the plugin and `TypedName().Name()` returns the string representing the plugins implementation. BBR is refactored to implement the registered factory pattern. To that end, a `PluginRegistry` interface and its implementation are added to register `BBRPlugin` factories and concrete implementations created by the factories. +In addition, a `PluginsChain` interface is defined to define an order of plugin executions. In the future, `PluginsChain` will be replaced by `PluginsDAG` to allow for more complex topological order and concurrency. + +`PluginsChain` only contains ordered `BBRPlugin` types registered in the `PluginRegistry`. `RequestPluginsChain` and `ResponsePluginsChain` are optionally configured for handling requests and responses respectively. If no configuration is provided, default `PluginsChain` instances will be configured automatically. + +Depending on a `BBRPlugin` functionality and implementation, the plugin might require full or selective body parsing. To save the parsing overhead, if there is at least one `BBRPlugin` in the `PluginsChain` that requires full body parsing, the parsing is performed only once into a shared official appropriate `openai-go` struct (either `openai.CompletionNewParams` or `openai.ChatCompletionNewParams` depending on the request endpoint). This struct is shared for read-only to all plugins in the chain. Each `BBRplugin` receives the shared struct by value. If a plugin needs to mutate the body, in the initial implementation, it MUST work on its own copy, and the a mutated body is returned separately by each plugiin. + +### Suggested Components + +The sketch of the proposed framework is shown in the figure below. +Components of the proposed framework + +### Suggested BBR Pluggable Framework Interfaces + +```go +// ------------------------------------ Defaults ------------------------------------------ +const DefaultPluginType = "MetadataExtractor" +const DefaultPluginImplementation = "simple-model-selector" + +// BBRPlugin defines the interface for plugins in the BBR framework +type BBRPlugin interface { + plugins.Plugin + + // RequiresFullParsing indicates whether full body parsing is required + // to facilitate efficient memory sharing across plugins in a chain. + RequiresFullParsing() bool + + // Execute runs the plugin logic on the request body. + // A plugin's imnplementation logic CAN mutate the body of the message. + // A plugin's implementation MUST return a map of headers + // If no headers are set by the implementation, the map must be empty + // A value of a header in an extended implementation NEED NOT to be identical to the value of that same header as would be set + // in a default implementation. + // Example: in the body of a request model is set to "semantic-model-selector", + // which, say, stands for "select a best model for this request at minimal cost" + // A plugin implementation of "semantic-model-selector" sets X-Gateway-Model-Name to any valid + // model name from the inventory of the backend models and also mutates the body accordingly + // In contrast, + Execute(requestBodyBytes []byte) ( + headers map[string]string, + mutatedBodyBytes []byte, + err error, + ) +} + + +// placeholder for BBRPlugin constructors +type PluginFactoryFunc func() bbrplugins.BBRPlugin //concrete constructors are assigned to this type + +// PluginRegistry defines operations for managing plugin factories and plugin instances +type PluginRegistry interface { + RegisterFactory(typeKey string, factory PluginFactoryFunc) error //constructors + RegisterPlugin(plugin bbrplugins.BBRPlugin) error //registers a plugin instance (the instance MUST be created via the factory first) + GetFactory(typeKey string) (PluginFactoryFunc, error) + GetPlugin(typeKey string) (bbrplugins.BBRPlugin, error) + GetFactories() map[string]PluginFactoryFunc + GetPlugins() map[string]bbrplugins.BBRPlugin + ListPlugins() []string + ListFactories() []string + CreatePlugin(typeKey string) (bbrplugins.BBRPlugin, error) + ContainsFactory(typeKey string) bool + ContainsPlugin(typeKey string) bool + String() string //human readable string for logging +} + +// PluginsChain is used to define a specific order of execution of the BBRPlugin instances stored in the registry +// The BBRPlugin instances +type PluginsChain interface { + AddPlugin(typeKey string, registry PluginRegistry) error //to be added to the chain the plugin should be registered in the registry first + AddPluginAtInd(typeKey string, i int, r PluginRegistry) error //only affects the instance of the plugin chain + GetPlugin(index int, registry PluginRegistry) (bbrplugins.BBRPlugin, error) //retrieves i-th plugin as defined in the chain from the registry + Length() int + ParseChatCompletion(data []byte) (openai.ChatCompletionNewParams, error) //parses the bytes slice into an appropriate openai-go struct + ParseCompletion(data []byte) (openai.CompletionNewParams, error) //likewise + GetSharedMemory(which string) interface{} //returns an appropriate shared open-ai struct dependent on whether which + //corresponds to Completion or ChatCompletion endpoint requested in the body + Run(bodyBytes []byte, registry PluginRegistry) ([]byte, map[string]string, error) //return potentially mutated body and all headers map safely merged + String() string +} +//NOTE: for simplicity, in the initial PR, PluginsChain instance will be defined request only +``` + +### Defaults + +```go + +const ( + //A deafult plugin implementation of this plugin type will always be configured for request plugins chain + //Even though BBRPlugin type is not (yet) a K8s resource, it's logically akin to `kind` + //MUST start wit an upper case letter, use CamelNotation, only aplhanumericals after the first letter + PluginTypePattern = `^[A-Z][A-Za-z0-9]*$` + MaxPluginTypeLength = 63 + DefaultPluginType = "MetaDataExtractor" + // Even though BBRPlugin is not a K8s resource yet, let's make its naming compliant with K8s resource naming + // Allows: lowercase letters, digits, hyphens, dots. + // Must start and end with a lowercase alphanumeric character. + // Middle characters group can contain lowercase alphanumerics, hyphens, and dots + // Middle and rightmost groups are optional + PluginNamePattern = `^[a-z0-9]([-a-z0-9.]*[a-z0-9])?$` + DefaultPluginName = "simple-model-extractor" + MaxPluginNameLength = 253 + //Well-known custom header set to a model name + ModelHeader = "X-Gateway-Model-Name" +) +``` + +### Current BBR reimplementation as BBRPlugin + +```go +/ ------------------------------------ DEFAULT PLUGIN IMPLEMENTATION ---------------------------------------------- + +type simpleModelExtractor struct { //implements the MetadataExtractor interface + typedName plugins.TypedName + requiresFullParsing bool +} + +// defaultMetaDataExtractor implements the MetadataExtractor interface and extracts only the mmodel name AS-IS +type defaultMetaDataExtractor struct { + typedName plugins.TypedName + requiresFullParsing bool //this field will be used to determine whether shared struct should be created in this chain +} + +// NewSimpleModelExtractor is a factory that constructs SimpleModelExtractor plugin +// A developer who wishes to create her own implementation, will implement the BBRPlugin interface and +// use Registry and PluginsChain to register and execute the plugin (together with other plugins in a chain) +func NewDefaultMetaDataExtractor() BBRPlugin { + return &defaultMetaDataExtractor{ + typedName: plugins.TypedName{ + Type: DefaultPluginType, + Name: "simple-model-extractor", + }, + requiresFullParsing: false, + } +} + +func (s *defaultMetaDataExtractor) RequiresFullParsing() bool { + return s.requiresFullParsing +} + +func (s *defaultMetaDataExtractor) TypedName() plugins.TypedName { + return s.typedName +} + +// Execute extracts the "model" from the JSON request body and sets X-Gateway-Model-Name header. +// This implementation intentionally ignores metaDataKeys and does not mutate the body. +// It expects the request body to be a JSON object containing a "model" field. +// A nil for metaDataKeysToHeaders map SHOULD be specified by a caller for clarity +// The metaDataKeysToHeaders is explicitly ignored in this implementation +// This implementation is simply refactoring of the default BBR implementation to work with the pluggable framework +func (s *defaultMetaDataExtractor) Execute(requestBodyBytes []byte) ( + headers map[string]string, + mutatedBodyBytes []byte, + err error) { + + type RequestBody struct { + Model string `json:"model"` + } + + h := make(map[string]string) + + var requestBody RequestBody + + if err := json.Unmarshal(requestBodyBytes, &requestBody); err != nil { + // return original body on decode failure + return nil, requestBodyBytes, err + } + + if requestBody.Model == "" { + return nil, requestBodyBytes, fmt.Errorf("missing required field: model") + } + + // ModelHeader is a constant defined in ./pkg/bbr/plugins/interfaces + h[ModelHeader] = requestBody.Model + + // Body is not mutated in this implementation hence returning original requestBodyBytes. This is intentional. + return h, requestBodyBytes, nil +} + +func (s *defaultMetaDataExtractor) String() string { + return fmt.Sprintf(("BBRPlugin{%v/requiresFullParsing=%v}"), s.TypedName(), s.requiresFullParsing) +} +``` + +### Implementation Phases + +The pluggable framework will be implemented iteratively over several phases. + +1. Introduce `BBRPlugin` `MetadataExtractor`, interface, registry, plugins chain, sample plugin implementation (`SimpleModelExtraction`) and its factory. Plugin configuration will be implemented via environment variables set in helm chart +1. Introduce a second plugin interface, `ModelSelector` and sample plugin implementation +1. Introduce shared struct (shared among the plugins of a plugins chain) +1. Introduce an interface for guardrail plugin, introduce simple reference implementation, experiment with plugins chains on request and response messages +1. Refactor metrics as needed to work with the new pluggable framework +1. Implement configuration via manifests similar to those in EPP +1. Implement `PluginsDAG` to allow for more complex topological order and concurrency. +1. Continously learn lessons from this implementation and scheduling framework to improve the implementation +1. Aim at aligning and cross-polination with the [AI GW WG]("https://github.com/kubernetes-sigs/wg-ai-gateway"). + +## Open Questions + +1. More elaborate shared memory architecture for the best performance +1. TBA + +## Note + +The proposed interfaces can slightly change from those implemented in the initial [PR 1981](https://github.com/kubernetes-sigs/gateway-api-inference-extension/pull/1981) \ No newline at end of file diff --git a/docs/proposals/1964-pluggable-bbr-framework/images/pluggable-framework-architecture-sketch.png b/docs/proposals/1964-pluggable-bbr-framework/images/pluggable-framework-architecture-sketch.png new file mode 100644 index 0000000000000000000000000000000000000000..68bccb6ea9c7501c7bcd168fe15b9bd0f98a6659 GIT binary patch literal 31847 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 literal 0 HcmV?d00001