news 2026/9/16 19:58:53

在 langchaingo 中实现 OpenAI 流式与非流式聊天:基于 openai-chat-example 的实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 langchaingo 中实现 OpenAI 流式与非流式聊天:基于 openai-chat-example 的实战指南

在 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.MessageContentllms.CallOption写出可运行的 LLM 聊天程序。

示例程序做了什么

这个名为 OpenAI Chat Example 的小程序,演示了 langchaingo 调用 OpenAI 聊天模型的最短可行路径。它的任务非常直观:让 AI 扮演“公司品牌设计魔法师”,为一个生产彩色袜子的公司起一个响亮的名字。

具体流程包含三步:

  1. 初始化一个 OpenAI 语言模型实例(openai.New());
  2. 构造两段聊天消息——系统消息设定 AI 的角色(branding design wizard),人类消息提出品牌命名的实际问题;
  3. 调用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.modgo 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_KEYAPI 令牌,缺失时报错
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)。如果需要定制,可以传入WithTokenWithModelWithHTTPClientOption

一个值得注意的实现细节: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,除示例用到的ChatMessageTypeSystemChatMessageTypeHuman外,还包括ChatMessageTypeAIChatMessageTypeGenericChatMessageTypeFunctionChatMessageTypeTool。所有消息类型都实现统一的ChatMessage接口(GetType()/GetContent()),这为后续接入多轮对话、工具调用等高级场景打下了基础。

由于Parts是切片,TextParts天然支持多模态内容——除了文本,还可以放入图片 URL、二进制内容等ContentPart,例如ImageURLContentBinaryContent(可在 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.WithStreamingFuncWithStreamingFunc的签名是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 keyOPENAI_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_URLOPENAI_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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 19:57:08

Go 1.27标准库UUID全解析:go-modern-guidelines解读crypto/rand/uuid

Go 1.27标准库UUID全解析:go-modern-guidelines解读crypto/rand/uuid 【免费下载链接】go-modern-guidelines Help AI coding agents write modern Go 项目地址: https://gitcode.com/GitHub_Trending/go/go-modern-guidelines 写 Go 项目要生成或解析 UUID&…

作者头像 李华
网站建设 2026/9/16 19:56:37

ELMAN神经网络:轻量时序建模的工业级利器

1. 项目概述:为什么ELMAN不是“另一个BP”或“简化版RNN”,而是一个被严重低估的时序建模利器你翻过MATLAB神经网络工具箱,见过newff、newcf、narnet,但很可能在newelm这个函数前只匆匆扫了一眼就跳过了。它不像BP神经网络那样被写…

作者头像 李华
网站建设 2026/9/16 19:56:34

Batch Normalization原理与工程实践全解析

1. BN层不是“魔法糖”,而是神经网络训练的“压力调节阀”你有没有遇到过这样的情况:模型在训练初期loss掉得飞快,但很快就在某个值附近反复震荡,怎么也下不去;或者明明加了更多层、更大容量,准确率反而不升…

作者头像 李华
网站建设 2026/9/16 19:56:04

Claude Agent Skills 实战指南:Python+Bash 构建可落地的智能体能力

1. 别被“Agent Skills”这个词唬住:它根本不是Claude官方术语,而是开发者社区自发形成的共识性表达最近在多个技术社区和开源项目里频繁看到“Claude’s Agent Skills”这个说法——有人把它当成功能模块,有人当成API能力清单,还…

作者头像 李华