redis缓存替换+pgvector向量替换

This commit is contained in:
1iaan
2026-04-04 22:39:16 +08:00
parent e993eb6c5c
commit 9d7c416737
124 changed files with 5460 additions and 141 deletions

5
.env
View File

@@ -9,4 +9,7 @@ OPENAI_FREQUENCY_PENALTY=0
TOKENIZER_BASE_URL=http://tokenizer:3002 TOKENIZER_BASE_URL=http://tokenizer:3002
# frontend 对外端口 # frontend 对外端口
FRONTEND_PORT=3002 FRONTEND_PORT=1025
# ai-chat-service embedding 配置
AI_CHAT_EMBEDDING_API_KEY=d51b903546814cc9981d3649a4a899a3.NQOtz3ocRtQwimh9

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
docs docs
.workspace.codex .workspace.codex
.env

View File

@@ -33,6 +33,9 @@ docker run -d --name chatgpt-web-backend \
--openai-presence-penalty 0 \ --openai-presence-penalty 0 \
--openai-frequency-penalty 0 --openai-frequency-penalty 0
# ai-chat-backend
docker build -t ai-chat-backend:1.0.0 .
# chatgpt-web-frontend # chatgpt-web-frontend
HUSKY=0 pnpm bootstrap HUSKY=0 pnpm bootstrap
pnpm dev pnpm dev
@@ -44,43 +47,4 @@ docker build -t tokenizer:1.0.0 .
# keywords-filter # keywords-filter
docker build -t keywords-filter:1.0.0 . docker build -t keywords-filter:1.0.0 .
```
## backend
访问 tokenizer
```go
url := fmt.Sprintf("http://127.0.0.1:5000/tokenizer/%s", model)
// 改成
tokenizerBaseURL := os.Getenv("TOKENIZER_BASE_URL")
if tokenizerBaseURL == "" {
tokenizerBaseURL = "http://tokenizer:3002"
}
url := fmt.Sprintf("%s/tokenizer/%s", tokenizerBaseURL, model)
// 直接访问 tokenizer
```
## frontend
访问 backend
```conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://chatgpt-web-backend:7080/api/;
}
}
``` ```

3
ai-chat-backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
dist/
__pycache__/
runtime

View File

@@ -0,0 +1,19 @@
# 编译阶段
FROM golang:1.24 AS stage0
RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
ADD ./ /src/ai-chat-backend
WORKDIR /src/ai-chat-backend
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ai-chat-backend cmd/*.go
FROM alpine:3.18 AS stage1
ADD ./curl-amd64 /usr/bin/curl
RUN chmod +x /usr/bin/curl
LABEL maintainer="nick"
WORKDIR /app/
ADD ./dev.config.yaml /app/config.yaml
ADD ./www /app/www
COPY --from=stage0 /src/ai-chat-backend/ai-chat-backend ./
# 指定入口程序
ENTRYPOINT ["./ai-chat-backend"]
# 指定容器的启动命令或者入口程序的参数
CMD ["--config=config.yaml"]

31
ai-chat-backend/Makefile Normal file
View File

@@ -0,0 +1,31 @@
REGISTRY = arvintian
PROJECT = chatgpt-web
BASE_VERSION = v1
GIT_VERSION = $(shell git rev-parse --short HEAD)
.PHONY: build-local
build-local:
go build -v --ldflags="-w -X main.Version=$(GIT_VERSION)" -o dist/server cmd/*.go
.PHONY: build
build:
mkdir -p dist && docker run --rm -ti -e GOPROXY=https://goproxy.cn,direct -v $(GOPATH):/go -v `pwd`:/app -w /app golang:1.19-alpine \
go build -v --ldflags="-w -X main.Version=$(GIT_VERSION)" -o dist/server cmd/*.go
package: build
docker build -t $(REGISTRY)/$(PROJECT):$(GIT_VERSION) .
release: package
docker tag $(REGISTRY)/$(PROJECT):$(GIT_VERSION) $(REGISTRY)/$(PROJECT):latest
docker push $(REGISTRY)/$(PROJECT):$(GIT_VERSION)
docker push $(REGISTRY)/$(PROJECT):latest
base:
docker build -t $(REGISTRY)/$(PROJECT)-base:$(BASE_VERSION) -f Dockerfile.base .
release-base:
docker push $(REGISTRY)/$(PROJECT)-base:$(BASE_VERSION)
clean:
rm -rf dist
docker images | grep -E "$(REGISTRY)/$(PROJECT)" | grep -v "base" | awk '{print $$3}' | uniq | xargs -I {} docker rmi --force {}

38
ai-chat-backend/README.md Normal file
View File

@@ -0,0 +1,38 @@
# ChatGPT-Web
[English](https://github.com/Arvintian/chatgpt-web/blob/main/README_en.md)
使用[Gin](https://github.com/gin-gonic/gin)搭建ChatGPT服务,使用[ChatGPT Web](https://github.com/Chanzhaoyu/chatgpt-web)作为前端
## Usage
[Docker Hub](https://hub.docker.com/repository/docker/arvintian/chatgpt-web/general)
```
docker run --restart unless-stopped -d --log-opt max-size=50m -p 7080:7080 \
-e OPENAI_KEY=openai-key \
-e BASIC_AUTH_USER=user1,user2 \
-e BASIC_AUTH_PASSWORD=passwd1,passwd2 \
arvintian/chatgpt-web
```
- SERVER_PORT 服务端口,默认7080
- SERVER_HOST 服务监听地址,默认0.0.0.0
- SOCKS_PROXY socks代理URL,例如socks5://user:password@127.0.0.1:1080
- BASIC_AUTH_USER 认证用户,多用户英文逗号分隔
- BASIC_AUTH_PASSWORD 认证用户密码,多用户英文逗号分隔
- CHAT_SESSION_TTL 会话上下文保持时间,默认30分钟
- CHAT_MIN_RESPONSE_TOKENS 预留给会话响应的token数,可能导致截断最久的上下文,默认600
- OPENAI_KEY openai api key,参考OpenAI文档
- OPENAI_BASE_URL openai api base url,默认https://api.openai.com/v1
- OPENAI_MODEL 调用模型,默认gpt-3.5-turbo-0301
- OPENAI_MAX_TOKENS 模型max_tokens参数,参考OpenAI文档
- OPENAI_TEMPERATURE 模型temperature参数,参考OpenAI文档
- OPENAI_PRESENCE_PENALTY 模型presence_penalty参数,参考OpenAI文档
- OPENAI_FREQUENCY_PENALTY 模型frequency_penalty参数,参考OpenAI文档
更详细参数参考: [启动函数](https://github.com/Arvintian/chatgpt-web/blob/main/cmd/main.go#L21)
Tips:
- 模型float32参数使用(整型/100)设置,例如: temperature设置0.8,需要设置为80
- 内置支持了对OPENAI_BASE_URL的正向代理,可以作为OpenAI接口的代理服务器

View File

@@ -0,0 +1,34 @@
Built ChatGPT service using [Gin](https://github.com/gin-gonic/gin) and [ChatGPT Web](https://github.com/Chanzhaoyu/chatgpt-web) as the front-end.
## Usage
[Docker Hub](https://hub.docker.com/repository/docker/arvintian/chatgpt-web/general)
```
docker run --restart unless-stopped -d --log-opt max-size=50m -p 7080:7080 \
-e OPENAI_KEY=openai-key \
-e BASIC_AUTH_USER=user1,user2 \
-e BASIC_AUTH_PASSWORD=passwd1,passwd2 \
arvintian/chatgpt-web
```
- SERVER_PORT: Server port, default 7080.
- SERVER_HOST: Server listen address, default 0.0.0.0.
- SOCKS_PROXY: Socks proxy URL, for example socks5://user:password@127.0.0.1:1080.
- BASIC_AUTH_USER: Authentication user, multiple users separated by English commas.
- BASIC_AUTH_PASSWORD: Authentication user passwords, multiple users separated by English commas.
- CHAT_SESSION_TTL: Session context retention time, default 30 minutes.
- CHAT_MIN_RESPONSE_TOKENS: Tokens reserved for session response, may lead to truncation of the longest context, default 600.
- OPENAI_KEY: OpenAI API key, refer to OpenAI documentation.
- OPENAI_BASE_URL: OpenAI API base URL, default https://api.openai.com/v1.
- OPENAI_MODEL: Model called, default gpt-3.5-turbo-0301.
- OPENAI_MAX_TOKENS: Model max_tokens parameter, refer to OpenAI documentation.
- OPENAI_TEMPERATURE: Model temperature parameter, refer to OpenAI documentation.
- OPENAI_PRESENCE_PENALTY: Model presence_penalty parameter, refer to OpenAI documentation.
- OPENAI_FREQUENCY_PENALTY: Model frequency_penalty parameter, refer to OpenAI documentation.
For more detailed parameters, please refer to the [start function](https://github.com/Arvintian/chatgpt-web/blob/main/cmd/main.go#L21).
Tips:
- Use (integer/100) to set the float32 model parameters. For example, if temperature is set to 0.8, it needs to be set to 80.
- The built-in support for a forward proxy of OPENAI_BASE_URL enables it to function as a proxy server for the OpenAI API.

128
ai-chat-backend/cmd/main.go Normal file
View File

@@ -0,0 +1,128 @@
package main
import (
"ai-chat-backend/pkg/config"
"ai-chat-backend/pkg/log"
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"strings"
"time"
"ai-chat-backend/pkg/controllers"
"ai-chat-backend/pkg/middlewares"
"github.com/gin-gonic/gin"
)
type ChatGPTWebServer struct {
config *config.Config
log log.ILogger
}
func (r *ChatGPTWebServer) Run(ctx context.Context) error {
go r.httpServer(ctx)
return nil
}
func (r *ChatGPTWebServer) httpServer(ctx context.Context) {
chatService, err := controllers.NewChatService(r.config, r.log)
if err != nil {
r.log.Fatal(err)
}
addr := fmt.Sprintf("%s:%d", r.config.Http.IP, r.config.Http.Port)
r.log.InfoF("ChatGPT Web Server on: %s", addr)
server := &http.Server{
Addr: addr,
}
gin.SetMode(gin.ReleaseMode)
entry := gin.Default()
entry.Use(middlewares.Cors())
chat := entry.Group("/api")
if strings.TrimSpace(r.config.BasicAuthUser) != "" {
accounts := gin.Accounts{}
users := strings.Split(r.config.BasicAuthUser, ",")
passwords := strings.Split(r.config.BasicAuthPassword, ",")
if len(users) != len(passwords) {
r.log.Fatal("basic auth setting error")
}
for i := 0; i < len(users); i++ {
accounts[strings.TrimSpace(users[i])] = strings.TrimSpace(passwords[i])
}
chat.POST("/chat-process", gin.BasicAuth(accounts), middlewares.RateLimitMiddleware(1, 2), chatService.ChatProcess)
} else {
chat.POST("/chat-process", middlewares.RateLimitMiddleware(1, 2), chatService.ChatProcess)
}
chat.POST("/config", func(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"status": "Success",
"data": map[string]string{
"apiModel": "ChatGPTAPI",
"socksProxy": "",
},
})
})
chat.POST("/session", func(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"status": "Success",
"message": "",
"data": gin.H{
"auth": false,
},
})
})
chat.GET("/health", func(c *gin.Context) {})
fs := http.FileServer(http.Dir(r.config.FrontendPath))
entry.NoRoute(func(ctx *gin.Context) {
fs.ServeHTTP(ctx.Writer, ctx.Request)
})
entry.GET("/", func(ctx *gin.Context) {
http.ServeFile(ctx.Writer, ctx.Request, r.config.FrontendPath+"/index.html")
})
server.Handler = entry
go func(ctx context.Context) {
<-ctx.Done()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
r.log.InfoF("Server shutdown with error %v", err)
}
}(ctx)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
r.log.FatalF("Server listen and serve error %v", err)
}
}
var (
configFile = flag.String("config", "dev.config.yaml", "")
)
func main() {
flag.Parse()
ctx, stop := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
defer stop()
config.InitConfig(*configFile)
cnf := config.GetConfig()
fmt.Printf("%+v\n", cnf)
//初始化日志
log.SetLevel(cnf.Log.Level)
log.SetOutput(log.GetRotateWriter(cnf.Log.LogPath))
log.SetPrintCaller(true)
logger := log.NewLogger()
logger.SetLevel(cnf.Log.Level)
logger.SetOutput(log.GetRotateWriter(cnf.Log.LogPath))
logger.SetPrintCaller(true)
webServer := &ChatGPTWebServer{
config: cnf,
log: logger,
}
webServer.Run(ctx)
<-ctx.Done()
}

BIN
ai-chat-backend/curl-amd64 Normal file

Binary file not shown.

View File

@@ -0,0 +1,37 @@
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
# 表示语言模型输出的随机性和创造性
# 取值范围 0 ~ 1值越大随机性越高
temperature: 1.0
# 用于生成文本时控制选词的随机程度
# 即下一个预测单词考虑的概率范围
# 取值范围0 ~ 1
top_p: 1.0
# 存在惩罚,用于生成文本时控制重复使用单词的程度
# 取值范围 0 ~ 1 0表示不惩罚1表示禁止重复
presence_penalty: 0
# 用于控制模型生成回复时重复单词出现的频率
# 取值范围 0~1值越大表示回复时会更注重避免使用已经出现的单词
frequency_penalty: 0
# AI助手特征描述
bot_desc: "你是一个AI助手我需要你模拟一名资深的软件工程师来回答我的问题"
# 单次请求保留的响应tokens数量
min_response_tokens: 600
# 上下文缓存时长单位s
context_ttl: 1800
# 上下文消息条数
context_len: 4
dependOn:
ai-chat-service:
address: "localhost:50055"
accessToken: "me256487ang1chubdpdialoud22sev1ozhoguumyqca"

60
ai-chat-backend/go.mod Normal file
View File

@@ -0,0 +1,60 @@
module ai-chat-backend
go 1.21
toolchain go1.24.2
require (
github.com/gin-gonic/gin v1.9.0
github.com/google/uuid v1.6.0
github.com/sashabaranov/go-openai v1.4.2
github.com/sirupsen/logrus v1.9.3
github.com/spf13/viper v1.19.0
golang.org/x/time v0.5.0
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.1
gopkg.in/natefinch/lumberjack.v2 v2.2.1
k8s.io/klog/v2 v2.60.1
)
require (
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

142
ai-chat-backend/go.sum Normal file
View File

@@ -0,0 +1,142 @@
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
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.4.2 h1:IhacPY7O+ljlBoZRQe9VpsLNm0b4PHa6fOBGA9O4vfc=
github.com/sashabaranov/go-openai v1.4.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
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/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc=
k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -0,0 +1,185 @@
package config
import (
"bufio"
"github.com/spf13/viper"
"log"
"os"
"path/filepath"
"strconv"
"strings"
)
type Config struct {
Http struct {
IP string
Port int
}
BasicAuthUser string `mapstructure:"basic_auth_user"`
BasicAuthPassword string `mapstructure:"basic_auth_password"`
FrontendPath string `mapstructure:"frontend_path"`
Log struct {
Level string
LogPath string `mapstructure:"logPath"`
} `mapstructure:"log"`
Chat struct {
Model string `mapstructure:"model"`
MaxTokens int `mapstructure:"max_tokens"`
Temperature float32 `mapstructure:"temperature"`
TopP float32 `mapstructure:"top_p"`
PresencePenalty float32 `mapstructure:"presence_penalty"`
FrequencyPenalty float32 `mapstructure:"frequency_penalty"`
BotDesc string `mapstructure:"bot_desc"`
MinResponseTokens int `mapstructure:"min_response_tokens"`
ContextTTL int `mapstructure:"context_ttl"`
ContextLen int `mapstructure:"context_len"`
}
DependOn struct {
AiChatService struct {
Address string
AccessToken string
} `mapstructure:"ai-chat-service"`
}
}
var conf *Config
func InitConfig(filePath string, typ ...string) {
loadProjectDotEnv(filePath)
v := viper.New()
v.SetConfigFile(filePath)
if len(typ) > 0 {
v.SetConfigType(typ[0])
}
err := v.ReadInConfig()
if err != nil {
log.Fatal(err)
}
conf = &Config{}
err = v.Unmarshal(conf)
if err != nil {
log.Fatal(err)
}
normalizeConfig(conf)
overrideConfigFromEnv(conf)
}
func GetConfig() *Config {
return conf
}
func normalizeConfig(conf *Config) {
if conf.Http.IP == "" {
conf.Http.IP = "0.0.0.0"
}
if conf.Http.Port == 0 {
conf.Http.Port = 7080
}
if conf.FrontendPath == "" {
conf.FrontendPath = "www"
}
if conf.Chat.Model == "" {
conf.Chat.Model = "kimi-k2.5"
}
if conf.Chat.MaxTokens == 0 {
conf.Chat.MaxTokens = 4096
}
if conf.Chat.Temperature == 0 {
conf.Chat.Temperature = 1.0
}
if conf.Chat.TopP == 0 {
conf.Chat.TopP = 1.0
}
if conf.Chat.MinResponseTokens == 0 {
conf.Chat.MinResponseTokens = 600
}
if conf.DependOn.AiChatService.Address == "" {
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)
}
}

View File

@@ -0,0 +1,192 @@
package controllers
import (
"ai-chat-backend/pkg/config"
"ai-chat-backend/pkg/log"
"ai-chat-backend/services"
ai_chat_service "ai-chat-backend/services/ai-chat-service"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"time"
ai_chat_service_proto "ai-chat-backend/services/ai-chat-service/proto"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
openai "github.com/sashabaranov/go-openai"
"k8s.io/klog/v2"
)
type ChatService struct {
config *config.Config
log log.ILogger
}
type ChatCompletionParams struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float32 `json:"temperature,omitempty"`
PresencePenalty float32 `json:"presence_penalty,omitempty"`
FrequencyPenalty float32 `json:"frequency_penalty,omitempty"`
ChatSessionTTL time.Duration `json:"chat_session_ttl"`
ChatMinResponseTokens int `json:"chat_min_response_tokens"`
}
type ChatMessageRequest struct {
Prompt string `json:"prompt"`
Options ChatMessageRequestOptions `json:"options"`
}
type ChatMessageRequestOptions struct {
Name string `json:"name"`
ParentMessageId string `json:"parentMessageId"`
}
type ChatMessage struct {
ID string `json:"id"`
Text string `json:"text"`
Role string `json:"role"`
Name string `json:"name"`
Delta string `json:"delta"`
Detail *ai_chat_service_proto.ChatCompletionStreamResponse `json:"detail"`
TokenCount int `json:"tokenCount"`
ParentMessageId string `json:"parentMessageId"`
}
func NewChatService(config *config.Config, log log.ILogger) (*ChatService, error) {
return &ChatService{
config: config,
log: log,
}, nil
}
func (chat *ChatService) ChatProcess(ctx *gin.Context) {
payload := ChatMessageRequest{}
if err := ctx.BindJSON(&payload); err != nil {
klog.Error(err)
ctx.JSON(200, gin.H{
"status": "Fail",
"message": fmt.Sprintf("%v", err),
"data": nil,
})
return
}
messageID := uuid.New().String()
result := ChatMessage{
ID: uuid.New().String(),
Role: openai.ChatMessageRoleAssistant,
Text: "",
ParentMessageId: messageID,
}
aiChatServicePool := ai_chat_service.GetAiChatServiceClientPool()
conn := aiChatServicePool.Get()
defer aiChatServicePool.Put(conn)
ctx1 := services.AppendBearerTokenToContext(context.Background(), chat.config.DependOn.AiChatService.AccessToken)
in := &ai_chat_service_proto.ChatCompletionRequest{
Id: messageID,
Message: payload.Prompt,
Pid: payload.Options.ParentMessageId,
EnableContext: false,
ChatParam: &ai_chat_service_proto.ChatParam{
Model: chat.config.Chat.Model,
MaxTokens: int32(chat.config.Chat.MaxTokens),
Temperature: chat.config.Chat.Temperature,
TopP: chat.topP(),
PresencePenalty: chat.config.Chat.PresencePenalty,
FrequencyPenalty: chat.config.Chat.FrequencyPenalty,
BotDesc: chat.config.Chat.BotDesc,
ContextTTL: int32(chat.config.Chat.ContextTTL),
ContextLen: int32(chat.config.Chat.ContextLen),
MinResponseTokens: int32(chat.config.Chat.MinResponseTokens),
},
}
if in.Pid != "" {
in.EnableContext = true
}
aiChatServiceClient := ai_chat_service_proto.NewChatClient(conn)
stream, err := aiChatServiceClient.ChatCompletionStream(ctx1, in)
if err != nil {
chat.log.Error(err)
ctx.JSON(200, gin.H{
"status": "Fail",
"message": fmt.Sprintf("%v", err),
"data": nil,
})
return
}
defer stream.CloseSend()
firstChunk := true
ctx.Header("Content-type", "application/octet-stream")
for {
rsp, err := stream.Recv()
if errors.Is(err, io.EOF) {
return
}
if err != nil {
klog.Error(err)
ctx.JSON(200, gin.H{
"status": "Fail",
"message": fmt.Sprintf("OpenAI Event Error %v", err),
"data": nil,
})
return
}
if rsp.Id != "" {
result.ID = rsp.Id
}
if len(rsp.Choices) > 0 {
content := rsp.Choices[0].Delta.Content
result.Delta = content
if len(content) > 0 {
result.Text += content
}
result.Detail = rsp
}
bts, err := json.Marshal(result)
if err != nil {
klog.Error(err)
ctx.JSON(200, gin.H{
"status": "Fail",
"message": fmt.Sprintf("OpenAI Event Marshal Error %v", err),
"data": nil,
})
return
}
if !firstChunk {
ctx.Writer.Write([]byte("\n"))
} else {
firstChunk = false
}
if _, err := ctx.Writer.Write(bts); err != nil {
klog.Error(err)
return
}
ctx.Writer.Flush()
}
}
func (chat *ChatService) topP() float32 {
model := strings.ToLower(chat.config.Chat.Model)
if strings.HasPrefix(model, "kimi-") || strings.HasPrefix(model, "moonshot-") {
return 0.95
}
if chat.config.Chat.TopP > 0 {
return chat.config.Chat.TopP
}
return 1
}

View File

@@ -0,0 +1,3 @@
# 日志框架
1. 可通过包调用日志打印,也可以通过对象调用日志打印
2. 可以自动切分日志文件

View File

@@ -0,0 +1,19 @@
package log
import "github.com/sirupsen/logrus"
import nativeLog "log"
type errorHook struct {
}
func (*errorHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
}
}
func (*errorHook) Fire(entry *logrus.Entry) error {
nativeLog.Println(entry.Message, entry.Data)
return nil
}

View File

@@ -0,0 +1,233 @@
package log
import (
"errors"
"fmt"
"github.com/sirupsen/logrus"
"io"
"runtime"
)
type ILogger interface {
SetLevel(lvl string)
SetOutput(writer io.Writer)
SetPrintCaller(bool)
SetCaller(caller func() (file string, line int, funcName string, err error))
Trace(args ...interface{})
Debug(args ...interface{})
Info(args ...interface{})
Warning(args ...interface{})
Error(args ...interface{})
Fatal(args ...interface{})
Panic(args ...interface{})
TraceF(format string, args ...interface{})
DebugF(format string, args ...interface{})
InfoF(format string, args ...interface{})
WarningF(format string, args ...interface{})
ErrorF(format string, args ...interface{})
FatalF(format string, args ...interface{})
PanicF(format string, args ...interface{})
WithFields(fields map[string]interface{}) ILogger
}
type Logger struct {
entry *logrus.Entry
// panic,fatal,error,warn,warning,info,debug,trace
level string
printCaller bool
caller func() (file string, line int, funcName string, err error)
}
// 设置日志打印级别
func (l *Logger) SetLevel(lvl string) {
if lvl == "" {
return
}
level, err := logrus.ParseLevel(lvl)
if err == nil {
l.level = lvl
l.entry.Logger.Level = level
}
}
// 设置日志输出位置
func (l *Logger) SetOutput(writer io.Writer) {
l.entry.Logger.SetOutput(writer)
}
// 设置是否打印调用信息
func (l *Logger) SetPrintCaller(printCaller bool) {
l.printCaller = printCaller
}
func (l *Logger) SetCaller(caller func() (file string, line int, funcName string, err error)) {
l.caller = caller
}
// 获取caller信息
func (l *Logger) getCallerInfo(level logrus.Level) map[string]interface{} {
mp := make(map[string]interface{})
if l.printCaller == true || level != logrus.InfoLevel {
file, line, funcName, err := l.caller()
if err == nil {
mp["file"] = fmt.Sprintf("%s:%d", file, line)
mp["func"] = funcName
}
}
return mp
}
func (l *Logger) log(level logrus.Level, args ...interface{}) {
l.entry.WithFields(l.getCallerInfo(level)).Log(level, args...)
}
func (l *Logger) logf(level logrus.Level, format string, args ...interface{}) {
l.entry.WithFields(l.getCallerInfo(level)).Logf(level, format, args...)
}
func (l *Logger) Trace(args ...interface{}) {
l.log(logrus.TraceLevel, args...)
}
func (l *Logger) Debug(args ...interface{}) {
l.log(logrus.DebugLevel, args...)
}
func (l *Logger) Info(args ...interface{}) {
l.log(logrus.InfoLevel, args...)
}
func (l *Logger) Warning(args ...interface{}) {
l.log(logrus.WarnLevel, args...)
}
func (l *Logger) Error(args ...interface{}) {
l.log(logrus.ErrorLevel, args...)
}
func (l *Logger) Fatal(args ...interface{}) {
l.log(logrus.FatalLevel, args...)
}
func (l *Logger) Panic(args ...interface{}) {
l.log(logrus.PanicLevel, args...)
}
func (l *Logger) TraceF(format string, args ...interface{}) {
l.logf(logrus.TraceLevel, format, args...)
}
func (l *Logger) DebugF(format string, args ...interface{}) {
l.logf(logrus.DebugLevel, format, args...)
}
func (l *Logger) InfoF(format string, args ...interface{}) {
l.logf(logrus.InfoLevel, format, args...)
}
func (l *Logger) WarningF(format string, args ...interface{}) {
l.logf(logrus.WarnLevel, format, args...)
}
func (l *Logger) ErrorF(format string, args ...interface{}) {
l.logf(logrus.ErrorLevel, format, args...)
}
func (l *Logger) FatalF(format string, args ...interface{}) {
l.logf(logrus.FatalLevel, format, args...)
}
func (l *Logger) PanicF(format string, args ...interface{}) {
l.logf(logrus.PanicLevel, format, args...)
}
func (l *Logger) WithFields(fields map[string]interface{}) ILogger {
entry := l.entry.WithFields(fields)
return &Logger{entry: entry, level: l.level, printCaller: l.printCaller, caller: l.caller}
}
var log *Logger
func NewLogger() ILogger {
return newLogger()
}
func newLogger() *Logger {
log := logrus.New()
log.SetLevel(logrus.InfoLevel)
log.AddHook(&errorHook{})
logger := &Logger{
entry: logrus.NewEntry(log),
caller: defaultCaller,
}
return logger
}
func init() {
log = newLogger()
}
// 设置日志打印级别
func SetLevel(lvl string) {
if lvl == "" {
return
}
level, err := logrus.ParseLevel(lvl)
if err == nil {
log.level = lvl
log.entry.Logger.Level = level
}
}
// 设置日志的输出位置
func SetOutput(writer io.Writer) {
log.entry.Logger.SetOutput(writer)
}
// 设置是否打印调用信息
func SetPrintCaller(printCaller bool) {
log.printCaller = printCaller
}
func SetCaller(caller func() (file string, line int, funcName string, err error)) {
log.caller = caller
}
func defaultCaller() (file string, line int, funcName string, err error) {
pc, f, l, ok := runtime.Caller(4)
if !ok {
err = errors.New("caller failure")
return
}
funcName = runtime.FuncForPC(pc).Name()
file, line = f, l
return
}
func Trace(args ...interface{}) {
log.log(logrus.TraceLevel, args...)
}
func Debug(args ...interface{}) {
log.log(logrus.DebugLevel, args...)
}
func Info(args ...interface{}) {
log.log(logrus.InfoLevel, args...)
}
func Warning(args ...interface{}) {
log.log(logrus.WarnLevel, args...)
}
func Error(args ...interface{}) {
log.log(logrus.ErrorLevel, args...)
}
func Fatal(args ...interface{}) {
log.log(logrus.FatalLevel, args...)
}
func Panic(args ...interface{}) {
log.log(logrus.PanicLevel, args...)
}
func TraceF(format string, args ...interface{}) {
log.logf(logrus.TraceLevel, format, args...)
}
func DebugF(format string, args ...interface{}) {
log.logf(logrus.DebugLevel, format, args...)
}
func InfoF(format string, args ...interface{}) {
log.logf(logrus.InfoLevel, format, args...)
}
func WarningF(format string, args ...interface{}) {
log.logf(logrus.WarnLevel, format, args...)
}
func ErrorF(format string, args ...interface{}) {
log.logf(logrus.ErrorLevel, format, args...)
}
func FatalF(format string, args ...interface{}) {
log.logf(logrus.FatalLevel, format, args...)
}
func PanicF(format string, args ...interface{}) {
log.logf(logrus.PanicLevel, format, args...)
}
func WithFields(fields map[string]interface{}) *Logger {
entry := log.entry.WithFields(fields)
return &Logger{entry: entry, level: log.level, printCaller: log.printCaller, caller: log.caller}
}

View File

@@ -0,0 +1,58 @@
package log
import (
"gopkg.in/natefinch/lumberjack.v2"
"io"
"sync"
)
type fileRotateWriter struct {
data map[string]io.Writer
sync.RWMutex
}
func (frw *fileRotateWriter) getWriter(logPath string) io.Writer {
frw.RLock()
defer frw.RUnlock()
w, ok := frw.data[logPath]
if !ok {
return nil
}
return w
}
func (frw *fileRotateWriter) setWriter(logPath string, w io.Writer) io.Writer {
frw.Lock()
defer frw.Unlock()
frw.data[logPath] = w
return w
}
var _fileRotateWriter *fileRotateWriter
func init() {
_fileRotateWriter = &fileRotateWriter{
data: map[string]io.Writer{},
}
}
func GetRotateWriter(logPath string) io.Writer {
if logPath == "" {
panic("日志文件路径不能为空")
}
writer := _fileRotateWriter.getWriter(logPath)
if writer != nil {
return writer
}
writer = &lumberjack.Logger{
//文件名
Filename: logPath,
//单个文件大小单位MB
MaxSize: 1,
//最多保留文件数
MaxBackups: 15,
//最长保留时间(天)
MaxAge: 7,
LocalTime: true,
}
return _fileRotateWriter.setWriter(logPath, writer)
}

View File

@@ -0,0 +1,24 @@
package middlewares
import (
"github.com/gin-gonic/gin"
"net/http"
)
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
origin := c.Request.Header.Get("Origin")
if origin != "" {
c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
}
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
c.Next()
}
}

View File

@@ -0,0 +1,23 @@
package middlewares
import (
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
func RateLimitMiddleware(r rate.Limit, b int) gin.HandlerFunc {
limiter := rate.NewLimiter(r, b)
return func(c *gin.Context) {
if !limiter.Allow() {
// 请求被限制,返回错误信息
c.JSON(429, gin.H{
"status": "Fail",
"message": "Too many requests, please try again later",
"data": nil,
})
c.Abort()
return
}
c.Next()
}
}

View File

@@ -0,0 +1,43 @@
package tokenizer
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"github.com/sashabaranov/go-openai"
)
type tokenInfo struct {
Code int `json:"code"`
Count int `json:"num_tokens"`
Msg string `json:"msg"`
}
func GetTokenCount(message openai.ChatCompletionMessage, model string) (int, error) {
url := fmt.Sprintf("http://192.168.239.161:3002/tokenizer/%s", model)
info := tokenInfo{}
if err := postJSON(url, &message, &info); err != nil {
return 0, err
}
if info.Code != 200 {
return 0, fmt.Errorf("%v", info.Msg)
}
return info.Count, nil
}
func postJSON(url string, requestData *openai.ChatCompletionMessage, responseData *tokenInfo) error {
requestBody, err := json.Marshal(requestData)
if err != nil {
return err
}
resp, err := http.Post(url, "application/json", bytes.NewBuffer(requestBody))
if err != nil {
return err
}
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(responseData)
}

View File

@@ -0,0 +1,65 @@
package utils
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"k8s.io/klog/v2"
)
func ReplaceInFile(filePath string, targetStr string, replaceStr string) error {
// 读取文件内容
content, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
// 判断是否需要替换
if !strings.Contains(string(content), targetStr) {
return nil
}
// 替换字符串并写回文件,保持原有 filemode
info, err := os.Stat(filePath)
if err != nil {
return err
}
newContent := strings.ReplaceAll(string(content), targetStr, replaceStr)
err = ioutil.WriteFile(filePath, []byte(newContent), info.Mode())
if err != nil {
return err
}
klog.Infof("Replaced in file: %s\n", filePath)
return nil
}
func ReplaceFiles(rootDir string, replacePairs map[string]string) error {
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 如果当前路径是目录,则继续遍历
if info.IsDir() {
return nil
}
// 处理文件
for targetStr, replaceStr := range replacePairs {
err = ReplaceInFile(path, targetStr, replaceStr)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
return nil
}
func Reverse[S ~[]E, E any](s S) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}

View File

@@ -0,0 +1,2 @@
nuxt>=0.2.0
tiktoken>=0.3.3

View File

@@ -0,0 +1,23 @@
package ai_chat_service
import (
"ai-chat-backend/pkg/config"
grpc_client "ai-chat-backend/services/grpc-client"
"sync"
)
var pool grpc_client.ClientPool
var once sync.Once
type client struct {
grpc_client.DefaultClient
}
func GetAiChatServiceClientPool() grpc_client.ClientPool {
once.Do(func() {
cnf := config.GetConfig()
c := &client{}
pool = c.GetPool(cnf.DependOn.AiChatService.Address)
})
return pool
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
syntax = "proto3";
option go_package = "ai-chat-service/proto";
package ai_chat_service.zvoice.com;
message ChatCompletionRequest {
string message = 1[json_name = "message"];
string id = 2[json_name = "id"];
string pid = 3[json_name = "p_id"];
bool enableContext = 4[json_name = "enable_context"];
ChatParam chatParam = 5[json_name = "chat_param"];
}
message ChatParam {
string model = 1[json_name = "model"];
int32 maxTokens = 2[json_name = "max_tokens"];
float temperature = 3[json_name = "temperature"];
float topP = 4[json_name = "top_p"];
float presencePenalty = 5[json_name = "presence_penalty"];
float frequencyPenalty = 6[json_name = "frequency_penalty"];
string botDesc =7[json_name = "bot_desc"];
int32 minResponseTokens = 8[json_name = "min_response_tokens"];
int32 contextTTL = 9[json_name = "context_ttl"];
int32 contextLen = 10[json_name = "context_len"];
}
// 服务响应消息,非流式响应
message ChatCompletionResponse {
string id = 1 [json_name = "id"];
string object = 2 [json_name = "object"];
int64 created = 3 [json_name = "created"];
string model = 4 [json_name = "model"];
repeated ChatCompletionChoice choices = 5 [json_name = "choices"];
Usage usage = 6[json_name = "usage"];
}
message ChatCompletionChoice {
int32 index = 1[json_name = "index"];
ChatCompletionMessage message = 2 [json_name = "message"];
string finishReason = 3[json_name = "finish_reason"];
}
message ChatCompletionMessage {
string role=1[json_name = "role"];
string content=2[json_name = "content"];
string name=3[json_name = "name"];
}
message Usage {
int32 promptTokens = 1 [json_name = "prompt_tokens"];
int32 completionTokens =2 [json_name = "completion_tokens"];
int32 totalTokens = 3 [json_name = "total_tokens"];
}
// 服务响应消息,流式响应
message ChatCompletionStreamResponse {
string id = 1 [json_name = "id"];
string object = 2 [json_name = "object"];
int64 created = 3 [json_name = "created"];
string model = 4 [json_name = "model"];
repeated ChatCompletionStreamChoice choices = 5 [json_name = "choices"];
}
message ChatCompletionStreamChoice {
int32 index = 1[json_name = "index"];
ChatCompletionStreamChoiceDelta delta =2 [json_name = "delta"];
string finishReason = 3[json_name="finish_reason"];
}
message ChatCompletionStreamChoiceDelta {
string content = 1 [json_name = "content"];
string role = 2 [json_name = "role"];
}
service Chat {
rpc ChatCompletion(ChatCompletionRequest) returns (ChatCompletionResponse);
rpc ChatCompletionStream(ChatCompletionRequest) returns (stream ChatCompletionStreamResponse);
}

View File

@@ -0,0 +1,169 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v4.22.0
// source: proto/chat.proto
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// ChatClient is the client API for Chat service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ChatClient interface {
ChatCompletion(ctx context.Context, in *ChatCompletionRequest, opts ...grpc.CallOption) (*ChatCompletionResponse, error)
ChatCompletionStream(ctx context.Context, in *ChatCompletionRequest, opts ...grpc.CallOption) (Chat_ChatCompletionStreamClient, error)
}
type chatClient struct {
cc grpc.ClientConnInterface
}
func NewChatClient(cc grpc.ClientConnInterface) ChatClient {
return &chatClient{cc}
}
func (c *chatClient) ChatCompletion(ctx context.Context, in *ChatCompletionRequest, opts ...grpc.CallOption) (*ChatCompletionResponse, error) {
out := new(ChatCompletionResponse)
err := c.cc.Invoke(ctx, "/ai_chat_service.zvoice.com.Chat/ChatCompletion", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *chatClient) ChatCompletionStream(ctx context.Context, in *ChatCompletionRequest, opts ...grpc.CallOption) (Chat_ChatCompletionStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &Chat_ServiceDesc.Streams[0], "/ai_chat_service.zvoice.com.Chat/ChatCompletionStream", opts...)
if err != nil {
return nil, err
}
x := &chatChatCompletionStreamClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Chat_ChatCompletionStreamClient interface {
Recv() (*ChatCompletionStreamResponse, error)
grpc.ClientStream
}
type chatChatCompletionStreamClient struct {
grpc.ClientStream
}
func (x *chatChatCompletionStreamClient) Recv() (*ChatCompletionStreamResponse, error) {
m := new(ChatCompletionStreamResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// ChatServer is the server API for Chat service.
// All implementations must embed UnimplementedChatServer
// for forward compatibility
type ChatServer interface {
ChatCompletion(context.Context, *ChatCompletionRequest) (*ChatCompletionResponse, error)
ChatCompletionStream(*ChatCompletionRequest, Chat_ChatCompletionStreamServer) error
mustEmbedUnimplementedChatServer()
}
// UnimplementedChatServer must be embedded to have forward compatible implementations.
type UnimplementedChatServer struct {
}
func (UnimplementedChatServer) ChatCompletion(context.Context, *ChatCompletionRequest) (*ChatCompletionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChatCompletion not implemented")
}
func (UnimplementedChatServer) ChatCompletionStream(*ChatCompletionRequest, Chat_ChatCompletionStreamServer) error {
return status.Errorf(codes.Unimplemented, "method ChatCompletionStream not implemented")
}
func (UnimplementedChatServer) mustEmbedUnimplementedChatServer() {}
// UnsafeChatServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ChatServer will
// result in compilation errors.
type UnsafeChatServer interface {
mustEmbedUnimplementedChatServer()
}
func RegisterChatServer(s grpc.ServiceRegistrar, srv ChatServer) {
s.RegisterService(&Chat_ServiceDesc, srv)
}
func _Chat_ChatCompletion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ChatCompletionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ChatServer).ChatCompletion(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ai_chat_service.zvoice.com.Chat/ChatCompletion",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ChatServer).ChatCompletion(ctx, req.(*ChatCompletionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Chat_ChatCompletionStream_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(ChatCompletionRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ChatServer).ChatCompletionStream(m, &chatChatCompletionStreamServer{stream})
}
type Chat_ChatCompletionStreamServer interface {
Send(*ChatCompletionStreamResponse) error
grpc.ServerStream
}
type chatChatCompletionStreamServer struct {
grpc.ServerStream
}
func (x *chatChatCompletionStreamServer) Send(m *ChatCompletionStreamResponse) error {
return x.ServerStream.SendMsg(m)
}
// Chat_ServiceDesc is the grpc.ServiceDesc for Chat service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Chat_ServiceDesc = grpc.ServiceDesc{
ServiceName: "ai_chat_service.zvoice.com.Chat",
HandlerType: (*ChatServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ChatCompletion",
Handler: _Chat_ChatCompletion_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "ChatCompletionStream",
Handler: _Chat_ChatCompletionStream_Handler,
ServerStreams: true,
},
},
Metadata: "proto/chat.proto",
}

View File

@@ -0,0 +1,28 @@
package grpc_client
import (
"ai-chat-backend/pkg/log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type ServiceClient interface {
GetPool(addr string) ClientPool
}
type DefaultClient struct {
}
func (c *DefaultClient) GetPool(addr string) ClientPool {
pool, err := NewPool(addr, c.getOptions()...)
if err != nil {
log.Error(err)
return nil
}
return pool
}
func (c *DefaultClient) getOptions() []grpc.DialOption {
opts := make([]grpc.DialOption, 0)
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
return opts
}

View File

@@ -0,0 +1,48 @@
package grpc_client
import (
"ai-chat-backend/pkg/log"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"sync"
)
type ClientPool interface {
Get() *grpc.ClientConn
Put(*grpc.ClientConn)
}
type clientPool struct {
pool sync.Pool
}
func NewPool(target string, opts ...grpc.DialOption) (ClientPool, error) {
return &clientPool{
pool: sync.Pool{
New: func() any {
conn, err := grpc.NewClient(target, opts...)
if err != nil {
log.Error(err)
return nil
}
return conn
},
},
}, nil
}
func (c *clientPool) Get() *grpc.ClientConn {
conn := c.pool.Get().(*grpc.ClientConn)
if conn.GetState() == connectivity.Shutdown || conn.GetState() == connectivity.TransientFailure {
conn.Close()
conn = c.pool.New().(*grpc.ClientConn)
}
return conn
}
func (c *clientPool) Put(conn *grpc.ClientConn) {
if conn.GetState() == connectivity.Shutdown || conn.GetState() == connectivity.TransientFailure {
conn.Close()
return
}
c.pool.Put(conn)
}

View File

@@ -0,0 +1,11 @@
package services
import (
"context"
"google.golang.org/grpc/metadata"
)
func AppendBearerTokenToContext(ctx context.Context, accessToken string) context.Context {
md := metadata.Pairs("Authorization", "Bearer "+accessToken)
return metadata.NewOutgoingContext(ctx, md)
}

View File

@@ -0,0 +1 @@
{avatar:"https://raw.githubusercontent.com/Chanzhaoyu/chatgpt-web/main/src/assets/avatar.jpg",name:"ChatGPT",description:'知之为知之'}

View File

@@ -0,0 +1,57 @@
from nuxt import route, logger, Request
from nuxt.repositorys.validation import fields, use_args
import traceback
import tiktoken
encoding_cache = {}
support_models = set(["gpt-3.5-turbo",
"gpt-3.5-turbo-16k",
"gpt-4",
"gpt-4-32k"])
@route("/tokenizer/<str:model_name>", methods=["POST"])
@use_args({
"role": fields.Str(required=True),
"content": fields.Str(required=True),
"name": fields.Str(required=False)
}, location="json")
def get_num_tokens(req: Request, message: dict, model_name: str):
try:
return {
"code": 200,
"num_tokens": num_tokens_from_messages([message], model=model_name)
}
except Exception as e:
logger.error(traceback.format_exc())
return {
"code": 500,
"msg": "{}".format(e)
}
def num_tokens_from_messages(messages, model="gpt-3.5-turbo"):
"""Returns the number of tokens used by a list of messages."""
encoding = None
if model in encoding_cache:
encoding = encoding_cache.get(model)
else:
try:
encoding = tiktoken.encoding_for_model(model)
except KeyError:
encoding = tiktoken.get_encoding("cl100k_base")
encoding_cache[model] = encoding
if model in support_models: # note: future models may deviate from this
num_tokens = 0
for message in messages:
num_tokens += 4 # every message follows <im_start>{role/name}\n{content}<im_end>\n
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name": # if there's a name, the role is omitted
num_tokens += -1 # role is always required and always 1 token
num_tokens += 3 # every reply is primed with <im_start>assistant
return num_tokens
else:
raise NotImplementedError(f"""num_tokens_from_messages() is not presently implemented for model {model}.
See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""")

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.markdown-body{background-color:transparent;font-size:14px}.markdown-body p{white-space:pre-wrap}.markdown-body ol{list-style-type:decimal}.markdown-body ul{list-style-type:disc}.markdown-body pre code,.markdown-body pre tt{line-height:1.65}.markdown-body .highlight pre,.markdown-body pre{background-color:#fff}.markdown-body code.hljs{padding:0}.markdown-body .code-block-wrapper{position:relative;padding-top:24px}.markdown-body .code-block-header{position:absolute;top:5px;right:0;width:100%;padding:0 1rem;display:flex;justify-content:flex-end;align-items:center;color:#b3b3b3}.markdown-body .code-block-header__copy{cursor:pointer;margin-left:.5rem;-webkit-user-select:none;-moz-user-select:none;user-select:none}.markdown-body .code-block-header__copy:hover{color:#65a665}html.dark .message-reply .whitespace-pre-wrap{white-space:pre-wrap;color:var(--n-text-color)}html.dark .highlight pre,html.dark pre{background-color:#282c34}

View File

@@ -0,0 +1 @@
import{n as s,b7 as a,bh as o,ba as r,bb as c,b9 as n,bB as i,bC as l,b6 as d,aM as u,bA as p}from"./index-79a31bf6.js";const x="/assets/404-451d813e.svg",_={class:"flex h-full"},m={class:"px-4 m-auto space-y-4 text-center max-[400px]"},f=i('<h1 class="text-4xl text-slate-800 dark:text-neutral-200"> Sorry, page not found! </h1><p class="text-base text-slate-500 dark:text-neutral-400"> Sorry, we couldnt find the page youre looking for. Perhaps youve mistyped the URL? Be sure to check your spelling. </p><div class="flex items-center justify-center text-center"><div class="w-[300px]"><img src="'+x+'" alt="404"></div></div>',3),g=s({__name:"index",setup(h){const e=l();function t(){e.push("/")}return(b,y)=>(d(),a("div",_,[o("div",m,[f,r(n(p),{type:"primary",onClick:t},{default:c(()=>[u(" Go to Home ")]),_:1})])]))}});export{g as default};

Some files were not shown because too many files have changed in this diff Show More