获取Moonshot API key并配置到环境变量

登录Moonshot AI开放平台, 在API管理 -> 新建 -> 输入key名, 确定-> 复制密钥sk-HL...Ehvcn.

将以下环境变量写入系统环境变量

1
2
3
GPT_BASE_URL=https://api.moonshot.cn/v1
GPT_API_KEY=sk-HLdyzmmUgXQnM5RbhyOBJs0Y5nvPlEdSBa8lWJGZ0GcEhvcn
GPT_MODEL=moonshot-v1-8k

或者写入项目下的.env文件并用github.com/joho/godotenv之类的库进行读取

读取环境变量代码:

1
2
3
4
5
6
// 如果有.env文件, 则导入库 go get -u github.com/joho/godotenv
godotenv.Load()

apiKey := os.Getenv("GPT_API_KEY")
baseUrl := os.Getenv("GPT_BASE_URL")
model := os.Getenv("GPT_MODEL")

使用go-gpt3构建Client

获取go-gpt3库 go get -u github.com/PullRequestInc/go-gpt3, go-gpt3是一个兼容OPEN-AI GPT3 API的开源库. 支持stream和non stream的completion请求.

单轮对话示例

构建gpt3.Client实例

1
client := gpt3.NewClient(apiKey, gpt3.WithBaseURL(baseUrl))

初始化一个对话, 这里使用moonshot文档提供的prompt

1
2
3
4
5
6
var history []gpt3.ChatCompletionRequestMessage = []gpt3.ChatCompletionRequestMessage{
	{
		Role:    "system",
		Content: "你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一切涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。",
	},
}

添加一个问题到对话中, moonshoot ai支持的对话Role为system, user, assistant, tool

1
2
3
4
5
6
history = append(history, gpt3.ChatCompletionRequestMessage{
		Role: "user",
		// Content: "How much water should I drink per day?",
		Content: "我每天喝多少水合适?",
		Name:    "Me",
	})

构建一个non stream的ChatCompletionRequest实例, 该实例包含了向Moonshot-AI API发生的消息, 包括使用的模型,对话内容,以及是否使用stream等参数.

1
2
3
4
request := gpt3.ChatCompletionRequest{
 	Model:    model,
 	Messages: history,
 }

发起单轮对话, 打印响应内容

1
2
3
4
5
6
response, err := client.ChatCompletion(ctx, request)
if err != nil {
 	log.Fatal("err: ", err.Error())
 } else {
 	fmt.Print("Answer:\n\t", response.Choices[0].Message.Content)
}

此时可以运行项目查看终端中ai的回复.

继续修改代码, 构建一个stream的ChatCompletionRequest实例, 指定Stream参数为true.

1
2
3
4
5
streamRequest := gpt3.ChatCompletionRequest{
		Model:    "moonshot-v1-8k",
		Messages: history,
		Stream:   true,
	}

由于stream式的对话内容是分段处理, 因此使用相对于的ChatCompletionStream函数进行处理

1
2
3
4
5
6
7
8
9
client.ChatCompletionStream(ctx, streamRequest, func(ccsr *gpt3.ChatCompletionStreamResponse) error {
		// 整段内容分别一个字符一个字符输出
		for _, c := range ccsr.Choices[0].Delta.Content {
			fmt.Print(string(c))
		}
		// 直接输出整段内容
		//fmt.Print(ccsr.Choices[0].Delta.Content)
		return nil
	})

运行项目,查看终端中的文字消息是否符合我们的预期

多轮对话示例

多轮对话的实现比较简单, 只需要将模型给出的回答再加入到对话中.

简单修改发起对话的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
var result strings.Builder
err := client.ChatCompletionStream(ctx, streamRequest, func(ccsr *gpt3.ChatCompletionStreamResponse) error {
	for _, c := range ccsr.Choices[0].Delta.Content {
		fmt.Print(string(c))
	}
	//fmt.Print(ccsr.Choices[0].Delta.Content)
	result.WriteString(ccsr.Choices[0].Delta.Content)
	return nil
})
if err != nil {
	log.Fatal("err: client.ChatCompletionStream: ", err.Error())
}

// 将模型回答再加入对话中
history = append(history, gpt3.ChatCompletionRequestMessage{
	Role:    "assistant",
	Content: result.String(),
})

最后达成的效果如图stream-gpt

最终完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package main

import (
	"bufio"
	"context"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/PullRequestInc/go-gpt3"
	"github.com/joho/godotenv"
)

var history []gpt3.ChatCompletionRequestMessage = []gpt3.ChatCompletionRequestMessage{
	{
		Role:    "system",
		Content: "你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一切涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。",
	},
}

var (
	apiKey, baseUrl, model string
)

func main() {
	// load the GPT key from file
	godotenv.Load()
	apiKey = os.Getenv("GPT_API_KEY")
	baseUrl = os.Getenv("GPT_BASE_URL")
	model = os.Getenv("GPT_MODEL")

	if apiKey == "" {
		log.Fatal("Missing GPT_API_KEY.")
	}

	if model == "" {
		log.Fatal("Missing GPT_MODEL.")
	}

	// instantiate a gpt3.CompletionRequest using github.com/PullRequestInc/go-gpt3
	client := gpt3.NewClient(apiKey, gpt3.WithBaseURL(baseUrl))
	// create a Context for the execution of requests.
	ctx := context.Background()
	// send the prompt questions and receive the repsonse via client.
	for {
		reader := bufio.NewReader(os.Stdin)
		fmt.Print("\n> ")
		line, err := reader.ReadString('\n')
		if err != nil {
			log.Fatal("err: reader.ReadString: ", err.Error())
		}
		complete(ctx, client, line)
	}

}

func complete(ctx context.Context, client gpt3.Client, question string) {
	// send a stream request and receive a stream response via client
	streamRequest := makeQuestion(question)
	fmt.Print("assistant: \n  ")
	var result strings.Builder
	err := client.ChatCompletionStream(ctx, streamRequest, func(ccsr *gpt3.ChatCompletionStreamResponse) error {
		for _, c := range ccsr.Choices[0].Delta.Content {
			fmt.Print(string(c))
		}
		//fmt.Print(ccsr.Choices[0].Delta.Content)
		result.WriteString(ccsr.Choices[0].Delta.Content)
		return nil
	})
	if err != nil {
		log.Fatal("err: client.ChatCompletionStream: ", err.Error())
	}

	history = append(history, gpt3.ChatCompletionRequestMessage{
		Role:    "assistant",
		Content: result.String(),
	})
}

func makeQuestion(question string) gpt3.ChatCompletionRequest {
	// 加入历史对话
	history = append(history, gpt3.ChatCompletionRequestMessage{
		Role: "user",
		// Content: "How much water should I drink per day?",
		Content: question,
		Name:    "Me",
	})

	return gpt3.ChatCompletionRequest{
		Model:    model,
		Messages: history,
		Stream:   true,
	}

}

总结

原来是打算使用OPEN AI API进行实现, 但由于各种客观原因现在OPEN AI的API key不宜获得. 不过得益于当前各个大模型AI厂商之间的竞争, 他们的API开放文档已经相当完善, 而且都兼容OPEN AI的API格式, 所以我选用moonshot ai的API作为替换, 注册用户会获得15元的余额,这足够我完成实验. 这篇文章我只是完成一个简单GPT的示例, 希望对大模型的应用方式有更多了解. 更多的多轮对话细节可以参考moonshot ai文档, 在其中可以获取可用的模型列表, 以及多轮对话的性能优化方法.

参考文档: