Skip to main content

Extractor

Struct Extractor 

Source
pub struct Extractor { /* private fields */ }
Available on crate feature extract only.
Expand description

Runs extraction with explicit, opt-in recovery.

The default extractor behaves exactly like ExtractExt::extract: one provider call, strict parsing.

use allama::ChatRequest;
use allama::extract::Extractor;
use allama::mock::MockProvider;
use allama::schemars::{self, JsonSchema};

#[derive(serde::Deserialize, JsonSchema)]
struct Person {
    name: String,
}

let provider = MockProvider::with_text("```json\n{\"name\": \"Ada\"}\n```");
let request = ChatRequest::new("m")
    .user("Ada wrote the first program.")
    .with_option(allama::extract::ExtractionMode::Native);
let extracted = Extractor::new()
    .repair(true) // strip the markdown fence locally
    .retries(1) // ask once more if it still does not parse
    .extract::<_, Person>(&provider, &request)
    .await?;
assert!(extracted.metadata.repaired);

§Extracting from a conversation

An extraction keeps every field of the request it is given (see Typical use). To extract from a conversation without the rest of the request, start a fresh request from its messages, or clear the tools on a copy. Either way, ask for the value in a final user message: a conversation that ends on the assistant’s answer would send that answer as a prefill, and several providers refuse a prefill with structured output.

use allama::extract::ExtractionMode;
use allama::{ChatRequest, ToolDefinition};

let chat = ChatRequest::new("m")
    .user("Who wrote the first program?")
    .assistant("Ada Lovelace, in 1843.")
    .tools([ToolDefinition::new("lookup", serde_json::json!({"type": "object"}))])
    .temperature(0.7);

// Only the model and the conversation, with the instruction last:
let fresh = ChatRequest::new(&chat.model)
    .extend_messages(chat.messages.clone())
    .user("Extract the person from this conversation.")
    .with_option(ExtractionMode::ForcedTool);

// Everything but the tools:
let mut without_tools = chat
    .clone()
    .user("Extract the person from this conversation.")
    .with_option(ExtractionMode::ForcedTool);
without_tools.tools.clear();
without_tools.tool_choice = None;

Implementations§

Source§

impl Extractor

Source

pub fn plan<T: JsonSchema>( &self, provider: &(impl ChatProvider + ?Sized), request: &ChatRequest, ) -> Result<ExtractionPlan>

Plan an extraction of T from request without sending it: the mode it would use, the enforcement its first pass would report, and how much of that the provider could check.

It runs the provider’s validation (validate_chat_request) on a schema-only copy of the extraction request: the same model, options, tools, response format and policy, with one placeholder message in place of the conversation, so the plan does not depend on what the conversation holds. Every first-party adapter validates with a dry run of the call (ValidationCoverage::Encoding).

§Errors

What the call would fail with before sending: a refusal under UnsupportedPolicy::Error, in terms of T, or an Error::Extract when no mode can be used.

Source§

impl Extractor

Source

pub fn new() -> Self

An extractor with no recovery.

Source

pub fn repair(self, enabled: bool) -> Self

Repair malformed output locally before giving up: strip markdown code fences and prose around the JSON value. Makes no provider calls; ExtractionMetadata::repaired reports when it was needed.

When the repaired value is valid JSON but does not fit T, the ExtractError::Parse message (and the retry feedback) is its error, such as an unknown enum variant, not the error of the unrepaired text.

Source

pub fn retries(self, retries: u32) -> Self

When the output cannot be used (unparseable, no structured text, or the extraction tool was not called once), send the failure back to the model and ask again, up to retries times.

The feedback names the failure: output that is not valid JSON (“it was not valid JSON: …”) is told apart from valid JSON that does not fit the schema (“it did not match the schema at items[2].price: …”). The path (ExtractError::Parse’s path) names the field to fix; it is found by deserializing again, only after a failure.

Each retry is one more provider call, billed and with its own latency; ExtractionMetadata counts the calls and sums their usage, on success and on failure. Truncated output (ExtractError::Truncated) is not retried: the same token limit would cut it off again. Neither is a refusal (ExtractError::Refused): asking again would pay for the same refusal.

A retry replays the failed reply. Each tool call in it is answered with an ordinary tool result whose text says why it could not be used, not a failed one (ToolMessage::is_error), so the retry turn is sent as is on every adapter, including one whose endpoint has no tool error flag under UnsupportedPolicy::Error.

Provider errors are not retried here; use RetryingProvider for those. A provider error on a retry is returned as itself, not as an extraction failure, with the passes and usage so far attached: read them with ExtractionMetadata::from_error.

Source

pub fn allow_undeclared_keys(self, allow: bool) -> Self

Accept output with keys the schema of T does not declare, as serde does, instead of rejecting it (the default; see undeclared keys).

use allama::extract::{ExtractionMode, Extractor};
use allama::mock::{ChatResponseBuilder, MockProvider};
use allama::schemars::{self, JsonSchema};
use allama::{ChatRequest, RequestField, Warning};

#[derive(serde::Deserialize, JsonSchema)]
struct Person {
    name: String,
}

// A provider that reports it approximated the schema, so the
// output is checked for undeclared keys.
let approximated = Warning::new(
    allama::WarningKind::Approximated,
    allama::Subject::Field { field: RequestField::ResponseFormat },
    "the grammar engine may not enforce every keyword; the schema was sent unchecked",
);
let provider = MockProvider::with_response(
    ChatResponseBuilder::new()
        .text(r#"{"name": "Ada", "born": 1815}"#)
        .warning(approximated)
        .build(),
);
let request = ChatRequest::new("m").user("Ada").with_option(ExtractionMode::Native);
let extracted = Extractor::new()
    .allow_undeclared_keys(true)
    .extract::<_, Person>(&provider, &request)
    .await?;
assert_eq!(extracted.value.name, "Ada");
Source

pub fn min_enforcement(self, floor: SchemaEnforcement) -> Self

Require at least floor schema enforcement (ExtractionMetadata::enforcement), failing with ExtractError::EnforcementBelow otherwise. Off by default.

When the provider’s validation is a dry run of the call (ValidationCoverage::Encoding, every first-party adapter), the floor is checked before sending, on a plan of the extraction, and a plan below it costs nothing. Otherwise (ValidationCoverage::Capabilities) the enforcement is known only from the response, so the floor is checked after each pass, and the failure carries that pass’s response and usage: it is a paid call.

Approximate is the practical portable floor: Exact rules out providers whose strict mode drops keywords the schema of T uses, such as Anthropic, which removes minimum and maximum, so any unsigned integer field is Approximate there.

Source

pub async fn extract<P, T>( &self, provider: &P, request: &ChatRequest, ) -> Result<Extracted<T>>

Extract a T from the response to request. Every field of request is kept except the ones the selected mode replaces (response format, or tools and tool choice); see extracting from a conversation.

§Errors

See ExtractExt::extract. After the last retry, the last extraction failure is returned, with the last response and the usage of every pass.

Source

pub fn parse<T: JsonSchema + DeserializeOwned>( &self, response: ChatResponse, mode: ExtractionMode, ) -> Result<Extracted<T>>

Parse a T from a response you already have, with this extractor’s repair setting. Makes no provider calls.

Use it for responses obtained outside an extraction call: a batch result, a recorded response, or a request you built yourself with a ResponseFormat::JsonSchema. mode says where the output is: the response text (Native) or the arguments of the single submit_structured_output tool call (ForcedTool). Auto has nothing to choose from and fails with ExtractError::Unsupported.

Strict nulls are normalized against the schema of T, as in extract; a parse failure keeps the text the model sent.

The response is returned in Extracted or in the failure. The metadata reports no passes, no usage, and no degradations (they are on the response), and enforcement is NotEnforced: whether the request asked for strict mode is unknown here.

use allama::ChatResponse;
use allama::extract::{ExtractionMode, Extractor};
use allama::schemars::{self, JsonSchema};

#[derive(serde::Deserialize, JsonSchema)]
struct Person {
    name: String,
}

let response = ChatResponse::new(vec![allama::ContentBlock::text(
    "```json\n{\"name\": \"Ada\"}\n```",
)]);
let extracted = Extractor::new()
    .repair(true)
    .parse::<Person>(response, ExtractionMode::Native)?;
assert_eq!(extracted.value.name, "Ada");
assert!(extracted.metadata.repaired);
§Errors

Returns Error::Extract when the output cannot be used.

Source

pub async fn extract_stream<P, T>( &self, provider: &P, request: &ChatRequest, ) -> Result<ExtractionStream<P::Stream, T>>
where P: ChatProvider + ?Sized, T: JsonSchema,

Stream the extraction of a T from the response to request. Local repair applies when the stream is finished; retries do not (a retry would be a new stream, which is the caller’s call to make).

§Errors

See ExtractExt::extract_stream.

Trait Implementations§

Source§

impl Clone for Extractor

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Extractor

Source§

impl Debug for Extractor

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Extractor

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more