Skip to content

Commit 73ec55c

Browse files
authored
refactor(headers): centralize custom HTTP headers into dedicated package (#362)
- Create new pkg/headers package to manage all x-* prefixed headers - Define constants for request, VSR decision tracking, and security headers - Update security headers to use x-vsr-* prefix for consistency - x-pii-violation -> x-vsr-pii-violation - x-jailbreak-blocked -> x-vsr-jailbreak-blocked - x-jailbreak-type -> x-vsr-jailbreak-type - x-jailbreak-confidence -> x-vsr-jailbreak-confidence - Replace all string literals with constants across codebase - Fix variable naming conflict in request_handler.go BREAKING CHANGE: Security header names changed from x-* to x-vsr-* prefix. Clients consuming these headers must update to use new names. Signed-off-by: bitliu <[email protected]>
1 parent a4f9d84 commit 73ec55c

File tree

5 files changed

+126
-19
lines changed

5 files changed

+126
-19
lines changed

src/semantic-router/pkg/extproc/request_handler.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/cache"
1919
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/config"
20+
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/headers"
2021
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/metrics"
2122
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability"
2223
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/utils/http"
@@ -285,8 +286,8 @@ func (r *OpenAIRouter) handleRequestHeaders(v *ext_proc.ProcessingRequest_Reques
285286
defer span.End()
286287

287288
// Store headers for later use
288-
headers := v.RequestHeaders.Headers
289-
for _, h := range headers.Headers {
289+
requestHeaders := v.RequestHeaders.Headers
290+
for _, h := range requestHeaders.Headers {
290291
// Prefer Value when available; fall back to RawValue
291292
headerValue := h.Value
292293
if headerValue == "" && len(h.RawValue) > 0 {
@@ -296,7 +297,7 @@ func (r *OpenAIRouter) handleRequestHeaders(v *ext_proc.ProcessingRequest_Reques
296297

297298
ctx.Headers[h.Key] = headerValue
298299
// Store request ID if present (case-insensitive)
299-
if strings.ToLower(h.Key) == "x-request-id" {
300+
if strings.ToLower(h.Key) == headers.RequestID {
300301
ctx.RequestID = headerValue
301302
}
302303
}
@@ -800,15 +801,15 @@ func (r *OpenAIRouter) handleModelRouting(openAIRequest *openai.ChatCompletionNe
800801
if selectedEndpoint != "" {
801802
setHeaders = append(setHeaders, &core.HeaderValueOption{
802803
Header: &core.HeaderValue{
803-
Key: "x-gateway-destination-endpoint",
804+
Key: headers.GatewayDestinationEndpoint,
804805
RawValue: []byte(selectedEndpoint),
805806
},
806807
})
807808
}
808809
if actualModel != "" {
809810
setHeaders = append(setHeaders, &core.HeaderValueOption{
810811
Header: &core.HeaderValue{
811-
Key: "x-selected-model",
812+
Key: headers.SelectedModel,
812813
RawValue: []byte(actualModel),
813814
},
814815
})
@@ -889,7 +890,7 @@ func (r *OpenAIRouter) handleModelRouting(openAIRequest *openai.ChatCompletionNe
889890
if selectedEndpoint != "" {
890891
setHeaders = append(setHeaders, &core.HeaderValueOption{
891892
Header: &core.HeaderValue{
892-
Key: "x-gateway-destination-endpoint",
893+
Key: headers.GatewayDestinationEndpoint,
893894
RawValue: []byte(selectedEndpoint),
894895
},
895896
})
@@ -1042,9 +1043,9 @@ func (r *OpenAIRouter) updateRequestWithTools(openAIRequest *openai.ChatCompleti
10421043
(*response).GetRequestBody().GetResponse().GetHeaderMutation().GetSetHeaders() != nil {
10431044
for _, header := range (*response).GetRequestBody().GetResponse().GetHeaderMutation().GetSetHeaders() {
10441045
switch header.Header.Key {
1045-
case "x-gateway-destination-endpoint":
1046+
case headers.GatewayDestinationEndpoint:
10461047
selectedEndpoint = header.Header.Value
1047-
case "x-selected-model":
1048+
case headers.SelectedModel:
10481049
actualModel = header.Header.Value
10491050
}
10501051
}
@@ -1054,15 +1055,15 @@ func (r *OpenAIRouter) updateRequestWithTools(openAIRequest *openai.ChatCompleti
10541055
if selectedEndpoint != "" {
10551056
setHeaders = append(setHeaders, &core.HeaderValueOption{
10561057
Header: &core.HeaderValue{
1057-
Key: "x-gateway-destination-endpoint",
1058+
Key: headers.GatewayDestinationEndpoint,
10581059
RawValue: []byte(selectedEndpoint),
10591060
},
10601061
})
10611062
}
10621063
if actualModel != "" {
10631064
setHeaders = append(setHeaders, &core.HeaderValueOption{
10641065
Header: &core.HeaderValue{
1065-
Key: "x-selected-model",
1066+
Key: headers.SelectedModel,
10661067
RawValue: []byte(actualModel),
10671068
},
10681069
})

src/semantic-router/pkg/extproc/response_handler.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
1212

1313
"github.com/openai/openai-go"
14+
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/headers"
1415
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/metrics"
1516
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability"
1617
)
@@ -60,7 +61,7 @@ func (r *OpenAIRouter) handleResponseHeaders(v *ext_proc.ProcessingRequest_Respo
6061
if ctx.VSRSelectedCategory != "" {
6162
setHeaders = append(setHeaders, &core.HeaderValueOption{
6263
Header: &core.HeaderValue{
63-
Key: "x-vsr-selected-category",
64+
Key: headers.VSRSelectedCategory,
6465
RawValue: []byte(ctx.VSRSelectedCategory),
6566
},
6667
})
@@ -70,7 +71,7 @@ func (r *OpenAIRouter) handleResponseHeaders(v *ext_proc.ProcessingRequest_Respo
7071
if ctx.VSRReasoningMode != "" {
7172
setHeaders = append(setHeaders, &core.HeaderValueOption{
7273
Header: &core.HeaderValue{
73-
Key: "x-vsr-selected-reasoning",
74+
Key: headers.VSRSelectedReasoning,
7475
RawValue: []byte(ctx.VSRReasoningMode),
7576
},
7677
})
@@ -80,7 +81,7 @@ func (r *OpenAIRouter) handleResponseHeaders(v *ext_proc.ProcessingRequest_Respo
8081
if ctx.VSRSelectedModel != "" {
8182
setHeaders = append(setHeaders, &core.HeaderValueOption{
8283
Header: &core.HeaderValue{
83-
Key: "x-vsr-selected-model",
84+
Key: headers.VSRSelectedModel,
8485
RawValue: []byte(ctx.VSRSelectedModel),
8586
},
8687
})
@@ -93,7 +94,7 @@ func (r *OpenAIRouter) handleResponseHeaders(v *ext_proc.ProcessingRequest_Respo
9394
}
9495
setHeaders = append(setHeaders, &core.HeaderValueOption{
9596
Header: &core.HeaderValue{
96-
Key: "x-vsr-injected-system-prompt",
97+
Key: headers.VSRInjectedSystemPrompt,
9798
RawValue: []byte(injectedValue),
9899
},
99100
})
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package headers
2+
3+
// Package headers provides constants for all custom HTTP headers used in the semantic router.
4+
// All custom headers follow the "x-" prefix convention for non-standard HTTP headers.
5+
6+
// Request Headers
7+
// These headers are used in incoming requests to the semantic router.
8+
const (
9+
// RequestID is the unique identifier for tracking a request through the system.
10+
// This header is case-insensitive when read from incoming requests.
11+
RequestID = "x-request-id"
12+
13+
// GatewayDestinationEndpoint specifies the backend endpoint address selected by the router.
14+
// This header is set by the router to direct Envoy to the appropriate upstream service.
15+
GatewayDestinationEndpoint = "x-gateway-destination-endpoint"
16+
17+
// SelectedModel indicates the model that was selected by the router for processing.
18+
// This header is set during the routing decision phase.
19+
SelectedModel = "x-selected-model"
20+
)
21+
22+
// VSR Decision Tracking Headers
23+
// These headers are added to successful responses (HTTP 200-299) to track
24+
// Vector Semantic Router decision-making information for debugging and monitoring.
25+
// Headers are only added when the request is successful and did not hit the cache.
26+
const (
27+
// VSRSelectedCategory indicates the category selected by VSR during classification.
28+
// Example values: "math", "business", "biology", "computer_science"
29+
VSRSelectedCategory = "x-vsr-selected-category"
30+
31+
// VSRSelectedReasoning indicates whether reasoning mode was determined to be used.
32+
// Values: "on" (reasoning enabled) or "off" (reasoning disabled)
33+
VSRSelectedReasoning = "x-vsr-selected-reasoning"
34+
35+
// VSRSelectedModel indicates the model selected by VSR for processing the request.
36+
// Example values: "deepseek-v31", "phi4", "gpt-4"
37+
VSRSelectedModel = "x-vsr-selected-model"
38+
39+
// VSRInjectedSystemPrompt indicates whether a system prompt was injected into the request.
40+
// Values: "true" or "false"
41+
VSRInjectedSystemPrompt = "x-vsr-injected-system-prompt"
42+
43+
// VSRCacheHit indicates that the response was served from cache.
44+
// Value: "true"
45+
VSRCacheHit = "x-vsr-cache-hit"
46+
)
47+
48+
// Security Headers
49+
// These headers are added to responses when security policies are violated
50+
// or security checks detect potential threats.
51+
const (
52+
// VSRPIIViolation indicates that the request was blocked due to PII policy violation.
53+
// Value: "true"
54+
VSRPIIViolation = "x-vsr-pii-violation"
55+
56+
// VSRJailbreakBlocked indicates that a jailbreak attempt was detected and blocked.
57+
// Value: "true"
58+
VSRJailbreakBlocked = "x-vsr-jailbreak-blocked"
59+
60+
// VSRJailbreakType specifies the type of jailbreak attempt that was detected.
61+
// Example values depend on the jailbreak detection classifier.
62+
VSRJailbreakType = "x-vsr-jailbreak-type"
63+
64+
// VSRJailbreakConfidence indicates the confidence level of the jailbreak detection.
65+
// Value: floating point number formatted as string (e.g., "0.950")
66+
VSRJailbreakConfidence = "x-vsr-jailbreak-confidence"
67+
)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package headers
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestHeaderConstants(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
header string
11+
expected string
12+
}{
13+
// Request headers
14+
{"RequestID", RequestID, "x-request-id"},
15+
{"GatewayDestinationEndpoint", GatewayDestinationEndpoint, "x-gateway-destination-endpoint"},
16+
{"SelectedModel", SelectedModel, "x-selected-model"},
17+
// VSR headers
18+
{"VSRSelectedCategory", VSRSelectedCategory, "x-vsr-selected-category"},
19+
{"VSRSelectedReasoning", VSRSelectedReasoning, "x-vsr-selected-reasoning"},
20+
{"VSRSelectedModel", VSRSelectedModel, "x-vsr-selected-model"},
21+
{"VSRInjectedSystemPrompt", VSRInjectedSystemPrompt, "x-vsr-injected-system-prompt"},
22+
{"VSRCacheHit", VSRCacheHit, "x-vsr-cache-hit"},
23+
// Security headers
24+
{"VSRPIIViolation", VSRPIIViolation, "x-vsr-pii-violation"},
25+
{"VSRJailbreakBlocked", VSRJailbreakBlocked, "x-vsr-jailbreak-blocked"},
26+
{"VSRJailbreakType", VSRJailbreakType, "x-vsr-jailbreak-type"},
27+
{"VSRJailbreakConfidence", VSRJailbreakConfidence, "x-vsr-jailbreak-confidence"},
28+
}
29+
30+
for _, tt := range tests {
31+
t.Run(tt.name, func(t *testing.T) {
32+
if tt.header != tt.expected {
33+
t.Errorf("Expected %s to be %q, got %q", tt.name, tt.expected, tt.header)
34+
}
35+
})
36+
}
37+
}

src/semantic-router/pkg/utils/http/response.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
ext_proc "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
1010
typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
1111
"github.com/openai/openai-go"
12+
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/headers"
1213
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/metrics"
1314
"github.com/vllm-project/semantic-router/src/semantic-router/pkg/observability"
1415
)
@@ -101,7 +102,7 @@ func CreatePIIViolationResponse(model string, deniedPII []string, isStreaming bo
101102
},
102103
{
103104
Header: &core.HeaderValue{
104-
Key: "x-pii-violation",
105+
Key: headers.VSRPIIViolation,
105106
RawValue: []byte("true"),
106107
},
107108
},
@@ -202,19 +203,19 @@ func CreateJailbreakViolationResponse(jailbreakType string, confidence float32,
202203
},
203204
{
204205
Header: &core.HeaderValue{
205-
Key: "x-jailbreak-blocked",
206+
Key: headers.VSRJailbreakBlocked,
206207
RawValue: []byte("true"),
207208
},
208209
},
209210
{
210211
Header: &core.HeaderValue{
211-
Key: "x-jailbreak-type",
212+
Key: headers.VSRJailbreakType,
212213
RawValue: []byte(jailbreakType),
213214
},
214215
},
215216
{
216217
Header: &core.HeaderValue{
217-
Key: "x-jailbreak-confidence",
218+
Key: headers.VSRJailbreakConfidence,
218219
RawValue: []byte(fmt.Sprintf("%.3f", confidence)),
219220
},
220221
},
@@ -246,7 +247,7 @@ func CreateCacheHitResponse(cachedResponse []byte) *ext_proc.ProcessingResponse
246247
},
247248
{
248249
Header: &core.HeaderValue{
249-
Key: "x-vsr-cache-hit",
250+
Key: headers.VSRCacheHit,
250251
RawValue: []byte("true"),
251252
},
252253
},

0 commit comments

Comments
 (0)