1. LangChainGo Agents 深度解析
作为一名长期从事AI应用开发的工程师,我最近深入研究了LangChainGo框架中的agents模块。这个模块的设计理念和实现方式给我留下了深刻印象,特别是在构建复杂AI工作流时的灵活性和扩展性。下面我将从实际开发角度,分享对LangChainGo Agents的全面解析。
2. 核心架构与设计理念
2.1 模块定位与核心功能
LangChainGo的agents包是框架中实现智能代理的核心模块。它本质上是一个LLM的包装器,主要承担三大职责:
- 输入处理:接收用户的各种形式输入
- 决策制定:决定执行什么动作(Action)及对应参数(Action Input)
- 结果返回:直接返回最终答案(Finish)或执行工具链
这种设计使得Agent能够像人类一样"思考"和"行动",通过循环迭代逐步解决问题。
2.2 核心组件交互流程
plaintext复制┌─────────────────────────────────────────────────────────────┐
│ Executor │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ for i < MaxIterations: │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ Agent.Plan(inputs, intermediateSteps) │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌───────────┴───────────┐ │ │
│ │ ▼ ▼ │ │
│ │ []AgentAction AgentFinish │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ Tool.Call() 返回最终结果 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 记录 AgentStep │ │
│ │ (Action + Observation) │ │
│ │ │ │ │
│ │ └──────────────────────────┐ │ │
│ │ ▼ │ │
│ │ 继续下一次迭代 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.3 组件职责划分
| 组件 | 职责描述 | 关键文件位置 |
|---|---|---|
| Agent | 决策核心,分析输入决定下一步行动 | agents.go |
| Executor | 执行循环,管理工具调用流程 | executor.go |
| Tool | 具体功能实现,执行原子操作 | tools/tool.go |
这种清晰的职责分离使得系统具有很好的扩展性,开发者可以专注于特定组件的实现而不影响整体架构。
3. Agent 接口与实现模式
3.1 基础接口定义
所有Agent必须实现以下核心接口(agents.go):
go复制type Agent interface {
Plan(ctx context.Context, intermediateSteps []schema.AgentStep,
inputs map[string]string, options ...chains.ChainCallOption)
([]schema.AgentAction, *schema.AgentFinish, error)
GetInputKeys() []string
GetOutputKeys() []string
GetTools() []tools.Tool
}
其中Plan方法是核心,它接收上下文、中间步骤历史和输入参数,返回下一步要执行的动作或最终结果。
3.2 Plan方法详解
go复制Plan(
ctx context.Context,
intermediateSteps []schema.AgentStep,
inputs map[string]string,
options ...chains.ChainCallOption
) ([]schema.AgentAction, *schema.AgentFinish, error)
参数解析:
intermediateSteps: 包含之前所有步骤的Action和Observationinputs: 用户输入的键值对,支持多输入源
返回值说明:
[]AgentAction: 需要执行的动作列表(可能并行)*AgentFinish: 标志任务完成,包含最终结果error: 执行过程中的错误
3.3 主要Agent实现类型
LangChainGo目前提供了三种主要Agent实现:
| Agent类型 | 文件位置 | 核心特点 |
|---|---|---|
| OneShotZeroAgent | mrkl.go |
基于ReAct框架,文本解析决策 |
| ConversationalAgent | conversational.go |
专为对话设计,支持聊天历史 |
| OpenAIFunctionsAgent | openai_functions_agent.go |
使用OpenAI原生Function Calling API |
4. Executor 执行器详解
4.1 执行器结构定义
go复制type Executor struct {
Agent Agent
Memory schema.Memory
CallbacksHandler callbacks.Handler
ErrorHandler *ParserErrorHandler
MaxIterations int
ReturnIntermediateSteps bool
}
4.2 核心执行流程
Executor的核心工作流程如下:
- 初始化工具映射表
- 进入循环(最多MaxIterations次)
- 每次迭代调用
doIteration执行:- 调用Agent.Plan获取下一步动作
- 执行工具并记录结果
- 检查是否完成
- 返回最终结果或错误
go复制func (e *Executor) Call(ctx context.Context, inputValues map[string]any, ...) {
// 初始化
nameToTool := getNameToTool(e.Agent.GetTools())
steps := make([]schema.AgentStep, 0)
// 主循环
for i := 0; i < e.MaxIterations; i++ {
steps, finish, err = e.doIteration(ctx, steps, nameToTool, inputs, options...)
if finish != nil || err != nil {
return finish, err
}
}
// 处理未完成情况
return e.getReturn(&schema.AgentFinish{...}, steps), ErrNotFinished
}
4.3 执行器创建与配置
Executor采用Option模式进行灵活配置:
go复制executor := agents.NewExecutor(agent,
agents.WithMaxIterations(5), // 最大迭代次数
agents.WithMemory(memory), // 记忆组件
agents.WithCallbacksHandler(handler), // 回调处理器
agents.WithReturnIntermediateSteps(), // 返回中间步骤
)
这种设计使得执行器可以根据不同场景灵活调整,而不需要修改核心逻辑。
5. Agent实现模式深度解析
5.1 ReAct模式(OneShotZeroAgent)
5.1.1 ReAct框架原理
ReAct = Reasoning + Acting,是一种结合推理和行动的Agent设计模式。其核心特点是:
- 思考(Thought): Agent分析当前状况
- 行动(Action): 决定执行哪个工具
- 观察(Observation): 获取工具执行结果
- 循环直到解决问题
5.1.2 结构定义
go复制type OneShotZeroAgent struct {
Chain chains.Chain
Tools []tools.Tool
OutputKey string
CallbacksHandler callbacks.Handler
}
5.1.3 输出解析逻辑
ReAct Agent通过文本模式匹配来解析LLM输出:
go复制func (a *OneShotZeroAgent) parseOutput(output string) ([]schema.AgentAction, *schema.AgentFinish, error) {
// 检查最终答案
if strings.Contains(output, _finalAnswerAction) {
splits := strings.Split(output, _finalAnswerAction)
return nil, &schema.AgentFinish{
ReturnValues: map[string]any{
a.OutputKey: strings.TrimSpace(splits[len(splits)-1]),
},
Log: output,
}, nil
}
// 解析Action/Action Input
r := regexp.MustCompile(`(?i)Action:\s*(.+?)\s*Action\s+Input:\s*(?s)(.+)`)
matches := r.FindStringSubmatch(output)
if len(matches) == 3 {
return []schema.AgentAction{
{Tool: strings.TrimSpace(matches[1]),
ToolInput: strings.TrimSpace(matches[2]),
Log: output},
}, nil, nil
}
return nil, nil, fmt.Errorf("%w: %s", ErrUnableToParseOutput, output)
}
5.1.4 ScratchPad机制
Agent通过agent_scratchpad记录思考过程:
go复制func constructMrklScratchPad(steps []schema.AgentStep) string {
var scratchPad string
if len(steps) > {
for _, step := range steps {
scratchPad += "\n" + step.Action.Log
scratchPad += "\nObservation: " + step.Observation + "\n"
}
}
return scratchPad
}
5.2 Conversational Agent
5.2.1 设计特点
专为对话场景优化:
- 支持聊天历史记忆
- 使用
AI:作为最终答案标记 - 更适合自然对话流
5.2.2 输出解析差异
go复制const _conversationalFinalAnswerAction = "AI:"
func (a *ConversationalAgent) parseOutput(output string) (...) {
if strings.Contains(output, _conversationalFinalAnswerAction) {
splits := strings.Split(output, _conversationalFinalAnswerAction)
finishAction := &schema.AgentFinish{
ReturnValues: map[string]any{
a.OutputKey: splits[len(splits)-1],
},
Log: output,
}
return nil, finishAction, nil
}
// ...相同Action解析逻辑
}
5.3 OpenAI Functions Agent
5.3.1 核心优势
- 结构化输出:不依赖文本解析,更可靠
- 参数校验:支持结构化参数定义
- 并行调用:可同时调用多个工具
- 原生集成:直接使用OpenAI Function Calling API
5.3.2 工具定义方式
go复制func (o *OpenAIFunctionsAgent) functions() []llms.FunctionDefinition {
res := make([]llms.FunctionDefinition, 0)
for _, tool := range o.Tools {
res = append(res, llms.FunctionDefinition{
Name: tool.Name(),
Description: tool.Description(),
Parameters: map[string]any{
"properties": map[string]any{
"__arg1": map[string]string{"title": "__arg1", "type": "string"},
},
"required": []string{"__arg1"},
"type": "object",
},
})
}
return res
}
5.3.3 并行调用支持
go复制func (o *OpenAIFunctionsAgent) constructScratchPad(steps []schema.AgentStep) []llms.ChatMessage {
// 支持处理多个并行tool calls
var currentToolCalls []llms.ToolCall
// ...
}
6. ReAct与ZeroShot的关系解析
6.1 代码层面的证据
在LangChainGo代码库中,ReAct和ZeroShot实际上是同一种实现:
doc.go明确说明:
go复制// Package agents provides and implementation of the agent interface called
// OneShotZeroAgent. This agent uses the ReAct Framework (based on the
// descriptions of tools) to decide what action to take.
initialize.go中的常量定义:
go复制const (
ZeroShotReactDescription AgentType = "zeroShotReactDescription"
// ...
)
6.2 概念关系梳理
| 概念 | 含义 |
|---|---|
| ReAct | Reasoning + Acting,一种Agent设计框架/方法论 |
| Zero-Shot | 无需示例,仅凭工具描述就能工作 |
| OneShotZeroAgent | 使用ReAct框架的Zero-Shot Agent实现 |
命名解析:
code复制OneShotZeroAgent = One-Shot + Zero-Shot Agent
= 单次规划 + 零样本Agent
= 基于ReAct框架,无需示例即可工作的Agent
7. 实战案例:视频脚本编辑工具链
7.1 场景需求
开发一个基于自然语言的视频脚本编辑工具链:
- 查询指定视频的当前脚本
- 根据用户要求修改脚本内容
- 返回修改后的完整脚本
7.2 系统架构设计
code复制script_editor/
├── main.go # 主程序入口
├── tools/
│ ├── get_script.go # 查询脚本工具
│ └── modify_script.go # 修改脚本工具
└── script_store.go # 脚本存储(模拟数据库)
7.3 核心组件实现
7.3.1 脚本存储(script_store.go)
go复制type ScriptStore struct {
mu sync.RWMutex
scripts map[string]string // videoID -> script content
}
func (s *ScriptStore) Get(videoID string) string {
s.mu.RLock()
defer s.mu.RUnlock()
return s.scripts[videoID]
}
func (s *ScriptStore) Set(videoID, content string) {
s.mu.Lock()
defer s.mu.Unlock()
s.scripts[videoID] = content
}
7.3.2 查询脚本工具(get_script.go)
go复制type GetScriptTool struct {
store *ScriptStore
}
func (t *GetScriptTool) Name() string { return "get_script" }
func (t *GetScriptTool) Description() string {
return `获取指定视频的口播脚本内容。
输入格式:视频ID,例如 "video_001"
返回:该视频的完整脚本文本。`
}
func (t *GetScriptTool) Call(ctx context.Context, input string) (string, error) {
videoID := input
script := t.store.Get(videoID)
if script == "" {
return "", fmt.Errorf("未找到视频 %s 的脚本", videoID)
}
return script, nil
}
7.3.3 修改脚本工具(modify_script.go)
go复制type ModifyScriptTool struct {
store *ScriptStore
llm llms.Model
}
func (t *ModifyScriptTool) Call(ctx context.Context, input string) (string, error) {
var params ModifyScriptInput
if err := json.Unmarshal([]byte(input), ¶ms); err != nil {
return "", fmt.Errorf("解析输入失败: %w", err)
}
prompt := fmt.Sprintf(`你是一个专业的视频脚本编辑助手。请根据用户的要求修改以下口播脚本。
原始脚本:
%s
修改要求:
%s
请直接输出修改后的完整脚本,不要添加任何解释或说明:`,
params.OriginalText, params.Modification)
resp, err := t.llm.Generate(ctx, []string{prompt})
if err != nil {
return "", fmt.Errorf("调用模型失败: %w", err)
}
modifiedScript := resp[0]
t.store.Set(params.VideoID, modifiedScript)
return modifiedScript, nil
}
7.4 主程序集成
go复制func main() {
// 1. 初始化LLM
llm, err := openai.New(openai.WithToken(os.Getenv("OPENAI_API_KEY")))
// 2. 初始化脚本存储
store := NewScriptStore()
// 3. 定义工具列表
toolList := []tools.Tool{
NewGetScriptTool(store),
NewModifyScriptTool(store, llm),
}
// 4. 创建Agent(可选择不同实现)
agent := agents.NewOneShotAgent(llm, toolList)
// 或使用OpenAI Functions Agent
// agent := agents.NewOpenAIFunctionsAgent(llm, toolList)
// 5. 创建Executor
executor := agents.NewExecutor(agent,
agents.WithMaxIterations(5),
agents.WithReturnIntermediateSteps(),
)
// 6. 执行用户请求
result, err := chains.Call(ctx, executor, map[string]any{
"input": "请把video_001的脚本改得更轻松幽默一些",
})
fmt.Println("修改后的脚本:", result["output"])
}
7.5 执行流程分析
- 用户输入修改要求
- Agent决定先调用get_script获取原始内容
- 获得原始脚本后,Agent调用modify_script进行修改
- 修改工具内部调用LLM完成内容改写
- 返回最终修改结果
7.6 进阶优化方向
7.6.1 添加记忆支持
go复制executor := agents.NewExecutor(agent,
agents.WithMemory(memory.NewConversationBuffer()),
)
7.6.2 扩展工具集
go复制// 预览修改(不保存)
type PreviewModificationTool struct { ... }
// 获取历史版本
type GetScriptHistoryTool struct { ... }
// 版本回滚
type RevertScriptTool struct { ... }
8. 开发经验与最佳实践
8.1 Agent模式选择建议
| 使用场景 | 推荐Agent类型 | 选择理由 |
|---|---|---|
| 通用任务处理 | OneShotZeroAgent | 基于ReAct框架,通用性强 |
| 对话交互系统 | ConversationalAgent | 支持上下文,对话更自然 |
| OpenAI环境 | OpenAIFunctionsAgent | 结构化输出更可靠,支持并行调用 |
8.2 性能优化技巧
- 合理设置MaxIterations:根据任务复杂度调整,避免不必要循环
- 工具描述优化:清晰准确的工具描述能提高Agent决策质量
- 缓存机制:对耗时工具实现结果缓存
- 并行工具设计:利用OpenAIFunctionsAgent的并行能力
8.3 常见问题排查
-
Agent陷入循环:
- 检查工具描述是否清晰
- 增加中间步骤日志
- 适当降低MaxIterations
-
工具调用失败:
- 验证工具输入格式
- 检查工具权限和依赖
-
结果不符合预期:
- 优化工具描述
- 调整LLM参数(temperature等)
- 增加输入验证
9. 模块设计评价与改进思考
9.1 设计优点
- 清晰的接口抽象:Agent、Executor、Tool职责分明
- 灵活的扩展机制:通过Option模式配置各种组件
- 多种实现选择:适应不同场景需求
- 良好的文档支持:代码注释和示例丰富
9.2 可能的改进方向
-
更强大的调试支持:
- 可视化执行流程
- 更详细的中间状态记录
-
性能监控集成:
- 执行时间统计
- 工具调用频率分析
-
更智能的错误恢复:
- 自动重试机制
- 替代方案建议
-
测试工具增强:
- 模拟工具调用
- 自动化测试框架
在实际项目中使用LangChainGo的agents模块后,我认为它非常适合构建需要复杂决策流程的AI应用。特别是OpenAIFunctionsAgent的引入,大大提高了工具调用的可靠性。对于需要精细控制的中小型项目,这个模块提供了恰到好处的抽象层次,既不会太底层难以使用,也不会太高层失去灵活性。