開発初心者がRustで作るAIチャットボット!(※初回で最終回のおしらせ。。。)
はじめに
はい!今回は早めに更新できた気がします!!
今回はいよいよRustでAIチャットボット開発を進めていきます!
ですが、今回でAIチャットボット開発は最終回を迎えることになりました。。
長らくご支援いただいていた皆様の期待に応えることができませんでした。
最終回の理由は、、AIチャットボット開発に興味がなくなってしまったためです!
で、他に考えていることの方が圧倒的に興味が湧いているからです!
なので、本当は詳細にいろいろお伝えしようと思っていましたが、
ざーっくりお伝えしていきたいと思います!
Gemini CLIに作ってもらった内容を公開していくような感じですね。。
では、気を取り直してAIチャットボット開発です!
AIチャットボット開発
まずは、AIチャットボット開発の進め方についてです!
私の進め方は下記の通りです
- オウム返し応答
- 特定の文字列に対して用意された応答、他定型文応答
- Gemini API による応答
それぞれの開発内容についてお伝えしていきます!
1. オウム返し応答
最初に全ての文章をそのまま応答するチャットボットを作成しました
AIに頼れば最初からGemini APIを仕込んで応答させることも可能でしたが、、
それだと構造や仕組みの理解ができずに終わってしまうと思ったため、
はじめはオウム返しの応答をさせることにしました!
内容は下記の通りです!
use std::io::{self, Write};
fn main() -> io::Result<()> {
loop {
print!("You: ");
io::stdout().flush()?;
let mut user_message = String::new(); ※変数宣言
io::stdin().read_line(&mut user_message)?; ※送られた内容の読み取り
let user_message = user_message.trim().to_string(); ※ここでオウム返し
if user_message.is_empty() {
continue;
}
println!("Bot: {}", "This is a fixed response.");
}
}
2. 特定の文字列に対して用意された応答、他定型文応答
次は特定文字列に対する決まった応答と定型文応答です!
hello, how are you, bye の3つに対して決められた応答をさせるようにしました!
それ以外は全て定型文の応答になります!
内容は下記の通りです!
use std::io::{self, Write};
fn main() -> io::Result<()> {
loop {
print!("You: ");
io::stdout().flush()?;
let mut user_message = String::new();
io::stdin().read_line(&mut user_message)?;
let user_message = user_message.trim().to_lowercase();
if user_message.is_empty() {
continue;
}
let bot_response = if user_message == "hello" {
"Hello there!" ※hello に対する応答
} else if user_message == "how are you" {
"I am a bot, I am doing great!" ※how are you に対する応答
} else if user_message == "bye" {
println!("Goodbye!"); ※byeに対する応答
break;
} else {
"I am not sure how to respond to that." ※定型文応答
};
println!("Bot: {}", bot_response);
}
Ok(())
}
3. Gemini API による応答
最後にGemini APIによる応答です!
これにより、普段使うようなAIツールと同じように応答してもらえるようになりました!
use std::io::{self, Write};
use std::env;
use dotenv::dotenv;
use reqwest::Client;
use serde::{Deserialize, Serialize};
// Structs for Gemini API Request & Conversation History
#[derive(Serialize, Deserialize, Clone, Debug)]
struct Content {
role: String,
parts: Vec<Part>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
struct Part {
text: String,
}
#[derive(Serialize)]
struct RequestBody {
contents: Vec<Content>,
}
// Structs for Gemini API Response
#[derive(Deserialize, Debug)]
struct ResponseBody {
candidates: Vec<Candidate>,
}
#[derive(Deserialize, Debug)]
struct Candidate {
content: Content, // Use the same Content struct for simplicity
}
async fn call_gemini_api(history: &Vec<Content>, client: &Client, api_key: &str) -> Result<String, reqwest::Error> {
let api_url = format!("https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={}", api_key);
let request_body = RequestBody {
contents: history.clone(),
};
let res = client.post(&api_url)
.json(&request_body)
.send()
.await?;
if res.status().is_success() {
let bytes = res.bytes().await?;
// Convert bytes to a UTF-8 string, replacing invalid sequences
let response_text = String::from_utf8_lossy(&bytes).into_owned();
let response_body: ResponseBody = match serde_json::from_str(&response_text) {
Ok(body) => body,
Err(e) => {
eprintln!("Failed to parse JSON: {}. Response: '{}'", e, response_text);
return Ok("Sorry, I received an invalid response from the server.".to_string());
}
};
// Extract the text from the first candidate's first part
if let Some(candidate) = response_body.candidates.first() {
if let Some(part) = candidate.content.parts.first() {
return Ok(part.text.clone());
}
}
Ok("Sorry, I couldn't get a proper response.".to_string())
} else {
let status = res.status();
let error_bytes = res.bytes().await?;
let error_text = String::from_utf8_lossy(&error_bytes);
eprintln!("API Error - Status: {}, Body: {}", status, error_text);
Ok("Sorry, there was an error communicating with the API.".to_string())
}
}
#[tokio::main]
async fn main() -> io::Result<()> {
// Load environment variables from .env file
dotenv().ok();
let api_key = env::var("GEMINI_API_KEY").expect("GEMINI_API_KEY must be set in .env file");
let client = Client::new();
let mut conversation_history: Vec<Content> = Vec::new();
println!("Chatbot initialized. I can now remember our conversation. Type 'bye' to exit.");
loop {
print!("You: ");
io::stdout().flush()?;
use std::io::BufRead; let mut user_message_bytes = Vec::new(); io::BufReader::new(io::stdin()).read_until(b'\n', &mut user_message_bytes)?; let user_message = String::from_utf8_lossy(&user_message_bytes).trim().to_string();
if user_message.is_empty() {
continue;
}
if user_message.to_lowercase() == "bye" {
println!("Goodbye!");
break;
}
// Add user message to history
conversation_history.push(Content {
role: "user".to_string(),
parts: vec![Part { text: user_message.to_string() }],
});
match call_gemini_api(&conversation_history, &client, &api_key).await {
Ok(bot_response) => {
let bot_response_trimmed = bot_response.trim();
println!("Bot: {}", bot_response_trimmed);
// Add bot response to history
conversation_history.push(Content {
role: "model".to_string(),
parts: vec![Part { text: bot_response_trimmed.to_string() }],
});
},
Err(e) => {
eprintln!("Error calling API: {}", e);
// If the API call fails, remove the last user message to avoid a broken history
conversation_history.pop();
}
}
}
Ok(())
}
AIチャットボット開発に興味がなくなった理由について
もう本当にざっくりですが、作成した(Geminiに作ってもらった)スクリプトを公開しました。
ここで改めてなぜ興味がなくなってしまったのかお伝えしたいと思います!
1. Gemini CLIが色々自動で作成してくれてしまった
私がCLIで動作するAIツールに対して理解が低かったこともありますが、、
〇〇の処理を入れてほしい、と伝えたらすぐに作成して更新確認をしてくれたんですよ。
で、Yesと返すとバーッとファイルを書き換えてくれて便利だなと思いました!
ですが、、何がどうなってそういう処理の流れになっているのか分からないし、
注釈つけてもらってもあまり理解度が高まりませんでした。。
2. そもそも興味なかったかもしれない
これは、、じゃあなんでチャットボット開発なんて進めようと思ったんだ!
とツッコまれてもしょうがないかなと思ってます。。
なんか、Claude Codeとか OpenHands CLIなどがリリースされているのを見て、
CLIで動くチャットツール作ってみたい! と思ったのがきっかけだったんです!
で、いざ作り始めようとしていたときにGemini CLIがリリースされて、
無料で使えるの助かる! と思って飛びつきました!
そんな流れで作り始めたのですが、いざGemini CLIに作成を頼んで作ってもらい、
チャットボットの出来栄えを見ていても、次にやりたいことが思いつかなかったんです。。
おわりに
今回はAIチャットボット開発と開発終了の理由についてお伝えしました!
自分でもこんなに早く最終回を迎えるとは思っていませんでしたが、、
もう興味が無くなってしまったので、終了とします!
次は自分の興味が湧いている開発内容についてお伝えしたいと思っています!
たった1回で終わってしまうくらいなら他も続かないんじゃない?と思われるのは想定しています。
ですが、自分の意思表明のためにも記事として残して、開発を進めたいと思います!!
ではまた!
Discussion