🚀 快速上手指南
🎯 5分钟快速体验
步骤1:环境准备
# 检查 Go 版本(需要 1.21+)
go version
# 检查 Ollama 是否安装
ollama --version
# 如果没有安装 Ollama
curl -fsSL https://ollama.ai/install.sh | sh
步骤2:拉取模型
# 拉取 llama3.2 模型(约 2GB)
ollama pull llama3.2
# 测试模型是否正常
ollama run llama3.2 "你好"
步骤3:运行项目
# 进入项目目录
cd learn-langchain
# 安装依赖
go mod tidy
# 编译运行
go run main.go chat
步骤4:测试功能
🧑 北京的天气怎么样 # 测试天气工具
🧑 现在几点了 # 测试时间工具
🧑 计算 15+25 # 测试计算器
🧑 你好,介绍一下自己 # 测试对话功能
🧑 quit # 退出程序
📖 核心知识点速查
1. langchain-go 基础概念
LLM 抽象
// 统一的大语言模型接口
type Model interface {
GenerateContent(ctx context.Context, messages []MessageContent, options ...CallOption) (*ContentResponse, error)
}
// Ollama 实现
llm, err := ollama.New(ollama.WithModel("llama3.2"))
关键理解:
- LLM 是一个抽象接口,可以切换不同的模型提供商
- Ollama 提供本地模型运行能力
- 统一的接口设计便于模型替换
Tools 工具系统
// 工具接口定义
type Tool interface {
Name() string // 工具名称
Description() string // 工具描述
Call(ctx context.Context, input string) (string, error) // 工具调用
}
设计原则:
- 单一职责:每个工具专注一个功能
- 标准化接口:便于集成和扩展
- 上下文传递:支持取消和超时控制
Chains 链式处理
// 创建 LLM Chain
prompt := prompts.NewPromptTemplate(template, []string{"input"})
llmChain := chains.NewLLMChain(llm, prompt)
// 执行链处理
result, err := chains.Call(ctx, llmChain, map[string]any{
"input": userInput,
})
核心思想:
- 模块化组合:将复杂任务分解为简单步骤
- 可复用性:同一个 Chain 可以处理不同输入
- 提示工程:通过模板管理 LLM 交互
2. 项目架构设计
分层架构
表示层(CLI) ←→ cmd/chat.go
业务逻辑层 ←→ processWithChain()
工具抽象层 ←→ tools.Tool interface
具体实现层 ←→ tool/*.go
数据流向
用户输入 → 工具识别 → 工具调用/LLM处理 → 结果返回
关键组件
- 工具注册器:
toolMap快速查找工具 - 智能路由:
identifyTool()识别用户意图 - 处理链路:
processWithChain()统一处理入口
3. 工具开发规范
标准实现模板
type XxxTool struct{}
func (x XxxTool) Name() string {
return "tool_name" // 全局唯一标识
}
func (x XxxTool) Description() string {
return "工具功能描述,用于 LLM 理解"
}
func (x XxxTool) Call(ctx context.Context, input string) (string, error) {
// 1. 输入验证
if input == "" {
return "", fmt.Errorf("输入不能为空")
}
// 2. 业务处理
result := processInput(input)
// 3. 返回结果
return result, nil
}
错误处理最佳实践
func (x XxxTool) Call(ctx context.Context, input string) (string, error) {
// 使用 defer 进行资源清理
defer func() {
// 清理逻辑
}()
// 检查上下文取消
select {
case <-ctx.Done():
return "", ctx.Err()
default:
}
// 具体业务逻辑
// ...
}
4. 智能路由算法
关键词匹配策略
func identifyTool(input string) (toolName, toolInput string) {
inputLower := strings.ToLower(input)
// 优先级匹配:精确 > 模糊 > 默认
patterns := map[string][]string{
"weather": {"天气", "weather", "气温"},
"current_time": {"时间", "几点", "现在", "当前"},
"calculator": {"计算", "+", "-", "*", "/", "="},
"text_summary": {"摘要", "总结", "概括"},
}
for toolName, keywords := range patterns {
for _, keyword := range keywords {
if strings.Contains(inputLower, keyword) {
return toolName, processInput(input, toolName)
}
}
}
return "", ""
}
输入预处理
func processInput(input, toolName string) string {
switch toolName {
case "weather":
return extractCityName(input)
case "calculator":
return extractMathExpression(input)
case "current_time":
return "" // 无需输入参数
default:
return input
}
}
5. 提示工程技巧
模板设计原则
template := `你是一个专业的 {{.role}} 助手。
任务要求:
{{.requirements}}
用户输入:{{.input}}
请按以下格式回复:
1. 理解:[重复用户需求]
2. 分析:[问题分析过程]
3. 回答:[具体答案]
回复:`
设计要点:
- 角色定义清晰
- 任务要求具体
- 输出格式规范
- 示例引导明确
上下文管理
type ConversationContext struct {
History []Message
UserInfo map[string]any
Session string
}
func buildPromptWithContext(template string, ctx ConversationContext, input string) string {
// 构建包含上下文的完整提示
}
🔍 业务逻辑深度解析
核心处理流程
1. 请求接收与预处理
func (chatCmd *cobra.Command) Run(cmd *cobra.Command, args []string) {
// 初始化组件
llm := initializeLLM()
toolMap := buildToolMap()
// 启动交互循环
for {
input := readUserInput()
response := processWithChain(llm, toolMap, input)
displayResponse(response)
}
}
关键点:
- 组件初始化:一次性完成 LLM 和工具的初始化
- 交互循环:保持会话状态,处理连续对话
- 异常处理:优雅处理用户中断和系统错误
2. 智能路由决策
func processWithChain(llm *ollama.LLM, toolMap map[string]tools.Tool, input string) string {
// 决策树:工具 > 对话
if toolName, toolInput := identifyTool(input); toolName != "" {
return callToolDirectly(toolMap[toolName], toolInput)
}
return callLLMChain(llm, input)
}
决策逻辑:
- 工具优先:明确的功能需求直接调用工具
- 对话降级:复杂或模糊需求使用 LLM
- 性能优化:避免不必要的 LLM 调用
3. 工具执行机制
func callToolDirectly(tool tools.Tool, input string) string {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := tool.Call(ctx, input)
if err != nil {
return formatError(tool.Name(), err)
}
return result
}
执行特点:
- 超时控制:防止工具调用阻塞
- 错误格式化:提供用户友好的错误信息
- 上下文传递:支持取消和清理操作
4. LLM 对话处理
func callLLMChain(llm *ollama.LLM, input string) string {
// 构建提示
prompt := buildConversationPrompt(input)
// 创建处理链
chain := chains.NewLLMChain(llm, prompt)
// 执行对话
result, err := chains.Call(context.Background(), chain, map[string]any{
"input": input,
})
return extractResponse(result, err)
}
处理特点:
- 提示工程:使用模板确保输出质量
- 链式处理:利用 langchain 的抽象能力
- 结果提取:处理不同格式的 LLM 响应
错误处理与容错机制
分层错误处理
// 工具层错误
func (t Tool) Call(ctx context.Context, input string) (string, error) {
if err := validateInput(input); err != nil {
return "", fmt.Errorf("输入验证失败: %w", err)
}
// ...
}
// 业务层错误
func callToolDirectly(tool tools.Tool, input string) string {
result, err := tool.Call(ctx, input)
if err != nil {
return fmt.Sprintf("⚠️ %s工具调用失败: %v", tool.Name(), err)
}
return result
}
// 表示层错误
func displayResponse(response string) {
if strings.HasPrefix(response, "⚠️") {
// 错误信息特殊显示
fmt.Printf("🔴 %s\n", response)
} else {
fmt.Printf("🤖 %s\n", response)
}
}
降级策略
func processWithChain(llm *ollama.LLM, toolMap map[string]tools.Tool, input string) string {
// 主路径:工具调用
if toolName, toolInput := identifyTool(input); toolName != "" {
if result := tryToolCall(toolMap[toolName], toolInput); result != "" {
return result
}
// 工具失败降级到 LLM
}
// 降级路径:LLM 对话
if result := tryLLMCall(llm, input); result != "" {
return result
}
// 最终降级:静态回复
return "抱歉,我暂时无法处理您的请求,请稍后再试。"
}
性能优化策略
1. 工具映射表
// 预构建映射表,避免线性查找
toolMap := make(map[string]tools.Tool)
for _, tool := range availableTools {
toolMap[tool.Name()] = tool
}
2. 关键词索引
// 构建关键词到工具的映射
keywordIndex := map[string]string{
"天气": "weather",
"时间": "current_time",
"计算": "calculator",
// ...
}
3. 缓存机制
type ResponseCache struct {
cache map[string]CacheEntry
mutex sync.RWMutex
}
func (c *ResponseCache) Get(key string) (string, bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()
entry, exists := c.cache[key]
if !exists || time.Since(entry.Timestamp) > entry.TTL {
return "", false
}
return entry.Value, true
}
🎓 进阶开发指南
1. 添加新工具
步骤详解
// 1. 定义工具结构
type TranslateTool struct{}
// 2. 实现接口方法
func (t TranslateTool) Name() string {
return "translate"
}
func (t TranslateTool) Description() string {
return "翻译文本到指定语言,格式:'翻译 [文本] 到 [目标语言]'"
}
func (t TranslateTool) Call(ctx context.Context, input string) (string, error) {
// 解析输入:源文本和目标语言
sourceText, targetLang := parseTranslateInput(input)
// 调用翻译服务(示例)
result := callTranslateAPI(sourceText, targetLang)
return result, nil
}
// 3. 注册工具
availableTools = append(availableTools, TranslateTool{})
// 4. 添加识别规则
if strings.Contains(inputLower, "翻译") || strings.Contains(inputLower, "translate") {
return "translate", input
}
2. 自定义提示模板
高级模板示例
template := `你是一个专业的编程助手,具有以下特点:
- 擅长 {{.language}} 编程语言
- 注重代码质量和最佳实践
- 提供清晰的解释和示例
用户问题:{{.question}}
相关上下文:{{.context}}
请按照以下结构回答:
1. 问题理解:简要复述用户需求
2. 解决方案:提供具体的代码或方法
3. 解释说明:解释关键点和注意事项
4. 最佳实践:相关的编程建议
回答:`
// 使用模板
prompt := prompts.NewPromptTemplate(template, []string{"language", "question", "context"})
3. 集成外部服务
HTTP 客户端封装
type HTTPTool struct {
client *http.Client
baseURL string
apiKey string
timeout time.Duration
}
func (h *HTTPTool) Call(ctx context.Context, input string) (string, error) {
// 构建请求
req, err := h.buildRequest(ctx, input)
if err != nil {
return "", fmt.Errorf("构建请求失败: %w", err)
}
// 发送请求
resp, err := h.client.Do(req)
if err != nil {
return "", fmt.Errorf("请求失败: %w", err)
}
defer resp.Body.Close()
// 处理响应
return h.parseResponse(resp)
}
📊 监控与调试
日志系统
import "log/slog"
// 结构化日志
func logToolCall(toolName, input string, duration time.Duration, err error) {
slog.Info("tool_call",
"tool", toolName,
"input_length", len(input),
"duration_ms", duration.Milliseconds(),
"success", err == nil,
"error", err,
)
}
性能监控
type Metrics struct {
ToolCalls map[string]int64 // 工具调用次数
Latencies map[string][]time.Duration // 延迟分布
ErrorRates map[string]float64 // 错误率
}
func (m *Metrics) RecordToolCall(toolName string, duration time.Duration, err error) {
m.ToolCalls[toolName]++
m.Latencies[toolName] = append(m.Latencies[toolName], duration)
if err != nil {
m.ErrorRates[toolName]++
}
}
调试技巧
// 开发模式
const DEBUG = true
func debugLog(format string, args ...any) {
if DEBUG {
log.Printf("[DEBUG] "+format, args...)
}
}
// 在关键路径添加调试信息
func identifyTool(input string) (toolName, toolInput string) {
debugLog("识别工具输入: %s", input)
// ... 识别逻辑
debugLog("识别结果: tool=%s, input=%s", toolName, toolInput)
return toolName, toolInput
}
🎯 现在您已经掌握了项目的核心知识点和业务逻辑,可以开始深入学习和实践了!