在 langchaingo 中实现 OpenAI 流式与非流式聊天:基于 openai-chat-example 的实战指南
【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo
本指南以仓库中的 examples/openai-chat-example 示例为骨架,完整讲解如何在 Go 语言中使用 langchaingo(LangChain for Go)调用 OpenAI 聊天模型:从创建模型实例、构造 system/human 消息,到通过GenerateContent拿到完整回复,再到如何切换为逐块流式输出。读完本文,你将能够独立复现该示例、读懂其底层调用链,并在自己的 Go 项目中基于llms.MessageContent与llms.CallOption写出可运行的 LLM 聊天程序。
示例程序做了什么
这个名为 OpenAI Chat Example 的小程序,演示了 langchaingo 调用 OpenAI 聊天模型的最短可行路径。它的任务非常直观:让 AI 扮演“公司品牌设计魔法师”,为一个生产彩色袜子的公司起一个响亮的名字。
具体流程包含三步:
- 初始化一个 OpenAI 语言模型实例(
openai.New()); - 构造两段聊天消息——系统消息设定 AI 的角色(branding design wizard),人类消息提出品牌命名的实际问题;
- 调用
GenerateContent生成内容,设置最大 token 数为 1024,并打印 AI 的回复。
示例完整源码位于 examples/openai-chat-example/openai_chat_example.go,其go.mod声明依赖github.com/tmc/langchaingo v0.1.14-pre.4(见 examples/openai-chat-example/go.mod),Go 版本要求为 1.24.3,读者可将其作为自己实验项目的最小依赖基线。
运行前提与准备
运行示例前需要确保本机具备两个条件:
- Go 工具链:与示例
go.mod中go 1.24.3兼容的版本; - OpenAI API Key:通过环境变量
OPENAI_API_KEY提供。
API Key 的读取逻辑在源码中有明确体现:llms/openai/openaillm_option.go定义了tokenEnvVarName = "OPENAI_API_KEY",而llms/openai/llm.go中的newClient在构造客户端时直接执行token: os.Getenv(tokenEnvVarName);若最终拿不到 token,会返回ErrMissingToken,错误信息明确提示“missing the OpenAI API key, set it in the OPENAI_API_KEY environment variable”。因此运行时只需:
export OPENAI_API_KEY="sk-..." cd examples/openai-chat-example go run openai_chat_example.go除 API Key 外,langchaingo 的 OpenAI 客户端还会读取以下环境变量(见 llms/openai/openaillm_option.go):
| 环境变量 | 作用 |
|---|---|
OPENAI_API_KEY | API 令牌,缺失时报错 |
OPENAI_MODEL | 默认模型名(如不设置,客户端级默认值兜底) |
OPENAI_BASE_URL/OPENAI_API_BASE | 自定义 API 基础地址,可用于代理或兼容服务 |
OPENAI_ORGANIZATION | 组织 ID |
核心代码逐段拆解
第一步:创建 OpenAI 模型实例
llm, err := openai.New() if err != nil { log.Fatal(err) }openai.New()的签名是func New(opts ...Option) (*LLM, error)(见 llms/openai/openaillm.go)。不传任何参数时,它通过newClient从环境变量读取默认配置,并默认使用APITypeOpenAI类型(见 llms/openai/llm.go)。如果需要定制,可以传入WithToken、WithModel、WithHTTPClient等Option。
一个值得注意的实现细节:langchaingo 会根据模型名自动判断模型能力。源码中维护了一张能力表(见 llms/openai/openaillm.go),通过正则匹配模型名:
o1/o3系列((?i)^o13?$):不支持 system 消息,支持思考(thinking);gpt-4系列((?i)^gpt-4):支持 system 消息;gpt-3.5系列((?i)^gpt-3\.5):支持 system 消息。
对于不支持 system 消息的模型,GenerateContent会在请求发出前把 system 消息的内容合并进 user 消息,避免请求被拒绝(见 llms/openai/openaillm.go)。
第二步:构造聊天消息
ctx := context.Background() content := []llms.MessageContent{ llms.TextParts(llms.ChatMessageTypeSystem, "You are a company branding design wizard."), llms.TextParts(llms.ChatMessageTypeHuman, "What would be a good company name a company that makes colorful socks?"), }这里出现了两个核心抽象:
llms.MessageContent:一条消息内容,包含Role(角色)与Parts(内容片段列表),定义在 llms/generatecontent.go;llms.TextParts(role, parts...):便捷构造函数,把若干文本片段包装成一条指定角色的MessageContent,实现见 llms/generatecontent.go。
角色常量定义在 llms/chat_messages.go,除示例用到的ChatMessageTypeSystem与ChatMessageTypeHuman外,还包括ChatMessageTypeAI、ChatMessageTypeGeneric、ChatMessageTypeFunction、ChatMessageTypeTool。所有消息类型都实现统一的ChatMessage接口(GetType()/GetContent()),这为后续接入多轮对话、工具调用等高级场景打下了基础。
由于Parts是切片,TextParts天然支持多模态内容——除了文本,还可以放入图片 URL、二进制内容等ContentPart,例如ImageURLContent、BinaryContent(可在 llms/generatecontent.go 的ShowMessageContents调试函数中看到完整类型清单)。
第三步:调用 GenerateContent 并输出
r, err := llm.GenerateContent(ctx, content, llms.WithMaxTokens(1024)) if err != nil { log.Fatal(err) } fmt.Println(r.Choices[0].Content)GenerateContent是 langchaingo 的Model接口核心方法,返回*llms.ContentResponse。从结构定义看(llms/generatecontent.go),ContentResponse包含Choices []*ContentChoice,每个ContentChoice除了Content字段外,还携带StopReason(停止原因)、GenerationInfo(附加信息)、FuncCall/ToolCalls(工具调用信息)等,因此示例代码取r.Choices[0].Content只是最基础的用法。
llms.WithMaxTokens(1024)是调用级选项(CallOption),它设置本次生成的最大 token 数。CallOption的函数式选项机制定义在 llms/options.go,可用的选项非常丰富,常用几个如下:
| 选项 | 作用 |
|---|---|
WithModel(model) | 指定本次调用使用的模型名(覆盖客户端默认) |
WithMaxTokens(n) | 最大生成 token 数(示例中为 1024) |
WithTemperature(t) | 控制输出随机性/创造性的温度参数 |
WithTopP(p)/WithTopK(k) | 核采样与 top-k 采样 |
WithStopWords([]string) | 遇到这些词即停止生成 |
WithSeed(seed) | 固定随机种子,获得更确定性的输出 |
WithStreamingFunc(fn) | 注册流式回调,逐块接收生成内容 |
WithN(n) | 每个输入消息生成 n 个候选回复 |
WithRepetitionPenalty(p) | 设置重复惩罚系数 |
在GenerateContent内部(llms/openai/openaillm.go),langchaingo 会先触发CallbacksHandler.HandleLLMGenerateContentStart回调,再逐个应用传入的CallOption填充CallOptions,随后依据本次调用的opts.Model计算“生效模型”(未显式指定则回退到客户端级模型),再按模型能力表决定消息的组装方式。
升级:如何切换为流式输出
示例源码中还附带了一段被注释掉的流式实现(examples/openai-chat-example/openai_chat_example.go),它展示了如何让 AI 的回复“逐块实时打印”,而不是一次性返回:
_, err := llm.GenerateContent(ctx, content, llms.WithMaxTokens(1024), llms.WithStreamingFunc(func(ctx context.Context, chunk []byte) error { fmt.Print(string(chunk)) return nil }))核心变化只有一个:额外传入llms.WithStreamingFunc。WithStreamingFunc的签名是func(ctx context.Context, chunk []byte) error(见 llms/options.go),它接收[]byte形式的文本块,回调内直接fmt.Print即可实现边生成边打印的效果——这也是 README 中“实时看到创意诞生”的由来。
实际工程中,流式回调常被用于:
- 逐字刷新聊天界面,降低用户等待焦虑;
- 把每个 chunk 累加到缓冲区,用于全文后处理;
- 在每块内容到达时执行自定义逻辑(如前端推送)。
此外,对于支持“思考”的推理模型,langchaingo 还提供了WithStreamingReasoningFunc(llms/options.go),它会把推理过程片段与最终内容片段分开回调,可用于展示模型“思考过程”。
从示例到实战:一份可运行的最小模板
将示例稍作整理,可以得到一个可复用的最小聊天调用模板(所有路径均位于当前仓库,读者可参考 examples/openai-chat-example/openai_chat_example.go):
package main import ( "context" "fmt" "log" "github.com/tmc/langchaingo/llms" "github.com/tmc/langchaingo/llms/openai" ) func main() { llm, err := openai.New() if err != nil { log.Fatal(err) } ctx := context.Background() messages := []llms.MessageContent{ llms.TextParts(llms.ChatMessageTypeSystem, "You are a helpful assistant."), llms.TextParts(llms.ChatMessageTypeHuman, "What would be a good company name for a company that makes colorful socks?"), } r, err := llm.GenerateContent(ctx, messages, llms.WithMaxTokens(1024)) if err != nil { log.Fatal(err) } fmt.Println(r.Choices[0].Content) }在此基础上可以继续扩展:把HumanChatMessage/AIChatMessage按轮次追加进[]llms.MessageContent,即可实现多轮对话;结合仓库 memory 包中的缓冲区实现,还能让对话具备记忆能力,这与 chains/conversation.go 中Conversation链的思路一脉相承。
常见问题排查
missing the OpenAI API key:OPENAI_API_KEY未设置或为空。检查export是否在当前 shell 生效,参见 llms/openai/llm.go 中的ErrMissingToken定义。- 想要切换模型:示例默认使用客户端级模型。可通过环境变量
OPENAI_MODEL,或在openai.New(openai.WithModel("gpt-4o"))中指定,也可以在每次调用时用llms.WithModel(...)覆盖。 - 返回内容为空:
r.Choices为空时访问[0]会越界。可先判断len(r.Choices),并检查StopReason字段定位原因。 - 调用兼容服务(代理/私有网关):设置
OPENAI_BASE_URL或OPENAI_API_BASE即可改变请求端点;需要 Azure OpenAI 时,langchaingo 也内置了APITypeAzure/APITypeAzureAD支持(见 llms/openai/openaillm_option.go)。
小结
openai-chat-example虽然只有几十行代码,却覆盖了 langchaingo 聊天场景的全部关键链路:环境变量驱动的客户端初始化、MessageContent+TextParts的消息构造、CallOption式的调用参数注入,以及GenerateContent对响应的结构化返回。掌握这个最小闭环后,再结合WithStreamingFunc实现流式输出、借助CallOptions精细控制生成参数,你就拥有了在 Go 中构建 LLM 聊天应用的核心能力。
【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考