
LangChain Go 顺序链实战用 Sequential Chain 串联多个 LLM 完成编剧 剧评流水线【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo导读本篇文章围绕 langchaingo 仓库中的 sequential-chain-example 示例展开系统讲解如何在 Go 中构建「Simple Sequential Chain简单顺序链」与「Sequential Chain顺序链」两种链式结构把多个 LLM 调用组织成编剧写剧情简介 → 剧评人写评论的生产流水线。读完本文你将掌握chains.NewSimpleSequentialChain与chains.NewSequentialChain的完整用法、OutputKey的接线原理、链校验与错误处理机制并能独立把它迁移到自己的 Go LLM 项目中。示例想解决的问题在 sequential_chain_example.go 中作者设计了一个非常直观的场景让 AI 先当剧作家根据戏剧标题以及时代背景写出剧情简介synopsis再让 AI 当纽约时报剧评人根据简介写出评论review。整个过程被拆成两步独立的 LLM 调用前一步的输出恰好是后一步的输入——这正是顺序链Sequential Chain最典型的应用形态。这个示例同时演示了两种链链类型输入特点Simple Sequential Chain单个输入每个子链都只有一个输入和一个输出输出自动透传给下一链Sequential Chain多个输入支持多输入、多输出通过OutputKey显式声明每个子链的输出变量名对应的数据流如下Play Title - AI Playwright - Synopsis - AI Critic - Review以及多输入版本Play Title ─┐ └─ AI Playwright - Synopsis - AI Critic - Review Era ────────┘前置条件与环境准备示例依赖 OpenAI 模型与 langchaingo 库其模块声明位于 examples/sequential-chain-example/go.mod核心依赖为github.com/tmc/langchaingo本仓库与github.com/tmc/langchaingo/examples/sequential-chain-example示例模块自身并间接依赖Masterminds/sprig/v3、gonja等模板与工具库。运行前需要设置 OpenAI API Key 环境变量OPENAI_API_KEY因为代码中通过openai.New()构造模型客户端时会读取该配置在示例目录内先拉取依赖并运行cd examples/sequential-chain-example go mod tidy go run sequential_chain_example.go程序启动后会先执行简单顺序链示例随后执行多输入顺序链示例最终把生成的评论打印到控制台两个示例之间会输出一个空行分隔见 sequential_chain_example.go 的main函数。Simple Sequential Chain单输入流水线第一步构造 LLM 与提示词模板代码首先创建 OpenAI 模型实例llm, err : openai.New() if err ! nil { log.Fatal(err) }接着为剧作家定义提示词模板模板使用 Go template 语法langchaingo 默认的TemplateFormatGoTemplate参见 prompts/prompt_template.go变量通过{{.变量名}}占位template1 : You are a playwright. Given the title of play, it is your job to write a synopsis for that title. Title: {{.title}} Playwright: This is a synopsis for the above play: chain1 : chains.NewLLMChain(llm, prompts.NewPromptTemplate(template1, []string{title}))prompts.NewPromptTemplate(template, []string{title})的第二个参数声明了模板的输入变量列表[title]。chains.NewLLMChain(llm, prompt)则把模型与提示词模板绑定成一个LLMChain。再为剧评人定义第二个模板其输入变量是synopsistemplate2 : You are a play critic from the New York Times. Given the synopsis of a play, it is your job to write a review for that play. Play Synopsis: {{.synopsis}} Review from a New York Times play critic of the above play: chain2 : chains.NewLLMChain(llm, prompts.NewPromptTemplate(template2, []string{synopsis}))从 llm.go 的源码可知LLMChain.GetInputKeys()返回的就是提示词模板的InputVariables因此chain1的输入键为[title]chain2的输入键为[synopsis]——这正是简单顺序链能自动接线的依据。第二步组装并运行simpleSeqChain, err : chains.NewSimpleSequentialChain([]chains.Chain{chain1, chain2}) if err ! nil { log.Fatal(err) } title : Tragedy at sunset on the beach res, err : chains.Run(context.Background(), simpleSeqChain, title) if err ! nil { log.Fatal(err) } fmt.Println(res)关键点chains.NewSimpleSequentialChain接收一个[]chains.Chain把两个LLMChain串联起来chains.Run(ctx, chain, title)是链的便捷入口它只接受单个输入且要求链只有一个输出参见 chains.go 的Run函数多输入会返回ErrMultipleInputsInRun多输出返回ErrMultipleOutputsInRun运行后chain1生成的 synopsis 会自动作为chain2的输入最终res就是剧评文本。底层透传机制SimpleSequentialChain.Call的实现sequential.go非常简洁func (c *SimpleSequentialChain) Call(ctx context.Context, inputs map[string]any, options ...ChainCallOption) (map[string]any, error) { input : inputs[input] for _, chain : range c.chains { var err error input, err Run(ctx, chain, input, options...) if err ! nil { return nil, err } } return map[string]any{output: input}, nil }它从inputs[input]取出初始值依次对每个子链调用Run并把上一个子链的返回字符串当作下一个子链的输入最后以output为键返回最终结果。内部常量input input、output output就是 Simple 链内部统一的接线键名。Sequential Chain多输入多输出流水线第一步带双输入的第一条链多输入版本的核心差异在于第一条链的提示词模板同时接收title和era两个变量并且通过修改OutputKey显式命名输出template1 : You are a playwright. Given the title of play and the era it is set in, it is your job to write a synopsis for that title. Title: {{.title}} Era: {{.era}} Playwright: This is a synopsis for the above play: chain1 : chains.NewLLMChain(llm, prompts.NewPromptTemplate(template1, []string{title, era})) chain1.OutputKey synopsisOutputKey是LLMChain暴露的可写字段llm.go默认值为text常量_llmChainDefaultOutputKey。LLMChain.Call最终返回map[string]any{c.OutputKey: finalOutput}llm.go也就是说把chain1.OutputKey设为synopsis后chain1的输出会以synopsis为键进入共享的键值空间供后续链引用。第二条链同样显式命名输出template2 : You are a play critic from the New York Times. Given the synopsis of a play, it is your job to write a review for that play. Play Synopsis: {{.synopsis}} Review from a New York Times play critic of the above play: chain2 : chains.NewLLMChain(llm, prompts.NewPromptTemplate(template2, []string{synopsis})) chain2.OutputKey review第二步声明整体输入输出键sequentialChain, err : chains.NewSequentialChain([]chains.Chain{chain1, chain2}, []string{title, era}, []string{review}) if err ! nil { log.Fatal(err) }NewSequentialChain的签名是sequential.gofunc NewSequentialChain(chains []Chain, inputKeys []string, outputKeys []string, opts ...SequentialChainOption) (*SequentialChain, error)inputKeys []string{title, era}声明整条链需要的外部输入outputKeys []string{review}声明整条链最终对外暴露的输出第三个可变参数opts可用于注入记忆如WithSeqChainMemory默认使用memory.NewSimple()。第三步以 map 形式调用inputs : map[string]any{ title: Mystery in the haunted mansion, era: 1930s in Haiti, } res, err : chains.Call(context.Background(), sequentialChain, inputs) if err ! nil { log.Fatal(err) } fmt.Println(res[review])注意这里用的是chains.Call接收map[string]any输入并返回map[string]any输出而不是chains.Run。最终结果res是map[string]any通过res[review]取出剧评文本。底层串联逻辑SequentialChain.Call的实现sequential.go展示了它的数据流核心func (c *SequentialChain) Call(ctx context.Context, inputs map[string]any, options ...ChainCallOption) (map[string]any, error) { var outputs map[string]any var err error for _, chain : range c.chains { outputs, err Call(ctx, chain, inputs, options...) if err ! nil { return nil, err } // Set the input for the next chain to the output of the current chain inputs outputs } return outputs, nil }每一轮循环都把上一个子链的全部输出 map 直接作为下一个子链的输入 map。因此在多输入链中chain1输出{synopsis: ...}之后chain2收到的输入 map 里就同时包含原始输入title、era以及新产生的synopsischain2的模板变量{{.synopsis}}得以正确填充。这就是OutputKey在共享键值空间中扮演的角色它是子链输出写入共享空间的键名也是下游模板变量引用的依据。顺序链的校验与错误处理顺序链并非简单的循环调用在构造阶段就做了严格的拓扑校验相关逻辑集中在 sequential.go 的validateSeqChain与 sequential.go 的validateSimpleSeq中。SimpleSequentialChain 的约束validateSimpleSeq要求每一个子链都必须恰好有一个输入键、一个输出键否则分别返回ErrInvalidInputNumberInSimpleSeq子链输入键数量不为 1ErrInvalidOutputNumberInSimpleSeq子链输出键数量不为 1。SequentialChain 的约束validateSeqChain执行的检查包括内存键与输入键不得冲突如果通过WithSeqChainMemory注入的记忆变量与inputKeys重叠直接返回初始化错误每个子链的输入必须已被已知键覆盖已知键集合由inputKeys加上记忆键、以及前面子链已产生的输出键逐步扩充而成若某子链引用了尚不存在的输入键返回missing required input keys错误子链输出键不得与已知键重叠即输出键必须是新名字避免覆盖已有变量整体outputKeys必须属于已知键集合否则报output key is not in the known keys。这些校验逻辑在 sequential_test.go 的TestSequentialChainErrors中有完整的负面用例覆盖missing input key、overlapping output key、missing output key、memory key collides with input key 等可以作为理解行为边界的参考。LLMChain 内部的调用链要彻底理解顺序链还需知道单个LLMChain执行时发生了什么。LLMChain.Callllm.go依次完成c.Prompt.FormatPrompt(values)用输入值渲染提示词模板Go template 语法llms.GenerateFromSinglePrompt(ctx, c.LLM, promptValue.String(), ...)调用模型生成文本c.OutputParser.ParseWithPrompt(result, promptValue)用输出解析器处理结果NewLLMChain默认挂载outputparser.NewSimple()即原样返回文本以map[string]any{c.OutputKey: finalOutput}的形式返回。而外层chains.Callchains.go还会在调用前后处理记忆加载LoadMemoryVariables、输入输出键校验validateInputs/validateOutputs以及回调事件HandleChainStart/HandleChainEnd。因此顺序链每次把一个链的输出交给下一个链实际都走完了这套完整的 LLM 执行 记忆 校验流程。通过测试理解行为预期仓库自带测试 chains/sequential_test.go 对两种链的核心行为做了断言可作为理解与验证的参考TestSimpleSequential两个LLMChain串联断言第二个链收到的提示词确实包含了第一个链的输出例如What happened after the chicken crossed the road?证明输出透传成立TestSequentialChain三个LLMChain串联写故事 → 评论 → 评判分别设置OutputKey为story、review断言每个下游链都收到了上游输出TestSimpleSequentialErrors与TestSequentialChainErrors覆盖了构造期与执行期的各类错误分支。如果你想在本地验证可在仓库根目录运行go test ./chains/ -run TestSequential -v小结与扩展方向这个示例虽小却完整展示了顺序链的两大核心能力SimpleSequentialChain适合单一输入单向流动的场景代码最简SequentialChain适合多输入、多中间产物、多输出的复杂流水线靠OutputKey在共享键值空间中接线。在此基础上可以继续探索通过WithSeqChainMemory为顺序链挂载 memory 包中的对话记忆让流水线具备上下文或把LLMChain替换为其他实现了chains.Chain接口的组件构建更复杂的多步智能体工作流。相关的链类型如 MapReduce、Refine、RetrievalQA也都在 chains 目录下可作为组合进阶的下一步方向。【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考