pub struct Extractor { /* private fields */ }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
impl Extractor
Sourcepub fn plan<T: JsonSchema>(
&self,
provider: &(impl ChatProvider + ?Sized),
request: &ChatRequest,
) -> Result<ExtractionPlan>
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
impl Extractor
Sourcepub fn repair(self, enabled: bool) -> Self
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.
Sourcepub fn retries(self, retries: u32) -> Self
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.
Sourcepub fn allow_undeclared_keys(self, allow: bool) -> Self
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");Sourcepub fn min_enforcement(self, floor: SchemaEnforcement) -> Self
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.
Sourcepub async fn extract<P, T>(
&self,
provider: &P,
request: &ChatRequest,
) -> Result<Extracted<T>>
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.
Sourcepub fn parse<T: JsonSchema + DeserializeOwned>(
&self,
response: ChatResponse,
mode: ExtractionMode,
) -> Result<Extracted<T>>
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.
Sourcepub async fn extract_stream<P, T>(
&self,
provider: &P,
request: &ChatRequest,
) -> Result<ExtractionStream<P::Stream, T>>
pub async fn extract_stream<P, T>( &self, provider: &P, request: &ChatRequest, ) -> Result<ExtractionStream<P::Stream, T>>
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).