Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d4ffa603d | ||
|
|
b461c75222 | ||
|
|
e2fd28897c | ||
|
|
bf908be12e | ||
|
|
dc4ffb96b3 | ||
|
|
39cbed1853 | ||
|
|
8d82c1b930 | ||
|
|
9d3c30785e | ||
|
|
f5068c3a58 | ||
|
|
df71f28edf | ||
|
|
6bb73cf30b | ||
|
|
37d888228c | ||
|
|
4291a95e27 | ||
|
|
5df6c92050 | ||
|
|
d7dfa7b216 | ||
|
|
f09b7c8d95 | ||
|
|
be04f67050 | ||
|
|
1b9d4b5e65 | ||
|
|
c37db35a33 | ||
|
|
062645499b | ||
|
|
ccdb3baa0d | ||
|
|
6de5909c48 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
go.sum
|
||||||
|
chatgpt-service
|
||||||
@@ -2,11 +2,11 @@ FROM alpine:3.15.3
|
|||||||
|
|
||||||
LABEL maintainer="cookeem"
|
LABEL maintainer="cookeem"
|
||||||
LABEL email="cookeem@qq.com"
|
LABEL email="cookeem@qq.com"
|
||||||
LABEL version="v1.0.0"
|
LABEL version="v1.0.1"
|
||||||
|
|
||||||
RUN adduser -h /chatgpt-service -u 1000 -D dory
|
RUN adduser -h /chatgpt-service -u 1000 -D dory
|
||||||
COPY chatgpt-service /chatgpt-service/
|
COPY chatgpt-service /chatgpt-service/
|
||||||
WORKDIR /chatgpt-service
|
WORKDIR /chatgpt-service
|
||||||
USER dory
|
USER dory
|
||||||
|
|
||||||
# docker build -t doryengine/chatgpt-service:v1.0.0-alpine .
|
# docker build -t doryengine/chatgpt-service:v1.0.1-alpine .
|
||||||
|
|||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 SeeFlowerX
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
14
README.md
14
README.md
@@ -1,4 +1,4 @@
|
|||||||
# 实时ChatGPT服务
|
# 实时ChatGPT服务,基于最新的gpt-3.5-turbo-0301模型
|
||||||
|
|
||||||
## chatGPT-service和chatGPT-stream
|
## chatGPT-service和chatGPT-stream
|
||||||
|
|
||||||
@@ -7,6 +7,11 @@
|
|||||||
- chatGPT-stream: [https://github.com/cookeem/chatgpt-stream](https://github.com/cookeem/chatgpt-stream)
|
- chatGPT-stream: [https://github.com/cookeem/chatgpt-stream](https://github.com/cookeem/chatgpt-stream)
|
||||||
- chatGPT-stream是一个前端服务,以websocket的方式实时接收chatGPT-service返回的消息
|
- chatGPT-stream是一个前端服务,以websocket的方式实时接收chatGPT-service返回的消息
|
||||||
|
|
||||||
|
## gitee传送门
|
||||||
|
|
||||||
|
- [https://gitee.com/cookeem/chatgpt-service](https://gitee.com/cookeem/chatgpt-service)
|
||||||
|
- [https://gitee.com/cookeem/chatgpt-stream](https://gitee.com/cookeem/chatgpt-stream)
|
||||||
|
|
||||||
## 效果图
|
## 效果图
|
||||||
|
|
||||||

|

|
||||||
@@ -16,7 +21,7 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 拉取代码
|
# 拉取代码
|
||||||
git clone https://github.com/chatgpt-service.git
|
git clone https://github.com/cookeem/chatgpt-service.git
|
||||||
cd chatgpt-service
|
cd chatgpt-service
|
||||||
|
|
||||||
# chatGPT的注册页面: https://beta.openai.com/signup
|
# chatGPT的注册页面: https://beta.openai.com/signup
|
||||||
@@ -29,7 +34,10 @@ vi config.yaml
|
|||||||
appKey: "xxxxxx"
|
appKey: "xxxxxx"
|
||||||
|
|
||||||
|
|
||||||
# 使用docker启动服务
|
# 使用docker-compose启动服务
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 查看服务状态
|
||||||
docker-compose ps
|
docker-compose ps
|
||||||
Name Command State Ports
|
Name Command State Ports
|
||||||
-----------------------------------------------------------------------------------------------
|
-----------------------------------------------------------------------------------------------
|
||||||
|
|||||||
105
chat/service.go
105
chat/service.go
@@ -2,17 +2,16 @@ package chat
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
gogpt "github.com/sashabaranov/go-gpt3"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
gogpt "github.com/sashabaranov/go-gpt3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Api struct {
|
type Api struct {
|
||||||
@@ -45,25 +44,6 @@ func (api *Api) responseFunc(c *gin.Context, startTime time.Time, status, msg st
|
|||||||
c.JSON(httpStatus, ar)
|
c.JSON(httpStatus, ar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *Api) wsCheckConnectStatus(conn *websocket.Conn, chClose chan int) {
|
|
||||||
var err error
|
|
||||||
defer func() {
|
|
||||||
conn.Close()
|
|
||||||
}()
|
|
||||||
conn.SetReadDeadline(time.Now().Add(pingWait))
|
|
||||||
conn.SetPongHandler(func(s string) error {
|
|
||||||
conn.SetReadDeadline(time.Now().Add(pingWait))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
for {
|
|
||||||
_, _, err = conn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
chClose <- 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *Api) wsPingMsg(conn *websocket.Conn, chClose, chIsCloseSet chan int) {
|
func (api *Api) wsPingMsg(conn *websocket.Conn, chClose, chIsCloseSet chan int) {
|
||||||
var err error
|
var err error
|
||||||
ticker := time.NewTicker(pingPeriod)
|
ticker := time.NewTicker(pingPeriod)
|
||||||
@@ -93,21 +73,27 @@ func (api *Api) wsPingMsg(conn *websocket.Conn, chClose, chIsCloseSet chan int)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *Api) GetChatMessage(conn *websocket.Conn, cli *gogpt.Client, mutex *sync.Mutex, requestMsg string) {
|
func (api *Api) GetChatMessage(conn *websocket.Conn, cli *gogpt.Client, mutex *sync.Mutex, requestMsg string) {
|
||||||
|
var err error
|
||||||
var strResp string
|
var strResp string
|
||||||
req := gogpt.CompletionRequest{
|
req := gogpt.ChatCompletionRequest{
|
||||||
Model: gogpt.GPT3TextDavinci003,
|
Model: gogpt.GPT3Dot5Turbo0301,
|
||||||
MaxTokens: api.Config.MaxLength,
|
MaxTokens: api.Config.MaxLength,
|
||||||
Temperature: 0.6,
|
Temperature: 1.0,
|
||||||
Prompt: requestMsg,
|
Messages: []gogpt.ChatCompletionMessage{
|
||||||
|
{
|
||||||
|
Role: "user",
|
||||||
|
Content: requestMsg,
|
||||||
|
},
|
||||||
|
},
|
||||||
Stream: true,
|
Stream: true,
|
||||||
Stop: []string{"\n\n\n"},
|
|
||||||
TopP: 1,
|
TopP: 1,
|
||||||
FrequencyPenalty: 0.1,
|
FrequencyPenalty: 0.1,
|
||||||
PresencePenalty: 0.1,
|
PresencePenalty: 0.1,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
stream, err := cli.CreateCompletionStream(ctx, req)
|
|
||||||
|
stream, err := cli.CreateChatCompletionStream(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("[ERROR] create chatGPT stream error: %s", err.Error())
|
err = fmt.Errorf("[ERROR] create chatGPT stream error: %s", err.Error())
|
||||||
chatMsg := Message{
|
chatMsg := Message{
|
||||||
@@ -128,7 +114,7 @@ func (api *Api) GetChatMessage(conn *websocket.Conn, cli *gogpt.Client, mutex *s
|
|||||||
var i int
|
var i int
|
||||||
for {
|
for {
|
||||||
response, err := stream.Recv()
|
response, err := stream.Recv()
|
||||||
if errors.Is(err, io.EOF) {
|
if err != nil {
|
||||||
var s string
|
var s string
|
||||||
var kind string
|
var kind string
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
@@ -147,25 +133,8 @@ func (api *Api) GetChatMessage(conn *websocket.Conn, cli *gogpt.Client, mutex *s
|
|||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
_ = conn.WriteJSON(chatMsg)
|
_ = conn.WriteJSON(chatMsg)
|
||||||
mutex.Unlock()
|
mutex.Unlock()
|
||||||
if kind == "retry" {
|
|
||||||
api.Logger.LogError(s)
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("[ERROR] receive chatGPT stream error: %s", err.Error())
|
|
||||||
chatMsg := Message{
|
|
||||||
Kind: "error",
|
|
||||||
Msg: err.Error(),
|
|
||||||
MsgId: id,
|
|
||||||
CreateTime: time.Now().Format("2006-01-02 15:04:05"),
|
|
||||||
}
|
|
||||||
mutex.Lock()
|
|
||||||
_ = conn.WriteJSON(chatMsg)
|
|
||||||
mutex.Unlock()
|
|
||||||
api.Logger.LogError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(response.Choices) > 0 {
|
if len(response.Choices) > 0 {
|
||||||
var s string
|
var s string
|
||||||
@@ -173,7 +142,7 @@ func (api *Api) GetChatMessage(conn *websocket.Conn, cli *gogpt.Client, mutex *s
|
|||||||
s = fmt.Sprintf(`%s# %s`, s, requestMsg)
|
s = fmt.Sprintf(`%s# %s`, s, requestMsg)
|
||||||
}
|
}
|
||||||
for _, choice := range response.Choices {
|
for _, choice := range response.Choices {
|
||||||
s = s + choice.Text
|
s = s + choice.Delta.Content
|
||||||
}
|
}
|
||||||
strResp = strResp + s
|
strResp = strResp + s
|
||||||
chatMsg := Message{
|
chatMsg := Message{
|
||||||
@@ -189,7 +158,7 @@ func (api *Api) GetChatMessage(conn *websocket.Conn, cli *gogpt.Client, mutex *s
|
|||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
if strResp != "" {
|
if strResp != "" {
|
||||||
api.Logger.LogInfo(fmt.Sprintf("[RESPONSE] %s%s", requestMsg, strResp))
|
api.Logger.LogInfo(fmt.Sprintf("[RESPONSE] %s\n", strResp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +179,7 @@ func (api *Api) WsChat(c *gin.Context) {
|
|||||||
mutex := &sync.Mutex{}
|
mutex := &sync.Mutex{}
|
||||||
conn, err := wsupgrader.Upgrade(c.Writer, c.Request, nil)
|
conn, err := wsupgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("failed to upgrade websocket %s", err.Error())
|
err = fmt.Errorf("[ERROR] failed to upgrade websocket %s", err.Error())
|
||||||
msg = err.Error()
|
msg = err.Error()
|
||||||
api.responseFunc(c, startTime, status, msg, httpStatus, data)
|
api.responseFunc(c, startTime, status, msg, httpStatus, data)
|
||||||
return
|
return
|
||||||
@@ -219,13 +188,18 @@ func (api *Api) WsChat(c *gin.Context) {
|
|||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(pingWait))
|
||||||
|
conn.SetPongHandler(func(s string) error {
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(pingWait))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
var isClosed bool
|
var isClosed bool
|
||||||
chClose := make(chan int)
|
chClose := make(chan int)
|
||||||
chIsCloseSet := make(chan int)
|
chIsCloseSet := make(chan int)
|
||||||
defer func() {
|
defer func() {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}()
|
}()
|
||||||
go api.wsCheckConnectStatus(conn, chClose)
|
|
||||||
go api.wsPingMsg(conn, chClose, chIsCloseSet)
|
go api.wsPingMsg(conn, chClose, chIsCloseSet)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
@@ -248,7 +222,7 @@ func (api *Api) WsChat(c *gin.Context) {
|
|||||||
// read in a message
|
// read in a message
|
||||||
messageType, bs, err := conn.ReadMessage()
|
messageType, bs, err := conn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("read message error: %s", err.Error())
|
err = fmt.Errorf("[ERROR] read message error: %s", err.Error())
|
||||||
api.Logger.LogError(err.Error())
|
api.Logger.LogError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -262,7 +236,7 @@ func (api *Api) WsChat(c *gin.Context) {
|
|||||||
ok = true
|
ok = true
|
||||||
} else {
|
} else {
|
||||||
if time.Since(latestRequestTime) < time.Second*time.Duration(api.Config.IntervalSeconds) {
|
if time.Since(latestRequestTime) < time.Second*time.Duration(api.Config.IntervalSeconds) {
|
||||||
err = fmt.Errorf("please wait %d seconds for next query", api.Config.IntervalSeconds)
|
err = fmt.Errorf("[ERROR] please wait %d seconds for next query", api.Config.IntervalSeconds)
|
||||||
chatMsg := Message{
|
chatMsg := Message{
|
||||||
Kind: "error",
|
Kind: "error",
|
||||||
Msg: err.Error(),
|
Msg: err.Error(),
|
||||||
@@ -280,7 +254,7 @@ func (api *Api) WsChat(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
if len(strings.Trim(requestMsg, " ")) < 2 {
|
if len(strings.Trim(requestMsg, " ")) < 2 {
|
||||||
err = fmt.Errorf("message too short")
|
err = fmt.Errorf("[ERROR] message too short")
|
||||||
chatMsg := Message{
|
chatMsg := Message{
|
||||||
Kind: "error",
|
Kind: "error",
|
||||||
Msg: err.Error(),
|
Msg: err.Error(),
|
||||||
@@ -292,7 +266,6 @@ func (api *Api) WsChat(c *gin.Context) {
|
|||||||
mutex.Unlock()
|
mutex.Unlock()
|
||||||
api.Logger.LogError(err.Error())
|
api.Logger.LogError(err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
chatMsg := Message{
|
chatMsg := Message{
|
||||||
Kind: "receive",
|
Kind: "receive",
|
||||||
Msg: requestMsg,
|
Msg: requestMsg,
|
||||||
@@ -309,9 +282,23 @@ func (api *Api) WsChat(c *gin.Context) {
|
|||||||
isClosed = true
|
isClosed = true
|
||||||
api.Logger.LogInfo("[CLOSED] websocket receive closed message")
|
api.Logger.LogInfo("[CLOSED] websocket receive closed message")
|
||||||
case websocket.PingMessage:
|
case websocket.PingMessage:
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(pingWait))
|
||||||
api.Logger.LogInfo("[PING] websocket receive ping message")
|
api.Logger.LogInfo("[PING] websocket receive ping message")
|
||||||
|
case websocket.PongMessage:
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(pingWait))
|
||||||
|
api.Logger.LogInfo("[PONG] websocket receive pong message")
|
||||||
default:
|
default:
|
||||||
api.Logger.LogError("websocket receive message type error")
|
err = fmt.Errorf("[ERROR] websocket receive message type not text")
|
||||||
|
chatMsg := Message{
|
||||||
|
Kind: "error",
|
||||||
|
Msg: err.Error(),
|
||||||
|
MsgId: uuid.New().String(),
|
||||||
|
CreateTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
}
|
||||||
|
mutex.Lock()
|
||||||
|
_ = conn.WriteJSON(chatMsg)
|
||||||
|
mutex.Unlock()
|
||||||
|
api.Logger.LogError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ port: 9000
|
|||||||
# 问题发送的时间间隔不能小于多长时间,单位:秒
|
# 问题发送的时间间隔不能小于多长时间,单位:秒
|
||||||
intervalSeconds: 5
|
intervalSeconds: 5
|
||||||
# 返回答案的最大长度
|
# 返回答案的最大长度
|
||||||
maxLength: 1500
|
maxLength: 2000
|
||||||
# 是否允许cors跨域
|
# 是否允许cors跨域
|
||||||
cors: true
|
cors: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
chatgpt-stream:
|
chatgpt-stream:
|
||||||
image: "doryengine/chatgpt-stream:v1.0.0"
|
image: "doryengine/chatgpt-stream:v1.0.1"
|
||||||
hostname: chatgpt-stream
|
hostname: chatgpt-stream
|
||||||
container_name: chatgpt-stream
|
container_name: chatgpt-stream
|
||||||
ports:
|
ports:
|
||||||
@@ -11,11 +11,11 @@ services:
|
|||||||
- chatgpt-service
|
- chatgpt-service
|
||||||
restart: always
|
restart: always
|
||||||
chatgpt-service:
|
chatgpt-service:
|
||||||
image: "doryengine/chatgpt-service:v1.0.0-alpine"
|
image: "doryengine/chatgpt-service:v1.0.1-alpine"
|
||||||
hostname: chatgpt-service
|
hostname: chatgpt-service
|
||||||
container_name: chatgpt-service
|
container_name: chatgpt-service
|
||||||
ports:
|
ports:
|
||||||
- "9000"
|
- "9000:9000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./config.yaml:/chatgpt-service/config.yaml
|
- ./config.yaml:/chatgpt-service/config.yaml
|
||||||
command: /chatgpt-service/chatgpt-service
|
command: /chatgpt-service/chatgpt-service
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -7,7 +7,7 @@ require (
|
|||||||
github.com/gin-gonic/gin v1.8.2
|
github.com/gin-gonic/gin v1.8.2
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/sashabaranov/go-gpt3 v1.0.0
|
github.com/sashabaranov/go-gpt3 v1.3.3
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user