Repository: envoyproxy/ai-gateway
Pull request #351 (2025-02-17 05:26:10)

site: fix security header to allow google analytics
**Commit Message**

Fix the headers set via Netlify so the google analytics script functions to capture analytics.



Patch:
diff --git a/examples/basic/localmodel.yaml b/examples/basic/localmodel.yaml
new file mode 100644
index 0000000..ea2d2b6
--- /dev/null
+++ b/examples/basic/localmodel.yaml
@@ -0,0 +1,59 @@
+# Copyright Envoy AI Gateway Authors
+# SPDX-License-Identifier: Apache-2.0
+# The full text of the Apache license is available in the LICENSE file at
+# the root of the repo.
+
+# localmodel.yaml
+
+---
+apiVersion: aigateway.envoyproxy.io/v1alpha1
+kind: AIGatewayRoute
+metadata:
+  # A unique name for this route
+  name: envoy-ai-gateway-deepseek-route
+  namespace: default
+spec:
+  schema:
+    # Could be 'OpenAI' if you want the same style of prompts,
+    # or you can rename if your local model uses a different schema.
+    name: OpenAI
+  targetRefs:
+    # This references the Gateway already created by 'basic.yaml'.
+    - name: envoy-ai-gateway-basic
+      kind: Gateway
+      group: gateway.networking.k8s.io
+  rules:
+    - matches:
+        - headers:
+            - type: Exact
+              name: x-ai-eg-model
+              value: deepseek-r1:7b
+      backendRefs:
+        - name: envoy-ai-gateway-basic-deepseek
+
+---
+apiVersion: aigateway.envoyproxy.io/v1alpha1
+kind: AIServiceBackend
+metadata:
+  name: envoy-ai-gateway-basic-deepseek
+  namespace: default
+spec:
+  # Keep the same 'schema' name as your route. Typically 'OpenAI'.
+  schema:
+    name: OpenAI
+  backendRef:
+    name: envoy-ai-gateway-basic-deepseek
+    kind: Backend
+    group: gateway.envoyproxy.io
+
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: Backend
+metadata:
+  name: envoy-ai-gateway-basic-deepseek
+  namespace: default
+spec:
+  endpoints:
+    - fqdn:
+        hostname: host.docker.internal
+        port: 11434
diff --git a/internal/extproc/chatcompletion_processor.go b/internal/extproc/chatcompletion_processor.go
index d7e9da2..d5ca921 100644
--- a/internal/extproc/chatcompletion_processor.go
+++ b/internal/extproc/chatcompletion_processor.go
@@ -22,8 +22,8 @@ import (
 	"github.com/envoyproxy/ai-gateway/internal/llmcostcel"
 )
 
-// NewChatCompletionProcessor implements [ProcessorIface] for the /chat/completions endpoint.
-func NewChatCompletionProcessor(config *processorConfig, requestHeaders map[string]string, logger *slog.Logger) ProcessorIface {
+// NewChatCompletionProcessor implements [Processor] for the /chat/completions endpoint.
+func NewChatCompletionProcessor(config *processorConfig, requestHeaders map[string]string, logger *slog.Logger) Processor {
 	return &chatCompletionProcessor{
 		config:         config,
 		requestHeaders: requestHeaders,
@@ -60,7 +60,7 @@ func (c *chatCompletionProcessor) selectTranslator(out filterapi.VersionedAPISch
 	return nil
 }
 
-// ProcessRequestHeaders implements [ProcessorIface.ProcessRequestHeaders].
+// ProcessRequestHeaders implements [Processor.ProcessRequestHeaders].
 func (c *chatCompletionProcessor) ProcessRequestHeaders(_ context.Context, _ *corev3.HeaderMap) (res *extprocv3.ProcessingResponse, err error) {
 	// The request headers have already been at the time the processor was created
 	return &extprocv3.ProcessingResponse{Response: &extprocv3.ProcessingResponse_RequestHeaders{
@@ -68,7 +68,7 @@ func (c *chatCompletionProcessor) ProcessRequestHeaders(_ context.Context, _ *co
 	}}, nil
 }
 
-// ProcessRequestBody implements [ProcessorIface.ProcessRequestBody].
+// ProcessRequestBody implements [Processor.ProcessRequestBody].
 func (c *chatCompletionProcessor) ProcessRequestBody(ctx context.Context, rawBody *extprocv3.HttpBody) (res *extprocv3.ProcessingResponse, err error) {
 	path := c.requestHeaders[":path"]
 	model, body, err := c.config.bodyParser(path, rawBody)
@@ -124,7 +124,7 @@ func (c *chatCompletionProcessor) ProcessRequestBody(ctx context.Context, rawBod
 	return resp, nil
 }
 
-// ProcessResponseHeaders implements [ProcessorIface.ProcessResponseHeaders].
+// ProcessResponseHeaders implements [Processor.ProcessResponseHeaders].
 func (c *chatCompletionProcessor) ProcessResponseHeaders(_ context.Context, headers *corev3.HeaderMap) (res *extprocv3.ProcessingResponse, err error) {
 	c.responseHeaders = headersToMap(headers)
 	if enc := c.responseHeaders["content-encoding"]; enc != "" {
@@ -148,7 +148,7 @@ func (c *chatCompletionProcessor) ProcessResponseHeaders(_ context.Context, head
 	}}, nil
 }
 
-// ProcessResponseBody implements [ProcessorIface.ProcessResponseBody].
+// ProcessResponseBody implements [Processor.ProcessResponseBody].
 func (c *chatCompletionProcessor) ProcessResponseBody(_ context.Context, body *extprocv3.HttpBody) (res *extprocv3.ProcessingResponse, err error) {
 	var br io.Reader
 	switch c.responseEncoding {
diff --git a/internal/extproc/models_processor.go b/internal/extproc/models_processor.go
index b7f9e9d..b799fb7 100644
--- a/internal/extproc/models_processor.go
+++ b/internal/extproc/models_processor.go
@@ -21,20 +21,20 @@ import (
 	"github.com/envoyproxy/ai-gateway/internal/apischema/openai"
 )
 
-// modelsProcessor implements [ProcessorIface] for the `/v1/models` endpoint.
+// modelsProcessor implements [Processor] for the `/v1/models` endpoint.
 // This processor returns an immediate response with the list of models that are declared in the filter
 // configuration.
 // Since it returns an immediate response after processing the headers, the rest of the methods of the
-// ProcessorIface are not implemented. Those should never be called.
+// Processor are not implemented. Those should never be called.
 type modelsProcessor struct {
 	logger *slog.Logger
 	models openai.ModelList
 }
 
-var _ ProcessorIface = (*modelsProcessor)(nil)
+var _ Processor = (*modelsProcessor)(nil)
 
 // NewModelsProcessor creates a new processor that returns the list of declared models
-func NewModelsProcessor(config *processorConfig, _ map[string]string, logger *slog.Logger) ProcessorIface {
+func NewModelsProcessor(config *processorConfig, _ map[string]string, logger *slog.Logger) Processor {
 	models := openai.ModelList{
 		Object: "list",
 		Data:   make([]openai.Model, 0, len(config.declaredModels)),
@@ -50,7 +50,7 @@ func NewModelsProcessor(config *processorConfig, _ map[string]string, logger *sl
 	return &modelsProcessor{logger: logger, models: models}
 }
 
-// ProcessRequestHeaders implements [ProcessorIface.ProcessRequestHeaders].
+// ProcessRequestHeaders implements [Processor.ProcessRequestHeaders].
 func (m *modelsProcessor) ProcessRequestHeaders(_ context.Context, _ *corev3.HeaderMap) (*extprocv3.ProcessingResponse, error) {
 	m.logger.Info("Serving list of declared models")
 
@@ -77,17 +77,17 @@ func (m *modelsProcessor) ProcessRequestHeaders(_ context.Context, _ *corev3.Hea
 
 var errUnexpectedCall = errors.New("unexpected method call")
 
-// ProcessRequestBody implements [ProcessorIface.ProcessRequestBody].
+// ProcessRequestBody implements [Processor.ProcessRequestBody].
 func (m *modelsProcessor) ProcessRequestBody(context.Context, *extprocv3.HttpBody) (*extprocv3.ProcessingResponse, error) {
 	return nil, fmt.Errorf("%w: ProcessRequestBody", errUnexpectedCall)
 }
 
-// ProcessResponseHeaders implements [ProcessorIface.ProcessResponseHeaders].
+// ProcessResponseHeaders implements [Processor.ProcessResponseHeaders].
 func (m *modelsProcessor) ProcessResponseHeaders(context.Context, *corev3.HeaderMap) (*extprocv3.ProcessingResponse, error) {
 	return nil, fmt.Errorf("%w: ProcessResponseHeaders", errUnexpectedCall)
 }
 
-// ProcessResponseBody implements [ProcessorIface.ProcessResponseBody].
+// ProcessResponseBody implements [Processor.ProcessResponseBody].
 func (m *modelsProcessor) ProcessResponseBody(context.Context, *extprocv3.HttpBody) (*extprocv3.ProcessingResponse, error) {
 	return nil, fmt.Errorf("%w: ProcessResponseBody", errUnexpectedCall)
 }
diff --git a/internal/extproc/processor.go b/internal/extproc/processor.go
index 22ea145..b36360d 100644
--- a/internal/extproc/processor.go
+++ b/internal/extproc/processor.go
@@ -39,11 +39,11 @@ type processorConfigRequestCost struct {
 }
 
 // ProcessorFactory is the factory function used to create new instances of a processor.
-type ProcessorFactory func(*processorConfig, map[string]string, *slog.Logger) ProcessorIface
+type ProcessorFactory func(*processorConfig, map[string]string, *slog.Logger) Processor
 
-// ProcessorIface is the interface for the processor.
+// Processor is the interface for the processor.
 // This decouples the processor implementation detail from the server implementation.
-type ProcessorIface interface {
+type Processor interface {
 	// ProcessRequestHeaders processes the request headers message.
 	ProcessRequestHeaders(context.Context, *corev3.HeaderMap) (*extprocv3.ProcessingResponse, error)
 	// ProcessRequestBody processes the request body message.
diff --git a/internal/extproc/server.go b/internal/extproc/server.go
index 872bbd5..874c4d5 100644
--- a/internal/extproc/server.go
+++ b/internal/extproc/server.go
@@ -124,7 +124,7 @@ func (s *Server) Register(path string, newProcessor ProcessorFactory) {
 
 // processorForPath returns the processor for the given path.
 // Only exact path matching is supported currently
-func (s *Server) processorForPath(requestHeaders map[string]string) (ProcessorIface, error) {
+func (s *Server) processorForPath(requestHeaders map[string]string) (Processor, error) {
 	path := requestHeaders[":path"]
 	newProcessor, ok := s.processors[path]
 	if !ok {
@@ -140,7 +140,7 @@ func (s *Server) Process(stream extprocv3.ExternalProcessor_ProcessServer) error
 
 	// The processor will be instantiated when the first message containing the request headers is received.
 	// The :path header is used to determine the processor to use, based on the registered ones.
-	var p ProcessorIface
+	var p Processor
 
 	for {
 		select {
@@ -182,7 +182,7 @@ func (s *Server) Process(stream extprocv3.ExternalProcessor_ProcessServer) error
 	}
 }
 
-func (s *Server) processMsg(ctx context.Context, p ProcessorIface, req *extprocv3.ProcessingRequest) (*extprocv3.ProcessingResponse, error) {
+func (s *Server) processMsg(ctx context.Context, p Processor, req *extprocv3.ProcessingRequest) (*extprocv3.ProcessingResponse, error) {
 	switch value := req.Request.(type) {
 	case *extprocv3.ProcessingRequest_RequestHeaders:
 		requestHdrs := req.GetRequestHeaders().Headers
diff --git a/netlify.toml b/netlify.toml
index 5c88b32..a1196c7 100644
--- a/netlify.toml
+++ b/netlify.toml
@@ -23,7 +23,7 @@ command = "npm run build"
     Strict-Transport-Security = "max-age=31536000; includeSubDomains"
     Content-Security-Policy = """
       default-src 'self';
-      script-src 'self' 'unsafe-inline' 'unsafe-eval';
+      script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com;
       style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
       img-src 'self' data: https: blob:;
       font-src 'self' data: https://fonts.gstatic.com;
diff --git a/site/docs/getting-started/connect-providers/localmodel.md b/site/docs/getting-started/connect-providers/localmodel.md
new file mode 100644
index 0000000..5bed36a
--- /dev/null
+++ b/site/docs/getting-started/connect-providers/localmodel.md
@@ -0,0 +1,106 @@
+---
+id: localmodel
+title: Connect Local Model
+sidebar_position: 3
+---
+
+# Connect Local Model
+
+This guide will help you configure Envoy AI Gateway to work with locally hosted model such as [DeepSeek R1](https://github.com/deepseek-ai/DeepSeek-R1).
+
+## Prerequisites
+
+Before you begin, you'll need:
+
+- [Ollama](https://ollama.com/) installed on local machine considering for self-hosted model
+- Serve DeepSeek R1 or similar model on your local machine
+
+```
+ollama pull deepseek-r1:7b
+OLLAMA_HOST=0.0.0.0 ollama serve
+```
+
+- Basic setup completed from the [Basic Usage](../basic-usage.md) guide
+- Basic configuration removed as described in the [Advanced Configuration](./index.md) overview
+
+## Configuration Steps
+
+:::info Ready to proceed?
+Ensure you have followed the steps in [Connect Providers](../connect-providers/)
+:::
+
+### Apply Configuration
+
+Apply the updated configuration and wait for the Gateway pod to be ready. If you already have a Gateway running,
+then the secret credential update will be picked up automatically in a few seconds.
+
+```shell
+kubectl apply -f localmodel.yaml
+
+kubectl wait pods --timeout=2m \
+  -l gateway.envoyproxy.io/owning-gateway-name=envoy-ai-gateway-basic \
+  -n envoy-gateway-system \
+  --for=condition=Ready
+```
+
+### Test the Configuration
+
+You should have set `$GATEWAY_URL` as part of the basic setup before connecting to providers.
+See the [Basic Usage](../basic-usage.md) page for instructions.
+
+```shell
+curl --fail \
+  -H "Content-Type: application/json" \
+  -d '{
+    "model": "deepseek-r1:7b",
+    "messages": [
+      {
+        "role": "user",
+        "content": "Hi."
+      }
+    ]
+  }' \
+  $GATEWAY_URL/v1/chat/completions
+```
+
+## Troubleshooting
+
+If you encounter issues:
+
+1. Verify that Ollama has loaded the model and you have memory and computer available on your local machine to run LLM
+
+2. Check pod status:
+
+   ```shell
+   kubectl get pods -n envoy-gateway-system
+   ```
+
+3. View controller logs:
+
+   ```shell
+   kubectl logs -n envoy-ai-gateway-system deployment/ai-gateway-controller
+   ```
+
+4. View External Process Logs
+
+   ```shell
+   kubectl logs services/ai-eg-route-extproc-envoy-ai-gateway-basic
+   ```
+5. If you are running Kubernetes locally, ensure that Envoy Service URL is correct and port 8080 is available on your local
+
+```
+export ENVOY_SERVICE=$(kubectl get svc -n envoy-gateway-system \
+    --selector=gateway.envoyproxy.io/owning-gateway-namespace=default,gateway.envoyproxy.io/owning-gateway-name=envoy-ai-gateway-basic \
+    -o jsonpath='{.items[0].metadata.name}')
+kubectl port-forward -n envoy-gateway-system svc/$ENVOY_SERVICE 8080:80
+```
+
+6. Common errors:
+   - 500: Incorrect Model Id in the HTTP request. Check localmodel.yaml for host name in backend configuration
+   - 503: Model is unavailable or request time out because of latency by model
+
+## Next Steps
+
+After configuring DeepSeek:
+
+- [Connect AWS Bedrock](./aws-bedrock.md) to add another provider


Test patch:
diff --git a/internal/extproc/mocks_test.go b/internal/extproc/mocks_test.go
index 513f134..0e064c6 100644
--- a/internal/extproc/mocks_test.go
+++ b/internal/extproc/mocks_test.go
@@ -24,12 +24,12 @@ import (
 )
 
 var (
-	_ ProcessorIface        = &mockProcessor{}
+	_ Processor             = &mockProcessor{}
 	_ translator.Translator = &mockTranslator{}
 	_ x.Router              = &mockRouter{}
 )
 
-func newMockProcessor(_ *processorConfig, _ *slog.Logger) ProcessorIface {
+func newMockProcessor(_ *processorConfig, _ *slog.Logger) Processor {
 	return &mockProcessor{}
 }
 
@@ -42,25 +42,25 @@ type mockProcessor struct {
 	retErr                error
 }
 
-// ProcessRequestHeaders implements [ProcessorIface.ProcessRequestHeaders].
+// ProcessRequestHeaders implements [Processor.ProcessRequestHeaders].
 func (m mockProcessor) ProcessRequestHeaders(_ context.Context, headerMap *corev3.HeaderMap) (*extprocv3.ProcessingResponse, error) {
 	require.Equal(m.t, m.expHeaderMap, headerMap)
 	return m.retProcessingResponse, m.retErr
 }
 
-// ProcessRequestBody implements [ProcessorIface.ProcessRequestBody].
+// ProcessRequestBody implements [Processor.ProcessRequestBody].
 func (m mockProcessor) ProcessRequestBody(_ context.Context, body *extprocv3.HttpBody) (*extprocv3.ProcessingResponse, error) {
 	require.Equal(m.t, m.expBody, body)
 	return m.retProcessingResponse, m.retErr
 }
 
-// ProcessResponseHeaders implements [ProcessorIface.ProcessResponseHeaders].
+// ProcessResponseHeaders implements [Processor.ProcessResponseHeaders].
 func (m mockProcessor) ProcessResponseHeaders(_ context.Context, headerMap *corev3.HeaderMap) (*extprocv3.ProcessingResponse, error) {
 	require.Equal(m.t, m.expHeaderMap, headerMap)
 	return m.retProcessingResponse, m.retErr
 }
 
-// ProcessResponseBody implements [ProcessorIface.ProcessResponseBody].
+// ProcessResponseBody implements [Processor.ProcessResponseBody].
 func (m mockProcessor) ProcessResponseBody(_ context.Context, body *extprocv3.HttpBody) (*extprocv3.ProcessingResponse, error) {
 	require.Equal(m.t, m.expBody, body)
 	return m.retProcessingResponse, m.retErr
diff --git a/internal/extproc/server_test.go b/internal/extproc/server_test.go
index dd46396..198ac1e 100644
--- a/internal/extproc/server_test.go
+++ b/internal/extproc/server_test.go
@@ -31,7 +31,7 @@ func requireNewServerWithMockProcessor(t *testing.T) (*Server, *mockProcessor) {
 	s.config = &processorConfig{}
 
 	m := newMockProcessor(s.config, s.logger)
-	s.Register("/", func(*processorConfig, map[string]string, *slog.Logger) ProcessorIface { return m })
+	s.Register("/", func(*processorConfig, map[string]string, *slog.Logger) Processor { return m })
 
 	return s, m.(*mockProcessor)
 }
@@ -266,11 +266,11 @@ func TestServer_ProcessorSelection(t *testing.T) {
 	require.NotNil(t, s)
 
 	s.config = &processorConfig{}
-	s.Register("/one", func(*processorConfig, map[string]string, *slog.Logger) ProcessorIface {
+	s.Register("/one", func(*processorConfig, map[string]string, *slog.Logger) Processor {
 		// Returning nil guarantees that the test will fail if this processor is selected
 		return nil
 	})
-	s.Register("/two", func(*processorConfig, map[string]string, *slog.Logger) ProcessorIface {
+	s.Register("/two", func(*processorConfig, map[string]string, *slog.Logger) Processor {
 		return &mockProcessor{
 			t:                     t,
 			expHeaderMap:          &corev3.HeaderMap{Headers: []*corev3.HeaderValue{{Key: ":path", Value: "/two"}}},