Golang 编写简单的 AI Agent Demo

✨ 文章介绍了一个使用 Go 语言实现的简单 AI Agent 示例,阐述了 AI Agent 的基本结构,即“循环 + AI(LLM) + 工具(Tools)”。文章详细说明了如何通过 Go 语言连接 OpenAI 的大型语言模型(LLM),以及如何定义和整合可供 AI 调用的工具函数(Function Calling),最终形成一个完整的 AI Agent 工作闭环。

AI Agent 并不复杂,粗略来说就是 循环 + AI(LLM) + 工具(Tools) 。

这里整理记录下之前用 Golang 写的 AI Agent Demo,可以当作一个最简的 AI Agent 示例,快速学习理解 AI Agent 的基本结构。

AI 的发展,有时候会让人觉得这些东西没必要再写了,毕竟有啥都可以直接问 AI ,回答又快又好。但 AI 的毕竟是 AI 的,自己动手整理记录的才能算是自己的。

创建 Go 项目

mkdir example-agent
cd example-agent
go mod init example-agent
touch main.go

循环输入输出

// main.go
package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	// 创建一个读取器,用来读取用户的输入
	reader := bufio.NewReader(os.Stdin)

	for {
		fmt.Print("You: ")

		// 读取用户输入,直到遇到换行符
		input, _ := reader.ReadString('\n')

		// 如果用户输入 "exit" 或 "quit",就退出循环
		if strings.TrimSpace(input) == "exit" || strings.TrimSpace(input) == "quit" {
			fmt.Println("再见!")
			break
		}

		fmt.Println("Agent: 你说的是:", input)
	}
}

连接 AI (LLM)

这里使用 OpenAI 的 SDK 来连接 AI(LLM) 。

注意: 需要一个 OpenAI API Key。或者使用第三方的 API 代理服务,修改 API 的 BaseURL 。

# 这个 star 多,就不用 OpenAI 官方 SDK 了
go get github.com/sashabaranov/go-openai
// main.go
package main

import (
	"bufio"
	"context"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/sashabaranov/go-openai"
)

func main() {
	// --- 初始化 AI 客户端 ---
	// 实际使用时,请务必使用安全的方式管理你的 API Key
	config := openai.DefaultConfig(os.Getenv("API_KEY"))
	// 使用第三方 API 代理,需设置 BaseURL
	config.BaseURL = "http://localhost:8080/v1"
	client := openai.NewClientWithConfig(config)

	// 创建一个列表来存储整个对话的上下文(历史消息)
	messages := []openai.ChatCompletionMessage{}

	reader := bufio.NewReader(os.Stdin)

	for {
		fmt.Print("You: ")
		input, _ := reader.ReadString('\n')
		if strings.TrimSpace(input) == "exit" || strings.TrimSpace(input) == "quit" {
			fmt.Println("再见!")
			break
		}

		// 将用户的消息添加到对话历史中
		messages = append(messages, openai.ChatCompletionMessage{
			Role:    openai.ChatMessageRoleUser,
			Content: input,
		})

		// --- 调用 LLM API ---
		resp, err := client.CreateChatCompletion(
			context.Background(),
			openai.ChatCompletionRequest{
				Model:    openai.GPT4Turbo,
				Messages: messages,
			},
		)

		if err != nil {
			log.Printf("ChatCompletion error: %v\n", err)
			continue
		}

		aiResponse := resp.Choices[0].Message

		// 将 AI 的回复也添加到对话历史中
		messages = append(messages, aiResponse)
	}
}

Agent 工具(Tools)

如下这些函数就是 AI Agent 可调用的工具(Tools),也就是 Function Calling 。

// ... 其他代码 ...

/*
readFile 读取指定文件的内容
*/
func readFile(filename string) (string, error) {
	data, err := os.ReadFile(filename)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

/*
listFiles 列出指定目录中的所有文件和子目录
*/
func listFiles(path string) (string, error) {
	files, err := os.ReadDir(path)
	if err != nil {
		return "", err
	}

	var fileNames []string
	for _, file := range files {
		fileNames = append(fileNames, file.Name())
	}

	return fmt.Sprintf("%v", fileNames), nil
}

/*
editFile 编辑文件内容,通过字符串替换的方式
*/
func editFile(filename, old, new string) error {
	data, err := os.ReadFile(filename)
	if err != nil {
		return err
	}

	newData := strings.Replace(string(data), old, new, -1)

	return os.WriteFile(filename, []byte(newData), 0644)
}

// ... main 函数 ...

整合调用

有了以上工具函数后,接下来在 client.CreateChatCompletion 的请求中,将这些可用工具信息发给 AI。

// 在 for 循环中
resp, err := client.CreateChatCompletion(
	context.Background(),
	openai.ChatCompletionRequest{
		Model:    openai.GPT4Turbo,
		Messages: messages,

		// 定义可用工具(Function Calling)告诉 AI 它可以调用哪些函数
		Tools: []openai.Tool{
			{
				Type: openai.ToolTypeFunction,
				Function: &openai.FunctionDefinition{
					Name:        "read_file",
					Description: "Reads the content of a file.",
					// JSON Schema 定义参数格式
					Parameters: json.RawMessage(`{
						"type": "object",
						"properties": {
							"filename": {
								"type": "string",
								"description": "The path to the file to read"
							}
						},
						"required": ["filename"]
					}`),
				},
			},
			{
				Type: openai.ToolTypeFunction,
				Function: &openai.FunctionDefinition{
					Name:        "list_files",
					Description: "Lists files in a directory.",
					Parameters: json.RawMessage(`{
						"type": "object",
						"properties": {
							"path": {
								"type": "string",
								"description": "The directory path to list files from"
							}
						},
						"required": ["path"]
					}`),
				},
			},
			{
				Type: openai.ToolTypeFunction,
				Function: &openai.FunctionDefinition{
					Name:        "edit_file",
					Description: "Edits a file by replacing a string.",
					Parameters: json.RawMessage(`{
						"type": "object",
						"properties": {
							"filename": {
								"type": "string",
								"description": "The path to the file to edit"
							},
							"old": {
								"type": "string",
								"description": "The string to be replaced"
							},
							"new": {
								"type": "string",
								"description": "The new string to replace with"
							}
						},
						"required": ["filename", "old", "new"]
					}`),
				},
			},
		},
	},
)

这样 AI 就知道了有 read_file、list_files、edit_file 这些工具了,当 AI 处理用户输入后决定使用这些工具时,其回复中会包含一个 ToolCalls 字段,根据 ToolCalls 中的信息来执行相应的工具函数,再将结果一起发送给 AI ,由 AI 根据工具执行结果生成最终响应。

// ... 其他代码 ...

func main() {
  // ...

  for {
      // ...

    messages = append(messages, openai.ChatCompletionMessage{
        Role:    openai.ChatMessageRoleUser,
        Content: input,
    })

    resp, err := client.CreateChatCompletion(
        // ... (请求信息,包含 list_files 等工具) ...
    )

    // ... (错误处理) ...

    message := resp.Choices[0].Message
    messages = append(messages, message)

    // --- 检查 AI 是否要调用工具 ---
    if message.ToolCalls != nil {
      fmt.Println("Agent: 正在调用工具...")
      for _, toolCall := range message.ToolCalls {
        var result string // 存储工具执行结果
        var err error

        // 根据工具名称执行相应的函数
        switch toolCall.Function.Name {
          case "read_file":
            var args struct {
              Filename string `json:"filename"`
            }
            json.Unmarshal([]byte(toolCall.Function.Arguments), &args)

            result, err = readFile(args.Filename)

          case "list_files":
            var args struct {
              Path string `json:"path"`
            }
            json.Unmarshal([]byte(toolCall.Function.Arguments), &args)

            result, err = listFiles(args.Path)

          case "edit_file":
            var args struct {
              Filename string `json:"filename"`
              Old      string `json:"old"`
              New      string `json:"new"`
            }
            json.Unmarshal([]byte(toolCall.Function.Arguments), &args)

            err = editFile(args.Filename, args.Old, args.New)
            if err == nil {
              result = "File edited successfully."
            }
        }

        if err != nil {
          result = err.Error()
        }

        // 将工具执行结果添加到历史消息
        messages = append(messages, openai.ChatCompletionMessage{
          Role:       openai.ChatMessageRoleTool,
          Content:    result,                     
          Name:       toolCall.Function.Name,     
          ToolCallID: toolCall.ID,               
        })
      }
      // 发送给 AI ,根据工具执行结果生成最终回复
      resp, err = client.CreateChatCompletion(
        context.Background(),
        openai.ChatCompletionRequest{
          Model:    openai.GPT4Turbo,
          Messages: messages, 
        },
      )

      if err != nil {
        log.Printf("Tool Result ChatCompletion error: %v\n", err)
        continue
      }

      // 显示 AI 的最终回复
      fmt.Println("Agent:", resp.Choices[0].Message.Content)

      // 将最终回复也添加到历史消息
      messages = append(messages, resp.Choices[0].Message)

    } else {
      // 如果 AI 没有调用工具,直接显示回复 
      fmt.Println("Agent:", message.Content)
    }
  }
}

以上整合后就形成了一个简单完整的 AI Agent 闭环:

用户提问 -> AI 响应 -> 工具调用 -> 执行函数 -> 将结果再发给 AI -> AI 生成最终回复