LangChain Go 中基于 Ollama 构建 Agent 的实战指南:MRKL 输出解析容错与提示工程最佳实践
2026/9/15 21:31:16 网站建设 项目流程

LangChain Go 中基于 Ollama 构建 Agent 的实战指南:MRKL 输出解析容错与提示工程最佳实践

【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo

本文以 langchaingo 仓库中的 Ollama Agent 使用指南 为核心,系统讲解在 Go 项目中使用 Ollama 模型驱动 MRKL Agent 的原理、配置与排障方法。文章将结合仓库源码,重点剖析针对 Ollama 模型没有原生 Function Calling 能力而引入的parseOutput容错解析逻辑,并给出可直接运行的完整代码示例与测试验证方案。读完本文,你将掌握如何在 langchaingo 中为 Ollama 模型配置稳定的 Agent 工作流,并具备独立排查 "unable to parse output" 等常见问题的能力。

背景:为什么 Ollama 模型需要特殊处理

OpenAI 等模型原生支持 function/tool calling,模型输出会按照约定的 JSON 结构返回工具调用参数,程序可以直接消费。而 Ollama 本地模型通常不具备这种原生能力,Agent 必须依赖 MRKL(Modular Reasoning, Knowledge and Language)式的纯文本协议进行推理:

  • 模型需要输出Thought:(思考)、Action:(动作)、Action Input:(动作输入)来表达"我要调用哪个工具";
  • 当推理完成时,模型需要输出Final Answer:来表达"最终答案"。

一旦模型输出的格式略有偏差(例如大小写不一致、冒号后多了空格、用了 "The answer is:" 这类自然语言表达),Agent 的解析器就可能直接报错。这正是仓库中 Issue #1045 描述的核心问题:Ollama 模型与 Agent 搭配时,模型可能不按预期格式生成响应,导致解析错误

MRKL Agent 与解析器的容错改进

解析器改进概览

针对上述问题,langchaingo 对 MRKL Agent 的parseOutput函数做了增强,使其能够更灵活地识别多种输出格式。改进点集中在 mrkl.go 的parseOutput方法上:

  1. "Final Answer" 的大小写变体识别:不仅支持标准的Final Answer:,还支持final answer:final answer :(冒号前多空格)等变体;
  2. 自然语言变体识别:支持the final answer is:the answer is:等表达;
  3. 大小写不敏感的 Action 模式匹配Action:Action Input:均可忽略大小写。

源码级解析流程

从源码看,parseOutput的判定顺序是(agents/mrkl.go):

第一步:标准格式向后兼容检查。先直接查找大写Final Answer:(常量_finalAnswerAction),命中即取最后一个分段作为最终答案并返回AgentFinish,同时把输出记录到Log字段。

第二步:大小写不敏感变体扫描。将模型输出转为小写后,依次匹配以下变体:

变体模式示例
final answer:final answer: 42
final answer :final answer : 42
the final answer is:the final answer is: 42
the answer is:the answer is: 42

这里有一个重要的保护逻辑:提取出的答案如果后面还跟着\naction:,说明模型并没有真正结束(它可能还在规划下一步工具调用),此时不会误判为最终答案,而是继续走 Action 解析。

第三步:Action/Action Input 模式匹配。使用正则(?i)Action:\s*(.+?)\s*Action\s+Input:\s*(?s)(.+)进行大小写不敏感、跨行((?s)使.匹配换行)的匹配,把模型输出解析为AgentAction{Tool, ToolInput}

第四步:向后兼容回退。若上面的正则没有命中,再尝试仓库原有的正则Action:\s*(.+)\s*Action Input:\s(?s)*(.+);仍失败则返回ErrUnableToParseOutput包装的错误(错误类型定义见 errors.go)。

测试用例佐证

仓库中的 markl_test.go 通过TestMRKLOutputParser覆盖了这些解析路径:

  • Action: foo Action Input: bar→ 解析出工具foo、输入bar
  • 多行输入Action: foo\nAction Input:\nbar\nbaz→ 工具输入为bar\nbaz
  • Observation:尾缀的输入Action: calculator\nAction Input: 5 + 3\nObservation:→ 工具calculator、输入5 + 3\nObservation:(该尾缀由 Executor 在调用工具时通过strings.TrimSuffix清理,见 executor.go)。

这些测试说明:解析器改进的目标是让"格式上不够规整"的模型输出也能被正确理解,从而显著降低 Ollama 模型的 Agent 使用门槛。

最佳实践:让 Ollama Agent 稳定工作

1. 使用清晰的系统提示词

Ollama 模型遵循提示词的指令程度较高,因此在创建 Agent 时,应在系统提示词中明确写出期望的输出格式,把Thought/Action/Action Input/Observation/Final Answer的结构逐行交代清楚:

systemPrompt := `You are a helpful assistant that uses tools to answer questions. IMPORTANT: You must follow this exact format: For using a tool: Thought: [your reasoning] Action: [tool name] Action Input: [tool input] For final answer: Thought: I now know the final answer Final Answer: [your answer] Always use "Final Answer:" to indicate your final response.` agent := agents.NewOneShotAgent( ollamaLLM, tools, agents.WithSystemMessage(systemPrompt), )

需要说明的是:在当前仓库源码中,WithSystemMessage是通过agents.NewOpenAIOption().WithSystemMessage(...)暴露给 OpenAI Functions Agent 的(见 options.go);对于 MRKL Agent(NewOneShotAgent),提示词模板由前缀、格式指令、后缀三部分组成(见 mrkl_prompt.go),你可以用 WithPromptPrefix、WithPromptSuffixWithPromptFormatInstructions以及WithPrompt选项来定制系统级指令。无论采用哪种方式,核心原则一致:把期望格式写进提示词,模型输出就越规整

2. 选择合适的模型

不同 Ollama 模型对格式指令的遵从度差异明显,官方指南 给出了经验性建议:

  • 推荐使用:llama3、mistral、mixtral、gemma2 —— 这些模型对 MRKL 格式理解较好;
  • 可能需要调优:llama2、codellama —— 需要更强的提示词约束;
  • 需要充分测试:phi 等小参数模型 —— 输出格式稳定性较弱,务必用测试用例验证后再上生产。

3. 调整温度等采样参数

降低温度(Temperature)通常能显著提升输出格式的一致性。langchaingo 的 Ollama 实现支持在创建 LLM 时直接传入底层采样参数:

llm, err := ollama.New( ollama.WithModel("llama3"), ollama.WithOptions(ollama.Options{ Temperature: 0.2, // Lower temperature for more consistent formatting }), )

从源码看,这些参数最终会被映射到 Ollama 的生成选项上:NumPredict对应MaxTokensTemperature直接透传、Stop对应停止词、TopK/TopPSeed、重复惩罚等也一一对应(见 ollamallm.go)。除了Temperature,仓库还提供了大量 Runner 级与采样级选项,例如 WithRunnerNumCtx(上下文窗口,默认 2048)、WithPredictRepeatLastNWithPredictMirostat等,均可按需组合。

4. 处理格式变体

得益于改进后的解析器,以下格式现在都能被正确识别为"最终答案":

  • Final Answer: X(标准格式)
  • final answer: X(全小写)
  • The answer is: X(自然语言表达)
  • Answer: X(简化表达)

同理,Action Input:Action input:等各种大小写组合都能被识别。这四种变体与 Action 的大小写容错,构成了 Ollama Agent 稳定性的第一道防线。

5. 完整示例实现

将以上最佳实践组合起来,即可得到一个完整的可运行程序:

package main import ( "context" "fmt" "log" "github.com/tmc/langchaingo/agents" "github.com/tmc/langchaingo/llms/ollama" "github.com/tmc/langchaingo/tools" ) func main() { // Create Ollama LLM with appropriate settings llm, err := ollama.New( ollama.WithModel("llama3"), ollama.WithOptions(ollama.Options{ Temperature: 0.2, NumPredict: 512, }), ) if err != nil { log.Fatal(err) } // Create tools calculator := tools.Calculator{} // Create agent with clear instructions systemPrompt := `You are a helpful math assistant. Use the calculator tool for computations. Format your responses as: - For calculations: "Action: calculator" then "Action Input: [expression]" - For final answers: "Final Answer: [result]"` agent := agents.NewOneShotAgent( llm, []tools.Tool{calculator}, agents.WithSystemMessage(systemPrompt), agents.WithMaxIterations(5), ) // Create executor executor := agents.NewExecutor( agent, agents.WithMaxIterations(5), ) // Run the agent result, err := executor.Call( context.Background(), map[string]any{ "input": "What is 25 * 4?", }, ) if err != nil { log.Printf("Error: %v", err) } else { fmt.Printf("Result: %v\n", result["output"]) } }

示例中的 Calculator 工具使用 starlark 求值器执行数学表达式,其Name()calculatorDescription()描述了输入格式,MRKL 提示词会通过toolNames/toolDescriptions自动将工具清单注入模板(见 mrkl_prompt.go)。

理解底层执行流程

要让上面的示例真正跑通,还需要理解 Agent 的迭代执行机制。NewExecutor返回的 Executor 是负责运行 Agent 的链式组件,其Call方法按MaxIterations次循环执行(executor.go):

  1. 调用Agent.Plan生成下一步计划(动作列表或结束信号);
  2. 若返回finish(最终答案),立即返回结果;
  3. 若返回动作列表,则逐个执行:doAction会按大写形式在工具映射表中查找工具(getNameToTool将工具名统一转为大写),找不到时把 "X is not a valid tool, try another one" 作为 Observation 反馈给模型,让它重新选择(executor.go);
  4. 每次工具执行的 Observation 会通过constructMrklScratchPad拼接进agent_scratchpad,形成下一轮推理的上下文(mrkl.go);
  5. 若迭代耗尽仍未产出Final Answer,返回ErrNotFinished("agent not finished before max iterations")。

另外,MRKL Agent 在调用 LLM 时还注入了停止词\nObservation:,避免模型"抢答"工具结果(mrkl.go)。默认最大迭代次数为 5(initialize.go),可通过 WithMaxIterations 调整。

故障排查

报错 "unable to parse output"

  • 原因:模型输出与期望格式不匹配,parseOutput的所有分支都未命中。
  • 解决方案(按优先级):
    1. 降低温度,减少随机性,让模型更保守地遵循格式;
    2. 换用能力更强的模型(如 llama3、mixtral);
    3. 在系统提示词中补充示例,展示"思考→动作→观察→最终答案"的完整链路;
    4. 考虑 few-shot prompting,给出 1~2 组正例。
  • 进阶:Executor 支持通过WithParserErrorHandler挂载解析错误处理器(options.go)。解析失败时,错误信息会被格式化为 Observation 追加到上下文,让模型在下一轮自行修正(executor.go),这比直接终止更有利于自愈。

报错 "agent not finished before max iterations"

  • 原因:模型始终没有生成 "Final Answer",迭代次数耗尽。
  • 解决方案
    1. 在系统提示词中明确写出Final Answer:,并说明何时该输出它;
    2. 临时调大MaxIterations(如 8~10)用于调试,观察模型是否只是"步子迈得慢";
    3. 检查模型是否在输出本文解析器已支持的变体(如The answer is:),确认是否有其他格式问题。

模型不断重复动作

  • 原因:模型不理解"拿到工具结果后就应该停止并给出最终答案"。
  • 解决方案
    1. 在提示词中加入明确的停止条件,例如"一旦得到计算结果,立即用 Final Answer 回复";
    2. 在系统提示词中给出完整示例,展示"动作→观察→最终答案"的完整闭环;
    3. 考虑编写自定义输出解析器,对模型行为做更严格的约束。

用测试验证你的 Agent 配置

将提示词与参数调整好后,建议用自动化测试固化验证,防止后续改动回归。以下是 官方指南 提供的测试骨架:

// Test function to verify Ollama agent works correctly func TestOllamaAgent(t *testing.T) { ctx := context.Background() llm, err := ollama.New( ollama.WithModel("llama3"), ) require.NoError(t, err) calculator := tools.Calculator{} agent := agents.NewOneShotAgent( llm, []tools.Tool{calculator}, agents.WithMaxIterations(3), ) executor := agents.NewExecutor(agent) testCases := []struct { input string expected string }{ {"What is 2+2?", "4"}, {"Calculate 10*5", "50"}, {"What is 100 divided by 4?", "25"}, } for _, tc := range testCases { result, err := executor.Call(ctx, map[string]any{ "input": tc.input, }) if err != nil { t.Logf("Warning: %s failed: %v", tc.input, err) continue } output := fmt.Sprintf("%v", result["output"]) if !strings.Contains(output, tc.expected) { t.Errorf("Expected %s in output, got: %s", tc.expected, output) } } }

测试中require来自github.com/stretchr/testify,这是 langchaingo 仓库测试中广泛使用的断言库(可参考 markl_test.go 与各包的*_test.go)。运行前请确保本地已启动 Ollama 服务并拉取对应模型(如ollama pull llama3);若网络环境无法访问模型仓库,也可以借助仓库内部使用的 httprr 录制回放机制将真实请求录制下来做离线回归测试。

总结

为了让 Ollama 模型在 Agent 场景下可靠工作,langchaingo 从两个层面做了工程化处理:

  1. 解析层容错:改进parseOutput,支持 "Final Answer" 大小写变体、The answer is:等自然语言表达,以及大小写不敏感的 Action 匹配,并配套TestMRKLOutputParser测试用例(mrkl.go、markl_test.go);
  2. 使用层最佳实践:清晰的系统提示词、合适的模型选型、降低温度、配合MaxIterations与解析错误处理器进行容错。

诚然,这些改进让 Ollama 模型在使用 Agent 时更可靠,但相比原生支持 function calling 的模型,它仍然依赖谨慎的提示词工程。建议在实际项目中:先用小规模测试用例验证模型对格式的遵从度,再逐步放开业务场景;遇到格式问题优先从"提示词是否明确"与"采样参数是否激进"两个方向排查。本文涉及的源码、测试与完整指南均可直接在 agents 目录 与 llms/ollama 目录 中继续深入阅读。

【免费下载链接】langchaingoLangChain for Go, the easiest way to write LLM-based programs in Go项目地址: https://gitcode.com/GitHub_Trending/la/langchaingo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询