【Go转型AI应用开发】01.Go+OpenAI原生SDK构建LLM-Client

学习如何使用Golang搭建一个AI应用的方法:

  1. 如何使用 go-openai 封装一个支持 OpenAI 和兼容接口(如 DeepSeek, Ollama)的通用客户端。
  2. 进一步能够实现尝试将上述 Client 改造成支持 **Stream(流式)输出,**因为 LLM 生成通常较慢,流式输出能极大提升用户体验。
  3. 使用 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文档如下:

https://bailian.console.aliyun.com/?spm=5176.29597918.0.0.6eb37b087xKMi2&tab=api#/api/?type=model&url=2712193

只需要进入控制台增加一个API key,并且再环境变量中写入自己的API key即可(防止key显式的存在代码中而出现key泄露),方法非常简单,可以试一试

相关推荐
思成Codes4 小时前
Go 语言中数组与切片的本质区别
开发语言·后端·golang
ChineHe5 小时前
Gin框架基础篇009_日志中间件详解
golang·web·gin
Digitally8 小时前
如何在Windows 10 PC上获取 iPhone短信
ios·iphone
Tony Bai17 小时前
Go 的 AI 时代宣言:我们如何用“老”原则,解决“新”问题?
开发语言·人工智能·后端·golang
L Jiawen18 小时前
【Golang基础】基础知识(下)
服务器·开发语言·golang
apocelipes20 小时前
docker-compose 部署单节点 kafka 4.0 测试环境
docker·golang·kafka·开发工具和环境
先跑起来再说1 天前
Go 语言的 Mutex 底层实现详解:状态位、CAS、自旋、饥饿模式与信号量
服务器·后端·golang
saber_andlibert1 天前
【C++转GO】文件操作+协程和管道
开发语言·c++·golang
teamlet1 天前
naviemail的升级之路-孵化mailrouter
golang