From dbd116ccbd6c406060e186d82ee8e5cb159f78d3 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 29 Jan 2025 12:23:50 -0800 Subject: [PATCH 01/13] ci: deletes doctests (#222) **Commit Message**: This deletes the doc test as it turns out that simply using a markdown parser seems difficult to makintain. As discussed offline, we decided to remove it for now. If any necessity comes up in the future, we revisit this and reevaluate what is the best way to ensure the correctness of the documentations with code blocks. Signed-off-by: Takeshi Yoneda --- .github/workflows/doctest.yaml | 52 --------- Makefile | 13 +-- go.mod | 1 - go.sum | 2 - tests/doctest/doctest.go | 150 -------------------------- tests/doctest/getting_started_test.go | 102 ------------------ 6 files changed, 2 insertions(+), 318 deletions(-) delete mode 100644 .github/workflows/doctest.yaml delete mode 100644 tests/doctest/doctest.go delete mode 100644 tests/doctest/getting_started_test.go diff --git a/.github/workflows/doctest.yaml b/.github/workflows/doctest.yaml deleted file mode 100644 index caf4fea8f..000000000 --- a/.github/workflows/doctest.yaml +++ /dev/null @@ -1,52 +0,0 @@ -name: Doc Test -on: - pull_request: - branches: - - main - push: - branches: - - main - # If the PR is coming from a fork, they are not allowed to access secrets by default. - # This even is triggered only if the PR gets labeled with 'safe to test' which can only be added by the maintainers. - # Jobs do not use secrets in the workflow will ignore this event. - pull_request_target: - types: [labeled] - branches: - - main - -concurrency: - # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-using-concurrency-to-cancel-any-in-progress-job-or-run - group: ${{ github.ref }}-${{ github.workflow }}-${{ github.actor }}-${{ github.event_name }} - cancel-in-progress: true - -jobs: - test_e2e: - if: (github.event_name != 'pull_request_target' || contains(github.event.pull_request.labels.*.name, 'safe to test')) - name: Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - if: github.event_name != 'pull_request_target' - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - if: github.event_name == 'pull_request_target' - - uses: actions/setup-go@v5 - with: - cache: false - go-version-file: go.mod - - uses: actions/cache@v4 - with: - path: | - ~/.cache/go-build - ~/.cache/golangci-lint - ~/go/pkg/mod - ~/go/bin - key: doctest-test-${{ hashFiles('**/go.mod', '**/go.sum', '**/Makefile') }} - - name: Run doctest tests - env: - TEST_AWS_ACCESS_KEY_ID: ${{ secrets.AWS_BEDROCK_USER_AWS_ACCESS_KEY_ID }} - TEST_AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_BEDROCK_USER_AWS_SECRET_ACCESS_KEY }} - TEST_OPENAI_API_KEY: ${{ secrets.ENVOY_AI_GATEWAY_OPENAI_API_KEY }} - run: make test-doctest diff --git a/Makefile b/Makefile index 9f49d66d6..843253537 100644 --- a/Makefile +++ b/Makefile @@ -34,13 +34,12 @@ help: @echo " test Run the unit tests for the codebase." @echo " test-cel Run the integration tests of CEL validation rules in API definitions with envtest." @echo " This will be needed when changing API definitions." - @echo " test-doctest Run the integration tests for documentation site." @echo " test-extproc Run the integration tests for extproc without controller or k8s at all." @echo " test-controller Run the integration tests for the controller with envtest." @echo " test-e2e Run the end-to-end tests with a local kind cluster." @echo "" @echo "For example, 'make precommit test' should be enough for initial iterations, and later 'make test-cel' etc. for the normal development cycle." - @echo "Note that some cases run by test-e2e, test-extproc, and test-doctest use credentials and these will be skipped when not available." + @echo "Note that some cases run by test-e2e or test-extproc use credentials and these will be skipped when not available." @echo "" @echo "" @@ -48,7 +47,7 @@ help: .PHONY: lint lint: golangci-lint @echo "lint => ./..." - @$(GOLANGCI_LINT) run --build-tags==test_cel_validation,test_controller,test_extproc,test_doctest ./... + @$(GOLANGCI_LINT) run --build-tags==test_cel_validation,test_controller,test_extproc ./... .PHONY: codespell CODESPELL_SKIP := $(shell cat .codespell.skip | tr \\n ',') @@ -163,14 +162,6 @@ test-e2e: kind @echo "Run E2E tests" @go test ./tests/e2e/... $(GO_TEST_ARGS) $(GO_TEST_E2E_ARGS) -tags test_e2e -# This runs the integration tests for the documentation site. -# -# The dependencies for this target depends on the test case being run. -.PHONY: test-doctest -test-doctest: kind - @echo "Run doctest" - @go test ./tests/doctest/... $(GO_TEST_ARGS) $(GO_TEST_E2E_ARGS) -tags test_doctest - # This builds a binary for the given command under the internal/cmd directory. # # Example: diff --git a/go.mod b/go.mod index 0bbcaa728..29b9e195a 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/google/go-cmp v0.6.0 github.com/openai/openai-go v0.1.0-alpha.49 github.com/stretchr/testify v1.10.0 - github.com/yuin/goldmark v1.7.8 golang.org/x/exp v0.0.0-20250128144449-3edf0e91c1ae google.golang.org/grpc v1.70.0 google.golang.org/protobuf v1.36.4 diff --git a/go.sum b/go.sum index 742b703bd..63017d0ae 100644 --- a/go.sum +++ b/go.sum @@ -163,8 +163,6 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= -github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= diff --git a/tests/doctest/doctest.go b/tests/doctest/doctest.go deleted file mode 100644 index 3738d8a84..000000000 --- a/tests/doctest/doctest.go +++ /dev/null @@ -1,150 +0,0 @@ -//go:build test_doctest - -package doctest - -import ( - "bytes" - "context" - "fmt" - "os" - "os/exec" - "testing" - "time" - - "github.com/stretchr/testify/require" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/ast" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/text" -) - -// codeBlock represents a single code block in a markdown file. -type codeBlock struct { - // lines is each line of the code block. - lines []string -} - -// String implements the fmt.Stringer interface for debugging. -func (c codeBlock) String() string { - var str string - for i, line := range c.lines { - str += fmt.Sprintf("%d: %s", i, line) - } - return str -} - -// requireRunAllLines runs all lines in a code block, skipping empty/commented lines. -func (c codeBlock) requireRunAllLines(t *testing.T) { - for _, line := range c.lines { - requireRunBashCommand(t, line) - } -} - -// requireExtractCodeBlocks extracts all code blocks from a markdown file. -// -// This skips all lines starting with "#", which are comments. -func requireExtractCodeBlocks(t *testing.T, path string) []codeBlock { - source, err := os.ReadFile(path) - require.NoError(t, err) - - md := goldmark.New(goldmark.WithParserOptions(parser.WithAutoHeadingID())) - doc := md.Parser().Parse(text.NewReader(source)) - - var codeBlocks []codeBlock - err = ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) { - if entering { - if rawBlock, ok := n.(*ast.FencedCodeBlock); ok { - var blk codeBlock - for i := 0; i < rawBlock.Lines().Len(); i++ { - line := rawBlock.Lines().At(i) - if len(line.Value(source)) == 0 || line.Value(source)[0] == '#' { - continue - } - blk.lines = append(blk.lines, string(line.Value(source))) - } - codeBlocks = append(codeBlocks, blk) - } - } - return ast.WalkContinue, nil - }) - require.NoError(t, err) - return codeBlocks -} - -// requireExecutableInPath checks if the executables are in the PATH. -func requireExecutableInPath(t *testing.T, executables ...string) { - // Always require "bash" to run the code blocks. - _, err := exec.LookPath("bash") - require.NoError(t, err, "bash not found in PATH") - for _, executable := range executables { - _, err := exec.LookPath(executable) - require.NoError(t, err, "executable %s not found in PATH", executable) - } -} - -// requireRunBashCommand runs a bash command. This is used to run the code blocks in the markdown file. -func requireRunBashCommand(t *testing.T, command string) { - fmt.Printf("\u001b[32m=== Running command: %s\u001B[0m\n", command) - cmd := newBashCommand(command) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - require.NoError(t, cmd.Run()) -} - -func requireRunBashCommandEventually(t *testing.T, command string, timeout, interval time.Duration) { - fmt.Printf("\u001b[32m=== Running command eventually (timeout=%s,interval=%s): %s\u001B[0m\n", - timeout, interval, command) - require.Eventually(t, func() bool { - cmd := newBashCommand(command) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() == nil - }, timeout, interval) -} - -func requireStartBackgroundBashCommand(t *testing.T, command string) (kill func()) { - fmt.Printf("\u001b[32m=== Starting background command: %s\u001B[0m\n", command) - cmd := newBashCommand(command) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - require.NoError(t, cmd.Start()) - return func() { _ = cmd.Process.Signal(os.Interrupt) } -} - -func runBashCommandAndIgnoreError(command string) { - cmd := newBashCommand(command) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - _ = cmd.Run() -} - -func newBashCommand(command string) *exec.Cmd { - return exec.Command("bash", "-c", command) -} - -// requireNewKindCluster creates a new kind cluster if it does not already exist. -func requireNewKindCluster(t *testing.T, clusterName string) { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) - defer cancel() - const kindPath = "../../.bin/kind" // This is automatically installed as a dependency in the Makefile. - - cmd := exec.CommandContext(ctx, kindPath, "create", "cluster", "--name", clusterName) - out, err := cmd.CombinedOutput() - if err != nil && !bytes.Contains(out, []byte("already exist")) { - require.NoError(t, err, "error creating kind cluster") - } - - cmd = exec.CommandContext(ctx, kindPath, "export", "kubeconfig", "--name", clusterName) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - require.NoError(t, cmd.Run()) -} - -// getEnvVarOrSkip requires an environment variable to be set. -func getEnvVarOrSkip(t *testing.T, envVar string) string { - value := os.Getenv(envVar) - if value == "" { - t.Skipf("Environment variable %s is not set", envVar) - } - return value -} diff --git a/tests/doctest/getting_started_test.go b/tests/doctest/getting_started_test.go deleted file mode 100644 index 4eb71b1de..000000000 --- a/tests/doctest/getting_started_test.go +++ /dev/null @@ -1,102 +0,0 @@ -//go:build test_doctest - -package doctest - -import ( - "os" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -// TestGettingStarted tests the code blocks of docs/getting_started.md file. -func TestGettingStarted(t *testing.T) { - t.Skip("TODO") - - requireNewKindCluster(t, "envoy-ai-gateway-getting-started") - requireExecutableInPath(t, "curl", "helm", "kubectl") - - path := "../../site/docs/getting_started.md" - codeBlocks := requireExtractCodeBlocks(t, path) - - for _, block := range codeBlocks { - t.Log(block) - } - - t.Run("EG Install", func(t *testing.T) { - egInstallBlock := codeBlocks[0] - require.Len(t, egInstallBlock.lines, 2) - egInstallBlock.requireRunAllLines(t) - }) - - t.Run("AI Gateway install", func(t *testing.T) { - aiGatewayBlock := codeBlocks[1] - require.Len(t, aiGatewayBlock.lines, 3) - aiGatewayBlock.requireRunAllLines(t) - }) - - t.Run("AI Gateway EG config", func(t *testing.T) { - aiGatewayEGConfigBlock := codeBlocks[2] - require.Len(t, aiGatewayEGConfigBlock.lines, 4) - aiGatewayEGConfigBlock.requireRunAllLines(t) - }) - - t.Run("Deploy Basic Gateway", func(t *testing.T) { - deployGatewayBlock := codeBlocks[3] - require.Len(t, deployGatewayBlock.lines, 2) - requireRunBashCommand(t, deployGatewayBlock.lines[0]) - // Gateway deployment may take a while to be ready (managed by the EG operator). - requireRunBashCommandEventually(t, deployGatewayBlock.lines[1], time.Minute, 2*time.Second) - }) - - t.Run("Make a request", func(t *testing.T) { - makeRequestBlock := codeBlocks[4] - require.Len(t, makeRequestBlock.lines, 2) - // Run the port-forward command in the background. - kill := requireStartBackgroundBashCommand(t, makeRequestBlock.lines[0]) - defer kill() - // Then make the request. - requireRunBashCommandEventually(t, makeRequestBlock.lines[1], time.Minute, 2*time.Second) - }) - - // The next code block is just the example output of the previous code block. - _ = codeBlocks[5] - - t.Run("Delete Gateway", func(t *testing.T) { - deleteGatewayBlock := codeBlocks[6] - require.Len(t, deleteGatewayBlock.lines, 2) - requireRunBashCommand(t, deleteGatewayBlock.lines[0]) // Delete the Gateway. - runBashCommandAndIgnoreError(deleteGatewayBlock.lines[1]) // Wait for the Gateway to be deleted. - }) - - t.Run("OpenAI and AWS", func(t *testing.T) { - openAIAPIKey := getEnvVarOrSkip(t, "TEST_OPENAI_API_KEY") - awsAccessKeyID := getEnvVarOrSkip(t, "TEST_AWS_ACCESS_KEY_ID") - awsSecretAccessKey := getEnvVarOrSkip(t, "TEST_AWS_SECRET_ACCESS_KEY") - - tmpFile := t.TempDir() + "/openai-and-aws.yaml" - // Replace the placeholders with the actual values. - _f, err := os.ReadFile("../../examples/basic/basic.yaml") - require.NoError(t, err) - f := strings.ReplaceAll(string(_f), "OPENAI_API_KEY", openAIAPIKey) - f = strings.ReplaceAll(f, "AWS_ACCESS_KEY_ID", awsAccessKeyID) - f = strings.ReplaceAll(f, "AWS_SECRET_ACCESS_KEY", awsSecretAccessKey) - require.NoError(t, os.WriteFile(tmpFile, []byte(f), 0o600)) - - // Apply the configuration. - requireRunBashCommand(t, "kubectl apply -f "+tmpFile) - - openAIAndAWSBlock := codeBlocks[7] - require.Len(t, openAIAndAWSBlock.lines, 4) - // Wait for the gateway to be ready. - requireRunBashCommandEventually(t, openAIAndAWSBlock.lines[0], time.Minute, 2*time.Second) - // Run the port-forward command in the background. - kill := requireStartBackgroundBashCommand(t, openAIAndAWSBlock.lines[1]) - defer kill() - // Then make the request to OpenAI and AWS. - requireRunBashCommandEventually(t, openAIAndAWSBlock.lines[2], 30*time.Second, 2*time.Second) - requireRunBashCommandEventually(t, openAIAndAWSBlock.lines[3], 30*time.Second, 2*time.Second) - }) -} From 571df2fb1e37e4e7b97435597db28698bcb112e0 Mon Sep 17 00:00:00 2001 From: David Xia Date: Wed, 29 Jan 2025 15:50:00 -0500 Subject: [PATCH 02/13] docs: add helpful links to overview page (#221) **Commit Message**: Make some minor Markdown formatting changes for readability and linting including wrapping long lines and having one space between paragraphs and lists. Signed-off-by: David Xia --- site/docs/index.md | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/site/docs/index.md b/site/docs/index.md index b2af47764..5fe720ec8 100644 --- a/site/docs/index.md +++ b/site/docs/index.md @@ -6,20 +6,26 @@ sidebar_position: 1 # Envoy AI Gateway Overview -Welcome to the **Envoy AI Gateway** documentation! This open-source project, built on **Envoy Proxy**, aims to simplify how application clients interact with **Generative AI (GenAI)** services. It provides a secure, scalable, and efficient way to manage LLM/AI traffic, with backend rate limiting and policy control. +Welcome to the **Envoy AI Gateway** documentation! This open-source project, built on **Envoy +Proxy**, aims to simplify how application clients interact with **Generative AI (GenAI)** services. +It provides a secure, scalable, and efficient way to manage LLM/AI traffic, with backend rate +limiting and policy control. ## **Project Overview** -The **Envoy AI Gateway** was created to address the complexity of connecting applications to GenAI services by leveraging Envoy's flexibility and Kubernetes-native features. The project has evolved through contributions from the Envoy community, fostering a collaborative approach to solving real-world challenges. +The **Envoy AI Gateway** was created to address the complexity of connecting applications to GenAI +services by leveraging Envoy's flexibility and Kubernetes-native features. The project has evolved +through contributions from the Envoy community, fostering a collaborative approach to solving +real-world challenges. ### **Key Objectives** + - Provide a unified layer for routing and managing LLM/AI traffic. - Support automatic failover mechanisms to ensure service reliability. - Ensure end-to-end security, including upstream authorization for LLM/AI traffic. - Implement a policy framework to support usage limiting use cases. - Foster an open-source community to address GenAI-specific routing and quality of service needs. - ## **Release Goals** The initial release focuses on key foundational features to provide LLM/AI traffic management: @@ -27,29 +33,41 @@ The initial release focuses on key foundational features to provide LLM/AI traff - **Request Routing**: Directs API requests to appropriate GenAI services - **Authentication and Authorization**: Implement API key validation to secure communication. - **Backend Security Policy**: Introduces fine-grained access control for backend services. -This also controls LLM/AI backend usage using token-per-second (TPS) policies to prevent overuse. -- **Multi-Upstream Provider Support for LLM/AI Services**: The ability to receive requests in the format of one LLM provider and route them to different upstream providers, ensuring compatibility with their expected formats. This is made possible through built-in transformation capabilities that adapt requests and responses accordingly. -- **AWS Request Signing**: Supports external processing for secure communication with AWS-hosted LLM/AI services. - -Documentation for installation, setup, and contribution guidelines is included to help new users and contributors get started easily. + This also controls LLM/AI backend usage using token-per-second (TPS) policies to prevent overuse. +- **Multi-Upstream Provider Support for LLM/AI Services**: The ability to receive requests in the + format of one LLM provider and route them to different upstream providers, ensuring compatibility + with their expected formats. This is made possible through built-in transformation capabilities that + adapt requests and responses accordingly. +- **AWS Request Signing**: Supports external processing for secure communication with AWS-hosted + LLM/AI services. +Documentation for installation, setup, and contribution guidelines is included to help new users and +contributors get started easily. ## **Community Collaboration** -Weekly community meetings are held every Thursday to discuss updates, address issues, and review contributions. +[Weekly community meetings][meeting-notes] are held every Thursday to discuss updates, address +issues, and review contributions. ## **Architecture Overview** - ## **Get Involved** We welcome community contributions! Here's how you can participate: -- Attend the weekly community meetings to stay updated and share ideas. + +- Attend the [weekly community meetings][meeting-notes] to stay updated and share ideas. - Submit feature requests and pull requests via the GitHub repository. -- Join discussions in the #envoy-ai-gateway Slack channel. +- Join discussions in the [#envoy-ai-gateway] Slack channel. -Refer to the contribution guide in the GitHub repository for detailed instructions on setting up your environment and contributing. +Refer to [this contributing guide][contributing.md] for detailed instructions on setting up your +environment and contributing. --- -The **Envoy AI Gateway** addresses the growing demand for secure, scalable, and efficient AI/LLM traffic management. Your contributions and feedback are key to its success and to advancing the future of AI service integration. +The **Envoy AI Gateway** addresses the growing demand for secure, scalable, and efficient AI/LLM +traffic management. Your contributions and feedback are key to its success and to advancing the +future of AI service integration. + +[meeting-notes]: https://docs.google.com/document/d/10e1sfsF-3G3Du5nBHGmLjXw5GVMqqCvFDqp_O65B0_w +[#envoy-ai-gateway]: https://envoyproxy.slack.com/archives/C07Q4N24VAA +[contributing.md]: https://github.com/envoyproxy/ai-gateway/blob/main/CONTRIBUTING.md From 3f99932001f7e138c4b9ace4759e1ffce09c3471 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 29 Jan 2025 14:13:16 -0800 Subject: [PATCH 03/13] controller: annotate extproc pods with uuid for faster refresh (#224) **Commit Message**: Without triggering pods into the reconcile loop of k8s server, the config map updates will take a few minutes to be picked up and reflected on the actual file of the pod [^1]. This commit changes the config sink so that it will add the config uuid to the extproc pods annotations. [^1]: https://neonmirrors.net/post/2022-12/reducing-pod-volume-update-times/ **Related Issues/PRs (if applicable)**: Follow up on #219 --------- Signed-off-by: Takeshi Yoneda --- internal/controller/sink.go | 41 ++++++++++- internal/controller/sink_test.go | 37 ++++++++++ internal/extproc/processor.go | 1 + internal/extproc/server.go | 2 + internal/extproc/server_test.go | 1 + tests/e2e/basic_test.go | 115 ++++++++++++++++--------------- 6 files changed, 140 insertions(+), 57 deletions(-) diff --git a/internal/controller/sink.go b/internal/controller/sink.go index 1ef628df9..4389974b4 100644 --- a/internal/controller/sink.go +++ b/internal/controller/sink.go @@ -11,6 +11,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" uuid2 "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/client-go/kubernetes" "k8s.io/utils/ptr" @@ -25,8 +26,9 @@ import ( ) const ( - selectedBackendHeaderKey = "x-ai-eg-selected-backend" - hostRewriteHTTPFilterName = "ai-eg-host-rewrite" + selectedBackendHeaderKey = "x-ai-eg-selected-backend" + hostRewriteHTTPFilterName = "ai-eg-host-rewrite" + extProcConfigAnnotationKey = "aigateway.envoyproxy.io/extproc-config-uuid" ) // mountedExtProcSecretPath specifies the secret file mounted on the external proc. The idea is to update the mounted @@ -197,7 +199,8 @@ func (c *configSink) syncAIGatewayRoute(aiGatewayRoute *aigv1a1.AIGatewayRoute) } // Update the extproc configmap. - if err := c.updateExtProcConfigMap(aiGatewayRoute, string(uuid2.NewUUID())); err != nil { + uuid := string(uuid2.NewUUID()) + if err := c.updateExtProcConfigMap(aiGatewayRoute, uuid); err != nil { c.logger.Error(err, "failed to update extproc configmap", "namespace", aiGatewayRoute.Namespace, "name", aiGatewayRoute.Name) return } @@ -208,6 +211,13 @@ func (c *configSink) syncAIGatewayRoute(aiGatewayRoute *aigv1a1.AIGatewayRoute) c.logger.Error(err, "failed to deploy ext proc", "namespace", aiGatewayRoute.Namespace, "name", aiGatewayRoute.Name) return } + + // Annotate all pods with the new config. + err = c.annotateExtProcPods(context.Background(), aiGatewayRoute, uuid) + if err != nil { + c.logger.Error(err, "failed to annotate pods", "namespace", aiGatewayRoute.Namespace, "name", aiGatewayRoute.Name) + return + } } func (c *configSink) syncAIServiceBackend(aiBackend *aigv1a1.AIServiceBackend) { @@ -422,6 +432,31 @@ func (c *configSink) newHTTPRoute(dst *gwapiv1.HTTPRoute, aiGatewayRoute *aigv1a return nil } +// annotateExtProcPods annotates the external processor pods with the new config uuid. +// This is necessary to make the config update faster. +// +// See https://neonmirrors.net/post/2022-12/reducing-pod-volume-update-times/ for explanation. +func (c *configSink) annotateExtProcPods(ctx context.Context, aiGatewayRoute *aigv1a1.AIGatewayRoute, uuid string) error { + pods, err := c.kube.CoreV1().Pods(aiGatewayRoute.Namespace).List(ctx, metav1.ListOptions{ + LabelSelector: fmt.Sprintf("app=%s", extProcName(aiGatewayRoute)), + }) + if err != nil { + return fmt.Errorf("failed to list pods: %w", err) + } + + for _, pod := range pods.Items { + c.logger.Info("annotating pod", "namespace", pod.Namespace, "name", pod.Name) + _, err = c.kube.CoreV1().Pods(pod.Namespace).Patch(ctx, pod.Name, types.MergePatchType, + []byte(fmt.Sprintf( + `{"metadata":{"annotations":{"%s":"%s"}}}`, extProcConfigAnnotationKey, uuid), + ), metav1.PatchOptions{}) + if err != nil { + return fmt.Errorf("failed to patch pod %s: %w", pod.Name, err) + } + } + return nil +} + // syncExtProcDeployment syncs the external processor's Deployment and Service. func (c *configSink) syncExtProcDeployment(ctx context.Context, aiGatewayRoute *aigv1a1.AIGatewayRoute) error { name := extProcName(aiGatewayRoute) diff --git a/internal/controller/sink_test.go b/internal/controller/sink_test.go index f700cc8d3..da1515489 100644 --- a/internal/controller/sink_test.go +++ b/internal/controller/sink_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log/slog" "os" + "strconv" "testing" "time" @@ -783,3 +784,39 @@ func Test_backendSecurityPolicyVolumeName(t *testing.T) { mountPath := backendSecurityPolicyVolumeName(1, 2, "name") require.Equal(t, "rule1-backref2-name", mountPath) } + +func Test_annotateExtProcPods(t *testing.T) { + fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + kube := fake2.NewClientset() + + eventChan := make(chan ConfigSinkEvent) + s := newConfigSink(fakeClient, kube, logr.Discard(), eventChan, "defaultExtProcImage") + + aiGatewayRoute := &aigv1a1.AIGatewayRoute{ + ObjectMeta: metav1.ObjectMeta{Name: "myroute", Namespace: "foons"}, + } + + for i := range 5 { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "somepod" + strconv.Itoa(i), + Namespace: "foons", + Labels: map[string]string{"app": extProcName(aiGatewayRoute)}, + }, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "someapp"}}}, + } + _, err := kube.CoreV1().Pods("foons").Create(context.Background(), pod, metav1.CreateOptions{}) + require.NoError(t, err) + } + + uuid := string(uuid2.NewUUID()) + err := s.annotateExtProcPods(context.Background(), aiGatewayRoute, uuid) + require.NoError(t, err) + + // Check that all pods have been annotated. + for i := range 5 { + pod, err := kube.CoreV1().Pods("foons").Get(context.Background(), "somepod"+strconv.Itoa(i), metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, uuid, pod.Annotations[extProcConfigAnnotationKey]) + } +} diff --git a/internal/extproc/processor.go b/internal/extproc/processor.go index dcc7b3b21..1e9cd616f 100644 --- a/internal/extproc/processor.go +++ b/internal/extproc/processor.go @@ -25,6 +25,7 @@ import ( // processorConfig is the configuration for the processor. // This will be created by the server and passed to the processor when it detects a new configuration. type processorConfig struct { + uuid string bodyParser router.RequestBodyParser router extprocapi.Router modelNameHeaderKey, selectedBackendHeaderKey string diff --git a/internal/extproc/server.go b/internal/extproc/server.go index 1e14b6b16..5fb86f012 100644 --- a/internal/extproc/server.go +++ b/internal/extproc/server.go @@ -80,6 +80,7 @@ func (s *Server[P]) LoadConfig(config *filterconfig.Config) error { } newConfig := &processorConfig{ + uuid: config.UUID, bodyParser: bodyParser, router: rt, selectedBackendHeaderKey: config.SelectedBackendHeaderKey, modelNameHeaderKey: config.ModelNameHeaderKey, @@ -95,6 +96,7 @@ func (s *Server[P]) LoadConfig(config *filterconfig.Config) error { // Process implements [extprocv3.ExternalProcessorServer]. func (s *Server[P]) Process(stream extprocv3.ExternalProcessor_ProcessServer) error { p := s.newProcessor(s.config, s.logger) + s.logger.Debug("handling a new stream", slog.Any("config_uuid", s.config.uuid)) return s.process(p, stream) } diff --git a/internal/extproc/server_test.go b/internal/extproc/server_test.go index 91e0e00cb..37434138a 100644 --- a/internal/extproc/server_test.go +++ b/internal/extproc/server_test.go @@ -23,6 +23,7 @@ func requireNewServerWithMockProcessor(t *testing.T) *Server[*mockProcessor] { s, err := NewServer[*mockProcessor](slog.Default(), newMockProcessor) require.NoError(t, err) require.NotNil(t, s) + s.config = &processorConfig{} return s } diff --git a/tests/e2e/basic_test.go b/tests/e2e/basic_test.go index 8ae2a368c..2c8230a64 100644 --- a/tests/e2e/basic_test.go +++ b/tests/e2e/basic_test.go @@ -15,38 +15,40 @@ import ( ) // TestExamplesBasic tests the basic example in examples/basic directory. -// -// This requires the following environment variables to be set: -// - TEST_AWS_ACCESS_KEY_ID -// - TEST_AWS_SECRET_ACCESS_KEY -// - TEST_OPENAI_API_KEY -// -// The test will be skipped if any of these are not set. func Test_Examples_Basic(t *testing.T) { - openAiApiKey := getEnvVarOrSkip(t, "TEST_OPENAI_API_KEY") - awsAccessKeyID := getEnvVarOrSkip(t, "TEST_AWS_ACCESS_KEY_ID") - awsSecretAccessKey := getEnvVarOrSkip(t, "TEST_AWS_SECRET_ACCESS_KEY") - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) defer cancel() const manifest = "../../examples/basic/basic.yaml" - read, err := os.ReadFile(manifest) - require.NoError(t, err) - // Replace the placeholder with the actual API key. - replaced := strings.ReplaceAll(string(read), "OPENAI_API_KEY", openAiApiKey) - replaced = strings.ReplaceAll(replaced, "AWS_ACCESS_KEY_ID", awsAccessKeyID) - replaced = strings.ReplaceAll(replaced, "AWS_SECRET_ACCESS_KEY", awsSecretAccessKey) - require.NoError(t, kubectlApplyManifestStdin(ctx, replaced)) + require.NoError(t, kubectlApplyManifest(ctx, manifest)) const egSelector = "gateway.envoyproxy.io/owning-gateway-name=envoy-ai-gateway-basic" requireWaitForPodReady(t, egNamespace, egSelector) - t.Run("/chat/completions", func(t *testing.T) { - for _, tc := range []struct { - name string - modelName string - }{ + testUpstreamCase := examplesBasicTestCase{name: "testupsream", modelName: "some-cool-self-hosted-model"} + testUpstreamCase.run(t, egNamespace, egSelector) + + // This requires the following environment variables to be set: + // - TEST_AWS_ACCESS_KEY_ID + // - TEST_AWS_SECRET_ACCESS_KEY + // - TEST_OPENAI_API_KEY + // + // The test will be skipped if any of these are not set. + t.Run("with credentials", func(t *testing.T) { + openAiApiKey := getEnvVarOrSkip(t, "TEST_OPENAI_API_KEY") + awsAccessKeyID := getEnvVarOrSkip(t, "TEST_AWS_ACCESS_KEY_ID") + awsSecretAccessKey := getEnvVarOrSkip(t, "TEST_AWS_SECRET_ACCESS_KEY") + read, err := os.ReadFile(manifest) + require.NoError(t, err) + // Replace the placeholder with the actual API key. + replaced := strings.ReplaceAll(string(read), "OPENAI_API_KEY", openAiApiKey) + replaced = strings.ReplaceAll(replaced, "AWS_ACCESS_KEY_ID", awsAccessKeyID) + replaced = strings.ReplaceAll(replaced, "AWS_SECRET_ACCESS_KEY", awsSecretAccessKey) + require.NoError(t, kubectlApplyManifestStdin(ctx, replaced)) + + time.Sleep(5 * time.Second) // At least 5 seconds for the updated secret to be propagated. + + for _, tc := range []examplesBasicTestCase{ { name: "openai", modelName: "gpt-4o-mini", @@ -55,41 +57,46 @@ func Test_Examples_Basic(t *testing.T) { name: "aws", modelName: "us.meta.llama3-2-1b-instruct-v1:0", }, - { - name: "testupsream", - modelName: "some-cool-self-hosted-model", - }, } { - t.Run(tc.name, func(t *testing.T) { - require.Eventually(t, func() bool { - fwd := requireNewHTTPPortForwarder(t, egNamespace, egSelector, egDefaultPort) - defer fwd.kill() + tc.run(t, egNamespace, egSelector) + } + }) +} + +type examplesBasicTestCase struct { + name string + modelName string +} + +func (tc examplesBasicTestCase) run(t *testing.T, egNamespace, egSelector string) { + t.Run(tc.name, func(t *testing.T) { + require.Eventually(t, func() bool { + fwd := requireNewHTTPPortForwarder(t, egNamespace, egSelector, egDefaultPort) + defer fwd.kill() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() - client := openai.NewClient(option.WithBaseURL(fwd.address() + "/v1/")) + client := openai.NewClient(option.WithBaseURL(fwd.address() + "/v1/")) - chatCompletion, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{ - Messages: openai.F([]openai.ChatCompletionMessageParamUnion{ - openai.UserMessage("Say this is a test"), - }), - Model: openai.F(tc.modelName), - }) - if err != nil { - t.Logf("error: %v", err) - return false - } - var choiceNonEmpty bool - for _, choice := range chatCompletion.Choices { - t.Logf("choice: %s", choice.Message.Content) - if choice.Message.Content != "" { - choiceNonEmpty = true - } - } - return choiceNonEmpty - }, 10*time.Second, 1*time.Second) + chatCompletion, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{ + Messages: openai.F([]openai.ChatCompletionMessageParamUnion{ + openai.UserMessage("Say this is a test"), + }), + Model: openai.F(tc.modelName), }) - } + if err != nil { + t.Logf("error: %v", err) + return false + } + var choiceNonEmpty bool + for _, choice := range chatCompletion.Choices { + t.Logf("choice: %s", choice.Message.Content) + if choice.Message.Content != "" { + choiceNonEmpty = true + } + } + return choiceNonEmpty + }, 20*time.Second, 3*time.Second) }) } From 0b982b605c4c65bb387f7db423c9ea827bd67c0e Mon Sep 17 00:00:00 2001 From: Erica Hughberg Date: Wed, 29 Jan 2025 17:25:03 -0500 Subject: [PATCH 04/13] docs: Adding Google analytics tag, updating site meta description. (#225) **Commit Message**: Adding Google analytics tag and making the site description use the tagline from the config. Signed-off-by: Erica Hughberg --- site/docusaurus.config.ts | 4 ++++ site/src/pages/index.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/site/docusaurus.config.ts b/site/docusaurus.config.ts index 05902a816..24211e69e 100644 --- a/site/docusaurus.config.ts +++ b/site/docusaurus.config.ts @@ -52,6 +52,10 @@ const config: Config = { theme: { customCss: './src/css/custom.css', }, + // Will be passed to @docusaurus/plugin-google-gtag (only enabled when explicitly specified) + gtag: { + trackingID: 'G-DXJEH1ZRXX', + }, } satisfies Preset.Options, ], ], diff --git a/site/src/pages/index.tsx b/site/src/pages/index.tsx index 08d705dab..a721f164f 100644 --- a/site/src/pages/index.tsx +++ b/site/src/pages/index.tsx @@ -29,7 +29,7 @@ export default function Home(): JSX.Element { return ( + description={`${siteConfig.tagline}`}>
From 13cacf44f4d6d7a89f31d29a9c50f1e6ef5bdf41 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 29 Jan 2025 14:44:51 -0800 Subject: [PATCH 05/13] controller: consistent logger prefix for controllers (#226) **Commit Message**: Previously, all loggers used by controllers are name-prefixed with "setup" which is not correct and confusing, so this fixes it. Also, this consolidates all the WithName option handling in StartController function so that we can ensure all controllers have consistent names. Signed-off-by: Takeshi Yoneda --- cmd/controller/main.go | 2 +- internal/controller/ai_gateway_route.go | 2 +- internal/controller/ai_service_backend.go | 2 +- internal/controller/controller.go | 17 ++++++++++------- internal/controller/sink.go | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 9388b7c45..bdea057c0 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -72,7 +72,7 @@ func main() { }() // Start the controller. - if err := controller.StartControllers(ctx, k8sConfig, setupLog, options); err != nil { + if err := controller.StartControllers(ctx, k8sConfig, ctrl.Log.WithName("controller"), options); err != nil { setupLog.Error(err, "failed to start controller") } } diff --git a/internal/controller/ai_gateway_route.go b/internal/controller/ai_gateway_route.go index a208981a4..9dfa25cc4 100644 --- a/internal/controller/ai_gateway_route.go +++ b/internal/controller/ai_gateway_route.go @@ -44,7 +44,7 @@ func NewAIGatewayRouteController( return &aiGatewayRouteController{ client: client, kube: kube, - logger: logger.WithName("ai-gateway-route-controller"), + logger: logger, eventChan: ch, } } diff --git a/internal/controller/ai_service_backend.go b/internal/controller/ai_service_backend.go index 148a67324..0b65893c2 100644 --- a/internal/controller/ai_service_backend.go +++ b/internal/controller/ai_service_backend.go @@ -27,7 +27,7 @@ func NewAIServiceBackendController(client client.Client, kube kubernetes.Interfa return &aiBackendController{ client: client, kube: kube, - logger: logger.WithName("ai-service-backend-controller"), + logger: logger, eventChan: ch, } } diff --git a/internal/controller/controller.go b/internal/controller/controller.go index c76711441..ecde9b72e 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -69,7 +69,8 @@ func StartControllers(ctx context.Context, config *rest.Config, logger logr.Logg } sinkChan := make(chan ConfigSinkEvent, 100) - routeC := NewAIGatewayRouteController(c, kubernetes.NewForConfigOrDie(config), logger, sinkChan) + routeC := NewAIGatewayRouteController(c, kubernetes.NewForConfigOrDie(config), logger. + WithName("ai-gateway-route"), sinkChan) if err = ctrl.NewControllerManagedBy(mgr). For(&aigv1a1.AIGatewayRoute{}). Owns(&egv1a1.EnvoyExtensionPolicy{}). @@ -80,36 +81,38 @@ func StartControllers(ctx context.Context, config *rest.Config, logger logr.Logg return fmt.Errorf("failed to create controller for AIGatewayRoute: %w", err) } - backendC := NewAIServiceBackendController(c, kubernetes.NewForConfigOrDie(config), logger, sinkChan) + backendC := NewAIServiceBackendController(c, kubernetes.NewForConfigOrDie(config), logger. + WithName("ai-service-backend"), sinkChan) if err = ctrl.NewControllerManagedBy(mgr). For(&aigv1a1.AIServiceBackend{}). Complete(backendC); err != nil { return fmt.Errorf("failed to create controller for AIServiceBackend: %w", err) } - backendSecurityPolicyC := newBackendSecurityPolicyController(c, kubernetes.NewForConfigOrDie(config), logger, sinkChan) + backendSecurityPolicyC := newBackendSecurityPolicyController(c, kubernetes.NewForConfigOrDie(config), logger. + WithName("backend-security-policy"), sinkChan) if err = ctrl.NewControllerManagedBy(mgr). For(&aigv1a1.BackendSecurityPolicy{}). Complete(backendSecurityPolicyC); err != nil { return fmt.Errorf("failed to create controller for BackendSecurityPolicy: %w", err) } - secretC := NewSecretController(c, kubernetes.NewForConfigOrDie(config), logger, sinkChan) + secretC := NewSecretController(c, kubernetes.NewForConfigOrDie(config), logger. + WithName("secret"), sinkChan) if err = ctrl.NewControllerManagedBy(mgr). For(&corev1.Secret{}). Complete(secretC); err != nil { return fmt.Errorf("failed to create controller for Secret: %w", err) } - sink := newConfigSink(c, kubernetes.NewForConfigOrDie(config), logger, sinkChan, options.ExtProcImage) + sink := newConfigSink(c, kubernetes.NewForConfigOrDie(config), logger. + WithName("config-sink"), sinkChan, options.ExtProcImage) // Before starting the manager, initialize the config sink to sync all AIServiceBackend and AIGatewayRoute objects in the cluster. - logger.Info("Initializing config sink") if err = sink.init(ctx); err != nil { return fmt.Errorf("failed to initialize config sink: %w", err) } - logger.Info("Starting controller manager") if err = mgr.Start(ctx); err != nil { // This blocks until the manager is stopped. return fmt.Errorf("failed to start controller manager: %w", err) } diff --git a/internal/controller/sink.go b/internal/controller/sink.go index 4389974b4..33194b432 100644 --- a/internal/controller/sink.go +++ b/internal/controller/sink.go @@ -72,7 +72,7 @@ func newConfigSink( c := &configSink{ client: kubeClient, kube: kube, - logger: logger.WithName("config-sink"), + logger: logger, defaultExtProcImage: extProcImage, defaultExtProcImagePullPolicy: corev1.PullIfNotPresent, eventChan: eventChan, From 23c93ee9b0673fed1f6b58ce0933fe379d60f49f Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 29 Jan 2025 15:02:05 -0800 Subject: [PATCH 06/13] docs: removes the instruction to restart extproc (#227) **Commit Message**: This was necessary before #219 and #224 landed the main branch. Now the secret updates will be automatically picked up by the extproc without restarts. This behavior is already being tested in an e2e test. Signed-off-by: Takeshi Yoneda --- site/docs/getting-started/connect-providers/aws-bedrock.md | 5 ++--- site/docs/getting-started/connect-providers/openai.md | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/site/docs/getting-started/connect-providers/aws-bedrock.md b/site/docs/getting-started/connect-providers/aws-bedrock.md index 803abda0b..24a78ec55 100644 --- a/site/docs/getting-started/connect-providers/aws-bedrock.md +++ b/site/docs/getting-started/connect-providers/aws-bedrock.md @@ -47,7 +47,8 @@ The credentials will be stored in Kubernetes secrets. ### 2. Apply Configuration -Apply the updated configuration and wait for the Gateway pod to be ready, and restart the ext-proc to pick up the updated secrets: +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 basic.yaml @@ -56,8 +57,6 @@ kubectl wait pods --timeout=2m \ -l gateway.envoyproxy.io/owning-gateway-name=envoy-ai-gateway-basic \ -n envoy-gateway-system \ --for=condition=Ready - -kubectl rollout restart deployment/ai-eg-route-extproc-envoy-ai-gateway-basic ``` ### 4. Test the Configuration diff --git a/site/docs/getting-started/connect-providers/openai.md b/site/docs/getting-started/connect-providers/openai.md index 1d4df2d1e..8e01e9772 100644 --- a/site/docs/getting-started/connect-providers/openai.md +++ b/site/docs/getting-started/connect-providers/openai.md @@ -32,7 +32,8 @@ The key will be stored in a Kubernetes secret. ### 2. Apply Configuration -Apply the updated configuration and wait for the Gateway pod to be ready, and restart the ext-proc to pick up the updated secrets: +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 basic.yaml @@ -41,8 +42,6 @@ kubectl wait pods --timeout=2m \ -l gateway.envoyproxy.io/owning-gateway-name=envoy-ai-gateway-basic \ -n envoy-gateway-system \ --for=condition=Ready - -kubectl rollout restart deployment/ai-eg-route-extproc-envoy-ai-gateway-basic ``` ### 3. Test the Configuration From cf4a181d928ad4e738bc02c1e99cd03dee927aa5 Mon Sep 17 00:00:00 2001 From: melsal13 Date: Wed, 29 Jan 2025 19:04:20 -0800 Subject: [PATCH 07/13] docs: Moved Required tools section of Prerequisite to the top (#228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Commit Message**: This commit moves the required tools subsection to the top of the "Prerequisite" page. Later in the page we ask users to use kubectl and helm commands so we need to verify installations first. **Related Issues/PRs (if applicable)**: n/a **Special notes for reviewers (if applicable)**: n/a Signed-off-by: melsal13 --- site/docs/getting-started/prerequisites.md | 54 +++++++++++----------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/site/docs/getting-started/prerequisites.md b/site/docs/getting-started/prerequisites.md index c7a2191f3..0dc53e8dd 100644 --- a/site/docs/getting-started/prerequisites.md +++ b/site/docs/getting-started/prerequisites.md @@ -9,6 +9,33 @@ import TabItem from '@theme/TabItem'; Before you begin using Envoy AI Gateway, you'll need to ensure you have the following prerequisites in place: +## Required Tools + +Make sure you have the following tools installed: + +- `kubectl` - The Kubernetes command-line tool +- `helm` - The package manager for Kubernetes +- `curl` - For testing API endpoints (installed by default on most systems) + +:::tip Verify Installation +Run these commands to verify your tools are properly installed: + +Verify kubectl installation: +```shell +kubectl version --client +``` + +Verify helm installation: +```shell +helm version +``` + +Verify curl installation: +```shell +curl --version +``` +::: + ## Kubernetes Cluster :::info Version Requirements @@ -114,30 +141,3 @@ helm upgrade -i eg oci://docker.io/envoyproxy/gateway-helm \ kubectl wait --timeout=2m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available ``` - -## Required Tools - -Make sure you have the following tools installed: - -- `kubectl` - The Kubernetes command-line tool -- `helm` - The package manager for Kubernetes -- `curl` - For testing API endpoints (installed by default on most systems) - -:::tip Verify Installation -Run these commands to verify your tools are properly installed: - -Verify kubectl installation: -```shell -kubectl version --client -``` - -Verify helm installation: -```shell -helm version -``` - -Verify curl installation: -```shell -curl --version -``` -::: From 3c9476a9604349beafd35c80796eafb03ee96840 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 29 Jan 2025 22:40:59 -0800 Subject: [PATCH 08/13] ci: adds PR description style check (#229) **Commit Message**: This checks the PR description so that it maintains the certain style. Notably, this forces everyone to remove the comment outlines as well as it always begins with "**Commit Message**:". This is to avoid the accidental garbage commit messages left in the merged commit like cf4a181d --------- Signed-off-by: Takeshi Yoneda --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++- .github/workflows/pr_title_check.yaml | 36 ++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f13d72669..9af24f5de 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,10 +12,12 @@ translate text from English to Spanish. --> **Related Issues/PRs (if applicable)**: + " ]]; then + echo "PR description contains '-->'. Please remove all the comment out lines in the template after carefully reading them." + exit 1 + fi + if [[ ! $TITLE =~ "**Commit Message**:" ]]; then + echo "PR description must begin with '**Commit Message**:'." + exit 1 + fi + + title: + name: Title runs-on: ubuntu-latest steps: - uses: amannn/action-semantic-pull-request@v5 @@ -33,3 +58,8 @@ jobs: controller translator examples + subjectPattern: ^(?![A-Z]).+$ + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + doesn't start with an uppercase character. From 4b198313d763dabbc9b8ae0cf2b5e318f6597815 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 29 Jan 2025 22:56:27 -0800 Subject: [PATCH 09/13] ci: separates out style checks from tests (#230) **Commit Message**: This moves the style checks from the test jobs, so that we can set the paths-ignores on these test targets while always running style check regardless of the changed files. Signed-off-by: Takeshi Yoneda --- .github/workflows/style.yaml | 30 ++++++++++++++++ .github/workflows/{commit.yaml => tests.yaml} | 35 +++++++------------ 2 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/style.yaml rename .github/workflows/{commit.yaml => tests.yaml} (90%) diff --git a/.github/workflows/style.yaml b/.github/workflows/style.yaml new file mode 100644 index 000000000..f7a9b3d5e --- /dev/null +++ b/.github/workflows/style.yaml @@ -0,0 +1,30 @@ +name: Style +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + style: + if: github.event_name == 'pull_request' || github.event_name == 'push' + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + cache: false + go-version-file: go.mod + - uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/.cache/golangci-lint + ~/go/pkg/mod + ~/go/bin + key: code-style-check-${{ hashFiles('**/go.mod', '**/go.sum', '**/Makefile') }} + - name: Run code style check + run: make check diff --git a/.github/workflows/commit.yaml b/.github/workflows/tests.yaml similarity index 90% rename from .github/workflows/commit.yaml rename to .github/workflows/tests.yaml index 10dcb78e7..1ebf8db9d 100644 --- a/.github/workflows/commit.yaml +++ b/.github/workflows/tests.yaml @@ -1,11 +1,21 @@ -name: Commit +name: Tests on: pull_request: branches: - main + paths-ignore: + - '**/*.md' + - 'site/**' + - 'netlify.toml' + push: branches: - main + paths-ignore: + - '**/*.md' + - 'site/**' + - 'netlify.toml' + # If the PR is coming from a fork, they are not allowed to access secrets by default. # This even is triggered only if the PR gets labeled with 'safe to test' which can only be added by the maintainers. # Jobs do not use secrets in the workflow will ignore this event. @@ -20,27 +30,6 @@ concurrency: cancel-in-progress: true jobs: - style: - if: github.event_name == 'pull_request' || github.event_name == 'push' - name: Code Style Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - cache: false - go-version-file: go.mod - - uses: actions/cache@v4 - with: - path: | - ~/.cache/go-build - ~/.cache/golangci-lint - ~/go/pkg/mod - ~/go/bin - key: code-style-check-${{ hashFiles('**/go.mod', '**/go.sum', '**/Makefile') }} - - name: Run code style check - run: make check - unittest: if: github.event_name == 'pull_request' || github.event_name == 'push' name: Unit Test @@ -193,7 +182,7 @@ jobs: # Docker builds are verified in test_e2e job, so we only need to push the images when the event is a push event. if: github.event_name == 'push' name: Push Docker Images - needs: [style, unittest, test_cel_validation, test_controller, test_extproc, test_e2e] + needs: [unittest, test_cel_validation, test_controller, test_extproc, test_e2e] uses: ./.github/workflows/docker_builds_template.yaml push_helm: From 2543efaeaf43220a77ae43d7d458e021b7c35637 Mon Sep 17 00:00:00 2001 From: David Xia Date: Thu, 30 Jan 2025 11:08:35 -0500 Subject: [PATCH 10/13] docs: fix code block indentation (#223) **Commit Message**: Block should be indented. Some unrelated minor Markdown formatting improvements like using auto-incrementing lists and removing unnecessary colons. Signed-off-by: David Xia --- .../getting-started/connect-providers/index.md | 12 +++++++----- .../getting-started/connect-providers/openai.md | 16 +++++++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/site/docs/getting-started/connect-providers/index.md b/site/docs/getting-started/connect-providers/index.md index c1aeaddb8..211d0fcf0 100644 --- a/site/docs/getting-started/connect-providers/index.md +++ b/site/docs/getting-started/connect-providers/index.md @@ -20,7 +20,8 @@ Currently, Envoy AI Gateway supports the following providers: Before configuring any provider: 1. Complete the [Basic Usage](../basic-usage.md) guide -2. Remove the basic configuration with the mock backend: +2. Remove the basic configuration with the mock backend + ```shell kubectl delete -f https://raw.githubusercontent.com/envoyproxy/ai-gateway/main/examples/basic/basic.yaml @@ -29,11 +30,12 @@ Before configuring any provider: -n envoy-gateway-system \ --for=delete ``` -3. Download Configuration Template: -```shell -curl -O https://raw.githubusercontent.com/envoyproxy/ai-gateway/main/examples/basic/basic.yaml -``` +3. Download configuration template + + ```shell + curl -O https://raw.githubusercontent.com/envoyproxy/ai-gateway/main/examples/basic/basic.yaml + ``` ## Security Best Practices diff --git a/site/docs/getting-started/connect-providers/openai.md b/site/docs/getting-started/connect-providers/openai.md index 8e01e9772..ce04fed49 100644 --- a/site/docs/getting-started/connect-providers/openai.md +++ b/site/docs/getting-started/connect-providers/openai.md @@ -11,17 +11,21 @@ This guide will help you configure Envoy AI Gateway to work with OpenAI's models ## Prerequisites Before you begin, you'll need: + - An OpenAI API key from [OpenAI's platform](https://platform.openai.com) - 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/) ::: + ### 1. Configure OpenAI Credentials Edit the `basic.yaml` file to replace the OpenAI placeholder value: + - Find the section containing `OPENAI_API_KEY` - Replace it with your actual OpenAI API key @@ -45,6 +49,7 @@ kubectl wait pods --timeout=2m \ ``` ### 3. 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. @@ -70,18 +75,22 @@ If you encounter issues: 1. Verify your API key is correct and active 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 - ``` + + ```shell + kubectl logs services/ai-eg-route-extproc-envoy-ai-gateway-basic + ``` 5. Common errors: - 401: Invalid API key @@ -91,4 +100,5 @@ If you encounter issues: ## Next Steps After configuring OpenAI: + - [Connect AWS Bedrock](./aws-bedrock.md) to add another provider From f43e8838b5b204f054ed99c81fe80b751d94f333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 30 Jan 2025 17:11:11 +0100 Subject: [PATCH 11/13] feat: redact sensitive headers and body content in debug logs (#217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Commit Message**: This commit introduces functions to redact sensitive headers and body content before logging, ensuring that sensitive information such as authorization tokens is not exposed in debug logs. The new filterSensitiveHeaders function redacts specified headers, replacing their values with [REDACTED], while filterSensitiveBody ensures that sensitive content in request bodies is also redacted before being logged. Additionally, the logging behavior has been updated so that request headers and body content are only logged when the DEBUG level is enabled, further improving security and performance. Unit tests have been added to verify the functionality of both filterSensitiveHeaders and filterSensitiveBody, ensuring that sensitive data is properly redacted and that the logging behavior works as expected. Signed-off-by: Sébastien Han --------- Signed-off-by: Sébastien Han --- internal/extproc/server.go | 76 ++++++++++++++++++++++++++++++++- internal/extproc/server_test.go | 48 +++++++++++++++++++++ 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/internal/extproc/server.go b/internal/extproc/server.go index 5fb86f012..fb67affc6 100644 --- a/internal/extproc/server.go +++ b/internal/extproc/server.go @@ -6,7 +6,10 @@ import ( "fmt" "io" "log/slog" + "slices" + "strings" + corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" "github.com/google/cel-go/cel" "google.golang.org/grpc/codes" @@ -21,6 +24,12 @@ import ( "github.com/envoyproxy/ai-gateway/internal/llmcostcel" ) +const ( + redactedKey = "[REDACTED]" +) + +var sensitiveHeaderKeys = []string{"authorization"} + // Server implements the external process server. type Server[P ProcessorIface] struct { logger *slog.Logger @@ -133,7 +142,11 @@ func (s *Server[P]) processMsg(ctx context.Context, p P, req *extprocv3.Processi switch value := req.Request.(type) { case *extprocv3.ProcessingRequest_RequestHeaders: requestHdrs := req.GetRequestHeaders().Headers - s.logger.Debug("request headers processing", slog.Any("request_headers", requestHdrs)) + // If DEBUG log level is enabled, filter sensitive headers before logging. + if s.logger.Enabled(ctx, slog.LevelDebug) { + filteredHdrs := filterSensitiveHeaders(requestHdrs, s.logger, sensitiveHeaderKeys) + s.logger.Debug("request headers processing", slog.Any("request_headers", filteredHdrs)) + } resp, err := p.ProcessRequestHeaders(ctx, requestHdrs) if err != nil { return nil, fmt.Errorf("cannot process request headers: %w", err) @@ -143,7 +156,11 @@ func (s *Server[P]) processMsg(ctx context.Context, p P, req *extprocv3.Processi case *extprocv3.ProcessingRequest_RequestBody: s.logger.Debug("request body processing", slog.Any("request", req)) resp, err := p.ProcessRequestBody(ctx, value.RequestBody) - s.logger.Debug("request body processed", slog.Any("response", resp)) + // If DEBUG log level is enabled, filter sensitive body before logging. + if s.logger.Enabled(ctx, slog.LevelDebug) { + filteredBody := filterSensitiveBody(resp, s.logger, sensitiveHeaderKeys) + s.logger.Debug("request body processed", slog.Any("response", filteredBody)) + } if err != nil { return nil, fmt.Errorf("cannot process request body: %w", err) } @@ -180,3 +197,58 @@ func (s *Server[P]) Check(context.Context, *grpc_health_v1.HealthCheckRequest) ( func (s *Server[P]) Watch(*grpc_health_v1.HealthCheckRequest, grpc_health_v1.Health_WatchServer) error { return status.Error(codes.Unimplemented, "Watch is not implemented") } + +// filterSensitiveHeaders filters out sensitive headers from the provided HeaderMap. +// Specifically, it redacts the value of the "authorization" header and logs this action. +// The function returns a new HeaderMap with the filtered headers. +func filterSensitiveHeaders(headers *corev3.HeaderMap, logger *slog.Logger, sensitiveKeys []string) *corev3.HeaderMap { + if headers == nil { + logger.Debug("received nil HeaderMap, returning empty HeaderMap") + return &corev3.HeaderMap{} + } + filteredHeaders := &corev3.HeaderMap{} + for _, header := range headers.Headers { + // We convert the header key to lowercase to make the comparison case-insensitive but we don't modify the original header. + if slices.Contains(sensitiveKeys, strings.ToLower(header.GetKey())) { + logger.Debug("filtering sensitive header", slog.String("header_key", header.Key)) + filteredHeaders.Headers = append(filteredHeaders.Headers, &corev3.HeaderValue{ + Key: header.Key, + Value: redactedKey, + }) + } else { + filteredHeaders.Headers = append(filteredHeaders.Headers, header) + } + } + return filteredHeaders +} + +// filterSensitiveBody filters out sensitive information from the response body. +// It creates a copy of the response body to avoid modifying the original body, +// as the API Key is needed for the request. The function returns a new +// ProcessingResponse with the filtered body for logging. +func filterSensitiveBody(resp *extprocv3.ProcessingResponse, logger *slog.Logger, sensitiveKeys []string) *extprocv3.ProcessingResponse { + if resp == nil { + logger.Debug("received nil ProcessingResponse, returning empty ProcessingResponse") + return &extprocv3.ProcessingResponse{} + } + filteredResp := &extprocv3.ProcessingResponse{ + Response: &extprocv3.ProcessingResponse_RequestBody{ + RequestBody: &extprocv3.BodyResponse{ + Response: &extprocv3.CommonResponse{ + HeaderMutation: resp.Response.(*extprocv3.ProcessingResponse_RequestBody).RequestBody.Response.GetHeaderMutation(), + BodyMutation: resp.Response.(*extprocv3.ProcessingResponse_RequestBody).RequestBody.Response.GetBodyMutation(), + ClearRouteCache: resp.Response.(*extprocv3.ProcessingResponse_RequestBody).RequestBody.Response.GetClearRouteCache(), + }, + }, + }, + ModeOverride: resp.ModeOverride, + } + for _, setHeader := range filteredResp.Response.(*extprocv3.ProcessingResponse_RequestBody).RequestBody.Response.GetHeaderMutation().GetSetHeaders() { + // We convert the header key to lowercase to make the comparison case-insensitive but we don't modify the original header. + if slices.Contains(sensitiveKeys, strings.ToLower(setHeader.Header.GetKey())) { + logger.Debug("filtering sensitive header", slog.String("header_key", setHeader.Header.Key)) + setHeader.Header.RawValue = []byte(redactedKey) + } + } + return filteredResp +} diff --git a/internal/extproc/server_test.go b/internal/extproc/server_test.go index 37434138a..f82551ed0 100644 --- a/internal/extproc/server_test.go +++ b/internal/extproc/server_test.go @@ -260,3 +260,51 @@ func TestServer_Process(t *testing.T) { require.Error(t, err, "context canceled") }) } + +func TestFilterSensitiveHeaders(t *testing.T) { + logger, buf := newTestLoggerWithBuffer() + hm := &corev3.HeaderMap{Headers: []*corev3.HeaderValue{{Key: "foo", Value: "bar"}, {Key: "authorization", Value: "sensitive"}}} + filtered := filterSensitiveHeaders(hm, logger, []string{"authorization"}) + require.Len(t, filtered.Headers, 2) + for _, h := range filtered.Headers { + if h.Key == "authorization" { + require.Equal(t, "[REDACTED]", h.Value) + } else { + require.Equal(t, "bar", h.Value) + } + } + require.Contains(t, buf.String(), "filtering sensitive header") +} + +func TestFilterSensitiveBody(t *testing.T) { + logger, buf := newTestLoggerWithBuffer() + resp := &extprocv3.ProcessingResponse{ + Response: &extprocv3.ProcessingResponse_RequestBody{ + RequestBody: &extprocv3.BodyResponse{ + Response: &extprocv3.CommonResponse{ + HeaderMutation: &extprocv3.HeaderMutation{ + SetHeaders: []*corev3.HeaderValueOption{ + {Header: &corev3.HeaderValue{ + Key: ":path", + Value: "/model/some-random-model/converse", + }}, + {Header: &corev3.HeaderValue{ + Key: "Authorization", + Value: "sensitive", + }}, + }, + }, + BodyMutation: &extprocv3.BodyMutation{}, + }, + }, + }, + } + filtered := filterSensitiveBody(resp, logger, []string{"authorization"}) + require.NotNil(t, filtered) + for _, h := range filtered.Response.(*extprocv3.ProcessingResponse_RequestBody).RequestBody.Response.GetHeaderMutation().GetSetHeaders() { + if h.Header.Key == "Authorization" { + require.Equal(t, "[REDACTED]", string(h.Header.RawValue)) + } + } + require.Contains(t, buf.String(), "filtering sensitive header") +} From 1ff7110a3841e834c1fc0dbe662e85da3122e041 Mon Sep 17 00:00:00 2001 From: Dan Sun Date: Thu, 30 Jan 2025 12:27:33 -0500 Subject: [PATCH 12/13] docs: Envoy AI Gateway v0.1 design doc (#52) This PR documents the envoy ai gateway 0.1 release API and architecture design. --------- Signed-off-by: Dan Sun --- .../001-ai-gateway-proposal/control_plane.png | Bin 0 -> 133673 bytes .../001-ai-gateway-proposal/data_plane.png | Bin 0 -> 162909 bytes .../001-ai-gateway-proposal/proposal.md | 591 ++++++++++++++++++ 3 files changed, 591 insertions(+) create mode 100644 docs/proposals/001-ai-gateway-proposal/control_plane.png create mode 100644 docs/proposals/001-ai-gateway-proposal/data_plane.png create mode 100644 docs/proposals/001-ai-gateway-proposal/proposal.md diff --git a/docs/proposals/001-ai-gateway-proposal/control_plane.png b/docs/proposals/001-ai-gateway-proposal/control_plane.png new file mode 100644 index 0000000000000000000000000000000000000000..d6086f6aa4b74c9af789ee5a906177cdfd08f660 GIT binary patch literal 133673 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
|phYNQ_gO?SSFN&y1{-f%W)sjrV)fHlm~ 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 literal 0 HcmV?d00001 diff --git a/docs/proposals/001-ai-gateway-proposal/data_plane.png b/docs/proposals/001-ai-gateway-proposal/data_plane.png new file mode 100644 index 0000000000000000000000000000000000000000..6b0df3a713ef6d5845bf69e8544566046a18f2aa GIT binary patch literal 162909 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 WyP3G!&>P@Cil@%~nIdQ4@qYlTQlk3+ literal 0 HcmV?d00001 diff --git a/docs/proposals/001-ai-gateway-proposal/proposal.md b/docs/proposals/001-ai-gateway-proposal/proposal.md new file mode 100644 index 000000000..7efcf48e6 --- /dev/null +++ b/docs/proposals/001-ai-gateway-proposal/proposal.md @@ -0,0 +1,591 @@ +# Envoy AI Gateway + +## Table of Contents + + + +- [Summary](#summary) +- [Goals](#goals) +- [Non-Goals](#non-goals) +- [Design](#design) + - [Personas](#personas) + - [Axioms](#axioms) + - [AIGatewayRoute](#aigatewayroute) + - [AIServiceBackend](#aiservicebackend) + - [BackendSecurityPolicy](#backendsecuritypolicy) + - [Token Usage based Rate Limiting](#token-usage-rate-limiting) + - [Diagrams](#diagrams) + + + +## Summary +The `Envoy AI Gateway` is to act as a centralized access point for managing and controlling access to various AI models within an organization. +It provides a single interface for developers to interact with different AI Services while ensuring security, governance and observability over AI traffic. + +It introduces new Custom Resource Definitions(CRD) to support the requirements of the `Envoy AI Gateway`: **AIGatewayRoute**, **AIServiceBackend** and **BackendSecurityPolicy**. + +* The `AIGatewayRoute` specifies the schema for the user requests and routing rules to the `AIServiceBackend`s. +* The `AIServiceBackend` defines the AI service backend schema and security policy for various AI Services. This resource is managed by the Inference Platform Admin persona. +* The `BackendSecurityPolicy` defines the authentication policy for upstream AI services using API key or cloud credentials. +* Rate Limiting for LLM workload is based on tokens, we extend `Envoy Gateway` to support generic cost based rate limiting as envoy so far only supports request based rate limiting. + +## Goals +- Documentation of the 0.1 API decisions for posterity. +- Document `Envoy AI Gateway` MVP features: + - Upstream Model Access: Support accessing models from an initial list of AI Services: AWS Bedrock, OpenAI. + - Unified Client Access: Support a unified AI gateway API across AI Services. + - Traffic Management: Monitor and regulate AI service traffic, including token rate limiting by tracking token usages for LLM models. + - Observability: Provide detailed insights into usage patterns, performance and potential issues through logging and metrics collection. + - Policy Enforcement: Allow organizations to set specific rules and guidelines for how AI models can be accessed and used. + + +## Non-Goals + +- non-MVP features +- Routing for LLM serving instances in a Kubernetes cluster + +## Design + +### Personas + +Before diving into the details of the API, descriptions of the personas will help shape the thought process of the API design. + +#### Inference Platform Admin + +The Inference Platform Admin manages the gateway infrastructure necessary to route inference requests to a variety of AI Services. +Including handling Ops for: + - A list of AI Services and supported models. + - AI Services API schema conversion and centralized upstream authentication configurations. + - Traffic policy including rate limiting, fallback resilience between AI Service backends. + +#### Payment Team + +- Reports per user/tenant/model LLM token usage for billing purpose. + +#### Security Team + +- Security team to control the ACL for accessing the models from AI Services. + +### Axioms + +The API design is based on these axioms: + +- This solution should be composable with other Gateway solutions. +- Gateway architecture should be extensible when customization is required. +- The MVP heavily assumes that the requests are sent using the OpenAI spec, but open to the extension in the future. + + +### AIGatewayRoute + +`AIGatewayRoute` defines the unified user request schema and the routing rules to a list of supported `AIServiceBackend`s such as AWS Bedrock, GCP Vertex AI, Azure OpenAI and KServe for self-hosted LLMs. + +- `AIGatewayRoute` serves as a way to define the unified AI Gateway API which allows downstream clients to use a single schema API to interact with multiple `AIServiceBackend`s. +- `AIGatewayRoute`s are defined to route to the `AIServiceBackend`s based on the HTTP header/path matching. The rules are matched in the `Envoy AI Gateway` external proc as the backend needs to be determined for request body transformation and upstream authentication. +The `HTTPRoute` handles upstream routing once backend is selected using the AI gateway routing header. + + +```golang +// AIGatewayRouteSpec details the AIGatewayRoute configuration. +type AIGatewayRouteSpec struct { +// TargetRefs are the names of the Gateway resources this AIGatewayRoute is being attached to. +// +// +kubebuilder:validation:MinItems=1 +// +kubebuilder:validation:MaxItems=128 +TargetRefs []gwapiv1a2.LocalPolicyTargetReferenceWithSectionName `json:"targetRefs"` +// APISchema specifies the API schema of the input that the target Gateway(s) will receive. +// Based on this schema, the ai-gateway will perform the necessary transformation to the +// output schema specified in the selected AIServiceBackend during the routing process. +// +// Currently, the only supported schema is OpenAI as the input schema. +// +// +kubebuilder:validation:Required +// +kubebuilder:validation:XValidation:rule="self.name == 'OpenAI'" +APISchema VersionedAPISchema `json:"schema"` +// Rules is the list of AIGatewayRouteRule that this AIGatewayRoute will match the traffic to. +// Each rule is a subset of the HTTPRoute in the Gateway API (https://gateway-api.sigs.k8s.io/api-types/httproute/). +// +// AI Gateway controller will generate a HTTPRoute based on the configuration given here with the additional +// modifications to achieve the necessary jobs, notably inserting the AI Gateway filter responsible for +// the transformation of the request and response, etc. +// +// In the matching conditions in the AIGatewayRouteRule, `x-ai-eg-model` header is available +// if we want to describe the routing behavior based on the model name. The model name is extracted +// from the request content before the routing decision. +// +// How multiple rules are matched is the same as the Gateway API. See for the details: +// https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1.HTTPRoute +// +// +kubebuilder:validation:Required +// +kubebuilder:validation:MaxItems=128 +Rules []AIGatewayRouteRule `json:"rules"` + +// FilterConfig is the configuration for the AI Gateway filter inserted in the generated HTTPRoute. +// +// An AI Gateway filter is responsible for the transformation of the request and response +// as well as the routing behavior based on the model name extracted from the request content, etc. +// +// Currently, the filter is only implemented as an external process filter, which might be +// extended to other types of filters in the future. See https://github.com/envoyproxy/ai-gateway/issues/90 +FilterConfig *AIGatewayFilterConfig `json:"filterConfig,omitempty"` + +// LLMRequestCosts specifies how to capture the cost of the LLM-related request, notably the token usage. +// The AI Gateway filter will capture each specified number and store it in the Envoy's dynamic +// metadata per HTTP request. The namespaced key is "io.envoy.ai_gateway", +// +// For example, let's say we have the following LLMRequestCosts configuration: +// +// llmRequestCosts: +// - metadataKey: llm_input_token +// type: InputToken +// - metadataKey: llm_output_token +// type: OutputToken +// - metadataKey: llm_total_token +// type: TotalToken +// +// Then, with the following BackendTrafficPolicy of Envoy Gateway, you can have three +// rate limit buckets for each unique x-user-id header value. One bucket is for the input token, +// the other is for the output token, and the last one is for the total token. +// Each bucket will be reduced by the corresponding token usage captured by the AI Gateway filter. +// +// +optional +// +kubebuilder:validation:MaxItems=36 +LLMRequestCosts []LLMRequestCost `json:"llmRequestCosts,omitempty"` +} + +// AIGatewayRouteRule is a rule that defines the routing behavior of the AIGatewayRoute. +type AIGatewayRouteRule struct { +// BackendRefs is the list of AIServiceBackend that this rule will route the traffic to. +// Each backend can have a weight that determines the traffic distribution. +// +// The namespace of each backend is "local", i.e. the same namespace as the AIGatewayRoute. +// +// +optional +// +kubebuilder:validation:MaxItems=128 +BackendRefs []AIGatewayRouteRuleBackendRef `json:"backendRefs,omitempty"` + +// Matches is the list of AIGatewayRouteMatch that this rule will match the traffic to. +// This is a subset of the HTTPRouteMatch in the Gateway API. See for the details: +// https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1.HTTPRouteMatch +// +// +optional +// +kubebuilder:validation:MaxItems=128 +Matches []AIGatewayRouteRuleMatch `json:"matches,omitempty"` +} + +// LLMRequestCost specifies "where" the request cost is stored in the filter metadata as well as +// "how" the cost is calculated. By default, the cost is retrieved from "output token" in the response body. +// +// This can be used to subtract the usage token from the usage quota in the rate limit filter when +// the request completes combined with `apply_on_stream_done` and `hits_addend` fields of +// the rate limit configuration https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#config-route-v3-ratelimit +// which is introduced in Envoy 1.33 (to be released soon as of writing). +type LLMRequestCost struct { +// MetadataKey is the key of the metadata storing the request cost. +MetadataKey string `json:"metadataKey"` +// Type is the kind of the request cost calculation. +Type LLMRequestCostType `json:"type"` +// CELExpression is the CEL expression to calculate the cost of the request. +// This is not empty when the Type is LLMRequestCostTypeCELExpression. +CELExpression string `json:"celExpression,omitempty"` +} +``` + + +### AIServiceBackend + +`AIServiceBackend` defines the AI service API schema and a reference to the `Envoy Gateway` backend for the target destination. + +- The Gateway routes the traffic to the appropriate `AIServiceBackend` by converting the unified API schema to the AI service API schema. +- The `AIServiceBackend` is attached with the `BackendSecurityPolicy` to perform the upstream authentication. + +```golang +// AIServiceBackendSpec details the AIServiceBackend configuration. +type AIServiceBackendSpec struct { +// APISchema specifies the API schema of the output format of requests from +// Envoy that this AIServiceBackend can accept as incoming requests. +// Based on this schema, the ai-gateway will perform the necessary transformation for +// the pair of AIGatewayRouteSpec.APISchema and AIServiceBackendSpec.APISchema. +// +// This is required to be set. +// +// +kubebuilder:validation:Required +APISchema VersionedAPISchema `json:"schema"` +// BackendRef is the reference to the Backend resource that this AIServiceBackend corresponds to. +// +// A backend can be of either k8s Service or Backend resource of Envoy Gateway. +// +// This is required to be set. +// +// +kubebuilder:validation:Required +BackendRef gwapiv1.BackendObjectReference `json:"backendRef"` + +// BackendSecurityPolicyRef is the name of the BackendSecurityPolicy resources this backend +// is being attached to. +// +// +optional +BackendSecurityPolicyRef *gwapiv1.LocalObjectReference `json:"backendSecurityPolicyRef,omitempty"` +} +``` + +### BackendSecurityPolicy +The `BeckendSecurityPolicy` defines the authentication methods of the upstream AI service. `APIKey` provides a simple authentication method to +authenticate with upstream AI services such as OpenAI or Anthropic. For accessing models via cloud providers such as AWS, GCP, the cloud credential is managed with Kubernetes secrets or exchanged +using OIDC federation. + +```golang +// BackendSecurityPolicySpec specifies authentication rules on access the provider from the Gateway. +// Only one mechanism to access a backend(s) can be specified. +// +// Only one type of BackendSecurityPolicy can be defined. +// +kubebuilder:validation:MaxProperties=2 +type BackendSecurityPolicySpec struct { +// Type specifies the auth mechanism used to access the provider. Currently, only "APIKey", AND "AWSCredentials" are supported. +// +// +kubebuilder:validation:Enum=APIKey;AWSCredentials +Type BackendSecurityPolicyType `json:"type"` + +// APIKey is a mechanism to access a backend(s). The API key will be injected into the Authorization header. +// +// +optional +APIKey *BackendSecurityPolicyAPIKey `json:"apiKey,omitempty"` + +// AWSCredentials is a mechanism to access a backend(s). AWS specific logic will be applied. +// +// +optional +AWSCredentials *BackendSecurityPolicyAWSCredentials `json:"awsCredentials,omitempty"` +} +// BackendSecurityPolicyAWSCredentials contains the supported authentication mechanisms to access aws +type BackendSecurityPolicyAWSCredentials struct { +// Region specifies the AWS region associated with the policy. +// +// +kubebuilder:validation:MinLength=1 +Region string `json:"region"` + +// CredentialsFile specifies the credentials file to use for the AWS provider. +// +// +optional +CredentialsFile *AWSCredentialsFile `json:"credentialsFile,omitempty"` + +// OIDCExchangeToken specifies the oidc configurations used to obtain an oidc token. The oidc token will be +// used to obtain temporary credentials to access AWS. +// +// +optional +OIDCExchangeToken *AWSOIDCExchangeToken `json:"oidcExchangeToken,omitempty"` +} +``` + +### Token Usage Rate Limiting + +AI Gateway project extended the `Envoy Gateway` `BackendTrafficPolicy` with a generic usage based rate limiting in [#4957](https://github.com/envoyproxy/gateway/pull/4957). +For supporting token usage based rate limiting, it reduces the rate limit counter in the response path. Since the reduction happens after the response is complete, the rate limiting is not enforced for the current but the subsequent requests. +The token usages are extracted from the standard token usage fields according to the OpenAI schema in the ext proc `processResponseBody` handler. + +```go +type RateLimitCost struct { + // Request specifies the number to reduce the rate limit counters + // on the request path. If this is not specified, the default behavior + // is to reduce the rate limit counters by 1. + // + // When Envoy receives a request that matches the rule, it tries to reduce the + // rate limit counters by the specified number. If the counter doesn't have + // enough capacity, the request is rate limited. + // + // +optional + // +notImplementedHide + Request *RateLimitCostSpecifier `json:"request,omitempty"` + // Response specifies the number to reduce the rate limit counters + // after the response is sent back to the client or the request stream is closed. + // + // The cost is used to reduce the rate limit counters for the matching requests. + // Since the reduction happens after the request stream is complete, the rate limit + // won't be enforced for the current request, but for the subsequent matching requests. + // + // This is optional and if not specified, the rate limit counters are not reduced + // on the response path. + // + // Currently, this is only supported for HTTP Global Rate Limits. + // + // +optional + // +notImplementedHide + Response *RateLimitCostSpecifier `json:"response,omitempty"` +} +// RateLimitCostSpecifier specifies where the Envoy retrieves the number to reduce the rate limit counters. +// +// +kubebuilder:validation:XValidation:rule="!(has(self.number) && has(self.metadata))",message="only one of number or metadata can be specified" +type RateLimitCostSpecifier struct { +// From specifies where to get the rate limit cost. Currently, only "Number" and "Metadata" are supported. +// +// +kubebuilder:validation:Required +From RateLimitCostFrom `json:"from"` +// Number specifies the fixed usage number to reduce the rate limit counters. +// Using zero can be used to only check the rate limit counters without reducing them. +// +// +optional +// +notImplementedHide +Number *uint64 `json:"number,omitempty"` +// Metadata specifies the per-request metadata to retrieve the usage number from. +// +// +optional +// +notImplementedHide +Metadata *RateLimitCostMetadata `json:"metadata,omitempty"` +} +// RateLimitCostMetadata specifies the filter metadata to retrieve the usage number from. +type RateLimitCostMetadata struct { +// Namespace is the namespace of the dynamic metadata. +// +// +kubebuilder:validation:Required +Namespace string `json:"namespace"` +// Key is the key to retrieve the usage number from the filter metadata. +// +// +kubebuilder:validation:Required +Key string `json:"key"` +} +``` + +```go +/// RateLimitRule defines the semantics for matching attributes +// from the incoming requests, and setting limits for them. +type RateLimitRule struct { +// ClientSelectors holds the list of select conditions to select +// specific clients using attributes from the traffic flow. +// All individual select conditions must hold True for this rule +// and its limit to be applied. +// +// If no client selectors are specified, the rule applies to all traffic of +// the targeted Route. +// +// If the policy targets a Gateway, the rule applies to each Route of the Gateway. +// Please note that each Route has its own rate limit counters. For example, +// if a Gateway has two Routes, and the policy has a rule with limit 10rps, +// each Route will have its own 10rps limit. +// +// +optional +// +kubebuilder:validation:MaxItems=8 +ClientSelectors []RateLimitSelectCondition `json:"clientSelectors,omitempty"` +// Limit holds the rate limit values. +// This limit is applied for traffic flows when the selectors +// compute to True, causing the request to be counted towards the limit. +// The limit is enforced and the request is rate limited, i.e. a response with +// 429 HTTP status code is sent back to the client when +// the selected requests have reached the limit. +Limit RateLimitValue `json:"limit"` +// Cost specifies the cost of requests and responses for the rule. +// +// This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on +// the request path and do not reduce the rate limit counters on the response path. +// +// +optional +// +notImplementedHide +Cost *RateLimitCost `json:"cost,omitempty"` +} +``` + +### Yaml Examples + +#### AIGatewayRoute +The routing calculation in done in the `ExtProc` by analyzing the match rules on `AIGatewayRoute` spec to emulate the behavior in order to perform the AI Service specific transformation and authentication before the routing filter is applied, + because it happens at the very end of the filter chain. + +The `AIServiceBackend` rules are specified on the `AIGatewayRoute` based on model header matching, in this example `anthropic.claude-3-5-sonnet` is routed to the AWS Bedrock and `llama-3.3-70b-instruction` is routed to the KServe backend for the self-hosted llama model. +`LLMRequestCost` is specified with the metadata key `llm_total_token` to store the cost of the LLM request. + +```yaml +apiVersion: aigateway.envoyproxy.io/v1alpha1 +kind: AIGatewayRoute +metadata: + name: llmroute + namespace: ai-gateway +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + schema: + name: OpenAI + rules: + - matches: + - headers: + - type: Exact + name: x-ai-eg-model + value: anthropic.claude-3-5-sonnet-20240620-v1:0 + backendRefs: + - name: awsbedrock-backend + - matches: + - headers: + - type: Exact + name: x-ai-eg-model + value: llama-3.3-70b-instruction + backendRefs: + - name: kserve-llama-backend + # The following metadata keys are used to store the costs from the LLM request. + llmRequestCosts: + - metadataKey: llm_total_token + type: TotalToken + filterConfig: + externalProcess: + replicas: 1 +``` + +#### BackendSecurityPolicy +`BackendSecurityPolicy` specifies the API key or credentials that `Envoy AI Gateway` uses to authenticate with the upstream AI service. +In this example API key is used to authenticate with OpenAI service and AWS credential is used to authenticate with AWS Bedrock service. +```yaml +apiVersion: aigateway.envoyproxy.io/v1alpha1 +kind: BackendSecurityPolicy +metadata: + name: aws-bedrock-credential + namespace: default +spec: + type: AWSCredentials + awsCredentials: + region: us-east-1 + credentialsFile: + secretRef: + name: aws-credential + profile: default +--- +apiVersion: aigateway.envoyproxy.io/v1alpha1 +kind: BackendSecurityPolicy +metadata: + name: openai-ai-key + namespace: default +spec: + type: APIKey + apiKey: + secretRef: + name: openai-api-key +``` + +#### AIServiceBackend +Based on the gateway routes, we define the AWS Bedrock and KServe `AIServiceBackend` along with the `Envoy Gateway` backend resource using the FQDN for the routing destination. +```yaml +apiVersion: aigateway.envoyproxy.io/v1alpha1 +kind: AIServiceBackend +metadata: + name: awsbedrock-backend + namespace: ai-gateway +spec: + schema: + name: "AWSBedrock" + backendRef: + group: "gateway.envoyproxy.io" + kind: "Backend" + name: "llm-bedrock-backend" + BackendSecurityPolicyRef: + group: "aigateway.envoyproxy.io" + kind: "BackendSecurityPolicy" + name: "aws-bedrock-credential" +--- +apiVersion: aigateway.envoyproxy.io/v1alpha1 +kind: AIServiceBackend +metadata: + name: kserve-llama-backend + namespace: ai-gateway +spec: + schema: + name: "OpenAI" + backendRef: + group: "gateway.envoyproxy.io" + kind: "Backend" + name: "kserve-llama-backend" + BackendSecurityPolicyRef: + group: "aigateway.envoyproxy.io" + kind: "BackendSecurityPolicy" + name: "openai-ai-key" +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: kserve-llama-backend + namespace: ai-gateway +spec: + endpoints: + - fqdn: + hostname: llama-3-3-70b-instruct-vllm.example.com + port: 443 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: llm-bedrock-backend + namespace: ai-gateway +spec: + endpoints: + - fqdn: + hostname: bedrock-runtime.us-east-1.amazonaws.com + port: 443 + +``` + +#### BackendTrafficPolicy + +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: llama-ratelimit +spec: + # Applies the rate limit policy to the gateway. + targetRefs: + - name: eg + kind: Gateway + group: gateway.networking.k8s.io + rateLimit: + type: Global + global: + rules: + - clientSelectors: + - name: x-ai-eg-model + type: exact + value: llama-3.3-70b-instruction + - name: x-user-id + type: Distinct + limit: + # configure the number of allowed token per minute, per user and model + requests: 1000 + unit: Minute + cost: + response: + from: Metadata + metadata: + namespace: "io.envoy.ai_gateway" + key: "llm_total_token" +``` + +## Diagrams +### Control Plane +`Envoy AI Gateway` extends `Envoy Gateway` using an Extension Server. `Envoy Gateway` can be configured to call an external server over gRPC with +the xDS configuration before it is sent to Envoy Proxy. The `Envoy Gateway` extension Server provides a mechanism where `Envoy Gateway` tracks +custom resources and then calls a set of hooks that allow the generated xDS configuration to be modified before it is sent to Envoy Proxy. + +![Data Plane](./control_plane.png) + +AI Gateway ExtProc controller watches the `AIGatewayRoute` resource and perform the follow steps: +- Reconciles the `Envoy Gateway` deployment and creates the extension policy. +- Reconciles the `Envoy Gateway` ext proc deployment and mount the API key or AWS credential secret if the `AIServiceBackend` is AWS. +- Reconciles `AIGatewayRoute` to calculate the backend and generates the `HTTPRoute` resource attaching the upstream host rewrite filter. + +Envoy Gateway controller watches the `BackendTrafficPolicy` to dynamically update the xDS configuration for the rate limiting filter. + +### Data Plane + +Much of this is better explained visually: + +Below is a detailed view how an inference request works on `Envoy AI Gateway`. + +![Data Plane](./data_plane.png) + +This diagram lightly follows the example request for routing to Anthropic claude 3.5 sonnet model on AWS Bedrock. +The flow can be described as: +- The request comes into `Envoy AI Gateway` instances. +- Ext Authorization filter is applied for checking if the user or account is authorized to access the model. +- `Envoy AI Gateway` ExtProc calculates the backend by matching request headers such as model name and inject the routing header `x-ai-eg-selected-backend` for envoy routing filter. +- `Envoy AI Gateway` ExtProc translates the user inference request (OpenAI) to the API schema of the AI service backend. +- AI service authentication policy is applied based on the AI service backend: + - API key is injected to the request headers for the AI Services that supports API keys. + - AWS requests are signed by ExtProc and credentials are injected for AWS Bedrock service authentication. +- Rate limiting filter is applied for request based usage tracking. +- Request is routed by the envoy proxy to the specified or calculated AI service backend. +- Upon receiving the response from the AI service, the token usage limit is reduced by extracting the usage fields of the chat completion response. + - the rate limit is enforced on the subsequent request. + From eb0e9097c7312ba74cbcaa167d24ce40f6f64e53 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Thu, 30 Jan 2025 10:08:37 -0800 Subject: [PATCH 13/13] chore: reorganizes custom router/filterapi package (#234) **Commit Message**: This does the two re-organization of packages: * Renames `filterconfig` to `filterapi` * Put `extprocapi` into `filterapi/x` package The `filterapi/x` package will serve as an experimental package that facilitates the development of such features. Notably, custom routers etc while keeping the ability to introduce breaking changes at any release. This will be for advanced users as well as core contributors to iterate on some advanced features in the single repo. **Related Issues/PRs (if applicable)**: For #233 Signed-off-by: Takeshi Yoneda --- examples/extproc_custom_router/main.go | 20 ++++----- {filterconfig => filterapi}/filterconfig.go | 4 +- .../filterconfig_test.go | 10 ++--- extprocapi/extproc.go => filterapi/x/x.go | 14 +++---- internal/controller/ai_gateway_route.go | 4 +- internal/controller/ai_gateway_route_test.go | 4 +- internal/controller/sink.go | 32 +++++++------- internal/controller/sink_test.go | 42 +++++++++---------- internal/extproc/backendauth/api_key.go | 4 +- internal/extproc/backendauth/api_key_test.go | 6 +-- internal/extproc/backendauth/auth.go | 4 +- internal/extproc/backendauth/aws.go | 4 +- internal/extproc/backendauth/aws_test.go | 6 +-- internal/extproc/mocks_test.go | 12 +++--- internal/extproc/processor.go | 18 ++++---- internal/extproc/processor_test.go | 26 ++++++------ internal/extproc/router/request_body.go | 6 +-- internal/extproc/router/request_body_test.go | 6 +-- internal/extproc/router/router.go | 20 ++++----- internal/extproc/router/router_test.go | 32 +++++++------- internal/extproc/server.go | 10 ++--- internal/extproc/server_test.go | 40 +++++++++--------- internal/extproc/translator/translator.go | 10 ++--- .../extproc/translator/translator_test.go | 14 +++---- internal/extproc/watcher.go | 8 ++-- internal/extproc/watcher_test.go | 8 ++-- tests/extproc/custom_extproc_test.go | 10 ++--- tests/extproc/extproc_test.go | 12 +++--- tests/extproc/real_providers_test.go | 24 +++++------ tests/extproc/testupstream_test.go | 18 ++++---- 30 files changed, 214 insertions(+), 214 deletions(-) rename {filterconfig => filterapi}/filterconfig.go (98%) rename {filterconfig => filterapi}/filterconfig_test.go (91%) rename extprocapi/extproc.go => filterapi/x/x.go (65%) diff --git a/examples/extproc_custom_router/main.go b/examples/extproc_custom_router/main.go index 49ad5120e..56fe9fcfd 100644 --- a/examples/extproc_custom_router/main.go +++ b/examples/extproc_custom_router/main.go @@ -4,25 +4,25 @@ import ( "fmt" "github.com/envoyproxy/ai-gateway/cmd/extproc/mainlib" - "github.com/envoyproxy/ai-gateway/extprocapi" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" + "github.com/envoyproxy/ai-gateway/filterapi/x" ) -// newCustomRouter implements [extprocapi.NewCustomRouter]. -func newCustomRouter(defaultRouter extprocapi.Router, config *filterconfig.Config) extprocapi.Router { +// newCustomRouter implements [x.NewCustomRouter]. +func newCustomRouter(defaultRouter x.Router, config *filterapi.Config) x.Router { // You can poke the current configuration of the routes, and the list of backends // specified in the AIGatewayRoute.Rules, etc. return &myCustomRouter{config: config, defaultRouter: defaultRouter} } -// myCustomRouter implements [extprocapi.Router]. +// myCustomRouter implements [filterapi.Router]. type myCustomRouter struct { - config *filterconfig.Config - defaultRouter extprocapi.Router + config *filterapi.Config + defaultRouter x.Router } -// Calculate implements [extprocapi.Router.Calculate]. -func (m *myCustomRouter) Calculate(headers map[string]string) (backend *filterconfig.Backend, err error) { +// Calculate implements [x.Router.Calculate]. +func (m *myCustomRouter) Calculate(headers map[string]string) (backend *filterapi.Backend, err error) { // Simply logs the headers and delegates the calculation to the default router. modelName, ok := headers[m.config.ModelNameHeaderKey] if !ok { @@ -35,7 +35,7 @@ func (m *myCustomRouter) Calculate(headers map[string]string) (backend *filterco // This demonstrates how to build a custom router for the external processor. func main() { // Initializes the custom router. - extprocapi.NewCustomRouter = newCustomRouter + x.NewCustomRouter = newCustomRouter // Executes the main function of the external processor. mainlib.Main() } diff --git a/filterconfig/filterconfig.go b/filterapi/filterconfig.go similarity index 98% rename from filterconfig/filterconfig.go rename to filterapi/filterconfig.go index d22c7dfa2..5d4e3fe85 100644 --- a/filterconfig/filterconfig.go +++ b/filterapi/filterconfig.go @@ -1,4 +1,4 @@ -// Package filterconfig provides the configuration for the AI Gateway-implemented filter +// Package filterapi provides the configuration for the AI Gateway-implemented filter // which is currently an external processor (See https://github.com/envoyproxy/ai-gateway/issues/90). // // This is a public package so that the filter can be testable without @@ -7,7 +7,7 @@ // This configuration must be decoupled from the Envoy Gateway types as well as its implementation // details. Also, the configuration must not be tied with k8s so it can be tested and iterated // without the need for the k8s cluster. -package filterconfig +package filterapi import ( "os" diff --git a/filterconfig/filterconfig_test.go b/filterapi/filterconfig_test.go similarity index 91% rename from filterconfig/filterconfig_test.go rename to filterapi/filterconfig_test.go index b55acb580..aca35fe17 100644 --- a/filterconfig/filterconfig_test.go +++ b/filterapi/filterconfig_test.go @@ -1,4 +1,4 @@ -package filterconfig_test +package filterapi_test import ( "log/slog" @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/util/yaml" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" "github.com/envoyproxy/ai-gateway/internal/extproc" ) @@ -18,8 +18,8 @@ func TestDefaultConfig(t *testing.T) { require.NoError(t, err) require.NotNil(t, server) - var cfg filterconfig.Config - err = yaml.Unmarshal([]byte(filterconfig.DefaultConfig), &cfg) + var cfg filterapi.Config + err = yaml.Unmarshal([]byte(filterapi.DefaultConfig), &cfg) require.NoError(t, err) err = server.LoadConfig(&cfg) @@ -66,7 +66,7 @@ rules: value: gpt4.4444 ` require.NoError(t, os.WriteFile(configPath, []byte(config), 0o600)) - cfg, err := filterconfig.UnmarshalConfigYaml(configPath) + cfg, err := filterapi.UnmarshalConfigYaml(configPath) require.NoError(t, err) require.Equal(t, "ai_gateway_llm_ns", cfg.MetadataNamespace) require.Equal(t, "token_usage_key", cfg.LLMRequestCosts[0].MetadataKey) diff --git a/extprocapi/extproc.go b/filterapi/x/x.go similarity index 65% rename from extprocapi/extproc.go rename to filterapi/x/x.go index 7efbfa221..5fc102a94 100644 --- a/extprocapi/extproc.go +++ b/filterapi/x/x.go @@ -1,7 +1,7 @@ -// Package extprocapi is for building a custom external process. -package extprocapi +// Package x is an experimental package that provides the customizability of the AI Gateway filter. +package x -import "github.com/envoyproxy/ai-gateway/filterconfig" +import "github.com/envoyproxy/ai-gateway/filterapi" // NewCustomRouter is the function to create a custom router over the default router. // This is nil by default and can be set by the custom build of external processor. @@ -13,7 +13,7 @@ var NewCustomRouter NewCustomRouterFn // This is called when the new configuration is loaded. // // The defaultRouter can be used to delegate the calculation to the default router implementation. -type NewCustomRouterFn func(defaultRouter Router, config *filterconfig.Config) Router +type NewCustomRouterFn func(defaultRouter Router, config *filterapi.Config) Router // Router is the interface for the router. // @@ -21,9 +21,9 @@ type NewCustomRouterFn func(defaultRouter Router, config *filterconfig.Config) R type Router interface { // Calculate determines the backend to route to based on the request headers. // - // The request headers include the populated [filterconfig.Config.ModelNameHeaderKey] - // with the parsed model name based on the [filterconfig.Config] given to the NewCustomRouterFn. + // The request headers include the populated [filterapi.Config.ModelNameHeaderKey] + // with the parsed model name based on the [filterapi.Config] given to the NewCustomRouterFn. // // Returns the backend. - Calculate(requestHeaders map[string]string) (backend *filterconfig.Backend, err error) + Calculate(requestHeaders map[string]string) (backend *filterapi.Backend, err error) } diff --git a/internal/controller/ai_gateway_route.go b/internal/controller/ai_gateway_route.go index 9dfa25cc4..b08ac1c2b 100644 --- a/internal/controller/ai_gateway_route.go +++ b/internal/controller/ai_gateway_route.go @@ -18,7 +18,7 @@ import ( gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" aigv1a1 "github.com/envoyproxy/ai-gateway/api/v1alpha1" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) const ( @@ -152,7 +152,7 @@ func (c *aiGatewayRouteController) ensuresExtProcConfigMapExists(ctx context.Con Name: name, Namespace: aiGatewayRoute.Namespace, }, - Data: map[string]string{expProcConfigFileName: filterconfig.DefaultConfig}, + Data: map[string]string{expProcConfigFileName: filterapi.DefaultConfig}, } if err := ctrlutil.SetControllerReference(aiGatewayRoute, configMap, c.client.Scheme()); err != nil { c.logger.Error(err, "failed to set controller reference for service", "namespace", configMap.Namespace, "name", configMap.Name) diff --git a/internal/controller/ai_gateway_route_test.go b/internal/controller/ai_gateway_route_test.go index 2d073f5b8..9cc7b6b1c 100644 --- a/internal/controller/ai_gateway_route_test.go +++ b/internal/controller/ai_gateway_route_test.go @@ -17,7 +17,7 @@ import ( gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" aigv1a1 "github.com/envoyproxy/ai-gateway/api/v1alpha1" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) func Test_extProcName(t *testing.T) { @@ -46,7 +46,7 @@ func TestAIGatewayRouteController_ensuresExtProcConfigMapExists(t *testing.T) { require.Equal(t, extProcName(aiGatewayRoute), configMap.Name) require.Equal(t, "default", configMap.Namespace) require.Equal(t, ownerRef, configMap.OwnerReferences) - require.Equal(t, filterconfig.DefaultConfig, configMap.Data[expProcConfigFileName]) + require.Equal(t, filterapi.DefaultConfig, configMap.Data[expProcConfigFileName]) // Doing it again should not fail. err = c.ensuresExtProcConfigMapExists(context.Background(), aiGatewayRoute) diff --git a/internal/controller/sink.go b/internal/controller/sink.go index 33194b432..f0f307dd3 100644 --- a/internal/controller/sink.go +++ b/internal/controller/sink.go @@ -21,7 +21,7 @@ import ( "sigs.k8s.io/yaml" aigv1a1 "github.com/envoyproxy/ai-gateway/api/v1alpha1" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" "github.com/envoyproxy/ai-gateway/internal/llmcostcel" ) @@ -259,17 +259,17 @@ func (c *configSink) updateExtProcConfigMap(aiGatewayRoute *aigv1a1.AIGatewayRou panic(fmt.Errorf("failed to get configmap %s: %w", extProcName(aiGatewayRoute), err)) } - ec := &filterconfig.Config{UUID: uuid} + ec := &filterapi.Config{UUID: uuid} spec := &aiGatewayRoute.Spec - ec.Schema.Name = filterconfig.APISchemaName(spec.APISchema.Name) + ec.Schema.Name = filterapi.APISchemaName(spec.APISchema.Name) ec.Schema.Version = spec.APISchema.Version ec.ModelNameHeaderKey = aigv1a1.AIModelHeaderKey ec.SelectedBackendHeaderKey = selectedBackendHeaderKey - ec.Rules = make([]filterconfig.RouteRule, len(spec.Rules)) + ec.Rules = make([]filterapi.RouteRule, len(spec.Rules)) for i := range spec.Rules { rule := &spec.Rules[i] - ec.Rules[i].Backends = make([]filterconfig.Backend, len(rule.BackendRefs)) + ec.Rules[i].Backends = make([]filterapi.Backend, len(rule.BackendRefs)) for j := range rule.BackendRefs { backend := &rule.BackendRefs[j] key := fmt.Sprintf("%s.%s", backend.Name, aiGatewayRoute.Namespace) @@ -279,7 +279,7 @@ func (c *configSink) updateExtProcConfigMap(aiGatewayRoute *aigv1a1.AIGatewayRou if err != nil { return fmt.Errorf("failed to get AIServiceBackend %s: %w", key, err) } else { - ec.Rules[i].Backends[j].Schema.Name = filterconfig.APISchemaName(backendObj.Spec.APISchema.Name) + ec.Rules[i].Backends[j].Schema.Name = filterapi.APISchemaName(backendObj.Spec.APISchema.Name) ec.Rules[i].Backends[j].Schema.Version = backendObj.Spec.APISchema.Version } @@ -294,16 +294,16 @@ func (c *configSink) updateExtProcConfigMap(aiGatewayRoute *aigv1a1.AIGatewayRou switch backendSecurityPolicy.Spec.Type { case aigv1a1.BackendSecurityPolicyTypeAPIKey: - ec.Rules[i].Backends[j].Auth = &filterconfig.BackendAuth{ - APIKey: &filterconfig.APIKeyAuth{Filename: path.Join(backendSecurityMountPath(volumeName), "/apiKey")}, + ec.Rules[i].Backends[j].Auth = &filterapi.BackendAuth{ + APIKey: &filterapi.APIKeyAuth{Filename: path.Join(backendSecurityMountPath(volumeName), "/apiKey")}, } case aigv1a1.BackendSecurityPolicyTypeAWSCredentials: if backendSecurityPolicy.Spec.AWSCredentials == nil { return fmt.Errorf("AWSCredentials type selected but not defined %s", backendSecurityPolicy.Name) } if backendSecurityPolicy.Spec.AWSCredentials.CredentialsFile != nil { - ec.Rules[i].Backends[j].Auth = &filterconfig.BackendAuth{ - AWSAuth: &filterconfig.AWSAuth{ + ec.Rules[i].Backends[j].Auth = &filterapi.BackendAuth{ + AWSAuth: &filterapi.AWSAuth{ CredentialFileName: path.Join(backendSecurityMountPath(volumeName), "/credentials"), Region: backendSecurityPolicy.Spec.AWSCredentials.Region, }, @@ -315,7 +315,7 @@ func (c *configSink) updateExtProcConfigMap(aiGatewayRoute *aigv1a1.AIGatewayRou } } } - ec.Rules[i].Headers = make([]filterconfig.HeaderMatch, len(rule.Matches)) + ec.Rules[i].Headers = make([]filterapi.HeaderMatch, len(rule.Matches)) for j, match := range rule.Matches { ec.Rules[i].Headers[j].Name = match.Headers[0].Name ec.Rules[i].Headers[j].Value = match.Headers[0].Value @@ -324,16 +324,16 @@ func (c *configSink) updateExtProcConfigMap(aiGatewayRoute *aigv1a1.AIGatewayRou ec.MetadataNamespace = aigv1a1.AIGatewayFilterMetadataNamespace for _, cost := range aiGatewayRoute.Spec.LLMRequestCosts { - fc := filterconfig.LLMRequestCost{MetadataKey: cost.MetadataKey} + fc := filterapi.LLMRequestCost{MetadataKey: cost.MetadataKey} switch cost.Type { case aigv1a1.LLMRequestCostTypeInputToken: - fc.Type = filterconfig.LLMRequestCostTypeInputToken + fc.Type = filterapi.LLMRequestCostTypeInputToken case aigv1a1.LLMRequestCostTypeOutputToken: - fc.Type = filterconfig.LLMRequestCostTypeOutputToken + fc.Type = filterapi.LLMRequestCostTypeOutputToken case aigv1a1.LLMRequestCostTypeTotalToken: - fc.Type = filterconfig.LLMRequestCostTypeTotalToken + fc.Type = filterapi.LLMRequestCostTypeTotalToken case aigv1a1.LLMRequestCostTypeCEL: - fc.Type = filterconfig.LLMRequestCostTypeCELExpression + fc.Type = filterapi.LLMRequestCostTypeCELExpression expr := *cost.CELExpression // Sanity check the CEL expression. _, err := llmcostcel.NewProgram(expr) diff --git a/internal/controller/sink_test.go b/internal/controller/sink_test.go index da1515489..5eb4da08c 100644 --- a/internal/controller/sink_test.go +++ b/internal/controller/sink_test.go @@ -25,7 +25,7 @@ import ( gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" aigv1a1 "github.com/envoyproxy/ai-gateway/api/v1alpha1" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) func TestConfigSink_init(t *testing.T) { @@ -315,7 +315,7 @@ func Test_updateExtProcConfigMap(t *testing.T) { for _, tc := range []struct { name string route *aigv1a1.AIGatewayRoute - exp *filterconfig.Config + exp *filterapi.Config }{ { name: "basic", @@ -369,46 +369,46 @@ func Test_updateExtProcConfigMap(t *testing.T) { }, }, }, - exp: &filterconfig.Config{ + exp: &filterapi.Config{ UUID: string(uuid2.NewUUID()), - Schema: filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI, Version: "v123"}, + Schema: filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI, Version: "v123"}, ModelNameHeaderKey: aigv1a1.AIModelHeaderKey, MetadataNamespace: aigv1a1.AIGatewayFilterMetadataNamespace, SelectedBackendHeaderKey: selectedBackendHeaderKey, - Rules: []filterconfig.RouteRule{ + Rules: []filterapi.RouteRule{ { - Backends: []filterconfig.Backend{ - {Name: "apple.ns", Weight: 1, Schema: filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaAWSBedrock}, Auth: &filterconfig.BackendAuth{ - APIKey: &filterconfig.APIKeyAuth{ + Backends: []filterapi.Backend{ + {Name: "apple.ns", Weight: 1, Schema: filterapi.VersionedAPISchema{Name: filterapi.APISchemaAWSBedrock}, Auth: &filterapi.BackendAuth{ + APIKey: &filterapi.APIKeyAuth{ Filename: "/etc/backend_security_policy/rule0-backref0-some-backend-security-policy-1/apiKey", }, }}, {Name: "pineapple.ns", Weight: 2}, }, - Headers: []filterconfig.HeaderMatch{{Name: aigv1a1.AIModelHeaderKey, Value: "some-ai"}}, + Headers: []filterapi.HeaderMatch{{Name: aigv1a1.AIModelHeaderKey, Value: "some-ai"}}, }, { - Backends: []filterconfig.Backend{{Name: "cat.ns", Weight: 1, Auth: &filterconfig.BackendAuth{ - APIKey: &filterconfig.APIKeyAuth{ + Backends: []filterapi.Backend{{Name: "cat.ns", Weight: 1, Auth: &filterapi.BackendAuth{ + APIKey: &filterapi.APIKeyAuth{ Filename: "/etc/backend_security_policy/rule1-backref0-some-backend-security-policy-1/apiKey", }, }}}, - Headers: []filterconfig.HeaderMatch{{Name: aigv1a1.AIModelHeaderKey, Value: "another-ai"}}, + Headers: []filterapi.HeaderMatch{{Name: aigv1a1.AIModelHeaderKey, Value: "another-ai"}}, }, { - Backends: []filterconfig.Backend{{Name: "pen.ns", Weight: 2, Auth: &filterconfig.BackendAuth{ - AWSAuth: &filterconfig.AWSAuth{ + Backends: []filterapi.Backend{{Name: "pen.ns", Weight: 2, Auth: &filterapi.BackendAuth{ + AWSAuth: &filterapi.AWSAuth{ CredentialFileName: "/etc/backend_security_policy/rule2-backref0-some-backend-security-policy-2/credentials", Region: "us-east-1", }, }}}, - Headers: []filterconfig.HeaderMatch{{Name: aigv1a1.AIModelHeaderKey, Value: "another-ai-2"}}, + Headers: []filterapi.HeaderMatch{{Name: aigv1a1.AIModelHeaderKey, Value: "another-ai-2"}}, }, }, - LLMRequestCosts: []filterconfig.LLMRequestCost{ - {Type: filterconfig.LLMRequestCostTypeOutputToken, MetadataKey: "output-token"}, - {Type: filterconfig.LLMRequestCostTypeInputToken, MetadataKey: "input-token"}, - {Type: filterconfig.LLMRequestCostTypeTotalToken, MetadataKey: "total-token"}, - {Type: filterconfig.LLMRequestCostTypeCELExpression, MetadataKey: "cel-token", CELExpression: "model == 'cool_model' ? input_tokens * output_tokens : total_tokens"}, + LLMRequestCosts: []filterapi.LLMRequestCost{ + {Type: filterapi.LLMRequestCostTypeOutputToken, MetadataKey: "output-token"}, + {Type: filterapi.LLMRequestCostTypeInputToken, MetadataKey: "input-token"}, + {Type: filterapi.LLMRequestCostTypeTotalToken, MetadataKey: "total-token"}, + {Type: filterapi.LLMRequestCostTypeCELExpression, MetadataKey: "cel-token", CELExpression: "model == 'cool_model' ? input_tokens * output_tokens : total_tokens"}, }, }, }, @@ -427,7 +427,7 @@ func Test_updateExtProcConfigMap(t *testing.T) { require.NotNil(t, cm) data := cm.Data[expProcConfigFileName] - var actual filterconfig.Config + var actual filterapi.Config require.NoError(t, yaml.Unmarshal([]byte(data), &actual)) require.Equal(t, tc.exp, &actual) }) diff --git a/internal/extproc/backendauth/api_key.go b/internal/extproc/backendauth/api_key.go index 231624a9a..3a94fb0e5 100644 --- a/internal/extproc/backendauth/api_key.go +++ b/internal/extproc/backendauth/api_key.go @@ -8,7 +8,7 @@ import ( corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) // apiKeyHandler implements [Handler] for api key authz. @@ -16,7 +16,7 @@ type apiKeyHandler struct { apiKey string } -func newAPIKeyHandler(auth *filterconfig.APIKeyAuth) (Handler, error) { +func newAPIKeyHandler(auth *filterapi.APIKeyAuth) (Handler, error) { secret, err := os.ReadFile(auth.Filename) if err != nil { return nil, fmt.Errorf("failed to read api key file: %w", err) diff --git a/internal/extproc/backendauth/api_key_test.go b/internal/extproc/backendauth/api_key_test.go index 69e9b52ea..cfb3ce135 100644 --- a/internal/extproc/backendauth/api_key_test.go +++ b/internal/extproc/backendauth/api_key_test.go @@ -8,7 +8,7 @@ import ( extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" "github.com/stretchr/testify/require" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) func TestNewAPIKeyHandler(t *testing.T) { @@ -21,7 +21,7 @@ func TestNewAPIKeyHandler(t *testing.T) { require.NoError(t, err) require.NoError(t, f.Sync()) - auth := filterconfig.APIKeyAuth{Filename: apiKeyFile} + auth := filterapi.APIKeyAuth{Filename: apiKeyFile} handler, err := newAPIKeyHandler(&auth) require.NoError(t, err) require.NotNil(t, handler) @@ -39,7 +39,7 @@ func TestApiKeyHandler_Do(t *testing.T) { require.NoError(t, err) require.NoError(t, f.Sync()) - auth := filterconfig.APIKeyAuth{Filename: apiKeyFile} + auth := filterapi.APIKeyAuth{Filename: apiKeyFile} handler, err := newAPIKeyHandler(&auth) require.NoError(t, err) require.NotNil(t, handler) diff --git a/internal/extproc/backendauth/auth.go b/internal/extproc/backendauth/auth.go index 6264230a4..e8375843f 100644 --- a/internal/extproc/backendauth/auth.go +++ b/internal/extproc/backendauth/auth.go @@ -5,7 +5,7 @@ import ( extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) // Handler is the interface that deals with the backend auth for a specific backend. @@ -17,7 +17,7 @@ type Handler interface { } // NewHandler returns a new implementation of [Handler] based on the configuration. -func NewHandler(config *filterconfig.BackendAuth) (Handler, error) { +func NewHandler(config *filterapi.BackendAuth) (Handler, error) { if config.AWSAuth != nil { return newAWSHandler(config.AWSAuth) } else if config.APIKey != nil { diff --git a/internal/extproc/backendauth/aws.go b/internal/extproc/backendauth/aws.go index dc5b88c8c..65fbf098c 100644 --- a/internal/extproc/backendauth/aws.go +++ b/internal/extproc/backendauth/aws.go @@ -17,7 +17,7 @@ import ( corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) // awsHandler implements [Handler] for AWS Bedrock authz. @@ -27,7 +27,7 @@ type awsHandler struct { region string } -func newAWSHandler(awsAuth *filterconfig.AWSAuth) (*awsHandler, error) { +func newAWSHandler(awsAuth *filterapi.AWSAuth) (*awsHandler, error) { var credentials aws.Credentials var region string diff --git a/internal/extproc/backendauth/aws_test.go b/internal/extproc/backendauth/aws_test.go index d7eda2950..3a3da56cb 100644 --- a/internal/extproc/backendauth/aws_test.go +++ b/internal/extproc/backendauth/aws_test.go @@ -8,14 +8,14 @@ import ( extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" "github.com/stretchr/testify/require" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) func TestNewAWSHandler(t *testing.T) { t.Setenv("AWS_ACCESS_KEY_ID", "test") t.Setenv("AWS_SECRET_ACCESS_KEY", "secret") - handler, err := newAWSHandler(&filterconfig.AWSAuth{}) + handler, err := newAWSHandler(&filterapi.AWSAuth{}) require.NoError(t, err) require.NotNil(t, handler) } @@ -35,7 +35,7 @@ func TestAWSHandler_Do(t *testing.T) { require.NoError(t, err) require.NoError(t, file.Sync()) - credentialFileHandler, err := newAWSHandler(&filterconfig.AWSAuth{ + credentialFileHandler, err := newAWSHandler(&filterapi.AWSAuth{ CredentialFileName: awsCredentialFile, Region: "us-east-1", }) diff --git a/internal/extproc/mocks_test.go b/internal/extproc/mocks_test.go index f0e684204..d1f2d5f9e 100644 --- a/internal/extproc/mocks_test.go +++ b/internal/extproc/mocks_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc/metadata" - "github.com/envoyproxy/ai-gateway/extprocapi" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" + "github.com/envoyproxy/ai-gateway/filterapi/x" "github.com/envoyproxy/ai-gateway/internal/extproc/router" "github.com/envoyproxy/ai-gateway/internal/extproc/translator" ) @@ -21,7 +21,7 @@ import ( var ( _ ProcessorIface = &mockProcessor{} _ translator.Translator = &mockTranslator{} - _ extprocapi.Router = &mockRouter{} + _ x.Router = &mockRouter{} ) func newMockProcessor(_ *processorConfig, _ *slog.Logger) *mockProcessor { @@ -111,14 +111,14 @@ type mockRouter struct { t *testing.T expHeaders map[string]string retBackendName string - retVersionedAPISchema filterconfig.VersionedAPISchema + retVersionedAPISchema filterapi.VersionedAPISchema retErr error } // Calculate implements [router.Router.Calculate]. -func (m mockRouter) Calculate(headers map[string]string) (*filterconfig.Backend, error) { +func (m mockRouter) Calculate(headers map[string]string) (*filterapi.Backend, error) { require.Equal(m.t, m.expHeaders, headers) - b := &filterconfig.Backend{Name: m.retBackendName, Schema: m.retVersionedAPISchema} + b := &filterapi.Backend{Name: m.retBackendName, Schema: m.retVersionedAPISchema} return b, m.retErr } diff --git a/internal/extproc/processor.go b/internal/extproc/processor.go index 1e9cd616f..7af784240 100644 --- a/internal/extproc/processor.go +++ b/internal/extproc/processor.go @@ -14,8 +14,8 @@ import ( "github.com/google/cel-go/cel" "google.golang.org/protobuf/types/known/structpb" - "github.com/envoyproxy/ai-gateway/extprocapi" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" + "github.com/envoyproxy/ai-gateway/filterapi/x" "github.com/envoyproxy/ai-gateway/internal/extproc/backendauth" "github.com/envoyproxy/ai-gateway/internal/extproc/router" "github.com/envoyproxy/ai-gateway/internal/extproc/translator" @@ -27,16 +27,16 @@ import ( type processorConfig struct { uuid string bodyParser router.RequestBodyParser - router extprocapi.Router + router x.Router modelNameHeaderKey, selectedBackendHeaderKey string - factories map[filterconfig.VersionedAPISchema]translator.Factory + factories map[filterapi.VersionedAPISchema]translator.Factory backendAuthHandlers map[string]backendauth.Handler metadataNamespace string requestCosts []processorConfigRequestCost } type processorConfigRequestCost struct { - *filterconfig.LLMRequestCost + *filterapi.LLMRequestCost celProg cel.Program } @@ -219,13 +219,13 @@ func (p *Processor) maybeBuildDynamicMetadata() (*structpb.Struct, error) { c := &p.config.requestCosts[i] var cost uint32 switch c.Type { - case filterconfig.LLMRequestCostTypeInputToken: + case filterapi.LLMRequestCostTypeInputToken: cost = p.costs.InputTokens - case filterconfig.LLMRequestCostTypeOutputToken: + case filterapi.LLMRequestCostTypeOutputToken: cost = p.costs.OutputTokens - case filterconfig.LLMRequestCostTypeTotalToken: + case filterapi.LLMRequestCostTypeTotalToken: cost = p.costs.TotalTokens - case filterconfig.LLMRequestCostTypeCELExpression: + case filterapi.LLMRequestCostTypeCELExpression: costU64, err := llmcostcel.EvaluateProgram( c.celProg, p.requestHeaders[p.config.modelNameHeaderKey], diff --git a/internal/extproc/processor_test.go b/internal/extproc/processor_test.go index 5f9f687e0..862f84751 100644 --- a/internal/extproc/processor_test.go +++ b/internal/extproc/processor_test.go @@ -10,7 +10,7 @@ import ( extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" "github.com/stretchr/testify/require" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" "github.com/envoyproxy/ai-gateway/internal/extproc/router" "github.com/envoyproxy/ai-gateway/internal/extproc/translator" "github.com/envoyproxy/ai-gateway/internal/llmcostcel" @@ -74,15 +74,15 @@ func TestProcessor_ProcessResponseBody(t *testing.T) { p := &Processor{translator: mt, logger: slog.Default(), config: &processorConfig{ metadataNamespace: "ai_gateway_llm_ns", requestCosts: []processorConfigRequestCost{ - {LLMRequestCost: &filterconfig.LLMRequestCost{Type: filterconfig.LLMRequestCostTypeOutputToken, MetadataKey: "output_token_usage"}}, - {LLMRequestCost: &filterconfig.LLMRequestCost{Type: filterconfig.LLMRequestCostTypeInputToken, MetadataKey: "input_token_usage"}}, + {LLMRequestCost: &filterapi.LLMRequestCost{Type: filterapi.LLMRequestCostTypeOutputToken, MetadataKey: "output_token_usage"}}, + {LLMRequestCost: &filterapi.LLMRequestCost{Type: filterapi.LLMRequestCostTypeInputToken, MetadataKey: "input_token_usage"}}, { celProg: celProgInt, - LLMRequestCost: &filterconfig.LLMRequestCost{Type: filterconfig.LLMRequestCostTypeCELExpression, MetadataKey: "cel_int"}, + LLMRequestCost: &filterapi.LLMRequestCost{Type: filterapi.LLMRequestCostTypeCELExpression, MetadataKey: "cel_int"}, }, { celProg: celProgUint, - LLMRequestCost: &filterconfig.LLMRequestCost{Type: filterconfig.LLMRequestCostTypeCELExpression, MetadataKey: "cel_uint"}, + LLMRequestCost: &filterapi.LLMRequestCost{Type: filterapi.LLMRequestCostTypeCELExpression, MetadataKey: "cel_uint"}, }, }, }} @@ -125,11 +125,11 @@ func TestProcessor_ProcessRequestBody(t *testing.T) { rbp := mockRequestBodyParser{t: t, retModelName: "some-model", expPath: "/foo"} rt := mockRouter{ t: t, expHeaders: headers, retBackendName: "some-backend", - retVersionedAPISchema: filterconfig.VersionedAPISchema{Name: "some-schema", Version: "v10.0"}, + retVersionedAPISchema: filterapi.VersionedAPISchema{Name: "some-schema", Version: "v10.0"}, } p := &Processor{config: &processorConfig{ bodyParser: rbp.impl, router: rt, - factories: make(map[filterconfig.VersionedAPISchema]translator.Factory), + factories: make(map[filterapi.VersionedAPISchema]translator.Factory), }, requestHeaders: headers, logger: slog.Default()} _, err := p.ProcessRequestBody(context.Background(), &extprocv3.HttpBody{}) require.ErrorContains(t, err, "failed to find factory for output schema {\"some-schema\" \"v10.0\"}") @@ -139,12 +139,12 @@ func TestProcessor_ProcessRequestBody(t *testing.T) { rbp := mockRequestBodyParser{t: t, retModelName: "some-model", expPath: "/foo"} rt := mockRouter{ t: t, expHeaders: headers, retBackendName: "some-backend", - retVersionedAPISchema: filterconfig.VersionedAPISchema{Name: "some-schema", Version: "v10.0"}, + retVersionedAPISchema: filterapi.VersionedAPISchema{Name: "some-schema", Version: "v10.0"}, } factory := mockTranslatorFactory{t: t, retErr: errors.New("test error"), expPath: "/foo"} p := &Processor{config: &processorConfig{ bodyParser: rbp.impl, router: rt, - factories: map[filterconfig.VersionedAPISchema]translator.Factory{ + factories: map[filterapi.VersionedAPISchema]translator.Factory{ {Name: "some-schema", Version: "v10.0"}: factory.impl, }, }, requestHeaders: headers, logger: slog.Default()} @@ -156,12 +156,12 @@ func TestProcessor_ProcessRequestBody(t *testing.T) { rbp := mockRequestBodyParser{t: t, retModelName: "some-model", expPath: "/foo"} rt := mockRouter{ t: t, expHeaders: headers, retBackendName: "some-backend", - retVersionedAPISchema: filterconfig.VersionedAPISchema{Name: "some-schema", Version: "v10.0"}, + retVersionedAPISchema: filterapi.VersionedAPISchema{Name: "some-schema", Version: "v10.0"}, } factory := mockTranslatorFactory{t: t, retTranslator: mockTranslator{t: t, retErr: errors.New("test error")}, expPath: "/foo"} p := &Processor{config: &processorConfig{ bodyParser: rbp.impl, router: rt, - factories: map[filterconfig.VersionedAPISchema]translator.Factory{ + factories: map[filterapi.VersionedAPISchema]translator.Factory{ {Name: "some-schema", Version: "v10.0"}: factory.impl, }, }, requestHeaders: headers, logger: slog.Default()} @@ -174,7 +174,7 @@ func TestProcessor_ProcessRequestBody(t *testing.T) { rbp := mockRequestBodyParser{t: t, retModelName: "some-model", expPath: "/foo", retRb: someBody} rt := mockRouter{ t: t, expHeaders: headers, retBackendName: "some-backend", - retVersionedAPISchema: filterconfig.VersionedAPISchema{Name: "some-schema", Version: "v10.0"}, + retVersionedAPISchema: filterapi.VersionedAPISchema{Name: "some-schema", Version: "v10.0"}, } headerMut := &extprocv3.HeaderMutation{} bodyMut := &extprocv3.BodyMutation{} @@ -182,7 +182,7 @@ func TestProcessor_ProcessRequestBody(t *testing.T) { factory := mockTranslatorFactory{t: t, retTranslator: mt, expPath: "/foo"} p := &Processor{config: &processorConfig{ bodyParser: rbp.impl, router: rt, - factories: map[filterconfig.VersionedAPISchema]translator.Factory{ + factories: map[filterapi.VersionedAPISchema]translator.Factory{ {Name: "some-schema", Version: "v10.0"}: factory.impl, }, selectedBackendHeaderKey: "x-ai-gateway-backend-key", diff --git a/internal/extproc/router/request_body.go b/internal/extproc/router/request_body.go index d1361fac8..724e039f7 100644 --- a/internal/extproc/router/request_body.go +++ b/internal/extproc/router/request_body.go @@ -6,7 +6,7 @@ import ( extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" "github.com/envoyproxy/ai-gateway/internal/apischema/openai" ) @@ -14,8 +14,8 @@ import ( type RequestBodyParser func(path string, body *extprocv3.HttpBody) (modelName string, rb RequestBody, err error) // NewRequestBodyParser creates a new RequestBodyParser based on the schema. -func NewRequestBodyParser(schema filterconfig.VersionedAPISchema) (RequestBodyParser, error) { - if schema.Name == filterconfig.APISchemaOpenAI { +func NewRequestBodyParser(schema filterapi.VersionedAPISchema) (RequestBodyParser, error) { + if schema.Name == filterapi.APISchemaOpenAI { return openAIParseBody, nil } return nil, fmt.Errorf("unsupported API schema: %s", schema) diff --git a/internal/extproc/router/request_body_test.go b/internal/extproc/router/request_body_test.go index e42469e9f..dc76f9f1d 100644 --- a/internal/extproc/router/request_body_test.go +++ b/internal/extproc/router/request_body_test.go @@ -7,18 +7,18 @@ import ( extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" "github.com/stretchr/testify/require" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" "github.com/envoyproxy/ai-gateway/internal/apischema/openai" ) func TestNewRequestBodyParser(t *testing.T) { t.Run("ok", func(t *testing.T) { - res, err := NewRequestBodyParser(filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI}) + res, err := NewRequestBodyParser(filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI}) require.NotNil(t, res) require.NoError(t, err) }) t.Run("error", func(t *testing.T) { - res, err := NewRequestBodyParser(filterconfig.VersionedAPISchema{Name: "foo"}) + res, err := NewRequestBodyParser(filterapi.VersionedAPISchema{Name: "foo"}) require.Nil(t, res) require.Error(t, err) }) diff --git a/internal/extproc/router/router.go b/internal/extproc/router/router.go index acb8996e5..87f5b4f96 100644 --- a/internal/extproc/router/router.go +++ b/internal/extproc/router/router.go @@ -6,18 +6,18 @@ import ( "golang.org/x/exp/rand" - "github.com/envoyproxy/ai-gateway/extprocapi" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" + "github.com/envoyproxy/ai-gateway/filterapi/x" ) -// router implements [extprocapi.Router]. +// router implements [filterapi.Router]. type router struct { - rules []filterconfig.RouteRule + rules []filterapi.RouteRule rng *rand.Rand } -// NewRouter creates a new [extprocapi.Router] implementation for the given config. -func NewRouter(config *filterconfig.Config, newCustomFn extprocapi.NewCustomRouterFn) (extprocapi.Router, error) { +// NewRouter creates a new [filterapi.Router] implementation for the given config. +func NewRouter(config *filterapi.Config, newCustomFn x.NewCustomRouterFn) (x.Router, error) { r := &router{rules: config.Rules, rng: rand.New(rand.NewSource(uint64(time.Now().UnixNano())))} //nolint:gosec if newCustomFn != nil { customRouter := newCustomFn(r, config) @@ -26,9 +26,9 @@ func NewRouter(config *filterconfig.Config, newCustomFn extprocapi.NewCustomRout return r, nil } -// Calculate implements [extprocapi.Router.Calculate]. -func (r *router) Calculate(headers map[string]string) (backend *filterconfig.Backend, err error) { - var rule *filterconfig.RouteRule +// Calculate implements [filterapi.Router.Calculate]. +func (r *router) Calculate(headers map[string]string) (backend *filterapi.Backend, err error) { + var rule *filterapi.RouteRule for i := range r.rules { _rule := &r.rules[i] for _, hdr := range _rule.Headers { @@ -46,7 +46,7 @@ func (r *router) Calculate(headers map[string]string) (backend *filterconfig.Bac return r.selectBackendFromRule(rule), nil } -func (r *router) selectBackendFromRule(rule *filterconfig.RouteRule) (backend *filterconfig.Backend) { +func (r *router) selectBackendFromRule(rule *filterapi.RouteRule) (backend *filterapi.Backend) { // Each backend has a weight, so we randomly select depending on the weight. // This is a pretty naive implementation and can be buggy, so fix it later. totalWeight := 0 diff --git a/internal/extproc/router/router_test.go b/internal/extproc/router/router_test.go index 15f7f2c8c..c6a359b02 100644 --- a/internal/extproc/router/router_test.go +++ b/internal/extproc/router/router_test.go @@ -5,20 +5,20 @@ import ( "github.com/stretchr/testify/require" - "github.com/envoyproxy/ai-gateway/extprocapi" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" + "github.com/envoyproxy/ai-gateway/filterapi/x" ) -// dummyCustomRouter implements [extprocapi.Router]. +// dummyCustomRouter implements [filterapi.Router]. type dummyCustomRouter struct{ called bool } -func (c *dummyCustomRouter) Calculate(map[string]string) (*filterconfig.Backend, error) { +func (c *dummyCustomRouter) Calculate(map[string]string) (*filterapi.Backend, error) { c.called = true return nil, nil } func TestRouter_NewRouter_Custom(t *testing.T) { - r, err := NewRouter(&filterconfig.Config{}, func(defaultRouter extprocapi.Router, config *filterconfig.Config) extprocapi.Router { + r, err := NewRouter(&filterapi.Config{}, func(defaultRouter x.Router, config *filterapi.Config) x.Router { require.NotNil(t, defaultRouter) _, ok := defaultRouter.(*router) require.True(t, ok) // Checking if the default router is correctly passed. @@ -34,23 +34,23 @@ func TestRouter_NewRouter_Custom(t *testing.T) { } func TestRouter_Calculate(t *testing.T) { - outSchema := filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI} - _r, err := NewRouter(&filterconfig.Config{ - Rules: []filterconfig.RouteRule{ + outSchema := filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI} + _r, err := NewRouter(&filterapi.Config{ + Rules: []filterapi.RouteRule{ { - Backends: []filterconfig.Backend{ + Backends: []filterapi.Backend{ {Name: "foo", Schema: outSchema, Weight: 1}, {Name: "bar", Schema: outSchema, Weight: 3}, }, - Headers: []filterconfig.HeaderMatch{ + Headers: []filterapi.HeaderMatch{ {Name: "x-model-name", Value: "llama3.3333"}, }, }, { - Backends: []filterconfig.Backend{ + Backends: []filterapi.Backend{ {Name: "openai", Schema: outSchema}, }, - Headers: []filterconfig.HeaderMatch{ + Headers: []filterapi.HeaderMatch{ {Name: "x-model-name", Value: "gpt4.4444"}, }, }, @@ -87,15 +87,15 @@ func TestRouter_Calculate(t *testing.T) { } func TestRouter_selectBackendFromRule(t *testing.T) { - _r, err := NewRouter(&filterconfig.Config{}, nil) + _r, err := NewRouter(&filterapi.Config{}, nil) require.NoError(t, err) r, ok := _r.(*router) require.True(t, ok) - outSchema := filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI} + outSchema := filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI} - rule := &filterconfig.RouteRule{ - Backends: []filterconfig.Backend{ + rule := &filterapi.RouteRule{ + Backends: []filterapi.Backend{ {Name: "foo", Schema: outSchema, Weight: 1}, {Name: "bar", Schema: outSchema, Weight: 3}, }, diff --git a/internal/extproc/server.go b/internal/extproc/server.go index fb67affc6..533e3418f 100644 --- a/internal/extproc/server.go +++ b/internal/extproc/server.go @@ -16,8 +16,8 @@ import ( "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/status" - "github.com/envoyproxy/ai-gateway/extprocapi" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" + "github.com/envoyproxy/ai-gateway/filterapi/x" "github.com/envoyproxy/ai-gateway/internal/extproc/backendauth" "github.com/envoyproxy/ai-gateway/internal/extproc/router" "github.com/envoyproxy/ai-gateway/internal/extproc/translator" @@ -44,17 +44,17 @@ func NewServer[P ProcessorIface](logger *slog.Logger, newProcessor func(*process } // LoadConfig updates the configuration of the external processor. -func (s *Server[P]) LoadConfig(config *filterconfig.Config) error { +func (s *Server[P]) LoadConfig(config *filterapi.Config) error { bodyParser, err := router.NewRequestBodyParser(config.Schema) if err != nil { return fmt.Errorf("cannot create request body parser: %w", err) } - rt, err := router.NewRouter(config, extprocapi.NewCustomRouter) + rt, err := router.NewRouter(config, x.NewCustomRouter) if err != nil { return fmt.Errorf("cannot create router: %w", err) } - factories := make(map[filterconfig.VersionedAPISchema]translator.Factory) + factories := make(map[filterapi.VersionedAPISchema]translator.Factory) backendAuthHandlers := make(map[string]backendauth.Handler) for _, r := range config.Rules { for _, b := range r.Backends { diff --git a/internal/extproc/server_test.go b/internal/extproc/server_test.go index f82551ed0..4d44ebf7a 100644 --- a/internal/extproc/server_test.go +++ b/internal/extproc/server_test.go @@ -15,7 +15,7 @@ import ( "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/status" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" "github.com/envoyproxy/ai-gateway/internal/llmcostcel" ) @@ -30,29 +30,29 @@ func requireNewServerWithMockProcessor(t *testing.T) *Server[*mockProcessor] { func TestServer_LoadConfig(t *testing.T) { t.Run("invalid input schema", func(t *testing.T) { s := requireNewServerWithMockProcessor(t) - err := s.LoadConfig(&filterconfig.Config{ - Schema: filterconfig.VersionedAPISchema{Name: "some-invalid-schema"}, + err := s.LoadConfig(&filterapi.Config{ + Schema: filterapi.VersionedAPISchema{Name: "some-invalid-schema"}, }) require.Error(t, err) require.ErrorContains(t, err, "cannot create request body parser") }) t.Run("ok", func(t *testing.T) { - config := &filterconfig.Config{ + config := &filterapi.Config{ MetadataNamespace: "ns", - LLMRequestCosts: []filterconfig.LLMRequestCost{ - {MetadataKey: "key", Type: filterconfig.LLMRequestCostTypeOutputToken}, - {MetadataKey: "cel_key", Type: filterconfig.LLMRequestCostTypeCELExpression, CELExpression: "1 + 1"}, + LLMRequestCosts: []filterapi.LLMRequestCost{ + {MetadataKey: "key", Type: filterapi.LLMRequestCostTypeOutputToken}, + {MetadataKey: "cel_key", Type: filterapi.LLMRequestCostTypeCELExpression, CELExpression: "1 + 1"}, }, - Schema: filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI}, + Schema: filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI}, SelectedBackendHeaderKey: "x-ai-eg-selected-backend", ModelNameHeaderKey: "x-model-name", - Rules: []filterconfig.RouteRule{ + Rules: []filterapi.RouteRule{ { - Backends: []filterconfig.Backend{ - {Name: "kserve", Schema: filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI}}, - {Name: "awsbedrock", Schema: filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaAWSBedrock}}, + Backends: []filterapi.Backend{ + {Name: "kserve", Schema: filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI}}, + {Name: "awsbedrock", Schema: filterapi.VersionedAPISchema{Name: filterapi.APISchemaAWSBedrock}}, }, - Headers: []filterconfig.HeaderMatch{ + Headers: []filterapi.HeaderMatch{ { Name: "x-model-name", Value: "llama3.3333", @@ -60,10 +60,10 @@ func TestServer_LoadConfig(t *testing.T) { }, }, { - Backends: []filterconfig.Backend{ - {Name: "openai", Schema: filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI}}, + Backends: []filterapi.Backend{ + {Name: "openai", Schema: filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI}}, }, - Headers: []filterconfig.HeaderMatch{ + Headers: []filterapi.HeaderMatch{ { Name: "x-model-name", Value: "gpt4.4444", @@ -83,13 +83,13 @@ func TestServer_LoadConfig(t *testing.T) { require.Equal(t, "x-ai-eg-selected-backend", s.config.selectedBackendHeaderKey) require.Equal(t, "x-model-name", s.config.modelNameHeaderKey) require.Len(t, s.config.factories, 2) - require.NotNil(t, s.config.factories[filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI}]) - require.NotNil(t, s.config.factories[filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaAWSBedrock}]) + require.NotNil(t, s.config.factories[filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI}]) + require.NotNil(t, s.config.factories[filterapi.VersionedAPISchema{Name: filterapi.APISchemaAWSBedrock}]) require.Len(t, s.config.requestCosts, 2) - require.Equal(t, filterconfig.LLMRequestCostTypeOutputToken, s.config.requestCosts[0].Type) + require.Equal(t, filterapi.LLMRequestCostTypeOutputToken, s.config.requestCosts[0].Type) require.Equal(t, "key", s.config.requestCosts[0].MetadataKey) - require.Equal(t, filterconfig.LLMRequestCostTypeCELExpression, s.config.requestCosts[1].Type) + require.Equal(t, filterapi.LLMRequestCostTypeCELExpression, s.config.requestCosts[1].Type) require.Equal(t, "1 + 1", s.config.requestCosts[1].CELExpression) prog := s.config.requestCosts[1].celProg require.NotNil(t, prog) diff --git a/internal/extproc/translator/translator.go b/internal/extproc/translator/translator.go index ed2eb603a..aa2b1e90e 100644 --- a/internal/extproc/translator/translator.go +++ b/internal/extproc/translator/translator.go @@ -8,7 +8,7 @@ import ( extprocv3http "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3" extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" "github.com/envoyproxy/ai-gateway/internal/extproc/router" ) @@ -35,13 +35,13 @@ func isGoodStatusCode(code int) bool { type Factory func(path string) (Translator, error) // NewFactory returns a callback function that creates a translator for the given API schema combination. -func NewFactory(in, out filterconfig.VersionedAPISchema) (Factory, error) { - if in.Name == filterconfig.APISchemaOpenAI { +func NewFactory(in, out filterapi.VersionedAPISchema) (Factory, error) { + if in.Name == filterapi.APISchemaOpenAI { // TODO: currently, we ignore the LLMAPISchema."Version" field. switch out.Name { - case filterconfig.APISchemaOpenAI: + case filterapi.APISchemaOpenAI: return newOpenAIToOpenAITranslator, nil - case filterconfig.APISchemaAWSBedrock: + case filterapi.APISchemaAWSBedrock: return newOpenAIToAWSBedrockTranslator, nil } } diff --git a/internal/extproc/translator/translator_test.go b/internal/extproc/translator/translator_test.go index 64c420236..bec838993 100644 --- a/internal/extproc/translator/translator_test.go +++ b/internal/extproc/translator/translator_test.go @@ -5,21 +5,21 @@ import ( "github.com/stretchr/testify/require" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) func TestNewFactory(t *testing.T) { t.Run("error", func(t *testing.T) { _, err := NewFactory( - filterconfig.VersionedAPISchema{Name: "Foo", Version: "v100"}, - filterconfig.VersionedAPISchema{Name: "Bar", Version: "v123"}, + filterapi.VersionedAPISchema{Name: "Foo", Version: "v100"}, + filterapi.VersionedAPISchema{Name: "Bar", Version: "v123"}, ) require.ErrorContains(t, err, "unsupported API schema combination: client={Foo v100}, backend={Bar v123}") }) t.Run("openai to openai", func(t *testing.T) { f, err := NewFactory( - filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI}, - filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI}, + filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI}, + filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI}, ) require.NoError(t, err) require.NotNil(t, f) @@ -32,8 +32,8 @@ func TestNewFactory(t *testing.T) { }) t.Run("openai to aws bedrock", func(t *testing.T) { f, err := NewFactory( - filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI}, - filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaAWSBedrock}, + filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI}, + filterapi.VersionedAPISchema{Name: filterapi.APISchemaAWSBedrock}, ) require.NoError(t, err) require.NotNil(t, f) diff --git a/internal/extproc/watcher.go b/internal/extproc/watcher.go index 14dce6135..70ffab516 100644 --- a/internal/extproc/watcher.go +++ b/internal/extproc/watcher.go @@ -8,13 +8,13 @@ import ( "strings" "time" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) -// ConfigReceiver is an interface that can receive *filterconfig.Config updates. +// ConfigReceiver is an interface that can receive *filterapi.Config updates. type ConfigReceiver interface { // LoadConfig updates the configuration. - LoadConfig(config *filterconfig.Config) error + LoadConfig(config *filterapi.Config) error } type configWatcher struct { @@ -81,7 +81,7 @@ func (cw *configWatcher) loadConfig(ctx context.Context) error { cw.diff(previous, current) } - cfg, err := filterconfig.UnmarshalConfigYaml(cw.path) + cfg, err := filterapi.UnmarshalConfigYaml(cw.path) if err != nil { return err } diff --git a/internal/extproc/watcher_test.go b/internal/extproc/watcher_test.go index 46000c62e..3f383bf57 100644 --- a/internal/extproc/watcher_test.go +++ b/internal/extproc/watcher_test.go @@ -12,24 +12,24 @@ import ( "github.com/stretchr/testify/require" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) // mockReceiver is a mock implementation of Receiver. type mockReceiver struct { - cfg *filterconfig.Config + cfg *filterapi.Config mux sync.Mutex } // LoadConfig implements ConfigReceiver. -func (m *mockReceiver) LoadConfig(cfg *filterconfig.Config) error { +func (m *mockReceiver) LoadConfig(cfg *filterapi.Config) error { m.mux.Lock() defer m.mux.Unlock() m.cfg = cfg return nil } -func (m *mockReceiver) getConfig() *filterconfig.Config { +func (m *mockReceiver) getConfig() *filterapi.Config { m.mux.Lock() defer m.mux.Unlock() return m.cfg diff --git a/tests/extproc/custom_extproc_test.go b/tests/extproc/custom_extproc_test.go index 44f21a17e..b86ef2e89 100644 --- a/tests/extproc/custom_extproc_test.go +++ b/tests/extproc/custom_extproc_test.go @@ -15,7 +15,7 @@ import ( "github.com/openai/openai-go/option" "github.com/stretchr/testify/require" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" "github.com/envoyproxy/ai-gateway/tests/internal/testupstreamlib" ) @@ -25,15 +25,15 @@ func TestExtProcCustomRouter(t *testing.T) { requireRunEnvoy(t, "/dev/null") requireTestUpstream(t) configPath := t.TempDir() + "/extproc-config.yaml" - requireWriteFilterConfig(t, configPath, &filterconfig.Config{ + requireWriteFilterConfig(t, configPath, &filterapi.Config{ Schema: openAISchema, // This can be any header key, but it must match the envoy.yaml routing configuration. SelectedBackendHeaderKey: "x-selected-backend-name", ModelNameHeaderKey: "x-model-name", - Rules: []filterconfig.RouteRule{ + Rules: []filterapi.RouteRule{ { - Backends: []filterconfig.Backend{{Name: "testupstream", Schema: openAISchema}}, - Headers: []filterconfig.HeaderMatch{{Name: "x-model-name", Value: "something-cool"}}, + Backends: []filterapi.Backend{{Name: "testupstream", Schema: openAISchema}}, + Headers: []filterapi.HeaderMatch{{Name: "x-model-name", Value: "something-cool"}}, }, }, }) diff --git a/tests/extproc/extproc_test.go b/tests/extproc/extproc_test.go index cb0be1f59..950c3beb9 100644 --- a/tests/extproc/extproc_test.go +++ b/tests/extproc/extproc_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) const listenerAddress = "http://localhost:1062" @@ -25,14 +25,14 @@ const listenerAddress = "http://localhost:1062" var envoyYamlBase string var ( - openAISchema = filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaOpenAI} - awsBedrockSchema = filterconfig.VersionedAPISchema{Name: filterconfig.APISchemaAWSBedrock} + openAISchema = filterapi.VersionedAPISchema{Name: filterapi.APISchemaOpenAI} + awsBedrockSchema = filterapi.VersionedAPISchema{Name: filterapi.APISchemaAWSBedrock} ) // requireExtProc starts the external processor with the provided executable and configPath // with additional environment variables. // -// The config must be in YAML format specified in [filterconfig.Config] type. +// The config must be in YAML format specified in [filterapi.Config] type. func requireExtProc(t *testing.T, stdout io.Writer, executable, configPath string, envs ...string) { cmd := exec.Command(executable) cmd.Stdout = stdout @@ -93,8 +93,8 @@ func getEnvVarOrSkip(t *testing.T, envVar string) string { return value } -// requireWriteFilterConfig writes the provided [filterconfig.Config] to the configPath in YAML format. -func requireWriteFilterConfig(t *testing.T, configPath string, config *filterconfig.Config) { +// requireWriteFilterConfig writes the provided [filterapi.Config] to the configPath in YAML format. +func requireWriteFilterConfig(t *testing.T, configPath string, config *filterapi.Config) { configBytes, err := yaml.Marshal(config) require.NoError(t, err) require.NoError(t, os.WriteFile(configPath, configBytes, 0o600)) diff --git a/tests/extproc/real_providers_test.go b/tests/extproc/real_providers_test.go index 0dd0a92a2..117882451 100644 --- a/tests/extproc/real_providers_test.go +++ b/tests/extproc/real_providers_test.go @@ -17,7 +17,7 @@ import ( "github.com/openai/openai-go/option" "github.com/stretchr/testify/require" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" ) // TestRealProviders tests the end-to-end flow of the external processor with Envoy and real providers. @@ -58,31 +58,31 @@ func TestWithRealProviders(t *testing.T) { require.NoError(t, err) require.NoError(t, awsFile.Sync()) - requireWriteFilterConfig(t, configPath, &filterconfig.Config{ + requireWriteFilterConfig(t, configPath, &filterapi.Config{ MetadataNamespace: "ai_gateway_llm_ns", - LLMRequestCosts: []filterconfig.LLMRequestCost{ - {MetadataKey: "used_token", Type: filterconfig.LLMRequestCostTypeInputToken}, - {MetadataKey: "some_cel", Type: filterconfig.LLMRequestCostTypeCELExpression, CELExpression: "1+1"}, + LLMRequestCosts: []filterapi.LLMRequestCost{ + {MetadataKey: "used_token", Type: filterapi.LLMRequestCostTypeInputToken}, + {MetadataKey: "some_cel", Type: filterapi.LLMRequestCostTypeCELExpression, CELExpression: "1+1"}, }, Schema: openAISchema, // This can be any header key, but it must match the envoy.yaml routing configuration. SelectedBackendHeaderKey: "x-selected-backend-name", ModelNameHeaderKey: "x-model-name", - Rules: []filterconfig.RouteRule{ + Rules: []filterapi.RouteRule{ { - Backends: []filterconfig.Backend{{Name: "openai", Schema: openAISchema, Auth: &filterconfig.BackendAuth{ - APIKey: &filterconfig.APIKeyAuth{Filename: apiKeyFilePath}, + Backends: []filterapi.Backend{{Name: "openai", Schema: openAISchema, Auth: &filterapi.BackendAuth{ + APIKey: &filterapi.APIKeyAuth{Filename: apiKeyFilePath}, }}}, - Headers: []filterconfig.HeaderMatch{{Name: "x-model-name", Value: "gpt-4o-mini"}}, + Headers: []filterapi.HeaderMatch{{Name: "x-model-name", Value: "gpt-4o-mini"}}, }, { - Backends: []filterconfig.Backend{ - {Name: "aws-bedrock", Schema: awsBedrockSchema, Auth: &filterconfig.BackendAuth{AWSAuth: &filterconfig.AWSAuth{ + Backends: []filterapi.Backend{ + {Name: "aws-bedrock", Schema: awsBedrockSchema, Auth: &filterapi.BackendAuth{AWSAuth: &filterapi.AWSAuth{ CredentialFileName: awsFilePath, Region: "us-east-1", }}}, }, - Headers: []filterconfig.HeaderMatch{ + Headers: []filterapi.HeaderMatch{ {Name: "x-model-name", Value: "us.meta.llama3-2-1b-instruct-v1:0"}, {Name: "x-model-name", Value: "us.anthropic.claude-3-5-sonnet-20240620-v1:0"}, }, diff --git a/tests/extproc/testupstream_test.go b/tests/extproc/testupstream_test.go index 6354ddf22..eb426fe7c 100644 --- a/tests/extproc/testupstream_test.go +++ b/tests/extproc/testupstream_test.go @@ -15,7 +15,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" - "github.com/envoyproxy/ai-gateway/filterconfig" + "github.com/envoyproxy/ai-gateway/filterapi" "github.com/envoyproxy/ai-gateway/tests/internal/testupstreamlib" ) @@ -29,23 +29,23 @@ func TestWithTestUpstream(t *testing.T) { configPath := t.TempDir() + "/extproc-config.yaml" requireTestUpstream(t) - requireWriteFilterConfig(t, configPath, &filterconfig.Config{ + requireWriteFilterConfig(t, configPath, &filterapi.Config{ MetadataNamespace: "ai_gateway_llm_ns", - LLMRequestCosts: []filterconfig.LLMRequestCost{ - {MetadataKey: "used_token", Type: filterconfig.LLMRequestCostTypeInputToken}, + LLMRequestCosts: []filterapi.LLMRequestCost{ + {MetadataKey: "used_token", Type: filterapi.LLMRequestCostTypeInputToken}, }, Schema: openAISchema, // This can be any header key, but it must match the envoy.yaml routing configuration. SelectedBackendHeaderKey: "x-selected-backend-name", ModelNameHeaderKey: "x-model-name", - Rules: []filterconfig.RouteRule{ + Rules: []filterapi.RouteRule{ { - Backends: []filterconfig.Backend{{Name: "testupstream", Schema: openAISchema}}, - Headers: []filterconfig.HeaderMatch{{Name: "x-test-backend", Value: "openai"}}, + Backends: []filterapi.Backend{{Name: "testupstream", Schema: openAISchema}}, + Headers: []filterapi.HeaderMatch{{Name: "x-test-backend", Value: "openai"}}, }, { - Backends: []filterconfig.Backend{{Name: "testupstream", Schema: awsBedrockSchema}}, - Headers: []filterconfig.HeaderMatch{{Name: "x-test-backend", Value: "aws-bedrock"}}, + Backends: []filterapi.Backend{{Name: "testupstream", Schema: awsBedrockSchema}}, + Headers: []filterapi.HeaderMatch{{Name: "x-test-backend", Value: "aws-bedrock"}}, }, }, })