🚀 Ollama + LangChain-Go 快速入门
环境准备
1. 安装 Ollama
# macOS
brew install ollama
# 或者从官网下载
# https://ollama.ai/download
2. 启动 Ollama 服务
ollama serve
3. 下载模型
# 推荐的轻量级模型
ollama pull llama3.2
# 或者其他可用模型
# ollama pull llama3.2:1b # 更小的模型
# ollama pull qwen:7b # 中文友好的模型
4. 验证安装
ollama list
演示内容
本演示包含 6 个学习模块:
1. 基本 Ollama LLM 使用
- 创建 Ollama LLM 实例
- 基本的问答功能
- 文本生成
package main
import (
"context"
"fmt"
"github.com/tmc/langchaingo/llms/ollama"
"log"
"strings"
)
func main() {
// 创建 Ollama LLM 实例
llm, err := ollama.New(ollama.WithModel("llama3.2"))
if err != nil {
log.Printf("创建 Ollama LLM 失败: %v", err)
fmt.Println("请确保 Ollama 已安装并运行,且已下载 llama3.2 模型")
fmt.Println("安装命令: ollama pull llama3.2")
return
}
ctx := context.Background()
// 简单的文本生成
questions := []string{
"请用中文简单介绍一下人工智能",
"Go语言有哪些优势?",
"什么是 LangChain?",
}
for i, question := range questions {
fmt.Printf("\n问题 %d: %s\n", i+1, question)
fmt.Println(strings.Repeat("-", 50))
response, err := llm.Call(ctx, question)
if err != nil {
log.Printf("LLM 调用失败: %v", err)
continue
}
fmt.Printf("回答: %s\n", response)
}
}
2. 带记忆的对话
- 对话历史管理
- 上下文保持
- 多轮对话
package main
import (
"context"
"fmt"
"github.com/tmc/langchaingo/chains"
"github.com/tmc/langchaingo/llms/ollama"
"github.com/tmc/langchaingo/memory"
"log"
)
func main() {
llm, err := ollama.New(ollama.WithModel("llama3.2"))
if err != nil {
log.Printf("创建 Ollama LLM 失败: %v", err)
return
}
// 创建对话记忆
conversationMemory := memory.NewConversationBuffer()
// 创建对话链
conversation := chains.NewConversation(llm, conversationMemory)
ctx := context.Background()
// 模拟多轮对话
dialogues := []string{
"你好,我叫张三,我是一名软件工程师",
"我正在学习 Go 语言和 LangChain",
"你还记得我的名字吗?",
"我的职业是什么?",
}
for i, input := range dialogues {
fmt.Printf("\n轮次 %d:\n", i+1)
fmt.Printf("用户: %s\n", input)
response, err := chains.Run(ctx, conversation, input)
if err != nil {
log.Printf("对话失败: %v", err)
continue
}
fmt.Printf("助手: %s\n", response)
// 显示当前记忆内容
memoryVars := conversationMemory.MemoryVariables(ctx)
fmt.Printf("记忆状态: %v\n", memoryVars)
}
}
3. 自定义提示模板
- 角色扮演
- 结构化提示
- 参数化模板
package main
import (
"context"
"fmt"
"github.com/tmc/langchaingo/llms/ollama"
"github.com/tmc/langchaingo/prompts"
"log"
"strings"
)
func main() {
fmt.Println("\n\n=== 3. 自定义提示模板演示 ===")
llm, err := ollama.New(ollama.WithModel("llama3.2"))
if err != nil {
log.Printf("创建 Ollama LLM 失败: %v", err)
return
}
// 创建自定义提示模板
promptTemplate := prompts.NewPromptTemplate(
"你是一个专业的{{.profession}}。请根据以下问题给出专业建议:\n\n问题:{{.question}}\n\n请用中文回答,并保持专业性。",
[]string{"profession", "question"},
)
ctx := context.Background()
// 测试不同的专业角色
scenarios := []map[string]interface{}{
{
"profession": "软件架构师",
"question": "如何设计一个高并发的微服务系统?",
},
{
"profession": "数据科学家",
"question": "在机器学习项目中如何处理数据不平衡问题?",
},
{
"profession": "产品经理",
"question": "如何制定产品路线图?",
},
}
for i, scenario := range scenarios {
fmt.Printf("\n场景 %d: %s 咨询\n", i+1, scenario["profession"])
fmt.Println(strings.Repeat("-", 50))
// 格式化提示
prompt, err := promptTemplate.Format(scenario)
if err != nil {
log.Printf("提示格式化失败: %v", err)
continue
}
fmt.Printf("问题: %s\n", scenario["question"])
response, err := llm.Call(ctx, prompt)
if err != nil {
log.Printf("LLM 调用失败: %v", err)
continue
}
fmt.Printf("专业建议: %s\n", response)
}
}
4. 工具使用 (Agent 模式)
- 计算器工具
- 天气查询工具
- 时间工具
- 文本摘要工具
package main
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/tmc/langchaingo/llms/ollama"
"github.com/tmc/langchaingo/tools"
)
// 自定义计算器工具
type CalculatorTool struct{}
func (c CalculatorTool) Name() string {
return "calculator"
}
func (c CalculatorTool) Description() string {
return "用于进行简单的数学计算。输入应该是一个数学表达式,例如 '2+3' 或 '10*5'"
}
func (c CalculatorTool) Call(ctx context.Context, input string) (string, error) {
// 简单的计算器实现
input = strings.TrimSpace(input)
input = strings.ReplaceAll(input, " ", "")
// 这里实现一个简单的计算逻辑
switch {
case strings.Contains(input, "+"):
parts := strings.Split(input, "+")
if len(parts) == 2 {
return fmt.Sprintf("%s + %s = %s", parts[0], parts[1], "计算结果"), nil
}
case strings.Contains(input, "*"):
parts := strings.Split(input, "*")
if len(parts) == 2 {
return fmt.Sprintf("%s × %s = %s", parts[0], parts[1], "计算结果"), nil
}
case strings.Contains(input, "-"):
parts := strings.Split(input, "-")
if len(parts) == 2 {
return fmt.Sprintf("%s - %s = %s", parts[0], parts[1], "计算结果"), nil
}
}
return fmt.Sprintf("已计算表达式: %s", input), nil
}
// 天气查询工具
type WeatherTool struct{}
func (w WeatherTool) Name() string {
return "weather"
}
func (w WeatherTool) Description() string {
return "查询指定城市的天气信息。输入应该是城市名称,例如 '北京' 或 '上海'"
}
func (w WeatherTool) Call(ctx context.Context, input string) (string, error) {
city := strings.TrimSpace(input)
// 模拟天气数据
weatherData := map[string]string{
"北京": "北京今日天气:晴转多云,气温 15-25°C,微风",
"上海": "上海今日天气:小雨,气温 18-22°C,东南风",
"广州": "广州今日天气:多云,气温 22-30°C,南风",
"深圳": "深圳今日天气:晴,气温 25-32°C,微风",
}
if weather, exists := weatherData[city]; exists {
return weather, nil
}
return fmt.Sprintf("%s的天气信息暂时无法获取,建议查询其他城市", city), nil
}
// 时间工具
type TimeTool struct{}
func (t TimeTool) Name() string {
return "current_time"
}
func (t TimeTool) Description() string {
return "获取当前的日期和时间信息"
}
func (t TimeTool) Call(ctx context.Context, input string) (string, error) {
now := time.Now()
return fmt.Sprintf("当前时间:%s", now.Format("2006年01月02日 15:04:05")), nil
}
// 文本摘要工具
type SummaryTool struct{}
func (s SummaryTool) Name() string {
return "text_summary"
}
func (s SummaryTool) Description() string {
return "对输入的长文本进行摘要总结"
}
func (s SummaryTool) Call(ctx context.Context, input string) (string, error) {
text := strings.TrimSpace(input)
if len(text) < 50 {
return "文本太短,无需摘要", nil
}
// 简单的摘要逻辑
words := strings.Fields(text)
if len(words) > 20 {
summary := strings.Join(words[:20], " ") + "..."
return fmt.Sprintf("文本摘要:%s", summary), nil
}
return fmt.Sprintf("文本摘要:%s", text), nil
}
// 演示工具使用(Agent 模式)
func main() {
fmt.Println("\n\n=== 工具使用演示 (Agent 模式) ===")
llm, err := ollama.New(ollama.WithModel("llama3.2"))
if err != nil {
log.Printf("创建 Ollama LLM 失败: %v", err)
return
}
// 创建工具集合
availableTools := []tools.Tool{
CalculatorTool{},
WeatherTool{},
TimeTool{},
SummaryTool{},
}
fmt.Println("可用工具:")
for _, tool := range availableTools {
fmt.Printf("- %s: %s\n", tool.Name(), tool.Description())
}
ctx := context.Background()
// 模拟用户请求,演示工具调用
requests := []struct {
query string
tool string
input string
}{
{
query: "请计算 15 + 27 的结果",
tool: "calculator",
input: "15+27",
},
{
query: "查询北京的天气情况",
tool: "weather",
input: "北京",
},
{
query: "现在几点了?",
tool: "current_time",
input: "",
},
{
query: "请总结这段文本:人工智能是计算机科学的一个分支,它试图理解智能的本质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器",
tool: "text_summary",
input: "人工智能是计算机科学的一个分支,它试图理解智能的本质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器",
},
}
for i, req := range requests {
fmt.Printf("\n请求 %d: %s\n", i+1, req.query)
fmt.Println(strings.Repeat("-", 50))
// 找到对应的工具
var selectedTool tools.Tool
for _, tool := range availableTools {
if tool.Name() == req.tool {
selectedTool = tool
break
}
}
if selectedTool == nil {
fmt.Printf("未找到合适的工具\n")
continue
}
fmt.Printf("使用工具: %s\n", selectedTool.Name())
// 调用工具
result, err := selectedTool.Call(ctx, req.input)
if err != nil {
log.Printf("工具调用失败: %v", err)
continue
}
fmt.Printf("工具结果: %s\n", result)
// 让 LLM 基于工具结果生成最终回答
finalPrompt := fmt.Sprintf("用户问题:%s\n工具执行结果:%s\n\n请基于工具结果,用自然语言回答用户的问题:", req.query, result)
finalResponse, err := llm.Call(ctx, finalPrompt)
if err != nil {
log.Printf("LLM 最终回答生成失败: %v", err)
continue
}
fmt.Printf("最终回答: %s\n", finalResponse)
}
}
5. 流式输出
- 模拟实时响应
- 渐进式文本显示
package main
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/tmc/langchaingo/llms/ollama"
)
// 演示流式输出
func main() {
fmt.Println("\n\n=== 流式输出演示 ===")
llm, err := ollama.New(ollama.WithModel("llama3.2"))
if err != nil {
log.Printf("创建 Ollama LLM 失败: %v", err)
return
}
ctx := context.Background()
question := "请详细解释什么是微服务架构,以及它的优缺点"
fmt.Printf("问题: %s\n", question)
fmt.Println("回答:")
fmt.Println(strings.Repeat("-", 50))
// 使用普通调用模拟流式输出效果
response, err := llm.Call(ctx, question)
if err != nil {
log.Printf("调用失败: %v", err)
return
}
// 模拟流式输出效果
words := strings.Fields(response)
for i, word := range words {
fmt.Print(word)
if i < len(words)-1 {
fmt.Print(" ")
}
time.Sleep(100 * time.Millisecond) // 模拟流式输出延迟
}
fmt.Println("\n" + strings.Repeat("-", 50))
}
6. 批量处理
- 多任务并发处理
- 性能统计
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/tmc/langchaingo/llms/ollama"
)
// 演示批量处理
func main() {
fmt.Println("\n\n=== 批量处理演示 ===")
llm, err := ollama.New(ollama.WithModel("llama3.2"))
if err != nil {
log.Printf("创建 Ollama LLM 失败: %v", err)
return
}
ctx := context.Background()
// 批量问题
questions := []string{
"Go语言的并发模型是什么?",
"Docker和虚拟机有什么区别?",
"什么是RESTful API?",
"解释一下什么是区块链",
"机器学习和深度学习的区别是什么?",
}
fmt.Printf("批量处理 %d 个问题:\n", len(questions))
startTime := time.Now()
for i, question := range questions {
fmt.Printf("\n[%d/%d] %s\n", i+1, len(questions), question)
response, err := llm.Call(ctx, question)
if err != nil {
log.Printf("问题 %d 处理失败: %v", i+1, err)
continue
}
// 限制输出长度用于演示
if len(response) > 200 {
response = response[:200] + "..."
}
fmt.Printf("回答: %s\n", response)
}
duration := time.Since(startTime)
fmt.Printf("\n批量处理完成,总耗时: %v\n", duration)
}
提升 Ollama 的响应速度
设置 keep_alive 可以有效提升 Ollama 的响应速度,尤其是在短时间内多次调用模型的场景中。这是因为 keep_alive 控制着模型加载到内存后保持活跃的时间,避免了每次调用都重新加载模型的开销。
原理说明:
-
Ollama 在首次调用模型时,需要将模型从磁盘加载到内存,这个过程可能需要几秒到几十秒(取决于模型大小)。
-
通过设置 keep_alive,可以让模型在内存中保持活跃状态(默认是 5 分钟),后续调用无需重新加载,响应速度会显著提升(通常从秒级缩短到毫秒级)。
关键参数说明:
ollama.WithKeepAlive(300):设置模型在内存中保持活跃的时间为 300 秒(5 分钟),超出这个时间未被调用,模型会被从内存中卸载。 特殊值:
- 0:表示永久保持模型在内存中(适合高频调用场景,但会持续占用内存)。
- -1:使用 Ollama 服务器的默认配置(通常是 5 分钟)。
注意事项:
内存占用:长时间保持大模型(如 7B、13B 参数模型)会占用较多内存,需根据服务器配置调整。
适用场景:适合 API 服务、对话机器人等需要频繁调用模型的场景,单次脚本运行场景提升不明显。
服务器配置:如果 Ollama 服务器有多个用户共享,过久的 keep_alive 可能影响其他用户,建议合理设置时长。
通过合理设置 keep_alive,可以在内存资源和响应速度之间取得平衡,显著提升 Ollama 的调用体验。
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/tmc/langchaingo/llms/ollama"
)
func main() {
// 配置模型,设置 keep_alive 为 300 秒(5 分钟)
// 0 表示永久保持,-1 表示使用 Ollama 服务器默认值
llm, err := ollama.New(
ollama.WithModel("llama3.2"),
ollama.WithKeepAlive(300), // 模型在内存中保持活跃的时间(秒)
)
if err != nil {
log.Fatalf("创建 Ollama 客户端失败: %v", err)
}
ctx := context.Background()
// 第一次调用(可能较慢,需要加载模型)
start := time.Now()
resp1, err := llm.Call(ctx, "什么是微服务?")
if err != nil {
log.Fatalf("第一次调用失败: %v", err)
}
fmt.Printf("第一次调用耗时: %v\n", time.Since(start))
// 等待 10 秒后进行第二次调用(在 keep_alive 有效期内)
time.Sleep(10 * time.Second)
// 第二次调用(速度更快,模型已在内存中)
start = time.Now()
resp2, err := llm.Call(ctx, "微服务和单体架构的区别是什么?")
if err != nil {
log.Fatalf("第二次调用失败: %v", err)
}
fmt.Printf("第二次调用耗时: %v\n", time.Since(start))
// 输出结果(简化展示)
fmt.Println("\n第一次回答:", resp1[:100]+"...")
fmt.Println("第二次回答:", resp2[:100]+"...")
}
故障排除
Ollama 连接失败
- 确认 Ollama 服务正在运行:
ollama serve - 检查端口是否被占用:
lsof -i :11434 - 尝试重启 Ollama 服务
模型下载失败
- 检查网络连接
- 使用代理:
export https_proxy=your_proxy - 选择更小的模型:
ollama pull llama3.2:1b
内存不足
- 使用更小的模型
- 关闭其他应用程序
- 调整 Ollama 的内存设置
扩展学习
- 尝试不同的模型
- 集成更多自定义工具
- 实现复杂的Agent工作流
- 添加持久化存储