学习如何使用Golang搭建一个AI应用的方法:
- 如何使用
go-openai封装一个支持 OpenAI 和兼容接口(如 DeepSeek, Ollama)的通用客户端。 - 进一步能够实现尝试将上述 Client 改造成支持 **Stream(流式)输出,**因为 LLM 生成通常较慢,流式输出能极大提升用户体验。
- 使用
tiktoken-go计算输入 Prompt 的 Token 数量,并在日志中打印。
安装依赖
go get github.com/sashabaranov/go-openai
示例代码
Go
package main
import (
"context"
"fmt"
"log"
"os"
"strings"
"github.com/pkoukk/tiktoken-go"
"github.com/sashabaranov/go-openai"
)
// LLMClient 封装通用调用逻辑,新增兼容流式/普通输出
type LLMClient struct {
client *openai.Client
model string
// 新增:token编码对象,初始化一次复用,提升性能
encoding *tiktoken.Encoding
}
// NewClient 支持 OpenAI 及兼容 OpenAI 协议的模型(如 DeepSeek, Ollama, vLLM)
// 新增:初始化token编码器,用于计算token数量
func NewClient(apiKey string, baseURL string, model string) *LLMClient {
config := openai.DefaultConfig(apiKey)
if baseURL != "" {
config.BaseURL = baseURL
}
// 初始化token编码器,使用gpt-3.5/4的编码规则(通用,ollama也兼容)
enc, err := tiktoken.GetEncoding("cl100k_base")
if err != nil {
log.Printf("初始化token编码器失败: %v,将跳过token计算", err)
}
return &LLMClient{
client: openai.NewClientWithConfig(config),
model: model,
encoding: enc,
}
}
// 【新增核心方法】计算文本的token数量
func (c *LLMClient) CountToken(text string) int {
if c.encoding == nil {
return 0
}
tokens := c.encoding.Encode(text, nil, nil)
return len(tokens)
}
// Chat 原有:简单对话 非流式输出
func (c *LLMClient) Chat(ctx context.Context, prompt string) (string, error) {
// 计算并打印token数
tokenNum := c.CountToken(prompt)
log.Printf("【非流式】输入Prompt的Token数量: %d", tokenNum)
resp, err := c.client.CreateChatCompletion(
ctx,
openai.ChatCompletionRequest{
Model: c.model,
Messages: buildMsg(prompt),
Temperature: 0.7,
},
)
if err != nil {
return "", err
}
return resp.Choices[0].Message.Content, nil
}
// ChatStream 【新增核心】流式对话输出,LLM生成一个字就返回一个字,极致提升体验
func (c *LLMClient) ChatStream(ctx context.Context, prompt string) (string, error) {
// 计算并打印token数
tokenNum := c.CountToken(prompt)
log.Printf("【流式输出】输入Prompt的Token数量: %d", tokenNum)
// 发起流式请求
stream, err := c.client.CreateChatCompletionStream(
ctx,
openai.ChatCompletionRequest{
Model: c.model,
Messages: buildMsg(prompt),
Temperature: 0.7,
},
)
if err != nil {
return "", fmt.Errorf("流式请求失败: %v", err)
}
defer stream.Close() // 必须关闭流,防止内存泄漏
var fullContent strings.Builder
fmt.Println("AI 流式回答: ")
// 循环读取流式返回的内容
for {
response, err := stream.Recv()
if err != nil {
// 流结束的正常错误,直接退出循环
if openai.IsEOF(err) {
fmt.Println("\n【流式回答结束】")
break
}
return fullContent.String(), fmt.Errorf("读取流失败: %v", err)
}
// 逐段拼接内容,同时控制台实时打印
content := response.Choices[0].Delta.Content
fullContent.WriteString(content)
fmt.Print(content)
}
// 返回完整的拼接结果
return fullContent.String(), nil
}
// 抽离公共方法:构建消息体,复用代码
func buildMsg(prompt string) []openai.ChatCompletionMessage {
return []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: prompt,
},
}
}
func main() {
// 示例 调用本地 Ollama (Llama3) ,也可以替换为OpenAI/DeepSeek等
client := NewClient("ollama", "http://localhost:11434/v1", "llama3")
// ========== 测试1:原有非流式调用(可选) ==========
// answer, err := client.Chat(context.Background(), "用 Go 语言写一个 Hello World HTTP Server")
// ========== 测试2:【重点】流式调用(推荐) ==========
answer, err := client.ChatStream(context.Background(), "用 Go 语言写一个 Hello World HTTP Server")
if err != nil {
log.Fatalf("对话失败: %v", err)
}
// 最终打印完整结果,便于后续业务处理
fmt.Printf("\n\n回答完整内容: \n%s\n", answer)
}
程序运行后的流式输出
bash
PS D:\PROJECT\GOLANG\GOAI\go-openai-streamout> go run main.go
Start...
开始流式对话...
2025/12/29 21:52:39 【流式输出】输入Prompt的Token数量: 0
AI 流式回答:
# Go 语言 Hello World HTTP Server
下面是一个简单的 Go 语言实现的 HTTP 服务器,它会响应 "Hello, World!":
```go
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
// 设置路由
http.HandleFunc("/", helloHandler)
// 启动服务器
fmt.Println("Starting server on :8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Printf("Server failed to start: %v\n", err)
}
}
```
## 代码说明
1. `package main` - 定义这是可执行程序的入口包
2. `import` - 导入必要的标准库包
3. `helloHandler` - 处理 HTTP 请求的函数,接收两个参数:
- `http.ResponseWriter` - 用于写入响应
- `*http.Request` - 包含请求信息
4. `http.HandleFunc` - 将路径("/")与处理函数绑定
5. `http.ListenAndServe` - 启动服务器监听 8080 端口
## 运行步骤
1. 将代码保存为 `main.go`
2. 在终端运行:`go run main.go`
3. 打开浏览器访问 `http://localhost:8080`
## 进阶版本
如果你想添加更多的路由和处理:
```go
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "About Page")
}
func main() {
// 设置路由
http.HandleFunc("/", helloHandler)
http.HandleFunc("/about", aboutHandler)
// 启动服务器
fmt.Println("Starting server on :8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Printf("Server failed to start: %v\n", err)
}
}
```
这个版本添加了 `/about` 路由,访问它会显示 "About Page"。
【流式回答结束】
回答完整内容:
# Go 语言 Hello World HTTP Server
下面是一个简单的 Go 语言实现的 HTTP 服务器,它会响应 "Hello, World!":
```go
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
// 设置路由
http.HandleFunc("/", helloHandler)
// 启动服务器
fmt.Println("Starting server on :8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Printf("Server failed to start: %v\n", err)
}
}
```
## 代码说明
1. `package main` - 定义这是可执行程序的入口包
2. `import` - 导入必要的标准库包
3. `helloHandler` - 处理 HTTP 请求的函数,接收两个参数:
- `http.ResponseWriter` - 用于写入响应
- `*http.Request` - 包含请求信息
4. `http.HandleFunc` - 将路径("/")与处理函数绑定
5. `http.ListenAndServe` - 启动服务器监听 8080 端口
## 运行步骤
1. 将代码保存为 `main.go`
2. 在终端运行:`go run main.go`
3. 打开浏览器访问 `http://localhost:8080`
## 进阶版本
如果你想添加更多的路由和处理:
```go
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "About Page")
}
func main() {
// 设置路由
http.HandleFunc("/", helloHandler)
http.HandleFunc("/about", aboutHandler)
// 启动服务器
fmt.Println("Starting server on :8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Printf("Server failed to start: %v\n", err)
}
}
```
这个版本添加了 `/about` 路由,访问它会显示 "About Page"。
备注
这里我没有使用本地模型,而是调用了阿里云百炼官方的 DashScope SDK ,API文档如下:
只需要进入控制台增加一个API key,并且再环境变量中写入自己的API key即可(防止key显式的存在代码中而出现key泄露),方法非常简单,可以试一试