anyllm 0.1.1

Low-level, generic LLM provider abstraction for Rust
Documentation
use anyllm::prelude::*;
use anyllm::{CapabilitySupport, ChatCapability, Error, ExtractError, ExtractingProvider};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::json;

#[derive(Debug, Deserialize, JsonSchema)]
struct Review {
    title: String,
    rating: u8,
}

fn lookup_customer_tool() -> Tool {
    Tool::new(
        "lookup_customer",
        json!({
            "type": "object",
            "properties": {
                "id": { "type": "string" }
            },
            "required": ["id"],
            "additionalProperties": false
        }),
    )
    .description("Look up customer details.")
}

fn build_request() -> ChatRequest {
    ChatRequest::new("demo-model")
        .system("A previous app step already gathered the review details. Extract only the final structured review.")
        .user("Customer 42 review summary: Heat deserves a 10 out of 10.")
        .tools([lookup_customer_tool()])
        .tool_choice(ToolChoice::Required)
}

fn build_provider() -> MockProvider {
    MockProvider::new([MockResponse::tool_call(
        "call_extract_1",
        "submit_structured_output",
        json!({ "title": "Heat", "rating": 10 }),
    )])
    .with_chat_capability(
        ChatCapability::StructuredOutput,
        CapabilitySupport::Unsupported,
    )
    .with_chat_capability(ChatCapability::ToolCalls, CapabilitySupport::Supported)
}

#[tokio::main]
async fn main() -> anyllm::Result<()> {
    let request = build_request();

    let direct = build_provider();
    let err = direct.extract::<Review>(&request).await.unwrap_err();
    match err {
        Error::Extract(inner) => match *inner {
            ExtractError::ToolConflict { mode, .. } => {
                println!(
                    "direct extract keeps caller tools, so extraction mode {mode:?} conflicts"
                );
            }
            other => {
                return Err(Error::UnexpectedResponse(format!(
                    "unexpected extract error: {other}"
                )));
            }
        },
        other => {
            return Err(Error::UnexpectedResponse(format!(
                "unexpected error: {other}"
            )));
        }
    }

    let wrapped = ExtractingProvider::new(build_provider());
    let extracted: Extracted<Review> = wrapped.extract(&request).await?;
    println!("wrapped extractor ran a dedicated extraction pass over the existing messages");
    println!("title: {}", extracted.value.title);
    println!("rating: {}", extracted.value.rating);
    println!("passes: {}", extracted.metadata.passes);

    Ok(())
}