程序员进阶必备:用Rust玩转AI,打造智能航班搜索工具!

  • King
  • 更新于 14小时前
  • 阅读 55

简介你是否曾经与Siri、Alexa等AI助手聊天,或者使用过那些帮助你预订航班或查询天气的智能聊天机器人?你是否好奇这些助手背后的工作原理?今天,我们将通过使用Rust和Rig库构建一个属于自己的航班搜索AI助手,来揭开这些技术的神秘面纱。你可能会想:“等等,Rust?那不是以难学著称的语言吗

简介

你是否曾经与Siri、Alexa等AI助手聊天,或者使用过那些帮助你预订航班或查询天气的智能聊天机器人?你是否好奇这些助手背后的工作原理?今天,我们将通过使用Rust和Rig库构建一个属于自己的航班搜索AI助手,来揭开这些技术的神秘面纱。

你可能会想:“等等,Rust?那不是以难学著称的语言吗?”别担心!我们将一步步带你走过整个过程,并解释每个概念。到最后,你不仅会拥有一个酷炫的AI助手,还会对Rust编程有初步的了解。

以下是我们的计划:

  1. 为什么选择Rust和Rig? 了解我们选择的工具。
  2. 环境设置:准备好Rust和Rig。
  3. 理解代理和工具:我们助手的“大脑”和“手”。
  4. 构建航班搜索工具:实现核心功能。
  5. 创建AI代理:让我们的助手“活”起来。
  6. 运行和测试:看看我们的成果。
  7. 总结:回顾和下一步计划。

本项目的完整源代码可以在我们的Replit页面Github上找到。

听起来很激动人心吗?让我们开始吧!


为什么选择Rust和Rig?

为什么选择Rust?

Rust是一种系统编程语言,以其高性能和安全性著称。但除此之外,Rust在Web开发、游戏开发以及现在的AI应用领域也崭露头角。以下是我们选择Rust的原因:

  • 性能:Rust速度极快,非常适合需要快速处理数据的应用。
  • 安全性:通过严格的编译器检查,Rust确保内存安全,防止常见错误。
  • 并发性:Rust使编写并发程序变得更容易,非常适合同时处理多个任务。了解更多关于Rust的并发模型。

为什么选择Rig?

Rig是一个开源的Rust库,简化了使用大型语言模型(LLM,如GPT-4、DeepSeek)构建应用程序的过程。你可以将Rig视为一个工具包,它提供了:

  • 统一的API:抽象了不同LLM提供商的复杂性。
  • 高级抽象:帮助你构建代理和工具,而无需从头开始。
  • 可扩展性:你可以创建适合你应用需求的自定义工具。

通过结合Rust和Rig,我们能够构建一个强大、高效且智能的助手。


环境设置

在开始编码之前,我们需要准备好一切。

先决条件

  1. 安装Rust:如果尚未安装Rust,请按照这里的说明进行安装。
  2. 基本的Rust知识:如果你是新手,别担心,我们会在过程中解释Rust的概念。
  3. API密钥
    • OpenAI API密钥:注册并获取你的密钥这里
    • RapidAPI密钥:我们将使用它来访问TripAdvisor的航班搜索API。获取密钥这里

项目设置

  1. 创建一个新的Rust项目
    打开终端并运行:

    cargo new flight_search_assistant
    cd flight_search_assistant

    这将初始化一个名为flight_search_assistant的新Rust项目。

  2. 更新Cargo.toml
    打开Cargo.toml文件并添加必要的依赖:

    [package]
    name = "flight_search_assistant"
    version = "0.1.0"
    edition = "2021"
    
    [dependencies]
    rig-core = "0.7.0"
    tokio = { version = "1.34.0", features = ["full"] }
    serde = { version = "1.0", features = ["derive"] }
    serde_json = "1.0"
    reqwest = { version = "0.11", features = ["json", "tls"] }
    dotenv = "0.15"
    thiserror = "1.0"
    chrono = { version = "0.4", features = ["serde"] }

    以下是这些依赖的简要说明:

    • rig-core:Rig的核心库。
    • tokio:Rust的异步运行时,允许我们并发执行任务。
    • serdeserde_json:用于序列化和反序列化数据的库。
    • reqwest:用于发送HTTP请求的客户端。
    • dotenv:从.env文件加载环境变量。
    • thiserror:用于更好的错误处理。
    • chrono:用于处理日期和时间。
  3. 设置环境变量
    为了安全起见,我们不希望硬编码API密钥。相反,我们将它们存储在.env文件中。
    创建文件:

    touch .env

    .env中添加你的API密钥:

    OPENAI_API_KEY=your_openai_api_key_here
    RAPIDAPI_KEY=your_rapidapi_key_here

    请将占位符替换为你的实际密钥。

  4. 安装依赖
    在终端中运行:

    cargo build

    这将下载并编译所有依赖项。


理解代理和工具

在开始编码之前,让我们澄清一些关键概念。

什么是代理?

在Rig(以及一般的AI应用)的上下文中,代理就像你助手的“大脑”。它负责解释用户输入、决定采取什么操作并生成响应。

你可以将代理视为乐队的指挥,协调不同的乐器(或工具)来创作和谐的音乐(或响应)。

什么是工具?

工具是代理用来完成任务的能力或操作。每个工具执行特定的功能。在我们的例子中,航班搜索功能就是代理用来获取航班信息的工具。

继续我们的比喻,工具是乐队中的乐器,每个乐器都扮演着特定的角色。

它们如何协同工作?

当用户问:“帮我找从纽约到洛杉矶的航班”时,代理会处理这个请求,并决定需要使用航班搜索工具来获取信息。


构建航班搜索工具

现在,让我们构建处理航班搜索的工具。

  1. 创建工具文件
    src目录中创建一个新文件flight_search_tool.rs

    touch src/flight_search_tool.rs
  2. 导入必要的库
    打开flight_search_tool.rs并添加:

    use chrono::{DateTime, Duration, Utc};
    use rig::completion::ToolDefinition;
    use rig::tool::Tool;
    use serde::{Deserialize, Serialize};
    use serde_json::{json, Value};
    use std::collections::HashMap;
    use std::env;
  3. 定义数据结构
    我们将定义结构来处理输入参数和输出结果。

    #[derive(Deserialize)]
    pub struct FlightSearchArgs {
       source: String,
       destination: String,
       date: Option<String>,
       sort: Option<String>,
       service: Option<String>,
       itinerary_type: Option<String>,
       adults: Option<u8>,
       seniors: Option<u8>,
       currency: Option<String>,
       nearby: Option<String>,
       nonstop: Option<String>,
    }
    
    #[derive(Serialize)]
    pub struct FlightOption {
       pub airline: String,
       pub flight_number: String,
       pub departure: String,
       pub arrival: String,
       pub duration: String,
       pub stops: usize,
       pub price: f64,
       pub currency: String,
       pub booking_url: String,
    }
    • FlightSearchArgs:表示用户提供的参数。
    • FlightOption:表示我们将展示给用户的每个航班选项。
  4. 使用thiserror进行错误处理
    Rust鼓励我们显式处理错误。我们将定义一个自定义错误类型:

    #[derive(Debug, thiserror::Error)]
    pub enum FlightSearchError {
       #[error("HTTP request failed: {0}")]
       HttpRequestFailed(String),
       #[error("Invalid response structure")]
       InvalidResponse,
       #[error("API error: {0}")]
       ApiError(String),
       #[error("Missing API key")]
       MissingApiKey,
    }
  5. 实现工具特性
    现在,我们将为FlightSearchTool实现Tool特性。
    首先,定义工具:

    pub struct FlightSearchTool;

    然后实现特性:

    impl Tool for FlightSearchTool {
       const NAME: &'static str = "search_flights";
    
       type Args = FlightSearchArgs;
       type Output = String;
       type Error = FlightSearchError;
    
       async fn definition(&self, _prompt: String) -> ToolDefinition {
           ToolDefinition {
               name: Self::NAME.to_string(),
               description: "Search for flights between two airports".to_string(),
               parameters: json!({
                   "type": "object",
                   "properties": {
                       "source": { "type": "string", "description": "Source airport code (e.g., 'JFK')" },
                       "destination": { "type": "string", "description": "Destination airport code (e.g., 'LAX')" },
                       "date": { "type": "string", "description": "Flight date in 'YYYY-MM-DD' format" },
                   },
                   "required": ["source", "destination"]
               }),
           }
       }
    
       async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
           // 我们将在下一步实现调用航班搜索API的逻辑。
           Ok("Flight search results".to_string())
       }
    }
  6. 实现call函数
    现在,让我们完善call函数。

    a. 获取API密钥

    let api_key = env::var("RAPIDAPI_KEY").map_err(|_| FlightSearchError::MissingApiKey)?;

    b. 设置默认值

    let date = args.date.unwrap_or_else(|| {
       let date = Utc::now() + Duration::days(30);
       date.format("%Y-%m-%d").to_string()
    });

    c. 构建查询参数

    let mut query_params = HashMap::new();
    query_params.insert("sourceAirportCode", args.source);
    query_params.insert("destinationAirportCode", args.destination);
    query_params.insert("date", date);

    d. 发送API请求

    let client = reqwest::Client::new();
    let response = client
       .get("https://tripadvisor16.p.rapidapi.com/api/v1/flights/searchFlights")
       .headers({
           let mut headers = reqwest::header::HeaderMap::new();
           headers.insert("X-RapidAPI-Host", "tripadvisor16.p.rapidapi.com".parse().unwrap());
           headers.insert("X-RapidAPI-Key", api_key.parse().unwrap());
           headers
       })
       .query(&query_params)
       .send()
       .await
       .map_err(|e| FlightSearchError::HttpRequestFailed(e.to_string()))?;

    e. 解析和格式化响应

    let text = response
       .text()
       .await
       .map_err(|e| FlightSearchError::HttpRequestFailed(e.to_string()))?;
    
    let data: Value = serde_json::from_str(&text)
       .map_err(|e| FlightSearchError::HttpRequestFailed(e.to_string()))?;
    
    let mut flight_options = Vec::new();
    
    // 这里我们需要提取航班选项。(为了简洁起见,我们省略了完整代码。)
    
    // 将航班选项格式化为可读的字符串
    let mut output = String::new();
    output.push_str("Here are some flight options:\n\n");
    
    for (i, option) in flight_options.iter().enumerate() {
       output.push_str(&format!("{}. **Airline**: {}\n", i + 1, option.airline));
       // 其他格式化...
    }
    
    Ok(output)

创建AI代理

现在我们的工具已经准备好了,让我们构建使用它的代理。

更新main.rs

打开src/main.rs并更新:

mod flight_search_tool;

use crate::flight_search_tool::FlightSearchTool;
use dotenv::dotenv;
use rig::completion::Prompt;
use rig::providers::openai;
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    dotenv().ok();

    let openai_client = openai::Client::from_env();

    let agent = openai_client
        .agent("gpt-4")
        .preamble("You are a helpful assistant that can find flights for users.")
        .tool(FlightSearchTool)
        .build();

    let response = agent
        .prompt("Find me flights from San Antonio (SAT) to Atlanta (ATL) on November 15th 2024.")
        .await?;

    println!("Agent response:\n{}", response);

    Ok(())
}

译者注:此处的 OpenAI 可替换为其他大语言模型(LLM),例如近期备受瞩目的 DeepSeek。在学习过程中,我为 Rig 官方补充完善了关于 DeepSeek 能力的相关内容。


运行和测试

让我们看看我们的助手是如何工作的!

  1. 构建项目
    在终端中运行:

    cargo build

    修复可能出现的编译错误。

  2. 运行应用程序

    cargo run

    你应该会看到类似以下的输出:

    Agent response:
    Here are some flight options:
    
    1. **Airline**: Spirit
      - **Flight Number**: NK123
      - **Departure**: 2024-11-15T05:00:00-06:00
      - **Arrival**: 2024-11-15T10:12:00-05:00
      - **Duration**: 4 hours 12 minutes
      - **Stops**: 1 stop(s)
      - **Price**: 77.97 USD
      - **Booking URL**: https://www.tripadvisor.com/CheapFlightsPartnerHandoff...
    
    2. **Airline**: American
      - **Flight Number**: AA456
      - **Departure**: 2024-11-15T18:40:00-06:00
      - **Arrival**: 2024-11-15T23:58:00-05:00
      - **Duration**: 4 hours 18 minutes
      - **Stops**: 1 stop(s)
      - **Price**: 119.97 USD
      - **Booking URL**: https://www.tripadvisor.com/CheapFlightsPartnerHandoff...

总结

恭喜!你已经使用Rust和Rig构建了一个功能齐全的航班搜索AI助手。以下是我们的成果:

  • 学习了Rust基础知识:我们探索了Rust的语法和结构,包括错误处理和异步编程。
  • 理解了代理和工具:我们了解了代理如何充当“大脑”,工具如何充当“技能”。
  • 构建了自定义工具:我们创建了一个与外部API交互的航班搜索工具。
  • 创建了AI代理:我们将工具集成到一个能够理解和响应用户查询的代理中。
  • 运行和测试了助手:我们看到了助手如何获取并展示航班选项。

下一步

  • 增强工具:添加更多参数,如服务等级、乘客数量或价格过滤。
  • 改进错误处理:处理没有找到航班或API速率限制达到的情况。
  • 用户界面:构建一个简单的命令行界面或Web前端。

资源

保持联系

如果你对Rust、LLM或构建智能助手感兴趣,欢迎随时联系我!我很乐意回答你关于Rig的问题,并看看你用Rig构建了什么酷炫的项目。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
King
King
0x56af...a0dd
擅长Rust/Solidity/FunC/Move开发