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

AI 大模型太火爆了!我在工作中经常使用它们,感觉已经离不开了!最近测试了智谱 AI 大模型,实测下来感觉还挺不错的。官方提供了体验包,注册并实名认证之后有 300 万 token,非常壕!

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

AI 大模型太火爆了!我在工作中经常使用它们,感觉已经离不开了!

最近测试了智谱 AI 大模型,实测下来感觉还挺不错的。

官方提供了体验包,注册并实名认证之后有 300 万 token,非常壕!

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

你是一名文学家,请你为我创作文案,主题是 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 }

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

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

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
人工智能

Prompt提示工程上手指南:基础原理及实践(二)-Prompt主流策略

2024-5-23 13:37:16

人工智能

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

2024-5-23 17:35:47

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索