【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泄露),方法非常简单,可以试一试

相关推荐
不老刘16 小时前
LiveKit 本地部署全流程指南(含 HTTPS/WSS)
golang·实时音视频·livekit
Tony Bai1 天前
再见,丑陋的 container/heap!Go 泛型堆 heap/v2 提案解析
开发语言·后端·golang
念何架构之路1 天前
Go进阶之panic
开发语言·后端·golang
先跑起来再说1 天前
Git 入门到实战:一篇搞懂安装、命令、远程仓库与 IDEA 集成
ide·git·后端·elasticsearch·golang·intellij-idea
2501_916008891 天前
深入解析iOS机审4.3原理与混淆实战方法
android·java·开发语言·ios·小程序·uni-app·iphone
Tony Bai1 天前
“Go 2,请不要发生!”:如果 Go 变成了“缝合怪”,你还会爱它吗?
开发语言·后端·golang
灰子学技术2 天前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
源代码•宸2 天前
大厂技术岗面试之谈薪资
经验分享·后端·面试·职场和发展·golang·大厂·职级水平的薪资
BBNbQKHXygfU2 天前
课程管理平台 SSM 技术栈:Java EE、Mysql-5.6、Spring、SpringM...
iphone
游戏开发爱好者82 天前
日常开发与测试的 App 测试方法、查看设备状态、实时日志、应用数据
android·ios·小程序·https·uni-app·iphone·webview