diff --git a/.env b/.env index 758f34b..e81953b 100644 --- a/.env +++ b/.env @@ -1,13 +1,5 @@ MOONSHOT_API_KEY=sk-8NMdsGbDAMpWdd6hrKHepr1tNVXTy2QppKAqJkoJcHd6TYLs -# backend 配置 -OPENAI_BASE_URL=https://api.moonshot.cn/v1 -OPENAI_MODEL=kimi-k2.5 -OPENAI_TEMPERATURE=100 -OPENAI_PRESENCE_PENALTY=0 -OPENAI_FREQUENCY_PENALTY=0 -TOKENIZER_BASE_URL=http://tokenizer:3002 - # frontend 对外端口 FRONTEND_PORT=1025 diff --git a/README.md b/README.md index 688aae8..cceaec9 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ docker run -d --name chatgpt-web-backend \ # ai-chat-backend docker build -t ai-chat-backend:1.0.0 . +# ai-chat-service +GOCACHE=/tmp/ai-chat-service-gocache go build -o ai-chat-service-bin ./chat-server +docker build -t ai-chat-service:1.0.0 . + # chatgpt-web-frontend HUSKY=0 pnpm bootstrap pnpm dev diff --git a/ai-chat-backend/docker.config.yaml b/ai-chat-backend/docker.config.yaml new file mode 100644 index 0000000..d993f0c --- /dev/null +++ b/ai-chat-backend/docker.config.yaml @@ -0,0 +1,22 @@ +http: + ip: 0.0.0.0 + port: 7080 +frontend_path: "www" +log: + level: "info" + logPath: "runtime/logs/app.log" +chat: + model: "kimi-k2.5" + max_tokens: 4096 + temperature: 1 + top_p: 1 + presence_penalty: 0 + frequency_penalty: 0 + bot_desc: "你是一个AI助手,我需要你模拟一名资深的软件工程师来回答我的问题" + min_response_tokens: 600 + context_ttl: 1800 + context_len: 4 +dependOn: + ai-chat-service: + address: "ai-chat-service:50055" + accessToken: "me256487ang1chubdpdialoud22sev1ozhoguumyqca" diff --git a/ai-chat-backend/pkg/config/config.go b/ai-chat-backend/pkg/config/config.go index 345cf2b..d92d989 100644 --- a/ai-chat-backend/pkg/config/config.go +++ b/ai-chat-backend/pkg/config/config.go @@ -1,13 +1,9 @@ package config import ( - "bufio" - "github.com/spf13/viper" "log" - "os" - "path/filepath" - "strconv" - "strings" + + "github.com/spf13/viper" ) type Config struct { @@ -45,7 +41,6 @@ type Config struct { var conf *Config func InitConfig(filePath string, typ ...string) { - loadProjectDotEnv(filePath) v := viper.New() v.SetConfigFile(filePath) if len(typ) > 0 { @@ -61,7 +56,6 @@ func InitConfig(filePath string, typ ...string) { log.Fatal(err) } normalizeConfig(conf) - overrideConfigFromEnv(conf) } @@ -98,88 +92,3 @@ func normalizeConfig(conf *Config) { conf.DependOn.AiChatService.Address = "localhost:50055" } } - -func overrideConfigFromEnv(conf *Config) { - overrideString(&conf.Http.IP, os.Getenv("SERVER_HOST")) - overrideInt(&conf.Http.Port, os.Getenv("SERVER_PORT")) - overrideString(&conf.BasicAuthUser, os.Getenv("BASIC_AUTH_USER")) - overrideString(&conf.BasicAuthPassword, os.Getenv("BASIC_AUTH_PASSWORD")) - overrideString(&conf.FrontendPath, os.Getenv("FRONTEND_PATH")) - overrideString(&conf.Chat.Model, os.Getenv("OPENAI_MODEL")) - overrideInt(&conf.Chat.MaxTokens, os.Getenv("OPENAI_MAX_TOKENS")) - overrideScaledFloat32(&conf.Chat.Temperature, os.Getenv("OPENAI_TEMPERATURE")) - overrideScaledFloat32(&conf.Chat.PresencePenalty, os.Getenv("OPENAI_PRESENCE_PENALTY")) - overrideScaledFloat32(&conf.Chat.FrequencyPenalty, os.Getenv("OPENAI_FREQUENCY_PENALTY")) - overrideInt(&conf.Chat.MinResponseTokens, os.Getenv("CHAT_MIN_RESPONSE_TOKENS")) - overrideString(&conf.DependOn.AiChatService.Address, firstNonEmpty( - os.Getenv("AI_CHAT_SERVICE_ADDRESS"), - os.Getenv("AI_CHAT_SERVICE_ADDR"), - )) - overrideString(&conf.DependOn.AiChatService.AccessToken, os.Getenv("AI_CHAT_SERVICE_ACCESS_TOKEN")) -} - -func loadProjectDotEnv(configFilePath string) { - projectRoot := filepath.Dir(filepath.Dir(configFilePath)) - envPath := filepath.Join(projectRoot, ".env") - file, err := os.Open(envPath) - if err != nil { - return - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if line == "" || strings.HasPrefix(line, "#") { - continue - } - key, value, ok := strings.Cut(line, "=") - if !ok { - continue - } - key = strings.TrimSpace(key) - value = strings.TrimSpace(value) - if key == "" { - continue - } - if _, exists := os.LookupEnv(key); exists { - continue - } - _ = os.Setenv(key, value) - } -} - -func firstNonEmpty(values ...string) string { - for _, value := range values { - if strings.TrimSpace(value) != "" { - return value - } - } - return "" -} - -func overrideString(target *string, value string) { - if strings.TrimSpace(value) != "" { - *target = strings.TrimSpace(value) - } -} - -func overrideInt(target *int, value string) { - value = strings.TrimSpace(value) - if value == "" { - return - } - if parsed, err := strconv.Atoi(value); err == nil { - *target = parsed - } -} - -func overrideScaledFloat32(target *float32, value string) { - value = strings.TrimSpace(value) - if value == "" { - return - } - if parsed, err := strconv.ParseFloat(value, 32); err == nil { - *target = float32(parsed / 100.0) - } -} diff --git a/ai-chat-service/Dockerfile b/ai-chat-service/Dockerfile index 5bda655..3399f12 100644 --- a/ai-chat-service/Dockerfile +++ b/ai-chat-service/Dockerfile @@ -1,17 +1,10 @@ -# 编译阶段 -FROM quay.io/0voice/golang:1.20 as stage0 -RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct -ADD ./ /src/ai-chat-service -WORKDIR /src/ai-chat-service -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ai-chat-service ./chat-server - -FROM quay.io/0voice/alpine:3.18 as stage1 +FROM alpine:3.18 ADD ./grpc_health_probe-linux-amd64 /usr/bin/grpc_health_probe RUN chmod +x /usr/bin/grpc_health_probe -MAINTAINER nick +LABEL maintainer="nick" WORKDIR /app/ -ADD ./dev.config.yaml /app/config.yaml -COPY --from=stage0 /src/ai-chat-service/ai-chat-service ./ +COPY ./ai-chat-service-bin ./ai-chat-service +COPY ./docker.config.yaml /app/config.yaml # 指定入口程序 ENTRYPOINT ["./ai-chat-service"] # 指定容器的启动命令或者入口程序的参数 diff --git a/ai-chat-service/ai-chat-service-bin b/ai-chat-service/ai-chat-service-bin new file mode 100755 index 0000000..a36dcf0 Binary files /dev/null and b/ai-chat-service/ai-chat-service-bin differ diff --git a/ai-chat-service/docker.config.yaml b/ai-chat-service/docker.config.yaml new file mode 100644 index 0000000..808c628 --- /dev/null +++ b/ai-chat-service/docker.config.yaml @@ -0,0 +1,54 @@ +server: + ip: 0.0.0.0 + port: 50055 + accessToken: "me256487ang1chubdpdialoud22sev1ozhoguumyqca" +log: + level: "info" + logPath: "runtime/logs/app.log" +chat: + api_key: "sk-8NMdsGbDAMpWdd6hrKHepr1tNVXTy2QppKAqJkoJcHd6TYLs" + base_url: "https://api.moonshot.cn/v1" + model: "kimi-k2.5" + max_tokens: 4096 + temperature: 1 + top_p: 0.95 + presence_penalty: 0 + frequency_penalty: 0 + bot_desc: "你是一个AI助手,我需要你模拟一名资深的软件工程师来回答我的问题" + min_response_tokens: 600 + context_ttl: 1800 + context_len: 4 +redis: + host: "host.docker.internal" + port: 8888 + pwd: "123456" +mysql: + dsn: "root:root@tcp(mysql:3306)/ai_chat?collation=utf8mb4_unicode_ci&charset=utf8mb4" + maxLifeTime: 3600 + maxOpenConn: 10 + maxIdleConn: 10 +dependOn: + sensitive: + address: "sensitive-filter:50053" + accessToken: "ang1chubdev1ozhome256487d22sapguuv1ozhom" + keywords: + address: "keywords-filter:50054" + accessToken: "ang1chubdev1ozhome256487d22sapguuv1ozhom" + tokenizer: + address: "http://tokenizer:3002" +vector: + provider: "pgvector" + threshold: 0.99 + pgvector: + dsn: "postgres://postgres:postgres@pgvector:5432/ai_chat?sslmode=disable" + table: "chat_record_vectors" + dimensions: 1024 + maxLifeTime: 3600 + maxOpenConn: 10 + maxIdleConn: 10 +embedding: + provider: "openai-compatible" + base_url: "https://open.bigmodel.cn/api/paas/v4" + api_key: "d51b903546814cc9981d3649a4a899a3.NQOtz3ocRtQwimh9" + model: "embedding-2" + timeout: 10 diff --git a/ai-chat-service/pkg/config/config.go b/ai-chat-service/pkg/config/config.go index 3a902da..095a568 100644 --- a/ai-chat-service/pkg/config/config.go +++ b/ai-chat-service/pkg/config/config.go @@ -1,11 +1,7 @@ package config import ( - "bufio" "log" - "os" - "path/filepath" - "strings" "github.com/spf13/viper" ) @@ -102,7 +98,6 @@ type Config struct { var conf *Config func InitConfig(filePath string, typ ...string) { - loadProjectDotEnv(filePath) v := viper.New() v.SetConfigFile(filePath) if len(typ) > 0 { @@ -171,83 +166,4 @@ func normalizeConfig(conf *Config) { if conf.Embedding.Timeout == 0 { conf.Embedding.Timeout = 10 } - overrideChatFromEnv(conf) - overrideEmbeddingFromEnv(conf) -} - -func overrideChatFromEnv(conf *Config) { - if value := os.Getenv("AI_CHAT_OPENAI_BASE_URL"); value != "" { - conf.Chat.BaseUrl = value - } else if value := os.Getenv("OPENAI_BASE_URL"); value != "" { - conf.Chat.BaseUrl = value - } - - if value := os.Getenv("AI_CHAT_OPENAI_MODEL"); value != "" { - conf.Chat.Model = value - } else if value := os.Getenv("OPENAI_MODEL"); value != "" { - conf.Chat.Model = value - } - - if value := os.Getenv("AI_CHAT_OPENAI_API_KEY"); value != "" { - conf.Chat.ApiKey = value - return - } - if value := os.Getenv("OPENAI_API_KEY"); value != "" { - conf.Chat.ApiKey = value - return - } - if value := os.Getenv("MOONSHOT_API_KEY"); value != "" { - conf.Chat.ApiKey = value - } -} - -func overrideEmbeddingFromEnv(conf *Config) { - if value := os.Getenv("AI_CHAT_EMBEDDING_BASE_URL"); value != "" { - conf.Embedding.BaseUrl = value - } - if value := os.Getenv("AI_CHAT_EMBEDDING_MODEL"); value != "" { - conf.Embedding.Model = value - } - if value := os.Getenv("AI_CHAT_EMBEDDING_API_KEY"); value != "" { - conf.Embedding.ApiKey = value - return - } - if value := os.Getenv("ZAI_API_KEY"); value != "" { - conf.Embedding.ApiKey = value - } -} - -func loadProjectDotEnv(configFilePath string) { - projectRoot := filepath.Dir(filepath.Dir(configFilePath)) - loadDotEnvFile(filepath.Join(projectRoot, ".env")) -} - -func loadDotEnvFile(path string) { - file, err := os.Open(path) - if err != nil { - return - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if line == "" || strings.HasPrefix(line, "#") { - continue - } - key, value, ok := strings.Cut(line, "=") - if !ok { - continue - } - key = strings.TrimSpace(key) - value = strings.TrimSpace(value) - value = strings.Trim(value, `"'`) - if key == "" { - continue - } - if _, exists := os.LookupEnv(key); exists { - continue - } - _ = os.Setenv(key, value) - } } diff --git a/docker-compose.yml b/docker-compose.yml index 1fef3a6..2b30a4b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,23 @@ services: + mysql: + image: mysql:8.0 + container_name: ai-chat-mysql + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: root + command: + - --default-authentication-plugin=mysql_native_password + # ports: + # - "3306:3306" + volumes: + - /data/mysql:/var/lib/mysql + - /home/lian/share/aichat/init/create_db.sql:/docker-entrypoint-initdb.d/create_db.sql:ro + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-proot"] + interval: 15s + timeout: 5s + retries: 10 + pgvector: image: pgvector/pgvector:pg16 container_name: ai-chat-pgvector @@ -49,24 +68,40 @@ services: - "50054:50054" restart: unless-stopped + ai-chat-service: + build: + context: ./ai-chat-service + image: ai-chat-service:1.0.0 + container_name: ai-chat-service + volumes: + - /home/lian/share/aichat/ai-chat-service/docker.config.yaml:/app/config.yaml:ro + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - "50055:50055" + depends_on: + - mysql + - tokenizer + - sensitive-filter + - keywords-filter + - pgvector + healthcheck: + test: ["CMD", "grpc_health_probe", "-addr=:50055"] + interval: 15s + timeout: 5s + retries: 5 + restart: unless-stopped + chatgpt-web-backend: build: context: ./ai-chat-backend image: ai-chat-backend:1.0.0 container_name: chatgpt-web-backend # 容器内端口:7080 - environment: - SERVER_HOST: 0.0.0.0 - SERVER_PORT: 7080 - FRONTEND_PATH: www - OPENAI_MODEL: ${OPENAI_MODEL} - OPENAI_TEMPERATURE: "${OPENAI_TEMPERATURE}" - OPENAI_PRESENCE_PENALTY: "${OPENAI_PRESENCE_PENALTY}" - OPENAI_FREQUENCY_PENALTY: "${OPENAI_FREQUENCY_PENALTY}" - AI_CHAT_SERVICE_ADDRESS: ${AI_CHAT_SERVICE_ADDRESS:-host.docker.internal:50055} - AI_CHAT_SERVICE_ACCESS_TOKEN: ${AI_CHAT_SERVICE_ACCESS_TOKEN:-me256487ang1chubdpdialoud22sev1ozhoguumyqca} - extra_hosts: - - "host.docker.internal:host-gateway" + volumes: + - /home/lian/share/aichat/ai-chat-backend/docker.config.yaml:/app/config.yaml:ro + depends_on: + - ai-chat-service restart: unless-stopped chatgpt-web-frontend: