返回
查看原链接原链接
Bilibili24分31秒 · —

使用 Rust 构建 AI Agent(二):大语言模型的选型、调用与核心概念

使用 Rust 构建 AI Agent(二):大语言模型的选型、调用与核心概念

大语言模型是 AI Agent 的决策大脑,其核心价值在于理解意图、规划步骤和迭代推理;开发者必须确保所选模型具备工具调用、结构化输出和长上下文三大能力,并通过统一调用层保持模型可替换性。

本笔记整理自“使用 Rust 构建 AI Agent”系列视频的第二期。上一期介绍了 AI Agent 的整体架构——一个能够自主感知、规划并执行任务的程序,而非单纯的聊天机器人。本期深入探讨 Agent 最核心的部件——大语言模型(LLM),围绕三个关键问题展开:应该选择哪个大语言模型、如何调用它、它能做什么不能做什么。


大语言模型在 Agent 架构中的角色

整体流程回顾

在 AI Agent 架构中,大语言模型扮演的是决策大脑的角色。整个运行流程如下:

  1. 用户用自然语言发出一个请求(以提示词形式),发送至大语言模型
  2. 大语言模型负责理解请求规划出要执行的步骤,并决定调用哪些工具(即工具调用环节)
  3. 工具执行完毕后,结果返回到大语言模型
  4. 大语言模型综合工具调用的结果以及其他信息,决定下一步怎么做
  5. 最终给出一个答案或输出

大语言模型在 Agent 中做的三件事

作用说明
理解意图解析用户的自然语言请求,弄清用户真正想要什么
规划步骤将复杂任务拆解为可执行的步骤序列
迭代推理根据中间结果动态调整策略,持续推理直至任务完成

关键概念:Agent 循环

需要特别强调的一点是:大语言模型需要根据工具返回的中间结果动态调整策略,而不是一次性生成答案就结束。这就是所谓的“Agent 循环”——一直循环执行,直到整个任务完成。

因此,所选大语言模型的能力越强,Agent 就越可靠。模型能力是整个 Agent 项目的基本前提。

大语言模型在 Agent 中能做什么、不能做什么

能做的:

  • 理解自然语言请求的意图
  • 规划多步执行策略
  • 决定调用哪些工具及传什么参数
  • 基于工具返回结果进行迭代推理
  • 给出最终答案或输出

不能做的(本视频指出的核心局限):

  • 大语言模型本身没有记忆——API 是无状态的(后续详述),每次调用完全独立,模型不会记住上次说了什么
  • 大语言模型默认输出自然语言,对程序不友好,程序无法直接处理,因此需要结构化输出能力
  • 不能自行执行工具操作——工具调用只是选择调用哪个工具和传参,实际执行仍需外部系统完成
  • 上下文窗口有限——如果上下文窗口太小,Agent 就无法完成复杂的多步任务(因为大量中间信息需要放在上下文内供模型推理)

大语言模型的两大阵营:闭源与开源

闭源大语言模型

代表模型: OpenAI 的 GPT 系列、Anthropic 的 Claude 系列

特点:

  • 推理能力强,属于最强的一系列模型
  • 只能通过 API 访问,拿不到模型权重和参数,不能本地运行
  • 开箱即用,能力最强

开源大语言模型

代表模型: DeepSeek、阿里千问(Qwen)、Meta 的 Llama 系列、法国的 Mistral(原文发音为“Mr”,课程中推测指 Mistral)

特点:

  • 权重可以下载
  • 有条件的话可以本地部署和微调
  • 自主可控,不依赖外部 API
  • 成本可以优化

选型建议:先闭源验证,再按需迁移

两种路线各有优势。课程建议的实践路径是:

  1. 先采用闭源大语言模型——快速验证 Agent 的设计是否可靠
  2. 等设计稳定之后,再按需考虑迁移到开源模型
  3. 不需要一开始就做这个决定,最重要的是先把 Agent 做起来

值得注意:无论选择闭源还是开源的模型,在开发初期都是通过 API 来访问的,包括开源模型也是走 API 方式。


开发 Agent 的三大硬性模型能力要求

无论选择哪类大语言模型,做 Agent 开发时必须保证模型具备以下三种能力,缺一不可

1. 工具调用能力(Tool Calling / Function Calling)

为什么重要: Agent 的核心价值在于能够采取行动、与外部世界交互,比如搜索网页、执行代码、读写文件。

具体要求: 大语言模型必须有能力准确选择调用哪个工具,并且正确传递参数。没有这个能力,Agent 就只是一个聊天机器人。

2. 结构化输出能力(Structured Output)

为什么重要: 大语言模型默认输出的全是自然语言,对人类友好,但对程序不友好,程序无法直接处理。

具体要求: Agent 需要把大语言模型的决策转化为机器能执行的命令,因此模型要能严格按照 JSON 格式输出,而不能只是返回自然语言。结构化输出是程序与程序之间沟通的语言,这是构建 Agent 时衔接模型与执行逻辑的关键能力。

3. 较长上下文窗口(Long Context)

为什么重要: 随着 Agent 的运行,它会积累大量信息,包括:历史对话、工具反馈的结果、检索到的文档等。这些都需要放在上下文中,让模型能够做推理。

具体要求: 如果上下文窗口太小,Agent 就没法完成复杂的多步任务。所选模型必须拥有足够大的上下文窗口来容纳这些信息并进行推理。

选型操作建议: 选择模型时,这三种能力必须逐个对照检查,满足后再看其他因素——这是硬性要求。


与模型无关的统一调用层设计

设计动机

大语言模型和整个 AI 领域的市场变化极快

  • 每个月都有新模型发布
  • 排行榜频繁更新
  • 定价也在不断变化
  • 今天最优的选择可能到明天或下个月就过时了

因此从项目一开始,就要把模型可替换性设计进架构中。

设计思路

引入一个统一的调用层:Agent 的业务逻辑只依赖于抽象接口,不直接绑定任何具体的 SDK 或模型。

一个有利的事实是:主流大语言模型的 API 基本都兼容 OpenAI 的消息格式。因此实现统一接口是完全可行的,不需要为每个 provider(提供商)编写差异很大的适配代码。

需要注意的细节: 尽管大部分大语言模型提供商的接口都兼容 OpenAI,但某些细节上仍然存在差异。针对某些具体模型,可能仍需要编写少量定制化代码来处理这些差异(视频中表示后续会进一步讨论)。

模型可替换的具体示例

在实际演示中可以观察到:同一个函数代码,只更换模型名称参数,即可调用不同提供商的模型(如 OpenAI 的 GPT 系列和 NVIDIA 的模型)。如果直接使用 Anthropic 的 API 而不走 OpenRouter 中转,则可能需要手动判断 API 类型,编写适配代码。若使用 Python 生态,则有现成库支持上百种模型,但 Rust 教程中通过 OpenRouter 中转来规避这一问题。


项目建立与环境配置

创建 Rust 项目

在命令行创建一个二进制(bin)项目,命名为 ai-agent(原文为“a agent”),然后在 VS Code 中打开。

安装依赖

需要安装以下 Rust 库:

  • ai-sir(原文如此,从上下文推断指相关 Rust AI 库)
  • serde/serde_json
  • tokio(含 subscriber 相关功能,用于异步运行时)
  • dotenv(处理环境变量)
  • tracing(日志追踪)
  • `async-openai`:最主要的库,一个非官方的针对 OpenAI API 的 Rust 库

配置环境变量

在项目根目录创建 .env 文件,存放以下环境变量:

`OPENAI_BASE_URL`

  • 视频中使用的是 OpenRouter(openrouter.ai)作为中转
  • 如果使用其他提供商(如 DeepSeek),需要查阅其官网查兼容 OpenAI API 的 base URL 并填入

`OPENAI_API_KEY`

  • 若使用 OpenRouter,需要登录账户,在 API Keys 菜单中创建新的 key 并复制粘贴

dotenv 的工作原理

dotenv 库会查找 .env 文件,先从当前目录找,如果没找到就往上逐级查找。调用 dotenv::dotenv() 函数加载环境变量后,程序内即可通过 std::env 读取这些变量。

// 示例:从代码片段推断的加载方式
dotenv::dotenv().ok();
// 随后即可通过环境变量读取配置

验证环境变量加载

加载后运行程序,看到环境变量成功打印输出,确认加载无误。课程中演示时还修正了代码中一个拼写错误。


调用大语言模型的代码实现

创建模块结构

创建模块目录结构:

  • llm/ 文件夹
  • llm/complete.rs 文件——调用大模型的函数

实现 chat_complete 函数

创建 chat_complete 函数,使用 async-openai 库。该函数允许用户发送提示词给大模型,支持两个关键参数:模型选择系统人设(系统提示词)

初始化客户端

需要创建 async-openaiClient。创建时发现没有类型提示,说明缺少 feature 配置——需要在 Cargo.toml 中启用 chat-completion(原文为 try-completion,实际应为 chat-completion)等相应 feature。

构建消息列表

函数内部构建消息(messages),支持两条消息:

系统消息(仅当传入 system 参数时):

  • 类型为 ChatCompletionRequestSystemMessage
  • 设置 content 为传入的 system 内容
  • 使用 build() 后通过 .into() 转换为所需类型

用户消息:

  • 类型为 ChatCompletionRequestUserMessage
  • 设置 content 为传入的 prompt 内容
  • 同样 build().into() 转换
// 示意结构(根据视频讲解整理)
if let Some(system) = system {
    messages.push(
        ChatCompletionRequestSystemMessage::default()
            .content(system.to_string())
            .build()
            .into(),
    );
}

messages.push(
    ChatCompletionRequestUserMessage::default()
        .content(prompt.to_string())
        .build()
        .into(),
);

构建并发送请求

let request = CreateChatCompletionRequest::default()
    .model(model.to_string())
    .messages(messages)
    .max_tokens(2048u32)  // 限制生成的最大 token 数
    .build()?;

let response = client.chat().create(request).await?;

解析响应内容

发送请求后返回 response,其中包含多个字段。首先通过 tracing 打印响应结构以了解返回格式,然后提取核心内容:

  1. 选择 choices 字段中的第一个元素
  2. 取其内部的 message(类型为 ChatCompletionResponseMessage
  3. 提取其中的 content 字段
// 参考实现逻辑(基于讲解推断整理)
let content = response
    .choices
    .into_iter()
    .next()
    .and_then(|choice| choice.message.content)
    .ok_or_else(|| anyhow::anyhow!("no content in response"))?;

Ok(content)

如果没有值则报错,使用 anyhow! 宏输出错误信息“no content in response”。最后返回内容字符串。

实际调用测试

main.rs 中调用 chat_complete

  1. 选择模型——在 OpenRouter 中挑选模型 ID。视频中测试了两个模型,最终选用 OpenAI GPT-4o(视频演示用的是一款 GPT o3 120b 之类的免费模型),并测试了 NVIDIA 模型但因其响应太慢而放弃
  2. 设置系统提示词为“一个全能的助手”
  3. 设置用户提示词为“爱尔兰的首都是哪里?”

测试结果:

  • 返回内容正确:“爱尔兰的首都是都柏林”
  • 输出中可见响应结构,包括:id、choices 数组、message 对象及 content、created 时间戳、model 名称、object 类型、usage 使用情况(prompt tokens 85 个、completion tokens 32 个、共 117 tokens)以及 token 明细信息

换用另一个问题“1988年中国国内足球冠军是谁?”测试,模型回复“1988年中国足球最高级别联赛冠军是辽宁足球队”,回答正确。再切换不同模型也能返回结果,但等待时间较长,由于体验不友好便切换回来了。

关于不同 API 的兼容性处理说明

async-openai 的代码支持 OpenAI API 标准,配合 OpenRouter 中转也可以支持 Anthropic 的模型。但若不使用 OpenRouter 中转而直接使用 Anthropic 的 API,现有代码可能需要进行加工,需要手动判断是哪种 API 类型(OpenAI 格式还是 Anthropic 格式)。Python 生态中有现成的库支持上百种模型,但本教程为了演示核心原理,选择通过 OpenRouter 中转的方式,把所有模型统一成 OpenAI API 格式来调用。


OpenRouter 简介

OpenRouter 是一个统一的 AI API 网关

  • 通过统一接口提供对几百种模型的访问,涵盖多个提供商
  • 开发者无需分别应对 OpenAI 的 API、Anthropic 的 API 等,只需访问一处
  • 完全兼容 OpenAI 的标准
  • 开发时可通过其界面搜索模型(如搜索 free 可找到免费模型)

大语言模型 API 的无状态性——最容易踩的坑

核心概念:LLM API 是无状态的

大语言模型的 API 是无状态(stateless)的,这是最重要且容易被忽视的概念之一:

  • 每次 API 调用完全独立
  • 每次调用都没有上下文记忆
  • 模型不会记住你上次说了什么

示例: 第一次调用时说“我叫李明,是一名 Rust 开发者”,第二次调用时模型对此一无所知

为什么 ChatGPT 网页版看起来有记忆

使用 ChatGPT 网页版时感觉它有记忆,是因为网页应用在背后帮你维护了对话历史。每次调用时,网页应用都会将完整的对话历史发给 API。

自主开发时的责任

自己开发 Agent 时,记忆功能需要由开发者自己实现

  • 把每一轮的用户消息和模型的回复都存起来
  • 每次调用 API 时都把完整的历史一起发过去

对 Agent 开发的特别重要性

在 Agent 开发中,上下文管理尤为重要。“历史”不只是普通的对话,还包括:

  • 工具调用的结果
  • 中间的推理步骤
  • 检索到的文档

这些都需要管理好并放进上下文中,模型才能做出合理的下一步决策。

代价与后续话题

随着对话变长,消息历史会占用越来越多的 token,这会带来:

  • 成本增加
  • 响应变慢

如何高效管理上下文,视频表示将在后续专门讨论。


消息历史的数据结构

基本结构

“消息历史”在 chat_complete 函数中已涉及——消息历史就是一个有序的消息列表。每条消息有两个关键字段:

  1. 角色(role)
  2. 内容(content)

三种角色详解

表:消息历史中的三种角色

角色对应英文写入者作用与特点
系统提示system开发者预先写好,贯穿整个对话始终。给大模型设定人设、说明能做什么不能做什么。是开发者能掌控的最重要的工具
用户user用户用户输入、提示词。每轮对话不同,无法预测也无法控制
助手assistant模型模型的回复。回复后也要追加到历史中

必须记住的关键点

模型回复也要追加到历史中——这是很多人容易忽略的地方。因为模型并没有记忆,如果不把模型的回复也存起来,那么它下一次就不知道自己之前说了什么。每次调用时都需要把完整列表发送过去,模型才能理解完整的对话上下文并给出连贯的答案。

后续预告

视频提到系统提示词(system prompt)对 Agent 的行为影响非常大,后续将专门讲解。


当前阶段的技术栈总结

本视频展示的技术选型决策:

维度选择原因/说明
模型阵营暂用闭源(OpenAI GPT 系列)快速验证设计;后续可迁移开源
模型访问方式OpenRouter 中转统一 OpenAI 格式 API,免去多提供商适配
Rust 库async-openai非官方 OpenAI API Rust 库,支持 chat completion
模型输出提取手动解析 choices[0].message.content进入 Agent 后再用结构化输出
模型可替换统一调用层 + 环境变量应对快速变化的模型市场
记忆管理尚未实现(下节讲解)API 无状态,需自行管理消息历史

限制与待确认问题

以下为本视频中明确提及尚未解决或将在后续讲解的话题,均直接保留原文信息:

  • [ ] 上下文的高效管理:随着对话变长,消息历史消耗 token 增加、成本上升、响应变慢,如何高效管理上下文——后续专门讨论
  • [ ] 针对特定模型的细节适配:不同提供商虽大都兼容 OpenAI API,但某些细节不同,可能需要少量定制化代码——后续讨论
  • [ ] 系统提示词(system prompt)的系统讲解:对 Agent 行为影响非常大——后续专门讲解
  • [ ] 工具调用的完整实现:本视频中完成的调用函数尚未涉及工具调用与结构化输出接入,仍在后续内容中展开
  • [ ] tracing 日志库的使用:本视频使用其输出,但表示会有单独视频介绍
  • [ ] Agent 循环的代码落地:本视频阐述了概念但尚未演示完整 Agent 循环的实现

核心要义回顾

  1. LLM 是 Agent 的“决策大脑”,任务是理解意图、规划步骤和迭代推理。模型能力越强,Agent 越可靠——它是 Agent 项目的基本前提。
  1. 模型选择三大硬性要求:工具调用(Tool Calling/Function Calling)、结构化输出(Structured Output)、较长的上下文窗口(Long Context),缺一不可,否则 Agent 无法正常运作。
  1. 先闭源快速验证,后按需迁移开源——闭源模型走 API 访问、能力开箱即用;开源模型可下载权重、本地部署微调、成本可控。初期不需要做最终选型决定。
  1. 设计模型无关的统一调用层——市场瞬息万变,今天的最优解明天就过期。主流模型 API 基本兼容 OpenAI 格式,统一接口是可行的。
  1. LLM API 是无状态的——每次调用完全独立,模型没有记忆。自己开发 Agent 时必须自行管理消息历史(用户消息和助手回复),每次完整发送。Agent 的“历史”还包含工具结果、推理步骤、检索文档等。
  1. 三种消息角色:system(开发者预设人设)、user(用户输入)、assistant(模型回复也须纳入历史)。系统提示词是开发者能掌控的最重要的工具。
  1. 基础调用代码可通过 async-openai 实现,通过 OpenRouter 中转可同时支持多种模型,而无需针对每家大模型编写各自的适配代码。

下一步行动清单(基于视频预告)

  1. 测试:将 chat_complete 函数接入一个汇总对话历史的简单循环,查看模型能否理解和引用前文(注意:当前调用函数中处理消息长度有限,仅支持两条消息,若需连续多轮需扩展消息列表结构)——但视频尚未展开实现这部分,需等待或自行探究
  2. 准备后续: 理解 Agent 中最重要的上下文管理机制、系统提示词设计和工具调用的实际集成方式。课程明确这些话题将在后续内容单独讲解