-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2d4a3b5
commit 30328c0
Showing
53 changed files
with
3,350 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.env | ||
test |
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,55 @@ | ||
# AI Proxy Service | ||
|
||
## Description | ||
|
||
The AI Proxy Service is a backend solution designed to securely manage and relay requests to various large language models (LLMs), including GPT-4 via the OpenAI API, while providing the flexibility to integrate other models that follow the OpenAI API standard format. This enables users to easily switch between different LLM providers, whether they are hosted externally or run locally, without major changes to client applications. | ||
|
||
The service is stateless and integrates with Single Sign-On (SSO) using Microsoft Entra ID and Google OAuth 2.0 for secure users authentication. It also supports server-side requests from other backend services through secret key-based authentication, making it easy to integrate AI capabilities securely into other systems. | ||
|
||
In addition to providing access to multiple AI models, the service enhances their functionality by addressing common limitations like context management, token efficiency, and cost control. Features such as token usage tracking and conversation summarization ensure that tokens are used optimally, reducing costs and improving performance. | ||
|
||
## Features | ||
- **Stateless Architecture**: Ensures scalability and performance by maintaining no internal state between requests. | ||
- **SSO Authentication**: Integrates with Microsoft Entra ID and Google OAuth 2.0 to authenticate users securely. | ||
- **Secret Key-based Authentication**: Allows other backend services to securely access GPT-4 using secret key-based authentication. | ||
- **Token Limitations**: Implements token consumption tracking over a specified period, ensuring cost control and preventing overuse. | ||
- **Token Efficiency with Summarization**: The service periodically summarizes after every n conversations to maintain token efficiency. This allows for a streamlined conversation history, reducing the amount of context needed while still retaining relevant information. | ||
- **Model Flexibility**: Supports multiple AI models that adhere to the OpenAI API standard format, providing flexibility to switch between different LLM providers. | ||
- **Context Awareness**: Manages and maintains conversational context across multiple requests by storing relevant information in a separate datastore, simulating long-term memory for conversations. | ||
- **Logging and Monitoring**: Logs requests, responses, and usage metrics, with integrations for observability platforms. | ||
- **Error Handling**: Graceful error handling with clear error messages and status codes. | ||
- **Extensible**: Designed for easy extension with new authentication providers or features. | ||
|
||
## Prerequisites | ||
|
||
- Go 1.20+ | ||
- Git | ||
- Docker | ||
|
||
|
||
## Installation | ||
|
||
1. Clone the repository: | ||
|
||
```sh | ||
git clone <repository_url> | ||
cd monitoring-app | ||
``` | ||
|
||
2. Install the dependencies: | ||
|
||
```sh | ||
go mod tidy | ||
``` | ||
|
||
## Configuration | ||
|
||
- **Main Configuration**: `config.yaml` | ||
|
||
### `config.yaml` | ||
|
||
This file contains the main configuration for the service, including logging levels, APM (Application Performance Monitoring), SSO, Datastore settings. | ||
|
||
|
||
## AI Proxy SAD | ||
 |
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,52 @@ | ||
app: | ||
name: "proxy-service" | ||
port: 8080 | ||
version: "1.0.0" | ||
env: "dev" | ||
tribe: "tribe" | ||
ui: | ||
host: "http://localhost:3000" | ||
log: | ||
level: "info" | ||
format: "json" | ||
redis: | ||
host: "redis" | ||
port: 6379 | ||
password: "redis" | ||
db: 0 | ||
mongo: | ||
host: "mongo" | ||
port: 27017 | ||
username: "admin" | ||
password: "admin" | ||
db: "proxy-service" | ||
apm: | ||
enabled: true | ||
host: "jaeger" | ||
port: 4317 | ||
rate: 1 | ||
microsoftOauth: | ||
tenantID: "your-tenant-id" | ||
clientID: "your-client-id" | ||
clientSecret: "your-client-secret" | ||
redirectURL: "your-redirect-url" | ||
googleOauth: | ||
clientID: "your-client-id" | ||
clientSecret: "your-client-secret" | ||
redirectURL: "your-redirect-url" | ||
openAI: | ||
host: "http://localhost:1234" | ||
path: "/v1/chat/completions" | ||
apiKey: "your-api-key" | ||
tokenLifetime: 3600 | ||
tokenLimit: 10000 | ||
services: | ||
- tribe: "tribeA" | ||
name: "code-review" | ||
username: "user" | ||
password: "password" | ||
- tribe: "tribeB" | ||
name: "chatbot" | ||
username: "user" | ||
password: "password" | ||
|
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,147 @@ | ||
package config | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
|
||
"github.com/go-playground/validator/v10" | ||
"github.com/joho/godotenv" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
type Config interface { | ||
Init(configPath string) error | ||
Get() *MainConfig | ||
} | ||
|
||
type config struct { | ||
Config *MainConfig | ||
} | ||
|
||
func New() Config { | ||
return &config{ | ||
Config: &MainConfig{}, | ||
} | ||
} | ||
|
||
func (c *config) Init(configPath string) error { | ||
// Load environment variables from .env file | ||
if err := godotenv.Load(); err != nil { | ||
// Log the error if needed, but continue to load other configurations | ||
} | ||
|
||
if err := c.load(c.Config, ".", configPath); err != nil { | ||
return err | ||
} | ||
err := validator.New().Struct(c.Config) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *config) Get() *MainConfig { | ||
return c.Config | ||
} | ||
|
||
func (c *config) load(cfg *MainConfig, path string, configPath string) error { | ||
// Set default values | ||
viper.SetDefault("log.level", "info") | ||
|
||
viper.AddConfigPath(path) | ||
if configPath != "" { | ||
viper.SetConfigFile(configPath) | ||
} | ||
viper.SetConfigType("yaml") | ||
viper.SetEnvKeyReplacer(strings.NewReplacer(`.`, `_`)) | ||
viper.AutomaticEnv() | ||
|
||
// Read the config file | ||
if err := viper.ReadInConfig(); err != nil && configPath != "" { | ||
return err | ||
} | ||
|
||
// Unmarshal the config into the struct | ||
if err := viper.Unmarshal(cfg); err != nil { | ||
return err | ||
} | ||
|
||
// Populate struct from environment variables using reflection | ||
if err := c.populateFromEnv(cfg); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// populateFromEnv populates struct fields from environment variables if the `env` tag is present | ||
func (c *config) populateFromEnv(cfg any) error { | ||
val := reflect.ValueOf(cfg) | ||
|
||
// Ensure that the value is a pointer to a struct | ||
if val.Kind() != reflect.Ptr { | ||
return fmt.Errorf("expected a pointer but got %v", val.Kind()) | ||
} | ||
|
||
if val.Elem().Kind() != reflect.Struct { | ||
return fmt.Errorf("expected a pointer to struct but got %v", val.Elem().Kind()) | ||
} | ||
|
||
val = val.Elem() // Dereference the pointer to get to the struct | ||
typ := val.Type() // Get the struct type | ||
|
||
return c.populate(val, typ) | ||
} | ||
|
||
// populate recursively populates struct fields from environment variables if the `env` tag is present | ||
func (c *config) populate(val reflect.Value, typ reflect.Type) error { | ||
for i := 0; i < val.NumField(); i++ { | ||
field := val.Field(i) | ||
structField := typ.Field(i) | ||
|
||
// Get the 'env' tag | ||
envTag := structField.Tag.Get("env") | ||
if envTag != "" { | ||
// Get the environment variable value from Viper | ||
envValue := viper.GetString(envTag) | ||
if envValue != "" { | ||
// Set the field based on its type | ||
switch field.Kind() { | ||
case reflect.Ptr: | ||
if field.Type().Elem().Kind() == reflect.String { | ||
field.Set(reflect.ValueOf(&envValue)) // set *string | ||
} else if field.Type().Elem().Kind() == reflect.Int { | ||
// Convert string to int before setting if it's an integer field | ||
var intVal int | ||
fmt.Sscanf(envValue, "%d", &intVal) | ||
field.Set(reflect.ValueOf(&intVal)) // set *int | ||
} | ||
case reflect.String: | ||
field.SetString(envValue) // set string | ||
case reflect.Int: | ||
// Convert string to int before setting if it's an integer field | ||
var intVal int | ||
fmt.Sscanf(envValue, "%d", &intVal) | ||
field.SetInt(int64(intVal)) // set int | ||
case reflect.Bool: | ||
// Convert string to bool before setting if it's a boolean field | ||
var boolVal bool | ||
fmt.Sscanf(envValue, "%t", &boolVal) | ||
field.SetBool(boolVal) // set bool | ||
} | ||
} | ||
} | ||
|
||
// Check if the field is a struct | ||
if field.Kind() == reflect.Struct { | ||
// Call populate recursively for nested structs | ||
if err := c.populate(field, field.Type()); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} |
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,49 @@ | ||
package config_test | ||
|
||
import ( | ||
"path/filepath" | ||
"runtime" | ||
"testing" | ||
|
||
config "github.com/abialemuel/AI-Proxy-Service/config" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
type Suite struct { | ||
cnf config.Config | ||
suite.Suite | ||
} | ||
|
||
func (c *Suite) SetupSuite() { | ||
c.cnf = config.New() | ||
} | ||
|
||
func (c *Suite) TestConfig() { | ||
c.Run("Wrong init file path", func() { | ||
err := c.cnf.Init("wrongPath") | ||
assert.NotNil(c.T(), err) | ||
}) | ||
|
||
c.Run("Failed config validation", func() { | ||
_, filename, _, _ := runtime.Caller(0) | ||
err := c.cnf.Init(filepath.Clean(filepath.Join(filepath.Dir(filename), "mocks/config.wrong.validate.yaml"))) | ||
assert.NotNil(c.T(), err) | ||
}) | ||
|
||
c.Run("Correct init file path", func() { | ||
_, filename, _, _ := runtime.Caller(0) | ||
err := c.cnf.Init(filepath.Clean(filepath.Join(filepath.Dir(filename), "mocks/config.yaml"))) | ||
assert.Nil(c.T(), err) | ||
}) | ||
|
||
c.Run("Get must be not nil", func() { | ||
assert.NotNil(c.T(), c.cnf.Get()) | ||
}) | ||
|
||
} | ||
|
||
func TestConfig(t *testing.T) { | ||
suite.Run(t, &Suite{}) | ||
} |
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,6 @@ | ||
# just sample config file for config testing | ||
APM: | ||
enabled: true | ||
host: "otel-collector" | ||
port: 4317 | ||
rate: 1 |
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,15 @@ | ||
# just sample config file for config testing | ||
App: | ||
name: "proxy-service" | ||
version: "1.0.0" | ||
env: "dev" | ||
tribe: "dpe" | ||
log: | ||
level: "info" | ||
format: "json" | ||
|
||
APM: | ||
enabled: true | ||
host: "otel-collector" | ||
port: 4317 | ||
rate: 1 |
Oops, something went wrong.