xqdoo00o / ChatGPT-to-API

Scalable unofficial ChatGPT API for production.
648 stars 129 forks source link

最新版本无法显示完整回复 #29

Closed Plus-xu closed 5 months ago

Plus-xu commented 6 months ago

谢谢David,最新版支持websocket后可以正常对话了,但是通过API获取的回答并不全,经常只能显示部分结果,我测试了ChatBox,NextChatWeb等第三方客户端,3.5和4.0都会出现这个问题。 一般像文本翻译等GPT可以一次性给出解答的基本可以完全显示,逐渐展示的只能显示前几秒的内容。

xqdoo00o commented 6 months ago

目前是这样的,只能单人用,不能多个请求并发。这个还在想办法搞

bi1101 commented 5 months ago

这个仓库中的wss方法似乎解决了并发问题。它没有连接到在进行HTTP请求时返回的wss_url。相反,它通过 /backend-api/register-websocket 注册了一个全新的wss_url。 https://github.com/oliverkirk-sudo/WarpGPT/blob/master/pkg/plugins/service/wsstostream/wsstostream.go

learnLi commented 5 months ago

目前是这样的,只能单人用,不能多个请求并发。这个还在想办法搞 这是由于聊天接口返回过快,有时候连接上wss的时候消息已经过去了,我也通过上面的接口/backend-api/register-websocket 提前拿到wss_url,然后解析access_token拿到账号的email来存储wss信息,每次发消息前都要提前连接wss才能保证第一时间能获取到消息。

xqdoo00o commented 5 months ago

Fixed. Please try to test

bi1101 commented 5 months ago

它现在大多数时候都可以使用,无论是 GPT 3.5 还是 4。但有时候我会遇到这个错误。

{ "error": "websocket: close 1000 (normal)" }

xqdoo00o commented 5 months ago

它现在大多数时候都可以使用,无论是 GPT 3.5 还是 4。但有时候我会遇到这个错误。

{ "error": "websocket: close 1000 (normal)" }

Tried Fix. Please test

bi1101 commented 5 months ago

Hi, I tried it and it seems the error still persist.

on client side: { "error": "websocket: close 1000 (normal)" }

On server side (crash):

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x10 pc=0x10ab52a]

goroutine 79 [running]:
github.com/gorilla/websocket.(*Conn).WriteMessage(0x11f9060?, 0xc0005b1560?, {0x17c92c0, 0x0, 0x0})
        C:/Users/***/go/pkg/mod/github.com/gorilla/websocket@v1.5.1/conn.go:775 +0x2a
freechatgpt/internal/chatgpt.InitWSConn.func1(0xc000b28e10)
        C:/Users/***/Documents/GitHub/ChatGPT-to-API/internal/chatgpt/request.go:89 +0xa5
created by freechatgpt/internal/chatgpt.InitWSConn in goroutine 71
        C:/Users/***/Documents/GitHub/ChatGPT-to-API/internal/chatgpt/request.go:85 +0x35f
xqdoo00o commented 5 months ago

Hi, I tried it and it seems the error still persist.

on client side: { "error": "websocket: close 1000 (normal)" }

On server side (crash):

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x10 pc=0x10ab52a]

goroutine 79 [running]:
github.com/gorilla/websocket.(*Conn).WriteMessage(0x11f9060?, 0xc0005b1560?, {0x17c92c0, 0x0, 0x0})
        C:/Users/***/go/pkg/mod/github.com/gorilla/websocket@v1.5.1/conn.go:775 +0x2a
freechatgpt/internal/chatgpt.InitWSConn.func1(0xc000b28e10)
        C:/Users/***/Documents/GitHub/ChatGPT-to-API/internal/chatgpt/request.go:89 +0xa5
created by freechatgpt/internal/chatgpt.InitWSConn in goroutine 71
        C:/Users/***/Documents/GitHub/ChatGPT-to-API/internal/chatgpt/request.go:85 +0x35f

Does client shows 500 error?

bi1101 commented 5 months ago

Sometimes the error get merge with the empty response (code 200)

{
    "error": "websocket: close 1000 (normal)"
}{
    "id": "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK",
    "object": "chat.completion",
    "created": 0,
    "model": "gpt-3.5-turbo-0301",
    "usage": {
        "prompt_tokens": 0,
        "completion_tokens": 0,
        "total_tokens": 0
    },
    "choices": [
        {
            "index": 0,
            "message": {
                "role": "assistant",
                "content": ""
            },
            "finish_reason": null
        }
    ]
}

Some times it show 500: { "error": "websocket: close 1000 (normal)" }

For stream the error is similar code 200

...
data: {
    "id": "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK",
    "object": "chat.completion.chunk",
    "created": 0,
    "model": "gpt-3.5-turbo-0301",
    "choices": [
        {
            "delta": {
                "content": "-"
            },
            "index": 0,
            "finish_reason": null
        }
    ]
}

{
    "error": "websocket: close 1000 (normal)"
}
data: [DONE]
xqdoo00o commented 5 months ago

@bi1101 tried fix panic, the websocket close is normal,

bi1101 commented 5 months ago

@bi1101 tried fix panic, the websocket close is normal,

Hi, I tried runnign 50 continuous requests to test with the lastest commit a few minutes ago and can confirm the panic has been fixed, however the WebSocket close still sometimes happens & cut off the response, code 200. Thanks

data: {
    "id": "chatcmpl-QXlha2FBbmROaXhpZUFyZUF3ZXNvbWUK",
    "object": "chat.completion.chunk",
    "created": 0,
    "model": "gpt-3.5-turbo-0301",
    "choices": [
        {
            "delta": {
                "content": " released"
            },
            "index": 0,
            "finish_reason": null
        }
    ]
}

{
    "error": "websocket: close 1000 (normal)"
}

data: [DONE]
learnLi commented 5 months ago

我也遇到同样的问题,应该是这个连接是有时限,到点服务端主动切断的,是不是可以记录这个wss的过期时间,重新拿连接,在InitWSConn的时候查一次是否过期,过期就重新拿连接地址再走一遍连接呢?

func GetAccountWss(token string) (WssInfo, bool, error) { wss := connPool[token] if wss == nil { return nil, false, nil } expiresAt, err := time.Parse(time.RFC3339Nano, wss.ExpiresAt) if err != nil { fmt.Println("解析时间出错:", err) return wss, false, err } expiresAt = expiresAt.Add(-time.Minute 30) if err == nil && expiresAt.Unix() > time.Now().Unix() { return wss, true, nil } return wss, false, err }

learnLi commented 5 months ago

这是我按照我的思路走的,wss在官网拿的过期时间好像是1小时,提前30分钟关掉,过期立马重新走一遍初始化wss逻辑

var (
    WssInfoPool       = map[string]*WssInfo{}
    connPool            = map[string]*websocket.Conn{}
)

type WssInfo struct {
    WssUrl    string `json:"wss_url"`
    ExpiresAt string `json:"expires_at"`
}

func (wss *WssInfo) CheckWssExpires() bool {
    expiresAt, err := time.Parse(time.RFC3339Nano, wss.ExpiresAt)
    if err != nil {
        fmt.Println("解析时间出错:", err)
        return false
    }
    expiresAt = expiresAt.Add(-time.Minute * 30)
    if err == nil && expiresAt.Unix() > time.Now().Unix() {
        return true
    }
    return false
}

func getWSURL(token string) (*WssInfo, error) {
    request, err := http.NewRequest(http.MethodPost, "https://chat.openai.com/backend-api/register-websocket", nil)
    if err != nil {
        return "", err
    }
    request.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36")
    request.Header.Set("Accept", "*/*")
    if token != "" {
        request.Header.Set("Authorization", "Bearer "+token)
    }
    response, err := client.Do(request)
    if err != nil {
        return "", err
    }
    defer response.Body.Close()
    var WSSResp chatgpt_types.ChatGPTWSSResponse
    err = json.NewDecoder(response.Body).Decode(&WSSResp)
    if err != nil {
        return "", err
    }
    return &WSSResp, nil
}

func InitWSConn(token string, proxy string) error {
    var err error

    wssURL := WssInfoPool[token]

    if wssURL != nil && wssURL.CheckWssExpires() && connPool[token] != nil {
        return nil
    }
    if proxy != "" {
        client.SetProxy(proxy)
    }
    wssURL, err = getWSURL(token)
    if err != nil {
        return err
    }
    header := make(hp.Header)
    header.Add("Sec-WebSocket-Protocol", "json.reliable.webpubsub.azure.v1")
    conn, _, err := websocket.DefaultDialer.Dial(wssURL.WssUrl, header)
    if err != nil {
        return nil
    }
    WssInfoPool[token] = wssURL
    connPool[token] = conn
    go func() {
        ticker := time.NewTicker(5 * time.Second)
        defer ticker.Stop()
        for {
            <-ticker.C
            if err := conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
                conn.Close()
                connPool[token] = nil
                return
            }
        }
    }()
    return nil
}
xqdoo00o commented 5 months ago

Should fixed, plz try the newest version

bi1101 commented 5 months ago

Hi, I tested it with continuous request, however, sometimes the request get stuck for a few minutes. If concurrent request are made, when other requests are stuck, this will trigger a crash

panic: concurrent write to websocket connection

goroutine 935 [running]:
github.com/gorilla/websocket.(*messageWriter).flushFrame(0xc000d836b0, 0x1, {0x0?, 0x0?, 0x0?})
        C:/Users/***/go/pkg/mod/github.com/gorilla/websocket@v1.5.1/conn.go:632 +0x4b8
github.com/gorilla/websocket.(*messageWriter).Close(0x0?)
        C:/Users/***/go/pkg/mod/github.com/gorilla/websocket@v1.5.1/conn.go:746 +0x35
github.com/gorilla/websocket.(*Conn).beginMessage(0xc000ff74a0, 0xc00047b380, 0x9)
        C:/Users/***/go/pkg/mod/github.com/gorilla/websocket@v1.5.1/conn.go:493 +0x47
github.com/gorilla/websocket.(*Conn).NextWriter(0xc000ff74a0, 0x9)
        C:/Users/***/go/pkg/mod/github.com/gorilla/websocket@v1.5.1/conn.go:535 +0x3f
github.com/gorilla/websocket.(*Conn).WriteMessage(0x829160?, 0xc0000313b0?, {0xdf92e0, 0x0, 0x0})
        C:/Users/***/go/pkg/mod/github.com/gorilla/websocket@v1.5.1/conn.go:788 +0x137
freechatgpt/internal/chatgpt.createWSConn.func1(0xc00068b040)
        C:/Users/***/Documents/GitHub/ChatGPT-to-API/internal/chatgpt/request.go:87 +0xa5
created by freechatgpt/internal/chatgpt.createWSConn in goroutine 7
        C:/Users/***/Documents/GitHub/ChatGPT-to-API/internal/chatgpt/request.go:83 +0x3c5
xqdoo00o commented 5 months ago

Try the newest, concurrent websocket now add lock

bi1101 commented 5 months ago

Hi, thanks for the new update. After a few requests, I still encounter a request where it get stuck for over 10 min. This request needs to be cancelled manually.

xqdoo00o commented 5 months ago

Hi, thanks for the new update. After a few requests, I still encounter a request where it get stuck for over 10 min. This request needs to be cancelled manually.

Does your area has a good internet connection with "azure.com" which the websocket address

bi1101 commented 5 months ago

Hi, thanks for the new update. After a few requests, I still encounter a request where it get stuck for over 10 min. This request needs to be cancelled manually.

Does your area has a good internet connection with "azure.com" which the websocket address

Yes, I can confirm the previous comits code works except for the sudden connection close, and I use ChatGPT regularly with no restrictions in my area.

learnLi commented 5 months ago

我尝试1个账号发起20个并发,也没有太大问题,会不会是你网络环境有关? image 不过我尝试过最新的代码,websoket也会莫名的断开,具体原因找不出来。 image 后来我改用最基础的sync.Mutex{},把每次初始化InitWSConn和协程中的ping信号、关闭conn的时候都上了一遍锁。反而没有断。写的比较差,只会用这种简单的思维做了,还是基于前几版的基础代码做的。


var (
    WssInfoPool       = map[string]*WssInfo{}
    connPool          = map[string]*websocket.Conn{}
    poolMutex         = sync.Mutex{} // 用于connPool和WssInfoPool的互斥锁
)

type WssInfo struct {
    WssUrl    string `json:"wss_url"`
    ExpiresAt string `json:"expires_at"`
}

func (wss *WssInfo) CheckWssExpires() bool {
    expiresAt, err := time.Parse(time.RFC3339Nano, wss.ExpiresAt)
    if err != nil {
        fmt.Println("解析时间出错:", err)
        return false
    }
    expiresAt = expiresAt.Add(-time.Minute * 30)
    if err == nil && expiresAt.Unix() > time.Now().Unix() {
        return true
    }
    return false
}

func getWSURL(token string) (*WssInfo, error) {
    request, err := http.NewRequest(http.MethodPost, "https://chat.openai.com/backend-api/register-websocket", nil)
    if err != nil {
        return "", err
    }
    request.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36")
    request.Header.Set("Accept", "*/*")
    if token != "" {
        request.Header.Set("Authorization", "Bearer "+token)
    }
    response, err := client.Do(request)
    if err != nil {
        return "", err
    }
    defer response.Body.Close()
    var WSSResp chatgpt_types.ChatGPTWSSResponse
    err = json.NewDecoder(response.Body).Decode(&WSSResp)
    if err != nil {
        return "", err
    }
    return &WSSResp, nil
}

func InitWSConn(token string, proxy string) error {
    poolMutex.Lock()
    defer poolMutex.Unlock()
    var err error
    wssURL := WssInfoPool[token]

    if wssURL != nil && wssURL.CheckWssExpires() && connPool[token] != nil {
        return nil
    }
    if proxy != "" {
        client.SetProxy(proxy)
    }
    wssURL, err := getWSURL(token)
    if err != nil {
        return err
    }
    header := make(hp.Header)
    header.Add("Sec-WebSocket-Protocol", "json.reliable.webpubsub.azure.v1")
    conn, _, err := websocket.DefaultDialer.Dial(wssURL, header)
    if err != nil {
        return nil
    }
    WssInfoPool[token] = wssURL
    connPool[token] = conn
    go keepAlive(conn, token)
    return nil
}

func keepAlive(conn *websocket.Conn, token string) {
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()
    for {
        <-ticker.C
        if err := conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
            conn.Close()
            poolMutex.Lock()
            connPool[token] = nil
            poolMutex.Unlock()
            return
        }
    }
}

func Handler(xxx) {
     // ...
     if !strings.Contains(response.Header.Get("Content-Type"), "text/event-stream") {
      isWSS = true
      conn = connPool[token]
     if conn == nil {
              c.JSON(500, gin.H{"error": "No websocket connection"})
          return "", nil
    }
     var conversationResult chatgpt_types.ChatGPTWSSResponse
     json.NewDecoder(response.Body).Decode(&conversationResult)
     respId = conversationResult.ResponseId
     convId = conversationResult.ConversationId
     }

     // ...
    messageType, message, err = conn.ReadMessage()
    if err != nil {
        // 感觉这里断开了连接的话,重新连接已经没有必要了,同一个连接如果都走到这一步再重试连接,消息一样已经过去了。
        c.JSON(500, gin.H{"error": err.Error()})
        poolMutex.Lock()
        conn.Close()
        connPool[token] = nil
        poolMutex.Unlock()
        return "", nil
    }
}
xqdoo00o commented 5 months ago
    messageType, message, err = conn.ReadMessage()
    if err != nil {
        // 感觉这里断开了连接的话,重新连接已经没有必要了,同一个连接如果都走到这一步再重试连接,消息一样已经过去了。
      c.JSON(500, gin.H{"error": err.Error()})
      poolMutex.Lock()
      conn.Close()
      connPool[token] = nil
      poolMutex.Unlock()
      return "", nil
    }
}

他的问题就是这里被服务端断开了ws,然后新的ws连接拿不到剩余的消息,就会一直卡着。 我这里只有多个并发的回复都非常长,才会出现这种情形,也不是必现的

learnLi commented 5 months ago
    messageType, message, err = conn.ReadMessage()
    if err != nil {
        // 感觉这里断开了连接的话,重新连接已经没有必要了,同一个连接如果都走到这一步再重试连接,消息一样已经过去了。
        c.JSON(500, gin.H{"error": err.Error()})
        poolMutex.Lock()
        conn.Close()
        connPool[token] = nil
        poolMutex.Unlock()
        return "", nil
    }
}

他的问题就是这里被服务端断开了ws,然后新的ws连接拿不到剩余的消息,就会一直卡着。 我这里只有多个并发的回复都非常长,才会出现这种情形,也不是必现的

那如果重新连接ws后,直接结束本次调用,再让外层的for循环重试呢?

messageType, message, err = connInfo.conn.ReadMessage()
if err != nil {
    connInfo.ticker.Stop()
    connInfo.conn.Close()
    connInfo.conn = nil
    err := createWSConn(wssUrl, connInfo, 0)
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return "", nil
    }

    return previous_text.Text, &ContinueInfo{
        ConversationID: original_response.ConversationID,
        ParentID:       original_response.Message.ID,
    }

}
xqdoo00o commented 5 months ago

May fixed, or just because the ws server load is not high in the morning in China。

bi1101 commented 5 months ago

Hi, I think it's still not fixed, I tested it with continuous requests (one after another) and got this error on the serverside when the response get stuck.

websocket: RSV1 set, RSV2 set, RSV3 set, bad opcode 11
websocket: close 1000 (normal)
websocket: close 1000 (normal)

When the response get stuck, other concurrent request still works.

xqdoo00o commented 5 months ago

Did you test open multi(your test amount) browser window of "chat.openai.com", then test concurrent request, does have this stuck issue

bi1101 commented 5 months ago

No, sir, I just tested concurent chat (at the same time) on browser with both GPT 4 and 3.5. No problem

bi1101 commented 5 months ago

With F12 on browser, we can see the wss connection.

It seems that both the browser and Go package share the same wssurl as the message stream in the Go package can also be seen streaming in realtime on browser F12. The user id is different but they share the same wss connection.

If on browser are streaming, and we make request with Go, we will get error concurrent request.

bi1101 commented 5 months ago

Hi, after logging, the error seems to be because the wss stop sending message in the middle of a stream. I added a simple time out to send code 500 back to the client side in this case. The wss connection seems still intact and still receiving messages from other conversation so I don't think reconnecting should fix the issue, the problem is likely on Chat GPT side.

const readTimeout = 10 * time.Second
    for {
        var line string
        var err error
        if isWSS {
            println("Reading message from WebSocket")
            if connInfo.conn != nil {
                connInfo.conn.SetReadDeadline(time.Now().Add(readTimeout))
            } else {
                // If the connection is nil, respond with an error immediately
                c.JSON(500, gin.H{"error": "WebSocket connection is closed or not initialized"})
                return "", nil
            }
            var messageType int
            var message []byte
            messageType, message, err = connInfo.conn.ReadMessage()
            if err != nil {
                if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
                    println("Read timeout:", err.Error())
                    connInfo.ticker.Stop()
                    connInfo.conn.Close()
                    connInfo.conn = nil
                    c.JSON(500, gin.H{"error": "WebSocket read took too long"})
                } else {
                    println(err.Error())
                    connInfo.ticker.Stop()
                    connInfo.conn.Close()
                    connInfo.conn = nil
                    c.JSON(500, gin.H{"error": err.Error()})
                    return "", nil
                }
            }
bi1101 commented 5 months ago

有人遇到过Plus账户出现这个错误吗?GPT 4 可以运行,但 GPT 3.5 出现了这个错误。

{ "error": "Our systems have detected unusual activity from your system. Please try again later." }

learnLi commented 5 months ago

有人遇到过Plus账户出现这个错误吗?GPT 4 可以运行,但 GPT 3.5 出现了这个错误。

{ "error": "Our systems have detected unusual activity from your system. Please try again later." }

打码呀,应该是有plus账户的都需要打码,3.5也一样吧。普通号没有这种情况

bi1101 commented 5 months ago

你能更具体一点吗?我尝试过使用代理、移除PUID、添加阿尔科斯,但似乎无法绕过它?

learnLi commented 5 months ago

你能更具体一点吗?我尝试过使用代理、移除PUID、添加阿尔科斯,但似乎无法绕过它?

今天oai的针对plus用户的打码升级了,比以前严格了。你可以网页上抓最新的打码请求包,更新一下github.com/xqdoo00o/funcaptcha包的参数,把最新的参数补一下。 单独使用postman把接口走一遍看看能不能过。

xqdoo00o commented 5 months ago

Fixed. As for ws stuck issue, trying other ws libs to check if the issue still exist.

xqdoo00o commented 5 months ago

Looks like other ws libs still have this issue. So just add a 10s timeout