教你使用 Go 语言访问智谱 AI 大模型!

最近文章快凉凉了,大家记得给文章点赞支持~谢谢大家的喜欢!关注我,分享有趣的知识共同学习!

AI 大模型太火爆了!工作中经常使用,感觉已经离不开了大模型了!最近测试了智谱AI大模型,实测下来真的很好用!

今天的股市太绿了,我让它给我写个文案,没想到还给股民按摩了哈哈!

你是一名文学家,请你为我创作文案,主题是 2024 年,A 股走势绿得可怕。要求:语言流畅通顺,观点独特,行文风骚。

2024年,A股走势绿意盎然,恍若春日里的翠竹,节节败退。那抹绿,如同一幅泼墨山水,挥毫之间,惊心动魄。

这一年,股市的绿意犹如江南的梅雨,绵延不绝,投资者们的心情也随着这绿意,起伏不定。有人调侃:"绿"成了心头大患,也有人戏言:"要想生活过得去,头上总得带点绿。"

然而,在这片绿海之中,智者看到了机遇,勇者捕捉到了逆袭的曙光。绿得可怕,恰似风雨前的乌云,预示着一场翻天覆地的变革即将来临。

风骚独步的投资者,把这片绿色视为一种挑战,一种磨砺。他们深知,在这片绿意盎然的A股市场,唯有独具慧眼,方能洞察先机,把握那稍纵即逝的盈利机会。

绿得可怕,是因为市场在洗牌,在重塑。那些站在风口浪尖的企业,或将涅槃重生,或将黯然离场。而作为投资者的我们,唯有紧跟时代步伐,拥抱变革,方能在这片绿色的股市中,破茧成蝶,翩翩起舞。

2024年,A股走势绿得可怕,但这绿色背后,却蕴藏着无尽的机遇与挑战。让我们以独特的视角,审视这片绿色的股市,携手共进,书写属于我们的辉煌篇章。

我平时使用 Go 语言,但我发现官方只给了 Python 和 Java 的接口!

Python: github.com/zhipuai/zhi...

Java: github.com/zhipuai/zhi...

这对于我们 Go 语言开发者来说,确实有点不方便。

翻遍了全球最大的男性交友平台------GitHub!Go 语言一个 SDK 都没有!难道要自己手写?

对我这种喜欢偷懒的人来说,是不可能自己写的。

我坏了十个键盘,每一个都是坏了 Ctrl C V 这三个键。

研究了下接口文档(智谱AI开放平台),发现智谱 AI 的 v4 版本接口和 OpenAI 的接口长得挺像的!

那就用 go-openai 这个包试试水!GitHub - sashabaranov/go-openai: OpenAI ChatGPT, GPT-3, GPT-4, DALL·E, Whisper API wrapper for Go

折腾下来,发现两个问题:

第一个问题是,智谱 AI 的接口认证要用 jwt 生成 token,这个在网上一搜,没人做过。

根据文档,我自己写了个函数,得到 Authorization 头部后,传给 go-openai 作为请求的 Header 头发出去!

go 复制代码
import "github.com/golang-jwt/jwt/v5"

// apikey 为智谱提供的 API Key
func splitAPIKey(apikey string) (string, string) {
	parts := strings.Split(apikey, ".")
	if len(parts) != 2 {
		return "", ""
	}
	return parts[0], parts[1]
}

func getAuthorization(authToken string) string {
	id, secret := splitAPIKey(authToken)
	payload := jwt.MapClaims{
		"api_key":   id,
		"exp":       time.Now().Add(3 * time.Minute).UnixMilli(),
		"timestamp": time.Now().UnixMilli(),
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
	token.Header["sign_type"] = "SIGN"
	res, _ := token.SignedString([]byte(secret))
	return res
}

第二个问题是,你能通过认证,但调用失败!!!这个我折腾了一下午,总算找到了罪魁祸首!

排查的方式是,将我的请求复制成 cURL 到终端,发现是能正常响应的!!!

而 go-openai 包在底层发送请求的时候,会自己加上以下头部:

go 复制代码
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "text/event-stream")
	req.Header.Set("Cache-Control", "no-cache")
	req.Header.Set("Connection", "keep-alive")

	resp, err := client.config.HTTPClient.Do(req) //nolint:bodyclose // body is closed in stream.Close()
	if err != nil {
		return new(streamReader[T]), err
	}

智谱 AI 服务器端,是不支持 Accept: text/event-stream 的!!!

感兴趣的同学可以自己测试下,只要带上 Accept 头就会报错,即便返回接口就是 text/event-stream,原因不明。

那该如何解决这个问题呢?我发现 go-openai 包里的 requestBuilder 是个 interface,但可惜没有暴露出来!

如果它暴露出来了,我可以套娃后替换它!不过请求头是在 requestBuilder 之后,所以这个方案只能放弃。

但它提供了自定义的 HTTPClient!

而 HTTPClient 最终会请求到 client.config.HTTPClient.Do(req) 方法!

所以用 RoundTrip(*Request) (*Response, error) 接口截获请求就完事了!

怎么搞呢?灰常简单:

go 复制代码
type removeAcceptHeaderRoundTrip struct{}

func (s *removeAcceptHeaderRoundTrip) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	req.Header.Del("Accept")
	resp, err = http.DefaultTransport.RoundTrip(req)
	return
}

然后初始化的时候,换成我们的 RoundTrip 就好了!

go 复制代码
	cfg := openai.DefaultConfig(authorization)
	cfg.BaseURL = "https://open.bigmodel.cn/api/paas/v4"
	cfg.HTTPClient.Transport = &removeAcceptHeaderRoundTrip{} // 换成我们的 RoundTrip
	cli := openai.NewClientWithConfig(cfg)
	stream, err := cli.CreateChatCompletionStream(ctx, openai.ChatCompletionRequest{
		// Model: "glm-3-turbo",
		Model: "glm-4",
		Messages: []openai.ChatCompletionMessage{
			{Role: openai.ChatMessageRoleUser, Content: input},
		},
	})

是不是很惊艳!!!interface 大法好!抽象得当,有时候写代码真的就是几分钟的事情!!!

不得不说 Go 的标准库是真的舒服,简单翻一下代码,就知道怎么写了,1 分钟的事情干完下班!!!

(如果觉得文章有帮助,建议收藏起来,方便下次阅读)

之前写文章的时候,有人喷我用 interface 用 wire 把代码搞得太复杂了......

仁扬:我是如何组织 Go 代码的(目录结构 依赖注入 wire)

现在这个就是活生生的例子......

假如没有暴露 RoundTrip 接口,那你什么都做不了,你只能硬质头皮自己写 SDK 写反射写各种恶心的黑科技......

总之,写代码抽象思维很重要,需要时刻注意代码的领域和边界!!!

夜已深,我把完整的代码贴上来吧,大家自行食用:

go 复制代码
package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/sashabaranov/go-openai"
)

func main() {
	input := `你是一名文学家,请你为我创作文案,主题是2024年,A股走势绿得可怕。要求:语言流畅通顺,观点独特,行文风骚。
`
	ctx := context.Background()
	authToken := "......"
	authorization := getAuthorization(authToken)
	cfg := openai.DefaultConfig(authorization)
	cfg.BaseURL = "https://open.bigmodel.cn/api/paas/v4"
	cfg.HTTPClient.Transport = &removeAcceptHeaderRoundTrip{} // 替换成我们自己的
	cli := openai.NewClientWithConfig(cfg)
	stream, err := cli.CreateChatCompletionStream(ctx, openai.ChatCompletionRequest{
		// Model: "glm-3-turbo",
		Model: "glm-4",
		Messages: []openai.ChatCompletionMessage{
			{Role: openai.ChatMessageRoleUser, Content: input},
		},
	})
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	defer stream.Close()

	fmt.Print(input)
	fmt.Println("")

	for {
		response, e := stream.Recv()
		if errors.Is(e, io.EOF) {
			break
		}
		if e != nil {
			break
		}
		content := response.Choices[0].Delta.Content
		fmt.Print(content)
	}

	fmt.Println("")

	return
}

func getAuthorization(authToken string) string {
	id, secret := splitAPIKey(authToken)
	payload := jwt.MapClaims{
		"api_key":   id,
		"exp":       time.Now().Add(3 * time.Minute).UnixMilli(),
		"timestamp": time.Now().UnixMilli(),
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
	token.Header["sign_type"] = "SIGN"
	res, _ := token.SignedString([]byte(secret))
	return res
}

func splitAPIKey(apikey string) (string, string) {
	parts := strings.Split(apikey, ".")
	if len(parts) != 2 {
		return "", ""
	}
	return parts[0], parts[1]
}

type removeAcceptHeaderRoundTrip struct{}

func (s *removeAcceptHeaderRoundTrip) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	req.Header.Del("Accept")
	resp, err = http.DefaultTransport.RoundTrip(req)
	return
}

如果觉得文章有帮助,建议收藏起来,方便下次阅读。

喜欢我的文章可以关注我,分享有趣的知识共同学习!

相关推荐
张声录133 分钟前
使用client-go在命令空间test里面对pod进行操作
开发语言·后端·golang
新智元1 小时前
AI卷翻科研!DeepMind 36页报告:全球实验室被「AI科学家」指数级接管
人工智能·后端
Adolf_19931 小时前
Django 自定义路由转换器
后端·python·django
ᝰꫝꪉꪯꫀ3612 小时前
JavaWeb——Mybatis
java·开发语言·后端·mybatis
机器之心2 小时前
跨模态大升级!少量数据高效微调,LLM教会CLIP玩转复杂文本
人工智能·后端
爱上语文3 小时前
Http 响应协议
网络·后端·网络协议·http
Smilejudy4 小时前
三行五行的 SQL 只存在于教科书和培训班
后端·github
爱上语文4 小时前
Http 请求协议
网络·后端·网络协议·http
贝克街的天才4 小时前
据说在代码里拼接查询条件不够优雅?Magic-1.0.2 发布
java·后端·开源
monkey_meng4 小时前
【Rust Iterator 之 fold,map,filter,for_each】
开发语言·后端·rust