找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

559

积分

0

好友

77

主题
发表于 3 天前 | 查看: 13| 回复: 0

AI Agent = 大模型 + 工具 + 控制循环(Agent Loop)

简单来说,就是让大语言模型不仅能回答问题,还能“动手”帮你做事,例如调用代码执行工具、读取/写入文件、运行测试或执行联网搜索。本文将带你使用 Golang 手把手实现这套底层逻辑,构建一个可本地离线运行、并能连接 DeepSeek-R1 与 Ollama 的 AI 编程助手。

1. 核心项目结构(MVP)

以下是本项目的简约目录结构:

easy-agent/
├── main.go
├── agent/
│   ├── agent.go
│   ├── agency.go
│   ├── memory.go
│   ├── ollama_client.go
│   └── tools.go
└── client/
    └── index.html
  • agent/:AI Agent 的核心逻辑层。
  • client/:一个基于 WebSocket 的简单前端,用于测试交互。
  • ollama_client.go:负责与本地模型服务通信。
  • agent.go:实现 Agent 主循环(处理 Function Calling)。
  • tools.go:定义并执行各类工具。
  • memory.go:实现简单的记忆与上下文管理系统。

2. 基础通信:消息格式与 Function Calling

大模型与 Agent 之间通过消息(messages)进行沟通,基本格式如下:

{ "role": "user", "content": "帮我解释下面代码" }
{ "role": "assistant", "content": "…" }
{ "role": "tool", "name": "run_code", "content": "执行结果…" }

当模型决定要执行某个工具时,会返回包含 function_call 的响应:

{
  "message": {
    "role": "assistant",
    "function_call": {
      "name": "run_code",
      "arguments": "{\"language\":\"go\",\"code\":\"...\"}"
    }
  }
}

Agent 的核心工作流程即为:

  1. 解析模型的 function_call
  2. 执行对应的工具(例如运行一段代码)。
  3. 将工具执行结果以 role: tool 的消息追加回对话历史。
  4. 再次调用模型,提供包含工具结果的上下文。
  5. 循环此过程,直到模型给出最终的自然语言回答。

这个流程就是 Agent Loop 的灵魂。

3. 实现 Ollama 客户端

文件:agent/ollama_client.go 首先,我们定义一个客户端结构体,用于管理与 Ollama 服务的连接。

type OllamaClient struct {
    Endpoint string
    Client   *http.Client
    Model    string
}

func NewOllamaClient(endpoint string, timeout time.Duration, model string) *OllamaClient {
    return &OllamaClient{
        Endpoint: endpoint,
        Client:   &http.Client{Timeout: timeout},
        Model:    model,
    }
}

接下来实现核心的调用方法,该方法发送请求并处理响应,支持传入工具定义。

func (c *OllamaClient) Call(ctx context.Context, messages []ChatMessage, tools any) (*ChatResponse, error) {
    reqBody := ChatRequest{
        Model:      c.Model,
        Messages:   messages,
        Tools:      tools,
        ToolChoice: "auto",
    }
    b, _ := json.Marshal(reqBody)
    req, _ := http.NewRequestWithContext(ctx, "POST", c.Endpoint, bytes.NewReader(b))
    req.Header.Set("Content-Type", "application/json")
    resp, err := c.Client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    var cr ChatResponse
    json.NewDecoder(resp.Body).Decode(&cr)
    return &cr, nil
}

至此,我们已拥有一个可调用 DeepSeek、Qwen 或 LLaMA 等模型的 Golang 客户端。

4. 实现 Agent 主循环

文件:agent/agency.go (示意代码,详情见完整源码) Agent Loop 是协调模型思考与工具执行的核心控制器。

func (a *Agent) Run(prompt string) (string, error) {
    messages := []ChatMessage{
        {Role: "system", Content: "你是 AI 编程伙伴,遇到需要执行的任务请调用工具"},
        {Role: "user", Content: prompt},
    }
    for i := 0; i < 6; i++ { // 限制循环次数,防止无限循环
        cr, _ := a.client.Call(context.Background(), messages, toolsMetadata())
        msg := cr.Choices[0].Message
        // 判断模型是否调用了工具
        if msg.FunctionCall != nil {
            messages = append(messages, ChatMessage{
                Role: msg.Role, Name: msg.FunctionCall.Name,
            })
            result := a.execTool(msg.FunctionCall) // 执行工具
            messages = append(messages, ChatMessage{
                Role: "tool", Name: msg.FunctionCall.Name, Content: result,
            })
            continue // 继续循环,将工具结果反馈给模型
        }
        return msg.Content, nil // 模型返回最终答案,循环结束
    }
    return "", fmt.Errorf("loop limit")
}

循环步骤解析:

  1. 初始化消息,包含系统指令和用户问题。
  2. 调用模型,让其进行思考。
  3. 若模型返回工具调用请求,则执行对应工具,并将结果作为新消息追加。
  4. 再次调用模型,此时模型已获得工具执行结果,可进行下一步推理或给出答案。
  5. 重复步骤3-4,直至模型输出最终结论。

5. 构建可扩展的工具系统

文件:tools.go 工具系统是 Agent 能力的体现。每个工具都是一个独立的函数。

func (a *Agent) execTool(fc *FunctionCall) string {
    switch fc.Name {
    case "run_code":
        return RunCodeSandbox(args)
    case "read_file":
        return ReadFile(args)
    case "web_search":
        return WebSearch(args)
    default:
        return "unknown tool"
    }
}

为了让模型知道有哪些工具可用,必须向其提供工具的定义(Schema):

func toolsMetadata() any {
    return []map[string]any{
        {
            "type": "function",
            "function": map[string]any{
                "name": "web_search",
                "description": "联网搜索相关信息",
                "parameters": map[string]any{
                    "type": "object",
                    "properties": map[string]any{
                        "query": {"type": "string"},
                        "num_results": {"type": "integer"},
                    },
                    "required": []string{"query"},
                },
            },
        },
        // ... 可以在此处添加更多工具定义
    }
}

模型之所以能自动调用工具,正是因为你通过 Schema 告诉了它工具的名称、描述和参数格式。 这涉及到如何高效地管理和交互数据,是构建健壮后端系统的重要一环。

6. 为 Agent 添加联网搜索能力

例如,我们可以为 Agent 增加一个 web_search 工具,使其具备从互联网获取信息并进行分析的能力。 工具函数伪代码如下:

func WebSearch(args WebSearchArgs) ([]SearchResult, error) {
    // 此处可集成 SerpAPI、DuckDuckGo 或 Bing Search API
    // 实现网络请求、页面抓取与结果解析
    return results, nil
}

定义后,模型在需要时便会自动生成如下调用:

{
  "function_call": {
    "name": "web_search",
    "arguments": "{\"query\":\"Go map race condition\", \"num_results\":3}"
  }
}

Agent 执行搜索后,将结果返回给模型,模型便能总结信息并输出智能结论,这正是 AI Agent 多步推理能力的体现。

7. 运行与调试:WebSocket 前端界面

项目已包含一个简单的 client/index.html 前端页面。 运行服务端:

go run main.go

然后在浏览器中打开页面,即可通过 WebSocket 与 Agent 进行实时、流式的对话交互。前端界面能直观展示 Agent 的思考过程、工具调用及最终回复,极大方便了开发和调试。 Agent 前端调试界面 流式输出演示

8. 总结与展望

本文完成了从零搭建一个最小可用(MVP)的本地 AI Agent,它基于 Golang,整合了 Ollama 与 DeepSeek 模型,并实现了基础的 Function Calling 与工具执行循环。这只是构建强大个人助手的起点。在 人工智能 应用蓬勃发展的今天,一个可扩展的 Agent 框架未来可以集成代码自动生成、性能分析与优化、复杂任务规划等高级能力,成为开发者真正的智能协作伙伴。

项目源码




上一篇:C/C++预处理器#if 0实战指南:临时调试与版本控制的高效实践
下一篇:IP地址定位精度与隐私安全解析:能否通过IP找到具体住址?
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-9 01:07 , Processed in 0.095384 second(s), 37 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表