ai_protocol_manager: make inline string threshold configurable
Project: envoyproxy/envoy | Language: C++ | Concepts: configuration-management, api-design, error-handling, testing-strategy | Difficulty: mid
Link: https://github.com/envoyproxy/envoy/pull/47075
The problem
The AI Protocol Manager filter uses a JSON parser (JsonWithExtBufParser) that keeps small string values inline and offloads larger ones to an external buffer, based on a threshold. This threshold was hardcoded to 1KiB because the parser was always constructed with a default config, making it impossible to adjust via configuration.
Why it's non-trivial
A naive fix would simply add a configuration field for the threshold, but:
- The threshold needs to be validated (not too small or too large).
- The value should be overridable per endpoint schema, not just globally.
- The code must ensure the threshold is single-sourced and defaults are consistent.
- Tests must verify both defaulting and override behaviors, as well as error handling for out-of-range values.
The approach
Expose the threshold in the API and config:
+ // Request-path parsing limits. Defaults apply when unset.
+ RequestParsingLimits limits = 2;
+}
+
+// Per-stream limits for request payload parsing.
+message RequestParsingLimits {
+ ...
+ google.protobuf.UInt32Value inline_string_threshold_bytes = 1
+ [(validate.rules).uint32 = {lte: 1048576 gte: 64}];
+}
Adds a new RequestParsingLimits message to the filter's config proto, with inline_string_threshold_bytes (min 64, max 1MiB) as a field. This allows users to configure the threshold.
Read and apply the config in the filter:
+ inline_string_threshold_bytes_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(
+ proto.request_handling().limits(), inline_string_threshold_bytes,
+ JsonWithExtBufParser::kDefaultInlineStringThresholdBytes)),
Reads the configured value, or falls back to the parser's default if unset.
Pass the threshold to the parser:
- request_parser_ = std::make_unique<JsonWithExtBufParser>(JsonWithExtBufParser::Config{});
+ JsonWithExtBufParser::Config parser_config;
+ parser_config.inline_string_threshold_bytes = inlineStringThresholdBytes();
+ request_parser_ = std::make_unique<JsonWithExtBufParser>(parser_config);
Ensures the parser is constructed with the correct threshold for each request.
Allow per-schema override of the threshold:
+uint32_t AiProtocolManagerFilter::inlineStringThresholdBytes() const {
+ if (isAiEndpoint()) {
+ if (const PayloadSchema* payload_schema =
+ AdapterRegistry::get(route_request_protocol_).schema();
+ payload_schema != nullptr) {
+ if (const std::optional<uint32_t> pinned =
+ payload_schema->requestInlineStringThresholdBytes();
+ pinned.has_value()) {
+ return *pinned;
+ }
+ }
+ }
+ return config_->inlineStringThresholdBytes();
+}
Checks if the request's schema pins its own threshold; if so, uses it, otherwise uses the filter-level default.
Support per-schema threshold in schema classes:
+ RequestSchema& inlineStringThresholdBytes(uint32_t bytes) {
+ inline_string_threshold_bytes_ = bytes;
+ return *this;
+ }
+ std::optional<uint32_t> inlineStringThresholdBytes() const {
+ return inline_string_threshold_bytes_;
+ }
Allows schemas to declare their own inline string thresholds.
Test configuration, defaults, overrides, and error handling:
+TEST(AiProtocolManagerConfigTest, InlineStringThresholdDefaultsAndOverrides) {
+ ...
+ {
+ envoy::extensions::filters::http::ai_protocol_manager::v3::AiProtocolManager proto_config;
+ proto_config.mutable_request_handling();
+ const FilterConfig config(proto_config, *stats_store.rootScope());
+ EXPECT_EQ(config.inlineStringThresholdBytes(),
+ JsonWithExtBufParser::kDefaultInlineStringThresholdBytes);
+ }
+ {
+ envoy::extensions::filters::http::ai_protocol_manager::v3::AiProtocolManager proto_config;
+ proto_config.mutable_request_handling()
+ ->mutable_limits()
+ ->mutable_inline_string_threshold_bytes()
+ ->set_value(4096);
+ const FilterConfig config(proto_config, *stats_store.rootScope());
+ EXPECT_EQ(config.inlineStringThresholdBytes(), 4096);
+ }
+}
+
+TEST(AiProtocolManagerConfigTest, RejectsOutOfRangeInlineStringThreshold) {
+ ...
+ for (const uint32_t value : {0u, 32u, 2u * 1024u * 1024u}) {
+ ...
+ EXPECT_THROW(factory.createFilterFactoryFromProto(proto_config, "stats", context).IgnoreError(),
+ EnvoyException);
+ }
+}
Tests that the filter uses the correct default, applies overrides, and rejects out-of-range values.
From the review thread
tyxia: This inline_string_threshold_bytes here is identical to max_per_scalar_bytes that was introduced in https://github.com/envoyproxy/envoy/pull/46300.
From design point of view, We should unify the limits and connect 3 layers: ai_protocol_manager, JsonWithExtBufParser and Json::Wuffs, to have single Source of Truth and to make it extensible for different handler types
tyxia: this gte: 1 is probably confusing,
Setting a small threshold that is larger than 1 but lower than the standard field like model names 30–80 bytes, roles like "assistant, will cause the legit requests to fail with HTTP 400.
We probably should have a more realistic lower bound like 64
penguingao: do we think this belongs to a filter level config, or individual schema? I feel like this can be a per-schema thing. I guess this can be an overall upper-bound, but individual schema should be able to override this.
tyxia: I think it makes sense to have a upper-bound in the filter level as coarse-grained threshold , and individual schema override as fine-grained control if needed
Takeaways
- When exposing resource or size limits via configuration, always validate the range to prevent both accidental breakage and resource exhaustion.
- Allowing both global (filter-level) defaults and per-endpoint (schema-level) overrides provides flexibility for deployment-wide and fine-grained tuning.
- Tests for configuration should cover defaulting, overrides, and error handling for invalid values.
- When introducing new configuration fields, ensure documentation and code comments are clear but concise to avoid confusion for future maintainers.