From 146aa273449cedb58306f4cb87b7625b863a5cbf Mon Sep 17 00:00:00 2001 From: MD Towhidul Islam Date: Fri, 22 Aug 2025 20:05:45 +0600 Subject: [PATCH 1/5] GE4-35 - Add OpenAI # Primary Commit --- config/config.go | 9 +- config/env_config.go | 1 + docker-compose.yml | 1 - domain/openai.go | 13 ++ env/app-config.json | 2 +- go.mod | 1 + go.sum | 2 + models/ai.go | 34 +++-- repositories/openai.go | 18 +++ scripts/public/ai_models.sql | 8 +- scripts/public/curl_requests.sql | 5 +- scripts/public/reminders.sql | 22 +-- scripts/public/request_ai_models.sql | 6 +- scripts/public/telegrams.sql | 23 +++ server/server.go | 2 + services/ai_dispatcher.go | 3 +- services/openai.go | 214 +++++++++++++++++++++++++++ types/ai.go | 21 +-- types/deepseek.go | 15 ++ types/openai.go | 28 ++++ 20 files changed, 378 insertions(+), 50 deletions(-) create mode 100644 domain/openai.go create mode 100644 repositories/openai.go create mode 100644 scripts/public/telegrams.sql create mode 100644 services/openai.go create mode 100644 types/openai.go diff --git a/config/config.go b/config/config.go index 8c93055..4d68add 100644 --- a/config/config.go +++ b/config/config.go @@ -3,9 +3,10 @@ package config import ( "NotificationManagement/config/helper" "fmt" - "github.com/spf13/viper" "os" "reflect" + + "github.com/spf13/viper" ) type Config struct { @@ -22,6 +23,7 @@ type Config struct { } type DevelopmentConfig struct { GeminiKey string `mapstructure:"geminikey"` + OpenAIKey string `mapstructure:"openaikey"` } type AppConfig struct { Name string `mapstructure:"name"` @@ -130,7 +132,7 @@ func LoadConfig() { fmt.Printf("Error unmarshaling config: %v\n", err) os.Exit(1) } - printAllVipers() + //printAllVipers() } func printAllVipers() { @@ -222,7 +224,7 @@ func loadDefaults() *Config { Format: "", }, Keycloak: KeycloakConfig{ - ServerURL: "http://localhost:8081", + ServerURL: "http://localhost:8081/keycloak/", Realm: "gocloak", ClientID: "gocloak", ClientSecret: "gocloak-secret", @@ -338,6 +340,7 @@ func loadFromEnv() *Config { }, Development: DevelopmentConfig{ GeminiKey: os.Getenv(EnvGeminiKey), + OpenAIKey: os.Getenv(EnvOpenaiKey), }, } setViperFields(c, "") diff --git a/config/env_config.go b/config/env_config.go index 893d708..a5f8727 100644 --- a/config/env_config.go +++ b/config/env_config.go @@ -16,6 +16,7 @@ const ( EnvAppDomain = "APP_DOMAIN" EnvGeminiKey = "GEMINI_KEY" + EnvOpenaiKey = "OPENAI_KEY" EnvDBHost = "DB_HOST" EnvDBPort = "DB_PORT" diff --git a/docker-compose.yml b/docker-compose.yml index 3823eb2..e079aff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,7 +56,6 @@ services: - --import-realm environment: KC_HOSTNAME: $KEYCLOAK_SERVER_URL - KC_HOSTNAME_STRICT: true KEYCLOAK_USER: admin KEYCLOAK_PASSWORD: secret KEYCLOAK_ADMIN: admin diff --git a/domain/openai.go b/domain/openai.go new file mode 100644 index 0000000..ae7e2dc --- /dev/null +++ b/domain/openai.go @@ -0,0 +1,13 @@ +package domain + +import ( + "NotificationManagement/models" +) + +type OpenAIService interface { + AIService[models.OpenAIModel] +} + +type OpenAIModelRepository interface { + Repository[models.OpenAIModel, uint] +} diff --git a/env/app-config.json b/env/app-config.json index ee06b11..4029bef 100644 --- a/env/app-config.json +++ b/env/app-config.json @@ -65,7 +65,7 @@ } }, "keycloak": { - "serverUrl": "http://localhost:8081", + "serverUrl": "http://localhost:8081/keycloak/", "realm": "gocloak", "clientId": "gocloak", "clientSecret": "gocloak-secret" diff --git a/go.mod b/go.mod index ba6cc6c..b7247b0 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/labstack/echo-contrib v0.17.4 github.com/labstack/echo/v4 v4.13.4 github.com/labstack/gommon v0.4.2 + github.com/sashabaranov/go-openai v1.41.1 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 go.uber.org/fx v1.24.0 diff --git a/go.sum b/go.sum index 99f2a70..5213dd1 100644 --- a/go.sum +++ b/go.sum @@ -185,6 +185,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sashabaranov/go-openai v1.41.1 h1:zf5tM+GuxpyiyD9XZg8nCqu52eYFQg9OOew0gnIuDy4= +github.com/sashabaranov/go-openai v1.41.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= diff --git a/models/ai.go b/models/ai.go index 9de75ac..2456b0f 100644 --- a/models/ai.go +++ b/models/ai.go @@ -2,6 +2,7 @@ package models import ( "NotificationManagement/config" + "gorm.io/gorm" ) @@ -14,6 +15,13 @@ type AIModel struct { Type string `gorm:"size:10;check:type IN ('local','openai','gemini','deepseek')"` } +type OpenAIModel struct { + AIModel `mapper:"inherit"` + Name string `gorm:"size:255;not null" json:"name"` + ModelName string `gorm:"size:255;not null;check:model_name <> '';index:idx_ai_model_model_secret,unique" json:"model"` + APISecret EncryptedString `gorm:"size:500;index:idx_ai_model_model_secret,unique" json:"-"` +} + type DeepseekModel struct { AIModel `mapper:"inherit"` Name string `gorm:"size:255;not null" json:"name"` @@ -33,9 +41,10 @@ func (d *AIModel) GetType() string { return d.Type } -func (d *GeminiModel) GetType() string { - return d.Type +func (*AIModel) TableName() string { + return "ai_models" } + func (d *GeminiModel) GetAPIKey() string { if config.IsDevelopment() && config.Development().GeminiKey != "" { return config.Development().GeminiKey @@ -43,16 +52,11 @@ func (d *GeminiModel) GetAPIKey() string { return string(d.APISecret) } -func (d *DeepseekModel) GetType() string { - return d.Type -} - -func (*DeepseekModel) TableName() string { - return "ai_models" -} - -func (*GeminiModel) TableName() string { - return "ai_models" +func (d *OpenAIModel) GetAPIKey() string { + if config.IsDevelopment() && config.Development().OpenAIKey != "" { + return config.Development().OpenAIKey + } + return string(d.APISecret) } func (d *DeepseekModel) UpdateFromModel(source ModelInterface) { @@ -65,3 +69,9 @@ func (d *GeminiModel) UpdateFromModel(source ModelInterface) { copyFields(d, src) } } + +func (d *OpenAIModel) UpdateFromModel(source ModelInterface) { + if src, ok := source.(*OpenAIModel); ok { + copyFields(d, src) + } +} diff --git a/repositories/openai.go b/repositories/openai.go new file mode 100644 index 0000000..9451d91 --- /dev/null +++ b/repositories/openai.go @@ -0,0 +1,18 @@ +package repositories + +import ( + "NotificationManagement/domain" + "NotificationManagement/models" + + "gorm.io/gorm" +) + +type OpenAIModelRepositoryImpl struct { + domain.Repository[models.OpenAIModel, uint] +} + +func NewOpenAIModelRepository(db *gorm.DB) domain.OpenAIModelRepository { + return &OpenAIModelRepositoryImpl{ + Repository: NewSQLRepository[models.OpenAIModel](db), + } +} diff --git a/scripts/public/ai_models.sql b/scripts/public/ai_models.sql index 8c600c2..8f0bd1e 100644 --- a/scripts/public/ai_models.sql +++ b/scripts/public/ai_models.sql @@ -14,15 +14,15 @@ CREATE TABLE IF NOT EXISTS public.ai_models CONSTRAINT chk_ai_models_model_name CHECK ((model_name)::text <> ''::text), CONSTRAINT chk_ai_models_type - CHECK ((type)::text = ANY (ARRAY [('local'::character varying)::text, ('openai'::character varying)::text, ('gemini'::character varying)::text, ('deepseek'::character varying)::text])) + CHECK ((type)::text = ANY ((ARRAY ['local'::character varying, 'openai'::character varying, 'gemini'::character varying, 'deepseek'::character varying])::text[])) ); -CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_model_model_secret - ON public.ai_models (model_name, api_secret); - CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_model_model_url ON public.ai_models (model_name, base_url); CREATE INDEX IF NOT EXISTS idx_ai_models_deleted_at ON public.ai_models (deleted_at); +CREATE UNIQUE INDEX IF NOT EXISTS idx_ai_model_model_secret + ON public.ai_models (model_name, api_secret); + diff --git a/scripts/public/curl_requests.sql b/scripts/public/curl_requests.sql index 7481e65..a558532 100644 --- a/scripts/public/curl_requests.sql +++ b/scripts/public/curl_requests.sql @@ -10,7 +10,10 @@ CREATE TABLE IF NOT EXISTS public.curl_requests body text, raw_curl text, response_type varchar(10), - PRIMARY KEY (id) + user_id bigint, + PRIMARY KEY (id), + CONSTRAINT fk_curl_requests_user + FOREIGN KEY (user_id) REFERENCES public.users ); CREATE INDEX IF NOT EXISTS idx_curl_requests_deleted_at diff --git a/scripts/public/reminders.sql b/scripts/public/reminders.sql index 0033047..1422701 100644 --- a/scripts/public/reminders.sql +++ b/scripts/public/reminders.sql @@ -5,28 +5,28 @@ CREATE TABLE IF NOT EXISTS public.reminders updated_at timestamp with time zone, deleted_at timestamp with time zone, request_id bigint, - message text NOT NULL, + message text NOT NULL, triggered_time timestamp with time zone, next_trigger_time timestamp with time zone, occurrence bigint DEFAULT 0, - recurrence varchar(50), + recurrence varchar(50) NOT NULL, + after_every bigint NOT NULL, + task_id text, + upto timestamp with time zone, PRIMARY KEY (id), CONSTRAINT fk_curl_requests_reminders - FOREIGN KEY (request_id) REFERENCES public.curl_requests, - CONSTRAINT chk_reminders_recurrence - CHECK ((recurrence)::text = ANY - (ARRAY [('once'::character varying)::text, ('minutes'::character varying)::text, ('hour'::character varying)::text, ('daily'::character varying)::text, ('weekly'::character varying)::text])) + FOREIGN KEY (request_id) REFERENCES public.curl_requests ); -CREATE INDEX IF NOT EXISTS idx_reminders_deleted_at - ON public.reminders (deleted_at); +CREATE INDEX IF NOT EXISTS idx_reminders_upto + ON public.reminders (upto); CREATE INDEX IF NOT EXISTS idx_reminders_next_trigger_time ON public.reminders (next_trigger_time); -CREATE INDEX IF NOT EXISTS idx_reminders_request_id - ON public.reminders (request_id); - CREATE INDEX IF NOT EXISTS idx_reminders_triggered_time ON public.reminders (triggered_time); +CREATE INDEX IF NOT EXISTS idx_reminders_deleted_at + ON public.reminders (deleted_at); + diff --git a/scripts/public/request_ai_models.sql b/scripts/public/request_ai_models.sql index e6358c4..ca47a46 100644 --- a/scripts/public/request_ai_models.sql +++ b/scripts/public/request_ai_models.sql @@ -8,10 +8,10 @@ CREATE TABLE IF NOT EXISTS public.request_ai_models is_active boolean DEFAULT TRUE, ai_model_id bigint, PRIMARY KEY (id), - CONSTRAINT fk_curl_requests_models - FOREIGN KEY (request_id) REFERENCES public.curl_requests, CONSTRAINT fk_request_ai_models_ai_model - FOREIGN KEY (ai_model_id) REFERENCES public.ai_models + FOREIGN KEY (ai_model_id) REFERENCES public.ai_models, + CONSTRAINT fk_curl_requests_models + FOREIGN KEY (request_id) REFERENCES public.curl_requests ); CREATE UNIQUE INDEX IF NOT EXISTS idx_request_ai_model diff --git a/scripts/public/telegrams.sql b/scripts/public/telegrams.sql new file mode 100644 index 0000000..d2f07c4 --- /dev/null +++ b/scripts/public/telegrams.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS public.telegrams +( + id bigserial, + created_at timestamp with time zone, + updated_at timestamp with time zone, + deleted_at timestamp with time zone, + user_id bigint, + chat_id bigint NOT NULL, + otp varchar(255) NOT NULL, + PRIMARY KEY (id), + CONSTRAINT fk_users_telegram + FOREIGN KEY (user_id) REFERENCES public.users +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_telegrams_chat_id + ON public.telegrams (chat_id); + +CREATE INDEX IF NOT EXISTS idx_telegrams_user_id + ON public.telegrams (user_id); + +CREATE INDEX IF NOT EXISTS idx_telegrams_deleted_at + ON public.telegrams (deleted_at); + diff --git a/server/server.go b/server/server.go index 3fdec1d..cba391c 100644 --- a/server/server.go +++ b/server/server.go @@ -75,12 +75,14 @@ var Module = fx.Options( repositories.NewReminderRepository, repositories.NewUserRepository, repositories.NewTelegramRepository, + repositories.NewOpenAIModelRepository, services.NewAIModelService, services.NewAsynqService, services.NewCurlService, services.NewDeepseekModelService, services.NewGeminiService, + services.NewOpenAIService, services.NewLLMService, services.NewReminderService, services.NewUserService, diff --git a/services/ai_dispatcher.go b/services/ai_dispatcher.go index e3c7344..8f9f50b 100644 --- a/services/ai_dispatcher.go +++ b/services/ai_dispatcher.go @@ -12,11 +12,12 @@ type AiDispatcherImpl struct { aiModel domain.AIModelService } -func NewAIDispatcher(geminiService domain.GeminiService, deepseekService domain.DeepseekService, ai domain.AIModelService) domain.AiDispatcher { +func NewAIDispatcher(geminiService domain.GeminiService, deepseekService domain.DeepseekService, openaiService domain.OpenAIService, ai domain.AIModelService) domain.AiDispatcher { return &AiDispatcherImpl{ services: &[]domain.DispatchableAIService{ geminiService, deepseekService, + openaiService, }, aiModel: ai, } diff --git a/services/openai.go b/services/openai.go new file mode 100644 index 0000000..6c083a7 --- /dev/null +++ b/services/openai.go @@ -0,0 +1,214 @@ +package services + +import ( + "NotificationManagement/domain" + "NotificationManagement/logger" + "NotificationManagement/models" + "NotificationManagement/repositories" + "NotificationManagement/types" + "NotificationManagement/utils/errutil" + "context" + "encoding/json" + + "github.com/sashabaranov/go-openai" +) + +// JSONSchemaProperty represents a property in a JSON schema +type JSONSchemaProperty struct { + Type string `json:"type"` + Description string `json:"description,omitempty"` +} + +// JSONSchema represents a JSON schema structure +type JSONSchema struct { + Type string `json:"type"` + Properties map[string]JSONSchemaProperty `json:"properties"` + Required []string `json:"required"` +} + +// MarshalJSON implements json.Marshaler interface +func (j JSONSchema) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type string `json:"type"` + Properties map[string]JSONSchemaProperty `json:"properties"` + Required []string `json:"required"` + }{ + Type: j.Type, + Properties: j.Properties, + Required: j.Required, + }) +} + +type OpenAIServiceImpl struct { + domain.CommonService[models.OpenAIModel] + CurlService domain.CurlService +} + +func NewOpenAIService(repo domain.OpenAIModelRepository, curl domain.CurlService) domain.OpenAIService { + service := &OpenAIServiceImpl{ + CurlService: curl, + } + service.CommonService = NewCommonService(repo, service) + return service +} + +func (s *OpenAIServiceImpl) ProcessContext(ctx context.Context) context.Context { + if txContext, ok := repositories.GetTxContext(ctx); ok { + filters := append(txContext.Filter, repositories.NewFilter("type", "=", s.GetModelType())) + txContext.Filter = filters + } + return ctx +} + +func (s *OpenAIServiceImpl) MakeAIRequest(c context.Context, m *models.AIModel, requestId uint) (interface{}, error) { + curl, err := s.CurlService.GetModelById(c, requestId, nil) + if err != nil { + return nil, err + } + curlResponse, err := s.CurlService.ProcessCurlRequest(c, curl) + if err != nil { + return nil, err + } + model, err := s.GetModelById(c, m.ID, nil) + if err != nil { + return nil, err + } + respBody, err := openAICall(c, model, curlResponse, curl) + if err != nil { + return nil, err + } + return respBody, nil +} + +func (s *OpenAIServiceImpl) GetAIJsonResponse(c context.Context, m *models.AIModel, requestId uint) (map[string]interface{}, error) { + request, err := s.MakeAIRequest(c, m, requestId) + if err != nil { + return nil, err + } + resp, _ := request.(*openai.ChatCompletionResponse) + var aiResp map[string]interface{} + if err := json.Unmarshal([]byte(resp.Choices[0].Message.Content), &aiResp); err != nil { + return map[string]interface{}{ + "comment": resp.Choices[0].Message.Content, + }, nil + } + return aiResp, nil +} + +func (s *OpenAIServiceImpl) GetModelType() string { + return "openai" +} + +// createJSONSchema creates a JSON schema from the CurlRequest additional fields +func createJSONSchema(req *models.CurlRequest) JSONSchema { + properties := make(map[string]JSONSchemaProperty) + required := []string{"IsCorrect"} + + properties["IsCorrect"] = JSONSchemaProperty{ + Type: "boolean", + Description: "Indicates whether the response is correct", + } + + // Add properties from additional fields + if req.AdditionalFields != nil { + for _, field := range *req.AdditionalFields { + var schemaType string + switch field.Type { + case "number": + schemaType = "number" + case "boolean": + schemaType = "boolean" + default: + schemaType = "string" + } + + properties[field.PropertyName] = JSONSchemaProperty{ + Type: schemaType, + Description: field.Description, + } + required = append(required, field.PropertyName) + } + } + + return JSONSchema{ + Type: "object", + Properties: properties, + Required: required, + } +} + +func openAICall(ctx context.Context, model *models.OpenAIModel, response *types.CurlResponse, req *models.CurlRequest) (*openai.ChatCompletionResponse, error) { + assistantContent, err := response.GetAssistantContent(req.ResponseType) + if err != nil { + return nil, err + } + + config := openai.DefaultConfig(model.GetAPIKey()) + config.BaseURL = "https://api.closerouter.com/v1" + client := openai.NewClientWithConfig(config) + + messages := []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleAssistant, + Content: *assistantContent, + }, + { + Role: openai.ChatMessageRoleUser, + Content: req.Body, + }, + } + + // Create JSON schema from additional fields for structured output + jsonSchema := createJSONSchema(req) + + logger.Debug(jsonSchema.Type) + + resp, err := client.CreateChatCompletion( + ctx, + openai.ChatCompletionRequest{ + Model: model.ModelName, + Messages: messages, + ResponseFormat: &openai.ChatCompletionResponseFormat{ + Type: openai.ChatCompletionResponseFormatTypeJSONSchema, + JSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{ + Name: "response_schema", + Description: "Structured response with required fields", + Schema: jsonSchema, + Strict: true, + }, + }, + }, + ) + + if err != nil { + return nil, errutil.NewAppError(errutil.ErrExternalServiceError, err) + } + + return &resp, nil +} + +func (s *OpenAIServiceImpl) CreateAIModel(c context.Context, model any) error { + openaiModel := (model).(*models.OpenAIModel) + return s.CreateModel(c, openaiModel) +} + +func (s *OpenAIServiceImpl) UpdateAIModel(c context.Context, model any) (any, error) { + openaiModel := (model).(*models.OpenAIModel) + return s.UpdateModel(c, openaiModel.ID, openaiModel) +} + +func (s *OpenAIServiceImpl) GetAIModelById(ctx context.Context, id uint) (any, error) { + return s.GetModelById(ctx, id, nil) +} + +func (s *OpenAIServiceImpl) GetAllAIModels(ctx context.Context) ([]any, error) { + allModels, err := s.GetAllModels(ctx, 100, 0) + if err != nil { + return nil, err + } + i := make([]any, len(allModels)) + for idx, model := range allModels { + i[idx] = model + } + return i, err +} diff --git a/types/ai.go b/types/ai.go index 06fde66..26d6dd9 100644 --- a/types/ai.go +++ b/types/ai.go @@ -4,6 +4,7 @@ import ( "NotificationManagement/models" "NotificationManagement/utils/errutil" "fmt" + "gorm.io/gorm" validation "github.com/go-ozzo/ozzo-validation/v4" @@ -77,20 +78,14 @@ func (dr *AIModelRequest) ToModel() (models.AIModelInterface, error) { ModelName: dr.ModelName, APISecret: models.EncryptedString(dr.APISecret), }, nil + case "openai": + return &models.OpenAIModel{ + AIModel: aiModel, + Name: dr.Name, + ModelName: dr.ModelName, + APISecret: models.EncryptedString(dr.APISecret), + }, nil default: return nil, errutil.NewAppError(errutil.ErrUnsupportedAIModelType, fmt.Errorf("unsupported AI model type: %s", dr.Type)) } } - -func FromDeepseekModel(model *models.DeepseekModel) *DeepseekModelResponse { - return &DeepseekModelResponse{ - ID: model.ID, - Name: model.Name, - Type: model.Type, - ModelName: model.ModelName, - BaseURL: model.BaseURL, - Size: model.Size, - CreatedAt: model.CreatedAt.Format(ResponseDateFormat), - UpdatedAt: model.UpdatedAt.Format(ResponseDateFormat), - } -} diff --git a/types/deepseek.go b/types/deepseek.go index 840a620..669f61d 100644 --- a/types/deepseek.go +++ b/types/deepseek.go @@ -1,5 +1,7 @@ package types +import "NotificationManagement/models" + type DeepseekModelResponse struct { ID uint `json:"id"` Name string `json:"name"` @@ -10,3 +12,16 @@ type DeepseekModelResponse struct { CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } + +func FromDeepseekModel(model *models.DeepseekModel) *DeepseekModelResponse { + return &DeepseekModelResponse{ + ID: model.ID, + Name: model.Name, + Type: model.Type, + ModelName: model.ModelName, + BaseURL: model.BaseURL, + Size: model.Size, + CreatedAt: model.CreatedAt.Format(ResponseDateFormat), + UpdatedAt: model.UpdatedAt.Format(ResponseDateFormat), + } +} diff --git a/types/openai.go b/types/openai.go new file mode 100644 index 0000000..81aacff --- /dev/null +++ b/types/openai.go @@ -0,0 +1,28 @@ +package types + +import ( + "NotificationManagement/models" +) + +type OpenAIModelResponse struct { + ID uint `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + ModelName string `json:"model"` + APISecret models.EncryptedString `json:"api_secret"` + ModifiedAt string `json:"modified_at"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +func FromOpenAIModel(model *models.OpenAIModel) *OpenAIModelResponse { + return &OpenAIModelResponse{ + ID: model.ID, + Type: model.Type, + Name: model.Name, + ModelName: model.ModelName, + APISecret: model.APISecret, + CreatedAt: model.CreatedAt.Format(ResponseDateFormat), + UpdatedAt: model.UpdatedAt.Format(ResponseDateFormat), + } +} From 6702d13207dab90fb2b426eadd36e663e6793eb4 Mon Sep 17 00:00:00 2001 From: MD Towhidul Islam Date: Fri, 22 Aug 2025 20:36:45 +0600 Subject: [PATCH 2/5] GE4-35 - Enhance OpenAI JSON schema handling and response validation --- services/openai.go | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/services/openai.go b/services/openai.go index 6c083a7..a8323f1 100644 --- a/services/openai.go +++ b/services/openai.go @@ -21,21 +21,24 @@ type JSONSchemaProperty struct { // JSONSchema represents a JSON schema structure type JSONSchema struct { - Type string `json:"type"` - Properties map[string]JSONSchemaProperty `json:"properties"` - Required []string `json:"required"` + Type string `json:"type"` + Properties map[string]JSONSchemaProperty `json:"properties"` + Required []string `json:"required"` + AdditionalProperties bool `json:"additionalProperties"` } // MarshalJSON implements json.Marshaler interface func (j JSONSchema) MarshalJSON() ([]byte, error) { return json.Marshal(struct { - Type string `json:"type"` - Properties map[string]JSONSchemaProperty `json:"properties"` - Required []string `json:"required"` + Type string `json:"type"` + Properties map[string]JSONSchemaProperty `json:"properties"` + Required []string `json:"required"` + AdditionalProperties bool `json:"additionalProperties"` }{ - Type: j.Type, - Properties: j.Properties, - Required: j.Required, + Type: j.Type, + Properties: j.Properties, + Required: j.Required, + AdditionalProperties: j.AdditionalProperties, }) } @@ -87,7 +90,11 @@ func (s *OpenAIServiceImpl) GetAIJsonResponse(c context.Context, m *models.AIMod } resp, _ := request.(*openai.ChatCompletionResponse) var aiResp map[string]interface{} - if err := json.Unmarshal([]byte(resp.Choices[0].Message.Content), &aiResp); err != nil { + if len(resp.Choices) == 0 { + return map[string]interface{}{ + "comment": "Failed", + }, nil + } else if err := json.Unmarshal([]byte(resp.Choices[0].Message.Content), &aiResp); err != nil { return map[string]interface{}{ "comment": resp.Choices[0].Message.Content, }, nil @@ -131,9 +138,10 @@ func createJSONSchema(req *models.CurlRequest) JSONSchema { } return JSONSchema{ - Type: "object", - Properties: properties, - Required: required, + Type: "object", + Properties: properties, + Required: required, + AdditionalProperties: false, } } @@ -144,7 +152,8 @@ func openAICall(ctx context.Context, model *models.OpenAIModel, response *types. } config := openai.DefaultConfig(model.GetAPIKey()) - config.BaseURL = "https://api.closerouter.com/v1" + // TODO : Here we have to define custom baseurl + //config.BaseURL = "https://api.sambanova.ai/v1" client := openai.NewClientWithConfig(config) messages := []openai.ChatCompletionMessage{ @@ -168,6 +177,7 @@ func openAICall(ctx context.Context, model *models.OpenAIModel, response *types. openai.ChatCompletionRequest{ Model: model.ModelName, Messages: messages, + Stream: false, ResponseFormat: &openai.ChatCompletionResponseFormat{ Type: openai.ChatCompletionResponseFormatTypeJSONSchema, JSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{ From 9b21a5e3fec1ac1ce00b4d2d0d109d96053cf9ee Mon Sep 17 00:00:00 2001 From: MD Towhidul Islam Date: Fri, 22 Aug 2025 20:55:45 +0600 Subject: [PATCH 3/5] GE4-35 - Refactor OpenAI JSON schema types to shared types package --- services/openai.go | 39 +++++---------------------------------- types/openai.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/services/openai.go b/services/openai.go index a8323f1..e5df19a 100644 --- a/services/openai.go +++ b/services/openai.go @@ -13,35 +13,6 @@ import ( "github.com/sashabaranov/go-openai" ) -// JSONSchemaProperty represents a property in a JSON schema -type JSONSchemaProperty struct { - Type string `json:"type"` - Description string `json:"description,omitempty"` -} - -// JSONSchema represents a JSON schema structure -type JSONSchema struct { - Type string `json:"type"` - Properties map[string]JSONSchemaProperty `json:"properties"` - Required []string `json:"required"` - AdditionalProperties bool `json:"additionalProperties"` -} - -// MarshalJSON implements json.Marshaler interface -func (j JSONSchema) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Type string `json:"type"` - Properties map[string]JSONSchemaProperty `json:"properties"` - Required []string `json:"required"` - AdditionalProperties bool `json:"additionalProperties"` - }{ - Type: j.Type, - Properties: j.Properties, - Required: j.Required, - AdditionalProperties: j.AdditionalProperties, - }) -} - type OpenAIServiceImpl struct { domain.CommonService[models.OpenAIModel] CurlService domain.CurlService @@ -107,11 +78,11 @@ func (s *OpenAIServiceImpl) GetModelType() string { } // createJSONSchema creates a JSON schema from the CurlRequest additional fields -func createJSONSchema(req *models.CurlRequest) JSONSchema { - properties := make(map[string]JSONSchemaProperty) +func createJSONSchema(req *models.CurlRequest) types.JSONSchema { + properties := make(map[string]types.JSONSchemaProperty) required := []string{"IsCorrect"} - properties["IsCorrect"] = JSONSchemaProperty{ + properties["IsCorrect"] = types.JSONSchemaProperty{ Type: "boolean", Description: "Indicates whether the response is correct", } @@ -129,7 +100,7 @@ func createJSONSchema(req *models.CurlRequest) JSONSchema { schemaType = "string" } - properties[field.PropertyName] = JSONSchemaProperty{ + properties[field.PropertyName] = types.JSONSchemaProperty{ Type: schemaType, Description: field.Description, } @@ -137,7 +108,7 @@ func createJSONSchema(req *models.CurlRequest) JSONSchema { } } - return JSONSchema{ + return types.JSONSchema{ Type: "object", Properties: properties, Required: required, diff --git a/types/openai.go b/types/openai.go index 81aacff..fab3a78 100644 --- a/types/openai.go +++ b/types/openai.go @@ -2,6 +2,7 @@ package types import ( "NotificationManagement/models" + "encoding/json" ) type OpenAIModelResponse struct { @@ -26,3 +27,32 @@ func FromOpenAIModel(model *models.OpenAIModel) *OpenAIModelResponse { UpdatedAt: model.UpdatedAt.Format(ResponseDateFormat), } } + +// JSONSchemaProperty represents a property in a JSON schema +type JSONSchemaProperty struct { + Type string `json:"type"` + Description string `json:"description,omitempty"` +} + +// JSONSchema represents a JSON schema structure +type JSONSchema struct { + Type string `json:"type"` + Properties map[string]JSONSchemaProperty `json:"properties"` + Required []string `json:"required"` + AdditionalProperties bool `json:"additionalProperties"` +} + +// MarshalJSON implements json.Marshaler interface +func (j JSONSchema) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type string `json:"type"` + Properties map[string]JSONSchemaProperty `json:"properties"` + Required []string `json:"required"` + AdditionalProperties bool `json:"additionalProperties"` + }{ + Type: j.Type, + Properties: j.Properties, + Required: j.Required, + AdditionalProperties: j.AdditionalProperties, + }) +} From 9540a15a4e5818c5a4cf70fae087872c2a09249c Mon Sep 17 00:00:00 2001 From: MD Towhidul Islam Date: Fri, 22 Aug 2025 21:25:45 +0600 Subject: [PATCH 4/5] GE4-35 - Add BaseURL support for OpenAI and Gemini models with dependency updates --- README.md | 13 +++++++------ go.mod | 19 +++++++++++-------- go.sum | 24 ++++++++++++++++++++++++ models/ai.go | 11 +++++++++-- services/deepseek.go | 4 ++-- services/gemini.go | 13 +++++++++---- services/openai.go | 10 ++++------ types/ai.go | 16 +++++++++------- types/deepseek.go | 2 +- types/openai.go | 2 +- 10 files changed, 77 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index ef02b84..e4808a5 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ * [Non-Functional Features](#non-functional-features) * [Quick Start](#quick-start) * [Prerequisites](#prerequisites) + * [Install awscli and awscli-local](#install-awscli-and-awscli-local) * [1. Setup Environment](#1-setup-environment) * [2. Build and Run the Application](#2-build-and-run-the-application) * [Services](#services) @@ -46,7 +47,8 @@ This project is a notification management system that leverages AI for processin - Docker and Docker Compose (for LocalStack) - AWS CLI -#### If you don't have AWS CLI installed, you can install it using the following command: +#### Install awscli and awscli-local +If you don't have AWS CLI installed, you can install it using the following command: ```bash sudo apt-get install python3-pip -y pip3 install awscli awscli-local @@ -117,8 +119,7 @@ Now you can use the APIs in the collection. ## Future Plans -- Authentication in requests -- AI based todo setup -- Report generation based on periodic results -- Additional notification formats (e.g., PDF, Excel) -- Add OPENAI integration for AI processing +- Sandbox for running curl command +- Add Front End +- Generic Pages +- Complex Search screens diff --git a/go.mod b/go.mod index b7247b0..9c75c42 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/spf13/viper v1.18.2 go.uber.org/fx v1.24.0 go.uber.org/zap v1.26.0 + google.golang.org/api v0.248.0 google.golang.org/genai v1.18.0 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.30.0 @@ -30,8 +31,9 @@ require ( require ( cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.16.3 // indirect - cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/auth v0.16.5 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.8.0 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 // indirect @@ -97,16 +99,17 @@ require ( go.opentelemetry.io/otel/trace v1.36.0 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.40.0 // indirect + golang.org/x/crypto v0.41.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.12.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect google.golang.org/grpc v1.74.2 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/protobuf v1.36.7 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5213dd1..bb87aa0 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,14 @@ cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc= cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA= +cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= +cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA= +cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw= github.com/Nerzal/gocloak/v13 v13.9.0 h1:YWsJsdM5b0yhM2Ba3MLydiOlujkBry4TtdzfIzSVZhw= github.com/Nerzal/gocloak/v13 v13.9.0/go.mod h1:YYuDcXZ7K2zKECyVP7pPqjKxx2AzYSpKDj8d6GuyM10= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= @@ -249,6 +255,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -260,6 +268,10 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -279,12 +291,16 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -294,10 +310,16 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo= +google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= +google.golang.org/api v0.248.0 h1:hUotakSkcwGdYUqzCRc5yGYsg4wXxpkKlW5ryVqvC1Y= +google.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k= google.golang.org/genai v1.18.0 h1:fTmK7y30CO0CL8xRyyFSjTkd1MNbYUeFUehvDyU/2gQ= google.golang.org/genai v1.18.0/go.mod h1:QPj5NGJw+3wEOHg+PrsWwJKvG6UC84ex5FR7qAYsN/M= google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM= google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -308,6 +330,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/models/ai.go b/models/ai.go index 2456b0f..6f7efca 100644 --- a/models/ai.go +++ b/models/ai.go @@ -12,7 +12,8 @@ type AIModelInterface interface { type AIModel struct { gorm.Model - Type string `gorm:"size:10;check:type IN ('local','openai','gemini','deepseek')"` + Type string `gorm:"size:10;check:type IN ('local','openai','gemini','deepseek')"` + BaseURL *string `gorm:"size:500" json:"base_url,omitempty"` } type OpenAIModel struct { @@ -26,7 +27,6 @@ type DeepseekModel struct { AIModel `mapper:"inherit"` Name string `gorm:"size:255;not null" json:"name"` ModelName string `gorm:"size:255;not null;check:model_name <> '';index:idx_ai_model_model_url,unique" json:"model"` - BaseURL string `gorm:"size:500;index:idx_ai_model_model_url,unique" json:"base_url"` Size int64 `json:"size"` } @@ -52,6 +52,13 @@ func (d *GeminiModel) GetAPIKey() string { return string(d.APISecret) } +func (d *AIModel) GetBaseURL() string { + if d.BaseURL != nil && *d.BaseURL != "" { + return *d.BaseURL + } + return "" +} + func (d *OpenAIModel) GetAPIKey() string { if config.IsDevelopment() && config.Development().OpenAIKey != "" { return config.Development().OpenAIKey diff --git a/services/deepseek.go b/services/deepseek.go index 2ba7c47..62accc7 100644 --- a/services/deepseek.go +++ b/services/deepseek.go @@ -92,7 +92,7 @@ func (s *DeepseekServiceImpl) PullModel(_ context.Context, model *models.Deepsee return errutil.NewAppError(errutil.ErrAIMarshalRequestFailed, err) } - url := fmt.Sprintf("%s/api/pull", model.BaseURL) + url := fmt.Sprintf("%s/api/pull", model.GetBaseURL()) req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) if err != nil { return errutil.NewAppError(errutil.ErrAICreateRequestFailed, err) @@ -166,7 +166,7 @@ func deepseekCall(model *models.DeepseekModel, response *types.CurlResponse, cur return nil, err } - url := fmt.Sprintf("%s/api/chat", model.BaseURL) + url := fmt.Sprintf("%s/api/chat", model.GetBaseURL()) client := &http.Client{} req, err := http.NewRequest("POST", url, strings.NewReader(string(reqBody))) if err != nil { diff --git a/services/gemini.go b/services/gemini.go index bc31774..bcb3b76 100644 --- a/services/gemini.go +++ b/services/gemini.go @@ -1,4 +1,4 @@ -package services +I want to thispackage services import ( "NotificationManagement/domain" @@ -11,6 +11,7 @@ import ( "encoding/json" "os" + "google.golang.org/api/option" "google.golang.org/genai" ) @@ -78,9 +79,13 @@ func geminiCall(ctx context.Context, model *models.GeminiModel, response *types. if err != nil { return nil, err } - client, err := genai.NewClient(ctx, &genai.ClientConfig{ - APIKey: model.GetAPIKey(), - }) + opts := []option.ClientOption{ + option.WithAPIKey(model.GetAPIKey()), + } + if model.GetBaseURL() != "" { + opts = append(opts, option.WithEndpoint(model.GetBaseURL())) + } + client, err := genai.NewClient(ctx, opts...) if err != nil { return nil, err } diff --git a/services/openai.go b/services/openai.go index e5df19a..2311844 100644 --- a/services/openai.go +++ b/services/openai.go @@ -2,7 +2,6 @@ package services import ( "NotificationManagement/domain" - "NotificationManagement/logger" "NotificationManagement/models" "NotificationManagement/repositories" "NotificationManagement/types" @@ -123,8 +122,9 @@ func openAICall(ctx context.Context, model *models.OpenAIModel, response *types. } config := openai.DefaultConfig(model.GetAPIKey()) - // TODO : Here we have to define custom baseurl - //config.BaseURL = "https://api.sambanova.ai/v1" + if model.GetBaseURL() != "" { + config.BaseURL = model.GetBaseURL() + } client := openai.NewClientWithConfig(config) messages := []openai.ChatCompletionMessage{ @@ -141,8 +141,6 @@ func openAICall(ctx context.Context, model *models.OpenAIModel, response *types. // Create JSON schema from additional fields for structured output jsonSchema := createJSONSchema(req) - logger.Debug(jsonSchema.Type) - resp, err := client.CreateChatCompletion( ctx, openai.ChatCompletionRequest{ @@ -154,7 +152,7 @@ func openAICall(ctx context.Context, model *models.OpenAIModel, response *types. JSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{ Name: "response_schema", Description: "Structured response with required fields", - Schema: jsonSchema, + Schema: &jsonSchema, // Pass a pointer to jsonSchema Strict: true, }, }, diff --git a/types/ai.go b/types/ai.go index 26d6dd9..d696239 100644 --- a/types/ai.go +++ b/types/ai.go @@ -45,7 +45,7 @@ func (r *AIModelRequest) Validate() error { rules = append(rules, validation.Field(&r.BaseURL, validation.Required, validation.Length(1, 500))) case "gemini", "openai": rules = append(rules, validation.Field(&r.APISecret, validation.Required, validation.Length(1, 500))) - + rules = append(rules, validation.Field(&r.BaseURL, validation.Length(0, 500))) // Optional BaseURL } return validation.ValidateStruct(r, rules...) @@ -60,7 +60,8 @@ func (dr *AIModelRequest) ToModel() (models.AIModelInterface, error) { Model: gorm.Model{ ID: dr.ID, }, - Type: dr.Type, + Type: dr.Type, + BaseURL: &dr.BaseURL, } switch dr.Type { case "deepseek", "local": @@ -68,23 +69,24 @@ func (dr *AIModelRequest) ToModel() (models.AIModelInterface, error) { AIModel: aiModel, Name: dr.Name, ModelName: dr.ModelName, - BaseURL: dr.BaseURL, Size: dr.Size, }, nil case "gemini": - return &models.GeminiModel{ + geminiModel := &models.GeminiModel{ AIModel: aiModel, Name: dr.Name, ModelName: dr.ModelName, APISecret: models.EncryptedString(dr.APISecret), - }, nil + } + return geminiModel, nil case "openai": - return &models.OpenAIModel{ + openaiModel := &models.OpenAIModel{ AIModel: aiModel, Name: dr.Name, ModelName: dr.ModelName, APISecret: models.EncryptedString(dr.APISecret), - }, nil + } + return openaiModel, nil default: return nil, errutil.NewAppError(errutil.ErrUnsupportedAIModelType, fmt.Errorf("unsupported AI model type: %s", dr.Type)) } diff --git a/types/deepseek.go b/types/deepseek.go index 669f61d..da035d9 100644 --- a/types/deepseek.go +++ b/types/deepseek.go @@ -19,7 +19,7 @@ func FromDeepseekModel(model *models.DeepseekModel) *DeepseekModelResponse { Name: model.Name, Type: model.Type, ModelName: model.ModelName, - BaseURL: model.BaseURL, + BaseURL: model.GetBaseURL(), Size: model.Size, CreatedAt: model.CreatedAt.Format(ResponseDateFormat), UpdatedAt: model.UpdatedAt.Format(ResponseDateFormat), diff --git a/types/openai.go b/types/openai.go index fab3a78..7055141 100644 --- a/types/openai.go +++ b/types/openai.go @@ -43,7 +43,7 @@ type JSONSchema struct { } // MarshalJSON implements json.Marshaler interface -func (j JSONSchema) MarshalJSON() ([]byte, error) { +func (j *JSONSchema) MarshalJSON() ([]byte, error) { return json.Marshal(struct { Type string `json:"type"` Properties map[string]JSONSchemaProperty `json:"properties"` From c1977f5e3ebae2acaf92a735f1b6250002f7e6d2 Mon Sep 17 00:00:00 2001 From: MD Towhidul Islam Date: Fri, 22 Aug 2025 21:25:45 +0600 Subject: [PATCH 5/5] GE4-35 - Remove Google API dependency and update Gemini client configuration --- go.mod | 19 ++++++++----------- go.sum | 24 ------------------------ services/gemini.go | 17 +++++++---------- 3 files changed, 15 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index 9c75c42..b7247b0 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,6 @@ require ( github.com/spf13/viper v1.18.2 go.uber.org/fx v1.24.0 go.uber.org/zap v1.26.0 - google.golang.org/api v0.248.0 google.golang.org/genai v1.18.0 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.30.0 @@ -31,9 +30,8 @@ require ( require ( cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.16.5 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.8.0 // indirect + cloud.google.com/go/auth v0.16.3 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 // indirect @@ -99,17 +97,16 @@ require ( go.opentelemetry.io/otel/trace v1.36.0 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.41.0 // indirect + golang.org/x/crypto v0.40.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/net v0.42.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect golang.org/x/time v0.12.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect google.golang.org/grpc v1.74.2 // indirect - google.golang.org/protobuf v1.36.7 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index bb87aa0..5213dd1 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,8 @@ cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc= cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA= -cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= -cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ= -cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= -cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= -cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA= -cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw= github.com/Nerzal/gocloak/v13 v13.9.0 h1:YWsJsdM5b0yhM2Ba3MLydiOlujkBry4TtdzfIzSVZhw= github.com/Nerzal/gocloak/v13 v13.9.0/go.mod h1:YYuDcXZ7K2zKECyVP7pPqjKxx2AzYSpKDj8d6GuyM10= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= @@ -255,8 +249,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -268,10 +260,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -291,16 +279,12 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -310,16 +294,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo= -google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= -google.golang.org/api v0.248.0 h1:hUotakSkcwGdYUqzCRc5yGYsg4wXxpkKlW5ryVqvC1Y= -google.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k= google.golang.org/genai v1.18.0 h1:fTmK7y30CO0CL8xRyyFSjTkd1MNbYUeFUehvDyU/2gQ= google.golang.org/genai v1.18.0/go.mod h1:QPj5NGJw+3wEOHg+PrsWwJKvG6UC84ex5FR7qAYsN/M= google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM= google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -330,8 +308,6 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/services/gemini.go b/services/gemini.go index bcb3b76..f1e35e5 100644 --- a/services/gemini.go +++ b/services/gemini.go @@ -1,4 +1,4 @@ -I want to thispackage services +package services import ( "NotificationManagement/domain" @@ -11,7 +11,6 @@ import ( "encoding/json" "os" - "google.golang.org/api/option" "google.golang.org/genai" ) @@ -79,17 +78,15 @@ func geminiCall(ctx context.Context, model *models.GeminiModel, response *types. if err != nil { return nil, err } - opts := []option.ClientOption{ - option.WithAPIKey(model.GetAPIKey()), - } - if model.GetBaseURL() != "" { - opts = append(opts, option.WithEndpoint(model.GetBaseURL())) - } - client, err := genai.NewClient(ctx, opts...) + client, err := genai.NewClient(ctx, &genai.ClientConfig{ + APIKey: model.GetAPIKey(), + HTTPOptions: genai.HTTPOptions{ + BaseURL: model.GetBaseURL(), + }, + }) if err != nil { return nil, err } - var parts []*genai.Part if req.ResponseType == types.ResponseTypeHTML {