usaidwat 2.0.3

Answers the age-old question, "Where does a Redditor comment the most?"
Documentation
// usaidwat
// Copyright (C) 2025 Michael Dippery <michael@monkey-robot.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! API clients for various AI services.

pub mod openai;

use crate::http::HTTPError;
use std::fmt::Debug;

/// A client for an AI service's API.
pub trait APIClient {
    /// The client can make API requests of this type.
    type APIRequest: APIRequest;

    /// The client receives API responses of this type.
    type APIResponse: APIResponse;

    /// Sends the request to the AI service and receives a response.
    fn send(
        &self,
        request: &Self::APIRequest,
    ) -> impl Future<Output = APIResult<Self::APIResponse>> + Send;
}

/// A request to an AI service's API.
///
/// This trait follows a "builder" pattern where elements of the request
/// are built up over time.
///
/// Assuming you have enum called `Model` that specifies available AI models
/// for your service, and a `ConcreteAPIRequest` struct that implements
/// `APIRequest`, you would create an API request like this:
///
/// ```
/// # use usaidwat::ai::client::{AIModel, APIRequest};
/// #
/// # #[derive(Clone, Copy, Debug, Default)]
/// # pub enum Model {
/// #     #[default]
/// #     AIModel,
/// # }
/// #
/// # impl AIModel for Model {
/// #     fn best() -> Self {
/// #         Model::AIModel
/// #     }
/// #
/// #     fn fastest() -> Self {
/// #         Model::AIModel
/// #     }
/// #
/// #     fn cheapest() -> Self {
/// #         Model::AIModel
/// #     }
/// # }
/// #
/// # #[derive(Default)]
/// # pub struct ConcreteAPIRequest;
/// #
/// # impl APIRequest for ConcreteAPIRequest {
/// #     type Model = Model;
/// #     fn model(self, model: Self::Model) -> Self { self }
/// #     fn instructions(self, instructions: impl Into<String>) -> Self { self }
/// #     fn input(self, input: impl Into<String>) -> Self { self }
/// # }
/// #
/// let request = ConcreteAPIRequest::default()
///     .model(Model::AIModel)
///     .instructions("Be really snarky.")
///     .input("How do I make an API request?");
/// ```
pub trait APIRequest: Default {
    /// An enum or other data structures providing options for different
    /// AI models, which are specific to each service.
    type Model: AIModel;

    /// Sets the model used by the API request and returns a new
    /// request.
    ///
    /// AI services often have many different models; consult the
    /// documentation for your specific AI model for options.
    fn model(self, model: Self::Model) -> Self;

    /// Sets specialized instructions for the request and returns a new
    /// request.
    ///
    /// Some AI models allow callers to specify instructions for
    /// generating responses, such as tone, goals, or examples of
    /// correct responses. Consult the API documentation for your
    /// specific service to see if it allows instructions to be
    /// specified. If not, this method can be a no-op.
    ///
    /// Often specialized instructions will take precedence over the
    /// request's [input](APIRequest::input). Consult the documentation
    /// for your specific service to see if that is the case.
    fn instructions(self, instructions: impl Into<String>) -> Self;

    /// Sets the request's input and returns a new request.
    ///
    /// The input is often referred to as a "prompt" and is the text
    /// for which an AI service generates a response.
    fn input(self, input: impl Into<String>) -> Self;
}

/// A response from an AI service's API.
pub trait APIResponse {
    /// Concatenates the output from an AI service into a single string.
    fn concatenate(&self) -> String;
}

/// An API result that includes the response if successful or an error
/// if unsuccessful.
pub type APIResult<T> = Result<T, APIError>;

/// An API error.
pub type APIError = HTTPError;

/// An AI model specification.
pub trait AIModel: Clone + Copy + Default + Debug {
    /// The "best" model available for a given LLM.
    ///
    /// "Best" is obviously subjective, but generally this is the model
    /// that offers the best price/performance ratio, and is what its
    /// provider has defined to be the "best".
    fn best() -> Self;

    /// The least expensive model available for a given LLM.
    fn cheapest() -> Self;

    /// The fastest model available for a given LLM.
    fn fastest() -> Self;
}