1. 项目概述:从零构建一个AI编程助手

如果你是一名开发者,每天在IDE和终端之间反复横跳,一边查文档一边写代码,那么你肯定幻想过有一个能理解你意图、帮你读文件、跑命令甚至直接修改代码的智能伙伴。这听起来像是科幻电影里的场景,但今天,借助像Claude这样的AI模型,我们完全可以用Go语言亲手搭建一个。这个项目不是一个简单的API调用演示,而是一个完整的、模块化的“智能体”构建教程。我们将从一个只会聊天的“复读机”开始,逐步赋予它文件读写、目录浏览、执行Shell命令、编辑代码乃至搜索代码库的能力。最终,你会得到一个运行在本地的、功能强大的个人编程副驾。整个过程不需要你是AI专家,只需要你对Go语言有基本了解,并准备好一颗愿意动手的心。

2. 核心架构与设计思路拆解

在开始敲代码之前,理解我们即将构建的这个“智能体”是如何工作的至关重要。这能帮助你在后续开发中,清晰地知道每一行代码在整体架构中的位置和作用。

2.1 智能体的核心:事件循环

我们构建的智能体,其核心是一个 事件循环 。你可以把它想象成一个永不疲倦的、高度自律的“大脑皮层”。它的工作流程是循环往复的:

  1. 等待用户输入 :智能体启动后,首先进入一个循环,等待你从终端输入问题或指令。
  2. 发送请求 :将你的输入,连同之前的对话历史(如果有的话),打包成一个结构化的请求,发送给远端的Claude API。
  3. 解析AI响应 :Claude收到请求后,会进行分析和思考。它的响应可能有两种:
    • 直接回答 :如果问题很简单(比如“你好”),Claude会直接生成一段文本回复。
    • 请求使用工具 :如果问题涉及外部操作(比如“帮我看看 main.go 文件里写了什么”),Claude不会直接“猜”文件内容,而是会返回一个结构化的“工具调用请求”,指明它想使用哪个工具(如 read_file ),以及调用这个工具所需的参数(如 file_path: “main.go” )。
  4. 执行工具调用 :我们的Go程序接收到这个工具调用请求后,会在本地注册的“工具箱”里找到对应的工具函数,并使用Claude提供的参数执行它(例如,真正去读取 main.go 文件的内容)。
  5. 收集结果并反馈 :工具执行完成后,会产生结果(成功读取的文件内容)或错误(文件不存在)。程序将这个结果再次打包,发送回Claude,告诉它:“嘿,这是你要的文件内容。”
  6. 生成最终答复 :Claude收到工具执行结果后,会结合这个新信息进行第二轮思考,最终生成一个包含文件内容的、人类可读的回答,发送回我们的程序。
  7. 输出并进入下一轮 :程序将Claude的最终回答输出到终端,然后事件循环回到第1步,等待你的下一个指令。

这个循环的精妙之处在于,它将AI强大的推理和语言能力,与我们本地环境的实际操控能力结合了起来。AI负责“思考”和“规划”,我们的程序负责“执行”和“感知”。

2.2 工具系统的设计哲学

工具是我们赋予智能体“手脚”和“感官”的关键。在设计工具系统时,我们遵循了几个核心原则:

  • 声明式定义 :每个工具都是一个清晰定义的结构体,包含名称、描述、输入参数模式和执行函数。这使得工具的注册、管理和调用变得非常规范。
  • 强类型安全 :利用Go语言的强类型特性,我们为每个工具的输入参数定义明确的Go结构体。这不仅能在编译期捕获许多错误,还能通过反射自动生成供AI理解的JSON Schema,确保AI传递的参数格式正确。
  • 职责分离 :工具函数本身只专注于完成具体的任务(如读取文件、运行命令),它不关心AI的对话逻辑。而事件循环则负责协调AI与工具之间的交互,不涉及具体业务逻辑。这种分离使得系统易于维护和扩展。
  • 安全性前置 :对于高风险操作(如执行任意Shell命令、编辑文件),我们在工具函数内部设计了安全边界。例如, bash_tool 工具可以限制可执行的命令范围, edit_tool 可以在写入前备份原文件。这是本地智能体相较于云端服务的一个重要优势——完全可控。

2.3 技术栈选型:为什么是Go和Claude?

  • Go语言 :我们选择Go作为实现语言,主要基于以下几点考量。首先,Go的静态编译特性使得最终生成的智能体是一个独立的二进制文件,无需复杂的运行时环境,分发和部署极其简单。其次,Go卓越的并发模型(goroutine和channel)为未来实现更复杂的异步工具调用或并行任务处理预留了空间。再者,Go的标准库非常强大,特别是在文件系统操作、命令行交互等方面,能让我们用更少的代码实现核心功能。最后,Go的工程化友好特性(如内置的测试、格式化、依赖管理)非常适合构建这种模块化、渐进式的教学项目。
  • Anthropic Claude API :在众多大语言模型中,Claude(特别是Claude 3系列)在代码理解、逻辑推理和遵循指令方面表现尤为出色。其API设计对“工具调用”有非常好的原生支持,响应结构清晰,便于解析。同时,Anthropic在模型安全性和可控性上做了大量工作,这对于构建一个能执行本地操作的智能体来说是一个重要的加分项。

3. 开发环境准备与项目初始化

工欲善其事,必先利其器。一个顺畅的开发环境能让你更专注于代码逻辑,而不是和环境问题作斗争。

3.1 环境准备:两种路径的选择

项目提供了两种环境配置方式,我强烈推荐第一种,它能帮你避开绝大多数环境依赖的坑。

首选方案:使用devenv(强烈推荐)

devenv 是一个基于Nix的声明式开发环境管理工具。它通过一个配置文件( devenv.nix )来精确描述项目所需的所有依赖(特定版本的Go、工具链等)。这意味着:

  • 环境隔离 :项目依赖不会污染你的全局系统环境。
  • 一致性 :在任何机器上,只要运行 devenv shell ,都能获得完全相同的开发环境,彻底解决“在我机器上是好的”这类问题。
  • 开箱即用 :项目已经配置好了所有必要依赖。
# 进入项目根目录
cd how-to-build-a-coding-agent

# 加载开发环境。这会在当前shell会话中激活所有工具和路径。
devenv shell

# 验证Go版本
go version
# 应该输出 go1.24.2 或更高版本

执行 devenv shell 后,你的终端提示符可能会发生变化,这表示你已进入一个受控的环境。在这个环境里,你可以直接使用项目指定的Go版本进行开发。

备选方案:手动配置Go环境

如果你对Go环境管理很熟悉,或者希望使用自己全局安装的Go,也可以选择手动配置。

# 1. 确保已安装Go 1.24.2或更高版本
go version

# 2. 在项目根目录初始化模块并下载依赖
go mod tidy

注意 :如果你在后续步骤中遇到诸如“找不到anthropic包”之类的错误,大概率是网络问题导致依赖下载失败。请确保你的网络环境能够访问Go模块代理(如proxy.golang.org)或GitHub。可以尝试设置 GOPROXY 环境变量: export GOPROXY=https://goproxy.cn,direct (国内用户)。

3.2 获取并配置Anthropic API密钥

我们的智能体需要与Claude API对话,因此需要一个有效的API密钥。

  1. 注册与获取 :访问 Anthropic官网 ,注册账号并登录控制台。在API Keys部分,创建一个新的密钥。请注意,Claude API是付费服务,新用户通常有一定量的免费额度供试用,请留意相关计费政策。
  2. 安全地设置环境变量 :永远不要将API密钥硬编码在代码中!我们通过环境变量来传递。
# 在终端中设置环境变量(仅当前会话有效)
export ANTHROPIC_API_KEY="sk-ant-xxxxxxxxxxxx"

# 验证是否设置成功
echo $ANTHROPIC_API_KEY

为了让这个环境变量在每次打开终端时都自动生效,你可以将其添加到你的shell配置文件中(如 ~/.bashrc , ~/.zshrc )。

# 使用你喜欢的编辑器打开配置文件,例如
nano ~/.zshrc

# 在文件末尾添加
export ANTHROPIC_API_KEY="sk-ant-xxxxxxxxxxxx"

# 保存退出后,使配置生效
source ~/.zshrc

实操心得 :我习惯使用 direnv 这类工具来管理项目级的环境变量。在项目根目录创建一个 .envrc 文件,写入 export ANTHROPIC_API_KEY=xxx ,然后运行 direnv allow 。这样,每当我 cd 到这个项目目录,变量自动加载;离开目录,变量自动卸载,既安全又方便。

4. 基础构建:从零到一的聊天机器人

让我们从最简单的开始,验证整个链路是否打通。第一个版本 chat.go 的目标是:实现一个能与Claude进行纯文本对话的程序。

4.1 代码解析: chat.go

这个文件虽然简单,但包含了与Claude API交互的所有核心要素。我们来拆解一下关键部分:

// 1. 创建API客户端
client := anthropic.NewClient(os.Getenv(“ANTHROPIC_API_KEY”))

// 2. 初始化对话消息切片
messages := []anthropic.Message{
    {Role: “user”, Content: “Hello!”},
}

// 3. 构建API请求
resp, err := client.CreateMessage(context.Background(), anthropic.CreateMessageParams{
    Model:     “claude-3-haiku-20240307”, // 使用一个快速且成本较低的模型
    MaxTokens: 1024,
    Messages:  messages,
})
  • 客户端创建 :使用从环境变量获取的API密钥初始化Anthropic客户端。这里依赖了 github.com/anthropics/anthropic-sdk-go 这个第三方库,它已经在 go.mod 中定义。
  • 消息结构 :与Claude的对话是以“消息”为单位的。每条消息都有一个 Role (角色),通常是 ”user” (用户)或 ”assistant” (助手)。我们发起对话,所以第一条消息角色是 ”user”
  • 模型选择 :这里选择了 claude-3-haiku 模型。Haiku是Claude 3系列中速度最快、成本最低的模型,非常适合用于开发和测试。在生产环境或需要更强推理能力时,可以考虑换用 claude-3-sonnet claude-3-opus
  • 运行与测试
    go run chat.go
    
    输入一些简单问题,如“用Go写一个Hello World程序”,你应该能立刻看到Claude的回复。加上 --verbose 标志运行,可以看到详细的请求和响应的JSON数据,这对于调试非常有用。

注意事项 :首次运行可能会因为网络或API密钥问题失败。请务必检查:1) ANTHROPIC_API_KEY 环境变量是否正确设置并已导出;2) 网络连接是否正常;3) 你的Anthropic账户是否有可用额度。

5. 能力扩展:为智能体添加工具

纯聊天已经实现,现在进入最激动人心的部分——赋予智能体“超能力”。我们将按照复杂度,逐个添加工具。

5.1 工具一:文件阅读器 ( read.go )

这个工具让智能体能够读取你本地文件的内容。这是它理解你代码库的第一步。

核心实现解析

首先,我们需要定义一个描述工具输入参数的结构体。Claude需要知道调用这个工具时需要提供什么信息。

type ReadFileInput struct {
    FilePath string `json:“file_path”` // JSON标签告诉序列化/反序列化时字段的对应名称
}

然后,实现工具函数本身。这个函数接收上面定义的结构体实例,并执行实际的读取操作。

func ReadFile(input ReadFileInput) (string, error) {
    content, err := os.ReadFile(input.FilePath)
    if err != nil {
        // 将错误信息清晰地返回,AI可以理解并告知用户
        return “”, fmt.Errorf(“failed to read file ‘%s’: %v”, input.FilePath, err)
    }
    // 注意:返回的是字符串内容
    return string(content), nil
}

最后,也是最关键的一步,将这个工具“注册”到智能体中。我们需要创建一个 ToolDefinition ,它包含了工具的名称、描述、输入模式和执行函数。 GenerateSchema 函数利用Go的反射机制,自动从 ReadFileInput 结构体生成AI能理解的JSON Schema。

var ReadFileTool = anthropic.ToolDefinition{
    Name:        “read_file”,
    Description: “Reads the contents of a file from the local filesystem.”,
    InputSchema: GenerateSchema[ReadFileInput](), // 自动生成模式
    Function:    ReadFile, // 绑定执行函数
}

main 函数中,我们需要将这个工具定义添加到发送给Claude的请求参数中:

resp, err := client.CreateMessage(context.Background(), anthropic.CreateMessageParams{
    Model:     model,
    MaxTokens: maxTokens,
    Messages:  messages,
    Tools:     []anthropic.ToolDefinition{ReadFileTool}, // 告诉Claude,本智能体拥有这个工具
})

测试一下

go run read.go

然后尝试提问:“请读取当前目录下的 fizzbuzz.js 文件内容。” 你会看到Claude先返回一个工具调用请求,然后程序读取文件,再将内容发送给Claude,最后Claude给出一个整合了文件内容的回答。

实操心得 :文件路径的处理是个小坑。如果用户提问“读取 fizzbuzz.js ”,Claude可能会生成 {“file_path”: “fizzbuzz.js”} 。我们的工具函数使用相对路径,所以能正确找到当前目录下的文件。但如果用户说“读取 /home/user/project/main.go ”,工具也会尝试读取这个绝对路径。这意味着,你的智能体理论上可以读取用户具有权限的任意文件。在更严格的应用中,可能需要将工具的工作目录限制在某个项目根目录下,以增强安全性。

5.2 工具二:目录浏览器 ( list_files.go )

仅能读取已知文件还不够,智能体需要能“浏览”目录,了解项目结构。 list_files 工具就是它的眼睛。

实现要点 : 这个工具的实现与 read_file 类似,但输入参数可能包含一个可选的目录路径。

type ListFilesInput struct {
    Directory string `json:“directory,omitempty“` // omitempty表示该字段可选
}

func ListFiles(input ListFilesInput) (string, error) {
    targetDir := input.Directory
    if targetDir == “” {
        targetDir = “.” // 默认为当前目录
    }

    entries, err := os.ReadDir(targetDir)
    // … (遍历entries,构建文件列表字符串并返回)
}

测试场景

  • “列出当前目录的所有文件。”
  • “查看 src 文件夹里有什么。”
  • 结合使用:“先列出当前目录,然后读取 AGENT.md 文件。”

此时,智能体已经具备了基础的“查看”能力。事件循环的逻辑也开始显现:Claude可能会在一次回复中连续请求使用多个工具(比如先 list_files ,再 read_file ),我们的程序需要能处理这种串联的工具调用。

5.3 工具三:Shell命令执行器 ( bash_tool.go )

这是赋予智能体“动手”能力的关键一步。通过执行Shell命令,它可以运行测试、安装依赖、执行Git操作等等。

安全与实现考量 : 允许AI执行任意Shell命令是 高风险 操作。在生产级应用中,必须实施严格的沙箱或命令白名单机制。在本教程中,我们基于学习目的,实现一个基础但带有简单防护的版本。

type BashToolInput struct {
    Command string `json:“command”`
}

func BashTool(input BashToolInput) (string, error) {
    // 简单的安全提示(实际应用中需要更严格的限制)
    if strings.Contains(strings.ToLower(input.Command), “rm -rf”) {
        return “”, fmt.Errorf(“command rejected: potentially dangerous operation”)
    }

    cmd := exec.Command(“bash”, “-c”, input.Command)
    cmd.Dir = “.” // 设定命令执行的工作目录
    var stdout, stderr bytes.Buffer
    cmd.Stdout = &stdout
    cmd.Stderr = &stderr

    err := cmd.Run()
    output := stdout.String()
    if err != nil {
        // 即使命令出错,也返回stderr信息,这对调试有帮助
        output += “\nError: “ + err.Error() + “\nStderr: “ + stderr.String()
    }
    return output, nil // 注意:这里将错误信息作为结果的一部分返回,函数本身不返回error
}

关键设计决策 : 为什么函数在命令执行失败时,不返回 error 而是将错误信息拼接到输出字符串中?这是因为我们希望Claude能接收到命令执行失败的 具体原因 (如“command not found”或“permission denied”)。如果函数直接返回error,事件循环可能会将其视为工具调用失败而终止会话。将错误信息作为结果的一部分返回,Claude就能分析错误并给出下一步建议(例如,“你尝试运行的 ll 命令不存在,可以试试 ls -la ”)。

测试与威力

go run bash_tool.go

尝试:

  • “运行 ls -la 看看详细信息。”
  • “用 go version 检查一下Go版本。”
  • “运行 git log --oneline -5 查看最近5次提交。”

你会发现,智能体瞬间变得能干了无数倍。它可以替你执行许多日常的、重复性的终端操作。

5.4 工具四:文件编辑器 ( edit_tool.go )

从“查看”和“执行”升级到“修改”,这是智能体能力的一次飞跃。 edit_tool 允许Claude直接创建或修改代码文件。

实现解析 : 这个工具需要两个核心参数:文件路径和新的文件内容。Claude需要理解“编辑”的意图,并生成一个完整的、修改后的文件内容。

type EditFileInput struct {
    FilePath string `json:“file_path”`
    NewContent string `json:“new_content”`
}

func EditFile(input EditFileInput) (string, error) {
    // 1. 安全检查:可以在这里添加对文件路径或扩展名的限制
    // 2. (可选)备份原文件
    // 3. 将NewContent写入FilePath
    // 4. 返回成功信息
}

一个精妙的交互案例 : 你可以这样测试:“在 fizzbuzz.js 文件的顶部添加一行注释: // This is a classic FizzBuzz implementation 。” Claude会先调用 read_file 读取文件当前内容,然后在其基础上添加注释,生成完整的 NewContent ,最后调用 edit_tool 进行写入。

重要警告 :文件编辑工具非常强大,但也极其危险。AI可能会生成错误的代码,导致文件损坏。 务必在重要项目中使用前,确保有版本控制系统(如Git)的备份。 一个良好的实践是在工具函数内部实现自动创建备份文件(如 filename.backup )的逻辑。

5.5 工具五:代码搜索器 ( code_search_tool.go )

当项目变大时,快速定位代码变得重要。我们集成 ripgrep rg ),这是一个比传统 grep 更快、更强大的代码搜索工具。

实现思路 : 这个工具本质上是对 ripgrep 命令的封装。我们需要检查系统是否安装了 rg ,然后构建并执行搜索命令。

type CodeSearchInput struct {
    Pattern string `json:“pattern”`
    Path    string `json:“path,omitempty“`
}

func CodeSearch(input CodeSearchInput) (string, error) {
    // 1. 检查ripgrep是否安装:exec.LookPath(“rg”)
    // 2. 构建命令参数,例如:rg -n “input.Pattern” input.Path
    // 3. 执行命令并捕获输出
}

测试场景

  • “搜索所有Go文件中包含 func main 的行。”
  • “在 src 目录下查找所有的 TODO 注释。”
  • “查找调用了 ReadFile 这个函数的所有地方。”

至此,一个功能完备的本地AI编程助手已经初具雏形。它可以看到你的项目结构,阅读和搜索代码,运行构建和测试命令,甚至直接修改文件。

6. 事件循环的完整实现与工具调度

前面我们分散地了解了各个工具。现在,让我们聚焦于将这些工具串联起来的“大脑”——事件循环的完整逻辑。这是整个智能体最核心的协调中枢。

6.1 核心循环逻辑详解

事件循环的伪代码可以概括如下,它清晰地展示了AI与工具交互的完整流程:

for {
    // 1. 获取用户输入
    userInput := getUserInput()

    // 2. 将用户输入追加到对话历史
    messages = append(messages, anthropic.Message{Role: “user”, Content: userInput})

    // 3. 进入“推理循环”,处理可能的多轮工具调用
    for {
        // 4. 调用Claude API,携带当前对话历史和所有工具定义
        apiResponse := client.CreateMessage(messages, tools)

        // 5. 分析Claude的响应
        for _, content := range apiResponse.Content {
            switch content.Type {
            case “text”:
                // 如果是纯文本,直接输出,并跳出本次推理循环
                fmt.Println(content.Text)
                break inferenceLoop
            case “tool_use”:
                // 如果是工具调用请求
                toolName := content.Name
                toolInput := content.Input

                // 6. 在本地工具集中查找对应的工具函数
                toolFunc := toolRegistry[toolName]

                // 7. 执行工具,并获取结果或错误
                toolResult, toolErr := toolFunc(toolInput)

                // 8. 将工具执行结果构建成一条“工具结果”消息,追加到对话历史
                //    这条消息的角色是“user”,但包含一个特殊的“tool_result”块
                messages = append(messages, anthropic.Message{
                    Role: “user”,
                    Content: []anthropic.ContentBlock{
                        {
                            Type: “tool_result”,
                            ToolUseID: content.ID, // 关联之前的工具调用请求
                            Content:  toolResult,
                            IsError:  (toolErr != nil),
                        },
                    },
                })
                // 注意:这里没有break,循环继续,将带着工具结果再次请求Claude
            }
        }
    }
    // 一轮完整的用户问答结束,回到最外层循环,等待下一个用户输入
}

6.2 工具注册与管理模式

如何优雅地管理越来越多的工具?一个常见的做法是使用一个 工具注册表 。在项目演进中,你可能会看到类似这样的设计:

// ToolRegistry 是一个全局映射,将工具名映射到其定义和执行函数
var ToolRegistry = map[string]ToolDef{}

func RegisterTool(name string, def ToolDef) {
    ToolRegistry[name] = def
}

// 在main函数初始化时注册所有工具
func init() {
    RegisterTool(“read_file”, ReadFileTool)
    RegisterTool(“list_files”, ListFilesTool)
    // … 注册其他工具
}

这样,在事件循环中,只需要通过 toolName 就能从 ToolRegistry 中快速找到对应的工具定义并执行。这种模式非常利于扩展,当你需要新增一个工具时,只需实现它并在 init 中注册即可。

7. 进阶技巧与实战心得

在完成了基础构建之后,我们可以探讨一些让这个智能体变得更强大、更实用的进阶思路和我在实践中总结的经验。

7.1 提升交互体验:上下文管理与会话持久化

目前的实现是单次会话,关闭程序后对话历史就丢失了。一个实用的智能体应该能记住之前的对话。

  • 实现思路 :可以将 messages 切片(即对话历史)序列化为JSON文件,在程序启动时加载,退出时保存。这样每次启动都能延续上次的对话。需要注意的是,Claude API有上下文长度限制,当历史消息过长时,需要设计策略来裁剪或总结旧的对话。

7.2 增强工具能力:链式调用与复杂规划

当前的工具调用是线性的(AI请求一个,我们执行一个,返回结果)。更智能的代理应该能处理更复杂的规划。

  • 场景示例 :用户请求“帮我修复这个文件中的拼写错误”。一个高级的智能体可能会:1) 调用 read_file 读取文件。2) 分析内容,找出可能的拼写错误。3) 对于每个错误,调用 edit_tool 进行修改(或者生成一个包含所有修改的新内容,一次性调用 edit_tool )。这要求AI具备多步规划和状态管理的能力。我们可以通过让Claude在单个回复中请求一个“工具调用序列”,或者在我们的循环中支持更复杂的逻辑来实现。

7.3 安全加固:生产环境部署须知

本教程侧重于教学,工具的安全性较为宽松。如果你计划将其用于实际工作,必须考虑以下加固措施:

  1. 命令执行沙箱 :对于 bash_tool ,考虑使用容器(如Docker)或系统级沙箱来隔离执行环境,限制其对主机系统的访问。
  2. 文件访问控制 :为 read_file edit_tool 设置工作根目录,禁止访问该目录之外的文件(使用 path/filepath 库的 Rel Clean 函数进行解析和校验)。
  3. 工具使用白名单 :可以设计一个配置,允许用户启用或禁用特定工具。例如,在服务器环境禁用 bash_tool edit_tool
  4. 操作确认 :对于高风险操作(如删除文件、覆盖重要文件),可以在执行前增加一个用户确认环节(例如,在终端输出“即将执行 rm -rf build/ ,确认请输入y”)。

7.4 性能与成本优化

  • 模型选择 :对于简单的文件查看、命令执行,使用 claude-3-haiku 足以胜任且成本低廉。对于复杂的代码重构或逻辑推理,再切换到 claude-3-sonnet opus
  • 流式响应 :Claude API支持流式传输。对于较长的回答,可以使用流式响应来提升用户体验,让答案逐字显示,而不是等待全部生成完毕。
  • 缓存 :对于频繁读取的、不常变化的文件(如项目配置文件),可以考虑在工具层增加简单的缓存机制,避免重复调用AI和读取磁盘。

8. 常见问题排查与调试指南

在构建和运行过程中,你可能会遇到一些问题。这里汇总了一些典型情况及解决方法。

8.1 API与网络相关问题

问题现象 可能原因 排查步骤
运行后无响应或超时 1. API密钥未设置或错误。
2. 网络无法访问Anthropic API。
1. 执行 echo $ANTHROPIC_API_KEY 确认密钥已加载且正确。
2. 尝试用 curl 命令测试API连通性。
3. 检查防火墙或代理设置。
返回权限错误或额度不足 1. API密钥无效或已禁用。
2. 账户余额或免费额度用尽。
1. 登录Anthropic控制台,检查密钥状态和用量。
响应速度极慢 1. 选择了较慢的模型(如Opus)。
2. 网络延迟高。
1. 在代码中暂时切换到 claude-3-haiku 模型测试。
2. 使用 --verbose 模式查看请求耗时。

8.2 Go编译与运行问题

问题现象 可能原因 排查步骤
go run 报错:找不到模块 1. 未在项目根目录执行。
2. 依赖下载失败。
1. 确认终端当前路径包含 go.mod 文件。
2. 运行 go mod tidy 重新下载依赖。
3. 设置国内Go代理: go env -w GOPROXY=https://goproxy.cn,direct
工具函数编译错误 1. 结构体标签或函数签名与库不匹配。
2. 使用了未导入的包。
1. 仔细对照教程检查 ToolDefinition 的字段名和类型。
2. 确保所有用到的包(如 os , fmt , exec )都已导入。

8.3 工具执行逻辑问题

问题现象 可能原因 排查步骤
AI不调用工具,总是直接回答 1. 工具定义未正确添加到API请求的 Tools 字段。
2. 工具描述不够清晰,AI不理解何时使用。
1. 使用 --verbose 模式,查看发送给API的请求JSON,确认 tools 数组存在且正确。
2. 优化工具的 Description 字段,用自然语言清晰说明工具的用途和适用场景。
工具调用失败,AI收到错误 1. 工具函数内部出错(如文件不存在)。
2. 输入参数格式与Schema不匹配。
1. 在工具函数内部增加详细的日志打印。
2. 查看 --verbose 输出中,AI发送的 tool_use 块里的 input ,是否与你定义的Go结构体匹配。
事件循环卡住或陷入死循环 1. AI连续返回 tool_use ,但工具结果未正确附加到历史消息。
2. tool_result 消息中的 ToolUseID 与请求不匹配。
1. 这是最难调试的问题。仔细检查事件循环中构建 tool_result 消息的代码,确保 ToolUseID 是从AI的 tool_use 响应中获取的 ID 字段。
2. 确保在每次工具调用后,都将结果追加到了 messages 切片,并且在下一次 client.CreateMessage 调用时传入了更新后的 messages

调试黄金法则 始终使用 --verbose 标志运行程序 。它会打印出所有发送和接收的原始JSON,这是诊断AI交互问题最直接有效的方法。你可以清晰地看到AI是否请求了工具、请求的参数是什么、工具返回的结果又是什么。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐