基础构建块

构建强大的 AI 应用就像搭积木。LangGraphGo 提供了一套精简而强大的核心原语,让你能够灵活地编排复杂的逻辑。本指南将深入探讨这些基础构建块:节点、边和条件路由。

1. 节点 (Nodes):执行单元

背景与功能

在任何工作流系统中,"做什么"是最基本的问题。节点就是 LangGraphGo 中的执行单元,它们负责具体的计算任务。无论是调用大语言模型 (LLM)、查询数据库、执行代码,还是简单的格式化数据,都是在节点中完成的。

节点的设计理念是函数式无状态(尽可能)。它们接收当前的状态作为输入,并返回状态的更新作为输出。这种设计使得节点易于测试、复用和并行化。

实现原理

在底层,节点被定义为一个接收 context.Context 和输入状态,返回输出状态和错误的函数。LangGraphGo 的运行时负责调度这些函数,处理并发,并管理状态的传递。

代码展示:集成 LLM

最常见的节点类型是调用 LLM。LangGraphGo 与 langchaingo 无缝集成,使得在节点中使用各种 LLM 变得非常简单。

import (
    "context"
    "github.com/tmc/langchaingo/llms"
    "github.com/tmc/langchaingo/llms/openai"
)

// 定义一个调用 LLM 的节点函数
func callLLM(ctx context.Context, state interface{}) (interface{}, error) {
    // 1. 从状态中提取消息历史
    messages := state.([]llms.MessageContent)
    
    // 2. 初始化 LLM (通常在图构建外部初始化一次)
    model, err := openai.New()
    if err != nil {
        return nil, err
    }
    
    // 3. 调用 LLM 生成回复
    response, err := model.GenerateContent(ctx, messages)
    if err != nil {
        return nil, err
    }
    
    // 4. 返回新的消息,这将通过 Reducer 追加到状态中
    return append(messages, llms.TextParts("ai", response.Choices[0].Content)), nil
}

// 将节点添加到图中
g.AddNode("chatbot", callLLM)

2. 边 (Edges):控制流

背景与功能

定义了"做什么"之后,我们需要定义"按什么顺序做"。边定义了节点之间的连接关系,即控制流。最简单的边是普通边,它表示确定的顺序执行。

通过组合简单的边,你可以构建出复杂的有向无环图 (DAG) 甚至包含循环的图(Cyclic Graph),这对于实现 Agent 的循环推理(思考-行动-观察)至关重要。

实现原理

图的数据结构维护了一个邻接表。当一个节点执行完成时,运行时会查找该节点的出边,并将下游节点加入到执行队列中。如果一个节点有多个出边,下游节点将被并行调度。

代码展示

// 定义线性流程:A -> B -> C -> END
g.AddEdge("node_a", "node_b")
g.AddEdge("node_b", "node_c")
g.AddEdge("node_c", graph.END) // END 是一个特殊的虚拟节点,表示图执行结束

3. 条件边 (Conditional Edges):动态路由

背景与功能

现实世界的逻辑往往不是线性的。Agent 需要根据 LLM 的输出、工具的执行结果或外部输入来动态决定下一步做什么。例如,如果 LLM 决定调用工具,则跳转到工具节点;如果 LLM 决定直接回复,则结束。

条件边引入了路由逻辑。它允许在运行时检查状态,并根据检查结果选择下一个节点。

实现原理

条件边关联了一个路由函数。当源节点执行完毕后,运行时会调用这个路由函数。路由函数返回目标节点的名称(字符串)。运行时根据这个返回值,动态地将控制权交给相应的节点。

代码展示:工具调用路由

以下是一个经典的 ReAct 模式中的路由逻辑:检查 LLM 是否发出了工具调用请求。

// 路由函数:决定下一步是执行工具还是结束
func routeToolOrEnd(ctx context.Context, state interface{}) string {
    messages := state.([]llms.MessageContent)
    lastMessage := messages[len(messages)-1]
    
    // 检查最后一条消息是否包含工具调用
    if len(lastMessage.ToolCalls) > 0 {
        return "tools" // 跳转到工具执行节点
    }
    return graph.END // 否则结束
}

// 添加条件边
// 从 "llm_node" 出发,根据 routeToolOrEnd 的返回值决定去向
g.AddConditionalEdge("llm_node", routeToolOrEnd)

// 可选:显式定义路径映射,有助于可视化和验证
g.AddConditionalEdge("llm_node", routeToolOrEnd, map[string]string{
    "tools": "tools",
    graph.END: graph.END,
})