use allama::extract::{ExtractError, Extracted, ExtractionMode, Extractor};
use allama::mock::{ChatResponseBuilder, MockProvider, MockResponse};
use allama::prelude::*;
use allama::schemars::{self, JsonSchema};
use allama::{CapabilitySupport, ChatCapability, FinishReason, Usage};
use serde::Deserialize;
use serde_json::json;
#[derive(Debug, Deserialize, JsonSchema)]
struct Review {
title: String,
rating: u8,
}
fn 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() -> anyhow::Result<()> {
let chat = ChatRequest::new("demo-model")
.system("You are a support assistant.")
.user("Summarize customer 42's review.")
.assistant("Customer 42 says Heat deserves a 10 out of 10.")
.tools([ToolDefinition::new(
"lookup_customer",
json!({"type": "object"}),
)]);
let instruction = "Extract the review from this conversation.";
let request = chat.clone().user(instruction);
match provider().extract::<Review>(&request).await {
Err(Error::Extract { failure, .. })
if matches!(failure.error(), ExtractError::ToolConflict { .. }) =>
{
println!("extraction refuses a request that carries its own tools");
}
other => println!("unexpected: {other:?}"),
}
let conversation = ChatRequest::new(&chat.model)
.extend_messages(chat.messages.clone())
.user(instruction);
let extracted: Extracted<Review> = Extractor::new()
.extract(
&provider(),
&conversation.clone().with_option(ExtractionMode::ForcedTool),
)
.await?;
println!(
"extraction from the conversation: {} ({}/10)",
extracted.value.title, extracted.value.rating
);
let flaky = MockProvider::build(|b| {
b.response(
ChatResponseBuilder::new()
.text(r#"Here you go: {"title": "Heat"}"#)
.usage(120, 12)
.build(),
)
.response(
ChatResponseBuilder::new()
.text("```json\n{\"title\": \"Heat\", \"rating\": 10}\n```")
.usage(160, 18)
.build(),
)
});
let recovered: Extracted<Review> = Extractor::new()
.repair(true)
.retries(1)
.extract(
&flaky,
&conversation.clone().with_option(ExtractionMode::Native),
)
.await?;
println!(
"recovered extraction: {} ({}/10) after {} calls, repaired: {}, total tokens: {:?}",
recovered.value.title,
recovered.value.rating,
recovered.metadata.passes,
recovered.metadata.repaired,
recovered.metadata.usage.as_ref().and_then(Usage::total),
);
let truncated = MockProvider::with_response(
ChatResponseBuilder::new()
.text(r#"{"title": "He"#)
.finish_reason(FinishReason::Length)
.usage(120, 8)
.build(),
);
match Extractor::new()
.retries(2)
.extract::<_, Review>(
&truncated,
&conversation.with_option(ExtractionMode::Native),
)
.await
{
Err(Error::Extract { failure, .. }) => println!(
"failed: {failure} (calls: {}, total tokens: {:?}, partial output: {:?})",
failure.metadata().passes,
failure.metadata().usage.as_ref().and_then(Usage::total),
failure.response().and_then(ChatResponse::text),
),
other => println!("unexpected: {other:?}"),
}
let stored = ChatResponseBuilder::new()
.text(r#"{"title": "Heat", "rating": 10}"#)
.build();
let parsed: Extracted<Review> = Extractor::new().parse(stored, ExtractionMode::Native)?;
println!("parsed stored response: {}", parsed.value.title);
Ok(())
}