-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit bd6cdb8
Showing
17 changed files
with
898 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
MIT License | ||
|
||
Copyright (c) Miguel Piedrafita | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
# chatgpt-go | ||
|
||
ChatGPT SDK 是用 Golang 编写的,可以用于快速集成 ChatGPT 的聊天机器人功能。 | ||
|
||
## 快速开始 | ||
|
||
1. 安装 ChatGPT-Go SDK。 | ||
|
||
```shell | ||
go get -u github.com/xyhelper/chatgpt-go | ||
``` | ||
|
||
2. 在独立的对话中进行聊天。 | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"log" | ||
"time" | ||
|
||
chatgpt "github.com/xyhelper/chatgpt-go" | ||
) | ||
|
||
func main() { | ||
token := `random token` | ||
|
||
cli := chatgpt.NewClient( | ||
chatgpt.WithDebug(true), // 开启调试模式 | ||
chatgpt.WithTimeout(120*time.Second), // 设置超时时间为120秒 | ||
chatgpt.WithAccessToken(token), // 设置token | ||
chatgpt.WithBaseURI("https://freechat.lidong.xin"), // 设置base uri | ||
) | ||
|
||
// chat in independent conversation | ||
message := "你好" | ||
text, err := cli.GetChatText(message) | ||
if err != nil { | ||
log.Fatalf("get chat text failed: %v", err) | ||
} | ||
|
||
log.Printf("q: %s, a: %s\n", message, text.Content) | ||
} | ||
|
||
``` | ||
|
||
3. 在连续的对话中进行聊天。 | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"log" | ||
"time" | ||
|
||
chatgpt "github.com/xyhelper/chatgpt-go" | ||
) | ||
|
||
func main() { | ||
// new chatgpt client | ||
token := `random token` | ||
|
||
cli := chatgpt.NewClient( | ||
chatgpt.WithDebug(true), | ||
chatgpt.WithTimeout(60*time.Second), | ||
chatgpt.WithAccessToken(token), | ||
chatgpt.WithBaseURI("https://freechat.lidong.xin"), | ||
) | ||
|
||
// chat in continuous conversation | ||
|
||
// first message | ||
message := "对我说你好" | ||
text, err := cli.GetChatText(message) | ||
if err != nil { | ||
log.Fatalf("get chat text failed: %v", err) | ||
} | ||
|
||
log.Printf("q: %s, a: %s\n", message, text.Content) | ||
|
||
// continue conversation with new message | ||
conversationID := text.ConversationID | ||
parentMessage := text.MessageID | ||
newMessage := "再说一次" | ||
|
||
newText, err := cli.GetChatText(newMessage, conversationID, parentMessage) | ||
if err != nil { | ||
log.Fatalf("get chat text failed: %v", err) | ||
} | ||
|
||
log.Printf("q: %s, a: %s\n", newMessage, newText.Content) | ||
} | ||
``` | ||
|
||
> 如果你想要在当前对话之外开始一个新的对话,你不需要重置客户端。只需在`GetChatText`方法中移除`conversationID`和`parentMessage`参数,即可获得一个新的文本回复,从而开始一个新的对话。 | ||
4. 使用流(stream)获取聊天内容。 | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"log" | ||
"time" | ||
|
||
chatgpt "github.com/xyhelper/chatgpt-go" | ||
) | ||
|
||
func main() { | ||
// new chatgpt client | ||
token := `random token` | ||
|
||
cli := chatgpt.NewClient( | ||
chatgpt.WithDebug(true), | ||
chatgpt.WithTimeout(120*time.Second), | ||
chatgpt.WithAccessToken(token), | ||
chatgpt.WithBaseURI("https://freechat.xyhelper.cn"), | ||
) | ||
|
||
message := "你好" | ||
stream, err := cli.GetChatStream(message) | ||
if err != nil { | ||
log.Fatalf("get chat stream failed: %v\n", err) | ||
} | ||
|
||
var answer string | ||
for text := range stream.Stream { | ||
log.Printf("stream text: %s\n", text.Content) | ||
|
||
answer = text.Content | ||
} | ||
|
||
if stream.Err != nil { | ||
log.Fatalf("stream closed with error: %v\n", stream.Err) | ||
} | ||
|
||
log.Printf("q: %s, a: %s\n", message, answer) | ||
} | ||
|
||
``` | ||
|
||
## DEMO | ||
|
||
cli/chatgpt-go 是一个使用 ChatGPT-Go SDK 的示例程序。 | ||
可以使用以下命令运行它: | ||
|
||
```shell | ||
cd cli/chatgpt-go | ||
go run main.go | ||
``` | ||
|
||
也可以使用 go install 命令安装它: | ||
|
||
```shell | ||
go install github.com/xyhelper/chatgpt-go/cli/chatgpt-go@latest | ||
``` | ||
|
||
然后运行: | ||
|
||
```shell | ||
chatgpt-go | ||
``` | ||
|
||
## 作品演示 | ||
|
||
[https://xyhelper.cn](https;//xyhelper.cn) | ||
|
||
## 友情链接 | ||
|
||
- [CoolAdmin](https://cool-js.com) - 一个项目,用 COOL 就够了。AI 编码、物联网、函数云开发等更加智能和全面的功能,让应用开发紧跟时代的步伐,cool 绝不落后!!! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
package chatgpt | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/google/uuid" | ||
"github.com/launchdarkly/eventsource" | ||
"github.com/tidwall/gjson" | ||
) | ||
|
||
// ChatText chat reply with text format | ||
type ChatText struct { | ||
data string // event data | ||
ConversationID string // conversation context id | ||
MessageID string // current message id, can used as next chat's parent_message_id | ||
Content string // text content | ||
} | ||
|
||
// ChatStream chat reply with sream | ||
type ChatStream struct { | ||
Stream chan *ChatText // chat text stream | ||
Err error // error message | ||
} | ||
|
||
// ChatText format to string | ||
func (c *ChatText) String() string { | ||
return c.data | ||
} | ||
|
||
// GetChatText will return text message | ||
func (c *Client) GetChatText(message string, args ...string) (*ChatText, error) { | ||
resp, err := c.sendMessage(message, args...) | ||
if err != nil { | ||
return nil, fmt.Errorf("send message failed: %v", err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
body, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, fmt.Errorf("read response body failed: %v", err) | ||
} | ||
|
||
arr := strings.Split(string(body), "\n\n") | ||
|
||
const TEXT_ARR_MIN_LEN = 3 | ||
const TEXT_STR_MIN_LEN = 6 | ||
|
||
l := len(arr) | ||
if l < TEXT_ARR_MIN_LEN { | ||
return nil, fmt.Errorf("invalid reply message: %s", body) | ||
} | ||
|
||
str := arr[l-TEXT_ARR_MIN_LEN] | ||
if len(str) < TEXT_STR_MIN_LEN { | ||
return nil, fmt.Errorf("invalid reply message: %s", body) | ||
} | ||
|
||
text := str[TEXT_STR_MIN_LEN:] | ||
|
||
return c.parseChatText(text) | ||
} | ||
|
||
// GetChatStream will return text stream | ||
func (c *Client) GetChatStream(message string, args ...string) (*ChatStream, error) { | ||
resp, err := c.sendMessage(message, args...) | ||
if err != nil { | ||
return nil, fmt.Errorf("send message failed: %v", err) | ||
} | ||
|
||
chatStream := &ChatStream{ | ||
Stream: make(chan *ChatText), | ||
Err: nil, | ||
} | ||
|
||
decoder := eventsource.NewDecoder(resp.Body) | ||
|
||
go func() { | ||
defer resp.Body.Close() | ||
defer close(chatStream.Stream) | ||
|
||
for { | ||
event, err := decoder.Decode() | ||
if err != nil { | ||
chatStream.Err = fmt.Errorf("decode data failed: %v", err) | ||
return | ||
} | ||
|
||
text := event.Data() | ||
if text == "" || text == EOF_TEXT { | ||
// read data finished, success return | ||
return | ||
} | ||
|
||
chatText, err := c.parseChatText(text) | ||
if err != nil { | ||
continue | ||
} | ||
|
||
chatStream.Stream <- chatText | ||
} | ||
}() | ||
|
||
return chatStream, nil | ||
} | ||
|
||
// parseChatText will return a ChatText struct from ChatText json | ||
func (c *Client) parseChatText(text string) (*ChatText, error) { | ||
if text == "" || text == EOF_TEXT { | ||
return nil, fmt.Errorf("invalid chat text: %s", text) | ||
} | ||
|
||
res := gjson.Parse(text) | ||
|
||
conversationID := res.Get("conversation_id").String() | ||
messageID := res.Get("message.id").String() | ||
content := res.Get("message.content.parts.0").String() | ||
|
||
if conversationID == "" || messageID == "" { | ||
return nil, fmt.Errorf("invalid chat text") | ||
} | ||
|
||
return &ChatText{ | ||
data: text, | ||
ConversationID: conversationID, | ||
MessageID: messageID, | ||
Content: content, | ||
}, nil | ||
} | ||
|
||
// sendMessage will send message to ChatGPT server | ||
func (c *Client) sendMessage(message string, args ...string) (*http.Response, error) { | ||
accessToken, err := c.getAccessToken() | ||
if err != nil { | ||
return nil, fmt.Errorf("get accessToken failed: %v", err) | ||
} | ||
|
||
var messageID string | ||
var conversationID string | ||
var parentMessageID string | ||
|
||
messageID = uuid.NewString() | ||
if len(args) > 0 { | ||
conversationID = args[0] | ||
} | ||
if len(args) > 1 { | ||
parentMessageID = args[1] | ||
} | ||
if parentMessageID == "" { | ||
parentMessageID = uuid.NewString() | ||
} | ||
|
||
params := MixMap{ | ||
"action": "next", | ||
"model": c.opts.Model, | ||
"parent_message_id": parentMessageID, | ||
"messages": []MixMap{ | ||
{ | ||
"role": "user", | ||
"id": messageID, | ||
"content": MixMap{ | ||
"content_type": "text", | ||
"parts": []string{message}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
if conversationID != "" { | ||
params["conversation_id"] = conversationID | ||
} | ||
|
||
data, err := json.Marshal(params) | ||
if err != nil { | ||
return nil, fmt.Errorf("marshal request body failed: %v", err) | ||
} | ||
|
||
req, err := http.NewRequest(http.MethodPost, c.baseURI+"/backend-api/conversation", bytes.NewReader(data)) | ||
if err != nil { | ||
return nil, fmt.Errorf("new request failed: %v", err) | ||
} | ||
|
||
bearerToken := fmt.Sprintf("Bearer %s", accessToken) | ||
req.Header.Set("Authorization", bearerToken) | ||
req.Header.Set("Accept", "text/event-stream") | ||
req.Header.Set("Content-Type", "application/json") | ||
|
||
resp, err := c.doRequest(req) | ||
|
||
return resp, err | ||
} |
Oops, something went wrong.