20个稳定版本

1.2.3 2023年9月14日
1.2.0 2023年8月20日
1.1.13 2023年7月24日
1.1.0 2023年3月30日
0.6.0 2022年12月11日

#236 in Web编程

Download history 355/week @ 2024-04-21 223/week @ 2024-04-28 192/week @ 2024-05-05 181/week @ 2024-05-12 196/week @ 2024-05-19 257/week @ 2024-05-26 228/week @ 2024-06-02 137/week @ 2024-06-09 213/week @ 2024-06-16 180/week @ 2024-06-23 93/week @ 2024-06-30 222/week @ 2024-07-07 180/week @ 2024-07-14 129/week @ 2024-07-21 184/week @ 2024-07-28 232/week @ 2024-08-04

733 每月下载量
7 crates 中使用

MIT 许可证

70KB
1K SLoC

ChatGPT-rs

这个库是OpenAI ChatGPT API的异步Rust包装器。它支持对话、消息持久化和ChatGPT功能。

关于ChatGPT功能

功能API(在 v1.2.0+ 版本中提供)目前处于实验性阶段,可能无法按预期工作。如果您遇到任何问题或未定义的行为,请在本仓库中创建一个问题!

使用方法

以下是一个API的简单使用示例,获取单个消息的完成结果。您可以在 examples 目录中找到更多实用示例。

use chatgpt::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    // Getting the API key here
    let key = args().nth(1).unwrap();

    /// Creating a new ChatGPT client.
    /// Note that it requires an API key, and uses
    /// tokens from your OpenAI API account balance.
    let client = ChatGPT::new(key)?;

    /// Sending a message and getting the completion
    let response: CompletionResponse = client
        .send_message("Describe in five words the Rust programming language.")
        .await?;

    println!("Response: {}", response.message().content);

    Ok(())
}

流式响应

如果您希望逐步构建响应消息,可以使用crate的 streams 功能(默认情况下未启用)和特殊方法来请求流式响应。

以下是一个示例

// Acquiring a streamed response
// Note, that the `futures_util` crate is required for most
// stream related utility methods
let stream = client
    .send_message_streaming("Could you name me a few popular Rust backend server frameworks?")
    .await?;

// Iterating over stream contents
stream
    .for_each(|each| async move {
        match each {
            ResponseChunk::Content {
                delta,
                response_index: _,
            } => {
                // Printing part of response without the newline
                print!("{delta}");
                // Manually flushing the standard output, as `print` macro does not do that
                stdout().lock().flush().unwrap();
            }
            _ => {}
        }
    })
    .await;
}

请注意,返回的流通常没有任何实用方法,因此您必须使用您选择的异步库中的 StreamExt 方法(例如 futures-utiltokio)。

对话

对话是ChatGPT分析先前消息并连接其思想的线程。它们还自动存储所有消息历史记录。

以下是一个示例

// Creating a new conversation
let mut conversation: Conversation = client.new_conversation();

// Sending messages to the conversation
let response_a: CompletionResponse = conversation
    .send_message("Could you describe the Rust programming language in 5 words?")
    .await?;
let response_b: CompletionResponse = conversation
    .send_message("Now could you do the same, but for Kotlin?")
    .await?;

// You can also access the message history itself
for message in &conversation.history {
    println!("{message:#?}")
}

这种方式创建对话时,会使用默认的介绍消息,大致内容如下: 您是ChatGPT, 一个由OpenAI开发的 AI 模型. 简洁地回答. 今天是: {today's date}

但是,您可以这样指定自己的介绍消息

let mut conversation: Conversation = client.new_conversation_directed("You are RustGPT, when answering any questions, you always shift the topic of the conversation to the Rust programming language.");
// Continue with the new conversation

对话流式传输

对话也支持返回流式响应(使用 streams 功能)。

注意: 流式响应 不会 自动将返回的消息保存到历史记录中,因此您必须自己手动完成。

以下是一个示例

// Acquiring a streamed response
// Note, that the `futures_util` crate is required for most
// stream related utility methods
let mut stream = conversation
    .send_message_streaming("Could you name me a few popular Rust backend server frameworks?")
    .await?;

    // Iterating over a stream and collecting the results into a vector
let mut output: Vec<ResponseChunk> = Vec::new();
while let Some(chunk) = stream.next().await {
    match chunk {
        ResponseChunk::Content {
            delta,
            response_index,
        } => {
            // Printing part of response without the newline
            print!("{delta}");
            // Manually flushing the standard output, as `print` macro does not do that
            stdout().lock().flush().unwrap();
            output.push(ResponseChunk::Content {
                delta,
                response_index,
            });
        }
        // We don't really care about other types, other than parsing them into a ChatMessage later
        other => output.push(other),
    }
}

// Parsing ChatMessage from the response chunks and saving it to the conversation history
let messages = ChatMessage::from_response_chunks(output);
conversation.history.push(messages[0].to_owned());

功能调用

ChatGPT-rs 支持功能调用API。需要 functions 功能。

您可以使用 gpt_function 属性宏来定义函数,如下所示

use chatgpt::prelude::*;

/// Says hello to a user
/// 
/// * user_name - Name of the user to greet
#[gpt_function]
async fn say_hello(user_name: String) {
    println!("Hello, {user_name}!")
}

// ... within your conversation, before sending first message
let mut conversation = client.new_conversation();
// note that you need to call the function when adding it
conversation.add_function(say_hello());
let response = conversation
    .send_message_functions("Could you greet user with name `maxus`?")
    .await?;
// At this point, if function call was issued it was already processed
// and subsequent response was sent

如您所见,GPT 函数必须有一个描述,以便模型知道何时调用它们以及它们的功能。在 ChatGPT-rs 中,函数描述以简单的 Rust 文档表示。每个参数都按照如下格式进行文档说明:* {参数名} - {参数描述}。函数参数从 JSON 处理,因此只要它们实现了 schemars::JsonSchemaserde::Deserialize,它们将被正确解析。

默认情况下,ChatGPT-rs 使用最小的 schemars 功能,启用功能 functions_extra 以添加对 uuidchronourleither 的支持,或者定义自己的结构并推导出 schemars::JsonSchemaserde::Deserialize

use schemars::JsonSchema;
use serde::Deserialize;

#[derive(JsonSchema, Deserialize)]
struct Args {
    /// Name of the user
    user_name: String,
    /// New age of the user
    user_age: u16
}

/// Wishes happy birthday to the user
/// 
/// * args - Arguments
#[gpt_function]
async fn happy_birthday(args: Args) {
    println!("Hello, {}, You are now {}!", args.user_name, args.user_age);
}

函数也可以返回任何数据(只要它实现了 serde::Serialize),并将其返回给模型。

/// Does some heavy computations and returns result
/// 
/// * input - Input data as vector of floats
#[gpt_function]
async fn do_heavy_computation(input: Vec<f64>) -> Vec<f64> {
    let output: Vec<f64> = // ... Do something with the input ...  
    return output;
}

默认情况下,只有通过调用 send_message_functions 方法将函数发送到API。如果您希望在每个消息中启用自动发送函数,可以将 Conversation 中的 always_send_functions 属性设置为 true。

当前函数的限制如下

  • 它们必须是异步的。
  • 由于它们被视为令牌,您可能希望限制函数发送和/或其描述的长度。

函数调用验证

如官方 ChatGPT 文档所述,ChatGPT 可能会幻想不存在的函数或提供无效的 JSON。为了减轻这种情况,ChatGPT-rs 提供了 FunctionValidationStrategy。如果将其设置为 Strict(在 客户端模型配置 中),则在模型无法正确调用函数时,将发送系统消息来纠正它。

会话持久性

您目前可以以两种格式存储会话的消息:JSON 或 postcard。可以使用相应的 jsonpostcard 功能来切换它们的开/关。

由于 ChatMessage 结构体推导了 serde 的 SerializeDeserialize 特性,您还可以使用任何与 serde 兼容的序列化库,因为 history 字段和 Conversation::new_with_history() 方法在 Conversation 结构体中是公开的。

使用 JSON 进行持久性

需要 json 功能(默认启用)

// Create a new conversation here
let mut conversation: Conversation = ...;

// ... send messages to the conversation ...

// Saving the conversation
conversation.save_history_json("my-conversation.json").await?;

// You can later read this conversation history again
let mut restored = client
    .restore_conversation_json("my-conversation.json")
    .await?;

使用 Postcard 进行持久性

需要 postcard 功能(默认禁用)

// Create a new conversation here
let mut conversation: Conversation = ...;

// ... send messages to the conversation ...

// Saving the conversation
conversation.save_history_postcard("my-conversation.bin").await?;

// You can later read this conversation history again
let mut restored = client
    .restore_conversation_postcard("my-conversation.bin")
    .await?;

高级配置

您可以使用 ModelConfigurationBuilder 进一步配置您的模型,这还允许使用代理

// Getting the API key here
let key = args().nth(1).unwrap();

// Creating a new ChatGPT client with extra settings.
// Note that it might not require an API key depending on proxy
let client = ChatGPT::new_with_config(
    key,
    ModelConfigurationBuilder::default()
        .api_url("https://api.pawan.krd/v1/chat/completions")
        .temperature(1.0)
        .engine(ChatGPTEngine::Gpt4_32k)
        .build()
        .unwrap(),
)?;

依赖项

~6–19MB
~275K SLoC