-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
extproc: adds ability to insert custom router (#104)
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
- Loading branch information
Showing
15 changed files
with
351 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,78 +1,5 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"log" | ||
"log/slog" | ||
"net" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
"time" | ||
import "github.com/envoyproxy/ai-gateway/cmd/extproc/mainlib" | ||
|
||
extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/health/grpc_health_v1" | ||
|
||
"github.com/envoyproxy/ai-gateway/internal/extproc" | ||
"github.com/envoyproxy/ai-gateway/internal/version" | ||
) | ||
|
||
var ( | ||
configPath = flag.String("configPath", "", "path to the configuration file. "+ | ||
"The file must be in YAML format specified in extprocconfig.Config type. The configuration file is watched for changes.") | ||
// TODO: unix domain socket support. | ||
extProcPort = flag.String("extProcPort", ":1063", "gRPC port for the external processor") | ||
logLevel = flag.String("logLevel", "info", "log level") | ||
) | ||
|
||
func main() { | ||
flag.Parse() | ||
|
||
var level slog.Level | ||
if err := level.UnmarshalText([]byte(*logLevel)); err != nil { | ||
log.Fatalf("failed to unmarshal log level: %v", err) | ||
} | ||
l := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ | ||
Level: level, | ||
})) | ||
|
||
l.Info("starting external processor", slog.String("version", version.Version)) | ||
|
||
if *configPath == "" { | ||
log.Fatal("configPath must be provided") | ||
} | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
signalsChan := make(chan os.Signal, 1) | ||
signal.Notify(signalsChan, syscall.SIGINT, syscall.SIGTERM) | ||
go func() { | ||
<-signalsChan | ||
cancel() | ||
}() | ||
|
||
// TODO: unix domain socket support. | ||
lis, err := net.Listen("tcp", *extProcPort) | ||
if err != nil { | ||
log.Fatalf("failed to listen: %v", err) | ||
} | ||
|
||
server, err := extproc.NewServer[*extproc.Processor](l, extproc.NewProcessor) | ||
if err != nil { | ||
log.Fatalf("failed to create external processor server: %v", err) | ||
} | ||
|
||
if err := extproc.StartConfigWatcher(ctx, *configPath, server, l, time.Second*5); err != nil { | ||
log.Fatalf("failed to start config watcher: %v", err) | ||
} | ||
|
||
s := grpc.NewServer() | ||
extprocv3.RegisterExternalProcessorServer(s, server) | ||
grpc_health_v1.RegisterHealthServer(s, server) | ||
go func() { | ||
<-ctx.Done() | ||
s.GracefulStop() | ||
}() | ||
_ = s.Serve(lis) | ||
} | ||
func main() { mainlib.Main() } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package mainlib | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"log" | ||
"log/slog" | ||
"net" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
"time" | ||
|
||
extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/health/grpc_health_v1" | ||
|
||
"github.com/envoyproxy/ai-gateway/internal/extproc" | ||
"github.com/envoyproxy/ai-gateway/internal/version" | ||
) | ||
|
||
var ( | ||
configPath = flag.String("configPath", "", "path to the configuration file. "+ | ||
"The file must be in YAML format specified in extprocconfig.Config type. The configuration file is watched for changes.") | ||
// TODO: unix domain socket support. | ||
extProcPort = flag.String("extProcPort", ":1063", "gRPC port for the external processor") | ||
logLevel = flag.String("logLevel", "info", "log level") | ||
) | ||
|
||
// Main is a main function for the external processor exposed | ||
// for allowing users to build their own external processor. | ||
func Main() { | ||
flag.Parse() | ||
|
||
var level slog.Level | ||
if err := level.UnmarshalText([]byte(*logLevel)); err != nil { | ||
log.Fatalf("failed to unmarshal log level: %v", err) | ||
} | ||
l := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ | ||
Level: level, | ||
})) | ||
|
||
l.Info("starting external processor", slog.String("version", version.Version)) | ||
|
||
if *configPath == "" { | ||
log.Fatal("configPath must be provided") | ||
} | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
signalsChan := make(chan os.Signal, 1) | ||
signal.Notify(signalsChan, syscall.SIGINT, syscall.SIGTERM) | ||
go func() { | ||
<-signalsChan | ||
cancel() | ||
}() | ||
|
||
// TODO: unix domain socket support. | ||
lis, err := net.Listen("tcp", *extProcPort) | ||
if err != nil { | ||
log.Fatalf("failed to listen: %v", err) | ||
} | ||
|
||
server, err := extproc.NewServer[*extproc.Processor](l, extproc.NewProcessor) | ||
if err != nil { | ||
log.Fatalf("failed to create external processor server: %v", err) | ||
} | ||
|
||
if err := extproc.StartConfigWatcher(ctx, *configPath, server, l, time.Second*5); err != nil { | ||
log.Fatalf("failed to start config watcher: %v", err) | ||
} | ||
|
||
s := grpc.NewServer() | ||
extprocv3.RegisterExternalProcessorServer(s, server) | ||
grpc_health_v1.RegisterHealthServer(s, server) | ||
go func() { | ||
<-ctx.Done() | ||
s.GracefulStop() | ||
}() | ||
_ = s.Serve(lis) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
This example shows how to insert a custom router in the custom external process using `filterconfig` package. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/envoyproxy/ai-gateway/cmd/extproc/mainlib" | ||
"github.com/envoyproxy/ai-gateway/extprocapi" | ||
"github.com/envoyproxy/ai-gateway/filterconfig" | ||
) | ||
|
||
// newCustomRouter implements [extprocapi.NewCustomRouter]. | ||
func newCustomRouter(defaultRouter extprocapi.Router, config *filterconfig.Config) extprocapi.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]. | ||
type myCustomRouter struct { | ||
config *filterconfig.Config | ||
defaultRouter extprocapi.Router | ||
} | ||
|
||
// Calculate implements [extprocapi.Router.Calculate]. | ||
func (m *myCustomRouter) Calculate(headers map[string]string) (backend *filterconfig.Backend, err error) { | ||
// Simply logs the headers and delegates the calculation to the default router. | ||
modelName, ok := headers[m.config.ModelNameHeaderKey] | ||
if !ok { | ||
panic("model name not found in the headers") | ||
} | ||
fmt.Printf("model name: %s\n", modelName) | ||
return m.defaultRouter.Calculate(headers) | ||
} | ||
|
||
// This demonstrates how to build a custom router for the external processor. | ||
func main() { | ||
// Initializes the custom router. | ||
extprocapi.NewCustomRouter = newCustomRouter | ||
// Executes the main function of the external processor. | ||
mainlib.Main() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Package extprocapi is for building a custom external process. | ||
package extprocapi | ||
|
||
import "github.com/envoyproxy/ai-gateway/filterconfig" | ||
|
||
// 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. | ||
var NewCustomRouter NewCustomRouterFn | ||
|
||
// NewCustomRouterFn is the function signature for [NewCustomRouter]. | ||
// | ||
// It accepts the exptproc config passed to the AI Gateway filter and returns a [Router]. | ||
// 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 | ||
|
||
// Router is the interface for the router. | ||
// | ||
// Router must be goroutine-safe as it is shared across multiple requests. | ||
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. | ||
// | ||
// Returns the backend. | ||
Calculate(requestHeaders map[string]string) (backend *filterconfig.Backend, err error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.