程序员进阶必备:用Rust玩转AI,打造智能航班搜索工具!
简介
你是否曾经与Siri、Alexa等AI助手聊天,或者使用过那些帮助你预订航班或查询天气的智能聊天机器人?你是否好奇这些助手背后的工作原理?今天,我们将通过使用Rust和Rig库构建一个属于自己的航班搜索AI助手,来揭开这些技术的神秘面纱。
你可能会想:“等等,Rust?那不是以难学著称的语言吗?”别担心!我们将一步步带你走过整个过程,并解释每个概念。到最后,你不仅会拥有一个酷炫的AI助手,还会对Rust编程有初步的了解。
以下是我们的计划:
- 为什么选择Rust和Rig? 了解我们选择的工具。
- 环境设置:准备好Rust和Rig。
- 理解代理和工具:我们助手的“大脑”和“手”。
- 构建航班搜索工具:实现核心功能。
- 创建AI代理:让我们的助手“活”起来。
- 运行和测试:看看我们的成果。
- 总结:回顾和下一步计划。
本项目的完整源代码可以在我们的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,我们能够构建一个强大、高效且智能的助手。
环境设置
在开始编码之前,我们需要准备好一切。
先决条件
- 安装Rust:如果尚未安装Rust,请按照这里的说明进行安装。
- 基本的Rust知识:如果你是新手,别担心,我们会在过程中解释Rust的概念。
- API密钥:
- OpenAI API密钥:注册并获取你的密钥这里。
- RapidAPI密钥:我们将使用它来访问TripAdvisor的航班搜索API。获取密钥这里。
项目设置
-
创建一个新的Rust项目
打开终端并运行:cargo new flight_search_assistant cd flight_search_assistant
这将初始化一个名为
flight_search_assistant
的新Rust项目。 -
更新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的异步运行时,允许我们并发执行任务。serde
和serde_json
:用于序列化和反序列化数据的库。reqwest
:用于发送HTTP请求的客户端。dotenv
:从.env
文件加载环境变量。thiserror
:用于更好的错误处理。chrono
:用于处理日期和时间。
-
设置环境变量
为了安全起见,我们不希望硬编码API密钥。相反,我们将它们存储在.env
文件中。
创建文件:touch .env
在
.env
中添加你的API密钥:OPENAI_API_KEY=your_openai_api_key_here RAPIDAPI_KEY=your_rapidapi_key_here
请将占位符替换为你的实际密钥。
-
安装依赖
在终端中运行:cargo build
这将下载并编译所有依赖项。
理解代理和工具
在开始编码之前,让我们澄清一些关键概念。
什么是代理?
在Rig(以及一般的AI应用)的上下文中,代理就像你助手的“大脑”。它负责解释用户输入、决定采取什么操作并生成响应。
你可以将代理视为乐队的指挥,协调不同的乐器(或工具)来创作和谐的音乐(或响应)。
什么是工具?
工具是代理用来完成任务的能力或操作。每个工具执行特定的功能。在我们的例子中,航班搜索功能就是代理用来获取航班信息的工具。
继续我们的比喻,工具是乐队中的乐器,每个乐器都扮演着特定的角色。
它们如何协同工作?
当用户问:“帮我找从纽约到洛杉矶的航班”时,代理会处理这个请求,并决定需要使用航班搜索工具来获取信息。
构建航班搜索工具
现在,让我们构建处理航班搜索的工具。
-
创建工具文件
在src
目录中创建一个新文件flight_search_tool.rs
:touch src/flight_search_tool.rs
-
导入必要的库
打开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;
-
定义数据结构
我们将定义结构来处理输入参数和输出结果。#[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
:表示我们将展示给用户的每个航班选项。
-
使用
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, }
-
实现工具特性
现在,我们将为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()) } }
-
实现
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 能力的相关内容。
运行和测试
让我们看看我们的助手是如何工作的!
-
构建项目
在终端中运行:cargo build
修复可能出现的编译错误。
-
运行应用程序
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前端。
资源
- Rig仓库:Github
- Rig文档:Rig Docs
- Rust编程语言:The Rust Book
- OpenAI API:OpenAI API Documentation
- RapidAPI航班API:TripAdvisor Flight Search API
保持联系
如果你对Rust、LLM或构建智能助手感兴趣,欢迎随时联系我!我很乐意回答你关于Rig的问题,并看看你用Rig构建了什么酷炫的项目。
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。