💯

Rustの勉強ついでにClaude3のAPIを呼び出してみた

2024/03/13に公開

はじめに

Rustを学習中 & Claude3も触ってみたい!状態だったので、勉強も兼ねてRustで書いてみました。
ClaudeのAPIキーを作成するところから、APIコールしてレスポンスを得るまでまとめています。

対象読者

Rust学習中の方(初学者)を対象に、主にRustのコードについて解説しています。
(ClaudeのAPIはドキュメントを見てAPIコールするだけなので説明は短めにします。)

使用するバージョン

$ cargo --version
cargo 1.76.0 (c84b36747 2024-01-18)

参考記事

最終的なコード

まずは最終的なコードから。

Cargo.toml
[package]
name = "rust-claude3"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1.0.197", features = ["derive"] }
reqwest = { version = "0.11.26", features = ["json"] }
tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] }
dotenv = "0.15.0"
main.rs
use serde::{Deserialize, Serialize};
use reqwest::Error;
use dotenv::dotenv;
use std::env;

#[derive(Serialize)]
struct Message {
    role: String,
    content: String,
}

#[derive(Serialize)]
struct RequestBody {
    model: String,
    max_tokens: u32,
    messages: Vec<Message>,
}

#[derive(Deserialize, Debug)]
struct Content {
    text: String,
}

#[derive(Deserialize, Debug)]
struct ResponseBody {
    content: Vec<Content>,
}

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

    const MODEL: &str = "claude-3-opus-20240229";
    const MAX_TOKENS: u32 = 1024;
    const ROLE_USER: &str = "user";
    const ANTHROPIC_VERSION: &str = "2023-06-01";

    let api_key = env::var("CLAUDE_API_KEY").expect("CLAUDE_API_KEY not set");
    let request_url = "https://api.anthropic.com/v1/messages";

    let content = "Hello, world";

    let request_body = RequestBody {
        model: MODEL.to_string(),
        max_tokens: MAX_TOKENS,
        messages: vec![Message {
            role: ROLE_USER.to_string(),
            content: content.to_string(),
        }],
    };

    let client = reqwest::Client::new();
    let response = client
        .post(request_url)
        .header("x-api-key", api_key)
        .header("anthropic-version", ANTHROPIC_VERSION)
        .header("content-type", "application/json")
        .json(&request_body)
        .send()
        .await?;

    let response_body: ResponseBody = response.json().await?;
    if let Some(content) = response_body.content.get(0) {
        println!("Response Text: {}", content.text);
    } else {
        println!("No content found in the response.");
    }

    Ok(())
}

使用するクレートを解説

Cargo.tomlで定義しているクレートについてまとめていきます。

[dependencies]
serde = { version = "1.0.197", features = ["derive"] }
reqwest = { version = "0.11.26", features = ["json"] }
tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] }
dotenv = "0.15.0"

1. serde

serdeとは

  • Rustのシリアライゼーション(Serialization)とデシリアライゼーション(Deserialization)のライブラリです。
  • シリアライゼーションとは、データ構造やオブジェクトを、保存や転送に適したフォーマット(例えば、JSON、YAML、バイナリなど)に変換することを指します。
  • serdeは、Rustの構造体やenumを、JSONやその他の形式に変換したり、その逆を行ったりするための機能を提供します。
  • ドキュメント

features = ["derive"]とは

  • serdeクレートには、deriveと呼ばれる機能(feature)があります。
  • features = ["derive"]は、serdeクレートのderive機能を有効にするための設定です。
  • derive機能を有効にすると、#[derive(Serialize, Deserialize)]のようなアトリビュートを使用して、構造体やenumに自動的にシリアライゼーション・デシリアライゼーションの実装を導出することができます。
  • フラグのドキュメント

2. reqwest

reqwestとは

  • reqwestは、RustのHTTPクライアントライブラリです。
  • HTTPリクエストを送信し、レスポンスを受信するためのAPIを提供します。
  • reqwestは、Rustの非同期プログラミングのエコシステムであるTokioと統合されており、非同期HTTPリクエストを扱うことができます。
  • RESTfulなWeb APIとのやり取りや、一般的なHTTPリクエストを行うためによく使用されます。

features = ["json"]とは

  • JSON 機能を有効にすると、reqwestは自動的にserde_jsonクレートに依存するようになります。
  • serde_jsonは、RustのJSONシリアライゼーション・デシリアライゼーションのためのクレートです。
  • リクエストボディとしてJSONデータを送信する際に、手動でJSONへの変換を行う必要がなくなります。
  • レスポンスボディのJSONデータを、Rustの構造体やenumに自動的にデシリアライズできます。

features = ["json"]が無かったら??

.json()のメソッドチェーンができなくなります。

3. tokio

tokioとは

  • 非同期プログラミングのための Rust のランタイムライブラリです。
  • 頻出なので、これについては改めて記事を作成します。

features = ["rt", "rt-multi-thread", "macros"]とは

featuresのそれぞれの機能を紹介します。

  • rt: Tokio のランタイム機能を有効化します。非同期タスクの実行に必要です。
  • rt-multi-thread: マルチスレッドランタイムを有効化し、並行処理を可能にします。
  • macros: async/await構文をサポートするマクロを有効化し、非同期コードを簡潔に記述できます。

これらの機能を組み合わせることで、Tokioを使用した効率的な非同期プログラミングが可能になります。rtmacrosは基本的な機能で、ほとんどの場合に必要のようです。rt-multi-threadは、並行処理を必要とする場合に追加で有効化します。

4. dotenv

dotenvとは

  • 他の言語にもあるように、.envファイルを読み込んで環境変数を設定することができます。

ClaudeのAPIキーを取得しよう

main.rsに入る前にClaudeのAPIを使うための準備として、APIキーを準備します。
Claudeのダッシュボードにログインし、APIキーを発行します。

※執筆時点では、最初に$5の無料クレジットが付与されます。

https://console.anthropic.com/settings/keys

main.rsを書こう

まずはコピペで動かしてみましょう。
動いたら、1つずつざっくりとみていきます。

ここは先ほど解説したserdeSerializeアトリビュートを使用して、.json()を使ってシリアライズできるようにしています。

#[derive(Serialize)]
struct Message {
    role: String,
    content: String,
}

#[derive(Serialize)]
struct RequestBody {
    model: String,
    max_tokens: u32,
    messages: Vec<Message>,
}

レスポンスは逆にデシリアライズできるようにしています。

#[derive(Deserialize, Debug)]
struct Content {
    text: String,
}

#[derive(Deserialize, Debug)]
struct ResponseBody {
    content: Vec<Content>,
}

次はmain関数です。

#[tokio::main]
async fn main() -> Result<(), Error> {
  • #[tokio::main]は、Tokioライブラリが提供するマクロです。このマクロを使用することで、main関数を非同期関数として定義できます。マクロは、必要なランタイムの初期化や終了処理を自動的に行ってくれます。
  • async fn main()は、main関数を非同期関数として宣言しています。asyncキーワードを使用することで、関数内で.awaitを使用して非同期処理を行うことができます。
  • -> Result<(), Error>は、main 関数の戻り値の型を指定しています。ここでは、Result型を使用しています。
  • Resultは、OkErrの2つの値を持つ列挙型で、関数が成功した場合はOk(())を、エラーが発生した場合はErr(Error)を返します。()は空のタプル(ユニット型)で、値を持たないことを示します。
  • Errorは、エラーを表す型です。

この#[tokio::main]async fn main()を組み合わせることで、Tokioランタイムを使用した非同期プログラミングのエントリーポイントを定義できます。main関数内で.awaitを使用して非同期処理を行い、必要に応じてResultを使ってエラーハンドリングを行います。

main関数の中の処理

ここは他の言語を書いたことのある方ならなんとなく読めるかと思います。
Rust独自の知識は少ないので説明は省略させていただきます。

さいごに

今回使用したコードの完全版は以下のGitリポジトリに入っているので、cloneするだけですぐに実行することができます。

https://github.com/ShotaTotsuka/rust-claude3

この記事がためになったらいいねをお願いします。
それではまた次回。

Discussion