1. 项目概述:当LangGraph遇上Go,构建下一代AI智能体的新选择
如果你正在用Go语言开发AI应用,尤其是涉及到多步骤推理、工具调用或者复杂工作流编排的场景,那么你很可能已经感受到了一个痛点:在Python生态里,LangGraph已经成为了构建智能体(Agent)和编排复杂AI工作流的事实标准,它那套基于状态图(State Graph)的编程模型,把智能体的“思考-行动-观察”循环抽象得既清晰又强大。但在Go的世界里,我们似乎总得自己从头造轮子,或者用一些不那么优雅的方式去拼接。
这就是 smallnest/langgraphgo (简称 lango ,中文“懒狗”)要解决的问题。它不是一个简单的LangGraph Go语言移植版,而是一个 旨在与Python LangGraph库达到功能对等 的、生产就绪的Go语言实现。简单来说,它让你能用Go写出和Python LangGraph一样强大、甚至在某些方面(比如并发性能)更出色的AI智能体应用。它的核心魅力,正如其摘要所言,在于用极其简洁的代码,实现了异常强大的功能。
我最初接触这个项目,是因为需要将一个用LangGraph编写的、包含复杂审批流和外部工具调用的客服机器人后端,从Python迁移到Go以追求更高的并发性能和更低的资源开销。在尝试了各种方案后,发现 lango 几乎完美地复现了LangGraph的核心抽象:状态图、节点、边、检查点。更重要的是,它补充了Go开发者急需的特性,比如 原生并行执行 、 完善的持久化支持 (Redis、Postgres)、 实时事件流 以及 预构建的智能体工厂 。这让我意识到,对于Go社区的AI开发者而言,这很可能是一个改变游戏规则的工具。
2. 核心设计哲学:状态即一切,图即流程
要理解 lango ,首先要吃透它的核心设计哲学,这直接决定了你能否用好它。这套哲学可以概括为: 将智能体的执行过程,建模为一个在状态图上流动的数据流。
2.1 状态(State)作为唯一真相源
在 lango 中, State 是一个泛型参数,通常是一个 map[string]any 或自定义的结构体。它代表了智能体在某一时刻的“全部记忆”。这个状态会在图的不同节点间传递和修改。这与我们常见的、将状态分散在各个变量或数据库记录中的做法截然不同。
为什么这么设计? 这种集中式状态管理带来了几个关键优势:
- 可序列化与持久化 :整个智能体的运行状态可以轻松地被保存(检查点)和恢复,实现了“时间旅行”和容错。
- 明确的接口 :每个节点都是一个纯函数,接收旧状态,返回新状态。这使得单元测试变得极其简单——你只需要准备输入状态,断言输出状态。
- 调试与观测 :由于所有变化都发生在一个对象上,你可以清晰地追踪状态是如何随着流程演变的,这对于理解复杂智能体的行为至关重要。
2.2 图(Graph)定义工作流
图由节点(Node)和边(Edge)构成。节点是执行单元(比如“调用LLM”、“搜索网络”、“更新数据库”),边定义了节点间的执行顺序和条件。
lango 支持几种关键的图结构,这也是其强大之处:
- 顺序图 :最基本的A->B->C流程。
- 条件分支 :根据状态中的某个值,决定下一步走向哪个节点。这是实现智能体“决策”能力的基础。
- 并行执行(Fan-out/Fan-in) :这是Go的强项。当多个边从同一个节点出发时,
lango会并发执行这些下游节点,最后将结果合并回状态。这在需要同时调用多个工具或API时,能带来巨大的性能提升。 - 子图(Subgraph) :你可以将一组节点和边打包成一个子图,作为一个“超级节点”嵌入到更大的图中。这实现了模块化和复用,让你可以像搭积木一样构建复杂的智能体。
一个常见的误区 是认为图只是定义静态流程。实际上,在 lango 中,图是动态的。节点可以根据当前状态,通过 Send 命令动态地向图中添加消息或改变流向,这为实现更灵活的、自适应的智能体行为打开了大门。
3. 从零构建你的第一个智能体:一个天气查询助手
理论说得再多,不如动手写一个。我们来构建一个简单的天气查询助手智能体。它的流程是:1)接收用户关于天气的提问;2)调用工具提取城市名;3)调用模拟的天气API获取信息;4)格式化回复。
3.1 环境准备与依赖安装
首先,确保你的Go版本在1.18+,然后获取 lango 库:
go get github.com/smallnest/langgraphgo
注意 :由于项目使用了Git子模块来管理 showcases 示例目录,克隆时最好使用 --recurse-submodules 参数,以便获得完整的示例代码,这是极佳的学习资料。
git clone --recurse-submodules https://github.com/smallnest/langgraphgo
cd langgraphgo
3.2 定义状态与工具
我们从一个简单的状态开始,并定义两个工具函数。
package main
import (
"context"
"fmt"
"strings"
"github.com/smallnest/langgraphgo/graph"
)
// 定义我们的状态结构。在实际项目中,你可能会用更复杂的结构。
type AgentState struct {
Messages []string // 存放对话消息
City string // 提取出的城市名
Weather string // 查询到的天气信息
FinalAnswer string // 最终回复
}
// 工具1:一个简单的(模拟的)城市提取器
func extractCity(ctx context.Context, state AgentState) (AgentState, error) {
// 假设最后一条消息是用户输入
if len(state.Messages) == 0 {
return state, fmt.Errorf("no message to extract from")
}
lastMsg := state.Messages[len(state.Messages)-1]
// 这里用一个极其简单的逻辑:寻找“天气”后面的词作为城市
// 实际应用中,你应该用一个NLU模型或更复杂的规则/API
if idx := strings.Index(lastMsg, "天气"); idx != -1 && idx+len("天气") < len(lastMsg) {
// 简单处理,取“天气”后的第一个词
parts := strings.Fields(lastMsg[idx+len("天气"):])
if len(parts) > 0 {
state.City = parts[0]
}
}
if state.City == "" {
state.City = "北京" // 默认城市
}
fmt.Printf("[工具调用] 提取城市: %s\n", state.City)
return state, nil
}
// 工具2:模拟天气查询API
func queryWeather(ctx context.Context, state AgentState) (AgentState, error) {
// 模拟一个天气查询,真实场景会调用外部API
weatherMap := map[string]string{
"北京": "晴,15~25°C,微风",
"上海": "多云,18~28°C,东南风3级",
"广州": "阵雨,22~30°C,南风4级",
}
if info, ok := weatherMap[state.City]; ok {
state.Weather = info
} else {
state.Weather = "未找到该城市天气信息"
}
fmt.Printf("[工具调用] 查询天气: %s -> %s\n", state.City, state.Weather)
return state, nil
}
3.3 构建状态图并编排节点
现在,我们将这些工具函数作为节点添加到图中,并定义执行流程。
func buildWeatherAgentGraph() (*graph.StateGraph[AgentState], error) {
// 1. 创建一个状态图,泛型参数为我们的 AgentState
g := graph.NewStateGraph[AgentState]()
// 2. 添加节点
// 节点“extract”:负责提取城市
err := g.AddNode("extract", extractCity)
if err != nil
2373




被折叠的 条评论
为什么被折叠?



