misanthropic 0.5.1

An async, ergonomic, client for Anthropic's Messages API
//! See `source` for an example of [`Client::stream`] using the "website wizard"
//! prompt. For a non-streaming example, see the `neologism` example.

// Note: This example uses blocking calls for simplicity such as `print`
// `read_to_string`, `stdin().lock()`, and `write`. In a real application, these
// should usually be replaced with async alternatives.

use clap::Parser;
use futures::TryStreamExt;
use misanthropic::{
    json, prompt::message::Role, stream::FilterExt, Client, Model,
};
use std::{
    io::{stdin, BufRead},
    path::PathBuf,
};

/// Generate a one-page website based on the given specifications. This is
/// adapted from the "Prompt library" example:
///
/// https://docs.anthropic.com/en/prompt-library/website-wizard
#[derive(Parser, Debug)]
#[command(version, about)]
struct Args {
    /// Specification text.
    #[arg(short, long)]
    specs: PathBuf,
    /// Output file.
    #[arg(short, long)]
    output: PathBuf,
    /// Maximum tokens.
    #[arg(short, long, default_value = "4000")]
    max_tokens: u64,
    /// System prompt
    #[arg(
        long,
        default_value = "Your task is to create a one-page website based on the given specifications, delivered as an HTML file with embedded JavaScript and CSS. The website should incorporate a variety of engaging and interactive design features, such as drop-down menus, dynamic text and content, clickable buttons, and more. Ensure that the design is visually appealing, responsive, and user-friendly. The HTML, CSS, and JavaScript code should be well-structured, efficiently organized, and properly commented for readability and maintainability."
    )]
    system: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(feature = "log")]
    env_logger::init();

    // Read the command line arguments.
    let args = Args::parse();

    // Read the specification text.
    let specs = std::fs::read_to_string(args.specs)?;

    // Get API key from stdin.
    println!("Enter your API key:");
    let key = stdin().lock().lines().next().unwrap()?;

    // Create a client. `key` will be consumed and zeroized.
    let client = Client::new(key)?;

    // Request a streaming completion. `json!` can be used, the concrete type,
    // `Request` or a combination of strings and concrete types like `Model`.
    // Client request methods accept anything serializable.
    let stream = client
        .stream(json!({
          "model": Model::Sonnet35,
          "max_tokens": args.max_tokens,
          "temperature": 0,
          "system": args.system,
          "messages": [
            {
              "role": Role::User,
              "content": specs,
            }
          ],
        }))
        .await?
        // Filter out rate limit and overloaded errors. This is optional but
        // recommended for most use cases. The stream will continue when the
        // server is ready. Otherwise the stream will include these errors.
        .filter_rate_limit()
        // Filter out everything but text pieces (and errors).
        .text();

    println!("Generating website...\n");
    // Collect the stream into a single string.
    let content: String = stream
        .map_ok(|piece| {
            print!("{}", &piece);
            piece
        })
        .try_collect()
        .await?;
    println!("\nWebsite generated.");

    // Write the output to a file.
    std::fs::write(args.output, content)?;

    Ok(())
}