chore: extraction model api (no proxying implemented yet)
This commit is contained in:
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -1496,9 +1496,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.37"
|
||||
version = "0.2.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb"
|
||||
checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
"jiff-core",
|
||||
@@ -1523,9 +1523,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.37"
|
||||
version = "0.2.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212"
|
||||
checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c"
|
||||
dependencies = [
|
||||
"jiff-core",
|
||||
"proc-macro2",
|
||||
@@ -1633,6 +1633,7 @@ dependencies = [
|
||||
"headers",
|
||||
"indexmap 2.14.2",
|
||||
"itertools 0.15.0",
|
||||
"jiff",
|
||||
"mitsein",
|
||||
"rand 0.10.3",
|
||||
"reqwest",
|
||||
|
||||
@@ -16,6 +16,7 @@ futures = "0.3.34"
|
||||
headers = "0.4.2"
|
||||
indexmap = "2.14.2"
|
||||
itertools = "0.15.0"
|
||||
jiff = { version = "0.2.38", features = ["serde"] }
|
||||
mitsein = { version = "0.9.0", features = ["indexmap"] }
|
||||
rand = "0.10.3"
|
||||
reqwest = { version = "0.13.5", features = ["brotli", "cookies", "deflate", "form", "gzip", "hickory-dns", "json", "multipart", "query", "rustls-no-provider", "socks", "stream", "zstd"], default-features = false }
|
||||
|
||||
524
src/openai/extraction.rs
Normal file
524
src/openai/extraction.rs
Normal file
@@ -0,0 +1,524 @@
|
||||
//! System One-style models that can extract, compute, estimate,
|
||||
//! and forecast values from inputs.
|
||||
//! They are strongly typed and output confidence scores and probabilities against their outputs.
|
||||
//!
|
||||
//! Request: [`ExtractionRequest`] · Response: [`ExtractionResponse`]
|
||||
|
||||
use jiff::civil::{Date, DateTime, Time};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// The item type required for `list` fields.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ListItems {
|
||||
/// List of text items.
|
||||
Text,
|
||||
/// List of numbers.
|
||||
Number,
|
||||
/// List of ISO dates.
|
||||
Date,
|
||||
}
|
||||
|
||||
/// Scale for estimate/forecast grids.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Scale {
|
||||
/// Linear scale (default).
|
||||
#[default]
|
||||
Linear,
|
||||
/// Logarithmic scale.
|
||||
Log,
|
||||
}
|
||||
|
||||
/// Interval for forecast fields.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ForecastInterval {
|
||||
/// One step of the grid.
|
||||
Step,
|
||||
/// Per minute.
|
||||
Minute,
|
||||
/// Per hour.
|
||||
Hour,
|
||||
/// Per day.
|
||||
Day,
|
||||
/// Per week.
|
||||
Week,
|
||||
/// Per month.
|
||||
Month,
|
||||
/// Per quarter.
|
||||
Quarter,
|
||||
/// Per year.
|
||||
Year,
|
||||
}
|
||||
|
||||
/// A value that may be a number or an ISO date string. Used for the
|
||||
/// `min`/`max` bounds of estimate/forecast fields (ISO date for date
|
||||
/// estimates) and for the `step` grid.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum NumberOrDate {
|
||||
/// A numeric bound or step size.
|
||||
Number(f64),
|
||||
/// An ISO date string (for date estimates).
|
||||
Date(Date),
|
||||
}
|
||||
|
||||
/// A step size for estimate grids: a positive number (days for date
|
||||
/// estimates), or the strings `"month"` / `"year"`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum EstimateStep {
|
||||
/// A positive numeric step (days for date estimates).
|
||||
Number(f64),
|
||||
/// Calendar-aware step: `"month"`.
|
||||
Month,
|
||||
/// Calendar-aware step: `"year"`.
|
||||
Year,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DurationUnit {
|
||||
Seconds,
|
||||
Minutes,
|
||||
Hours,
|
||||
Days,
|
||||
Weeks,
|
||||
Months,
|
||||
Years,
|
||||
}
|
||||
|
||||
/// A typed field specification
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "type", rename_all = "lowercase")]
|
||||
pub enum Field {
|
||||
/// A string extracted from the input.
|
||||
#[serde(rename = "text")]
|
||||
Text {
|
||||
/// What the field should contain.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instructions: Option<String>,
|
||||
/// Probability of null above which the value is null (default 0.5).
|
||||
/// Must be in `(0, 1]`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
null_threshold: Option<f64>,
|
||||
},
|
||||
/// A numeric value.
|
||||
#[serde(rename = "number")]
|
||||
Number {
|
||||
/// What the field should contain.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instructions: Option<String>,
|
||||
/// Unit label for the number (e.g. `USD`).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unit: Option<String>,
|
||||
/// Whether the number must be an integer. Numbers and durations only.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
integer: Option<bool>,
|
||||
/// Number of decimal places (0–6). Numbers, durations and number
|
||||
/// lists only.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
decimals: Option<u8>,
|
||||
/// Probability of null above which the value is null (default 0.5).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
null_threshold: Option<f64>,
|
||||
},
|
||||
/// An ISO date value.
|
||||
#[serde(rename = "date")]
|
||||
Date {
|
||||
/// What the field should contain.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instructions: Option<String>,
|
||||
/// Probability of null above which the value is null (default 0.5).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
null_threshold: Option<f64>,
|
||||
},
|
||||
/// An ISO time value.
|
||||
#[serde(rename = "time")]
|
||||
Time {
|
||||
/// What the field should contain.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instructions: Option<String>,
|
||||
/// Probability of null above which the value is null (default 0.5).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
null_threshold: Option<f64>,
|
||||
},
|
||||
/// An ISO date-time value.
|
||||
#[serde(rename = "datetime")]
|
||||
Datetime {
|
||||
/// What the field should contain.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instructions: Option<String>,
|
||||
/// Probability of null above which the value is null (default 0.5).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
null_threshold: Option<f64>,
|
||||
},
|
||||
/// A duration value.
|
||||
#[serde(rename = "duration")]
|
||||
Duration {
|
||||
/// What the field should contain.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instructions: Option<String>,
|
||||
/// Unit label
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unit: Option<DurationUnit>,
|
||||
/// Whether the duration must be an integer. Numbers and durations only.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
integer: Option<bool>,
|
||||
/// Number of decimal places (0–6). Numbers, durations and number
|
||||
/// lists only.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
decimals: Option<u8>,
|
||||
/// Probability of null above which the value is null (default 0.5).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
null_threshold: Option<f64>,
|
||||
},
|
||||
/// A list of typed items.
|
||||
#[serde(rename = "list")]
|
||||
List {
|
||||
/// What the field should contain.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instructions: Option<String>,
|
||||
/// Required for list fields: the type of each item.
|
||||
items: ListItems,
|
||||
/// Number of decimal places (0–6) for number lists.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
decimals: Option<u8>,
|
||||
/// Probability of null above which the value is null (default 0.5).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
null_threshold: Option<f64>,
|
||||
},
|
||||
/// A probabilistic estimate over a bounded grid.
|
||||
#[serde(rename = "estimate")]
|
||||
Estimate {
|
||||
/// What the field should contain.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instructions: Option<String>,
|
||||
/// Required for estimate/forecast: lower bound (a number, or an ISO
|
||||
/// date for date estimates).
|
||||
min: NumberOrDate,
|
||||
/// Required for estimate/forecast: upper bound (a number, or an ISO
|
||||
/// date for date estimates).
|
||||
max: NumberOrDate,
|
||||
/// Estimate grid: a positive number (days for date estimates), or
|
||||
/// `"month"` / `"year"`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
step: Option<EstimateStep>,
|
||||
/// Grid scale: `linear` (default) or `log`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
scale: Option<Scale>,
|
||||
/// Probability of null above which the value is null (default 0.5).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
null_threshold: Option<f64>,
|
||||
},
|
||||
/// A forecast: a sequence of estimates over a horizon.
|
||||
#[serde(rename = "forecast")]
|
||||
Forecast {
|
||||
/// What the field should contain.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instructions: Option<String>,
|
||||
/// Required for estimate/forecast: lower bound (a number, or an ISO
|
||||
/// date for date estimates).
|
||||
min: NumberOrDate,
|
||||
/// Required for estimate/forecast: upper bound (a number, or an ISO
|
||||
/// date for date estimates).
|
||||
max: NumberOrDate,
|
||||
/// Estimate grid: a positive number (days for date estimates), or
|
||||
/// `"month"` / `"year"`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
step: Option<EstimateStep>,
|
||||
/// Grid scale: `linear` (default) or `log`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
scale: Option<Scale>,
|
||||
/// Forecast only: number of steps (1–64).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
horizon: Option<u32>,
|
||||
/// Forecast only: the interval between steps.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
interval: Option<ForecastInterval>,
|
||||
/// Probability of null above which the value is null (default 0.5).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
null_threshold: Option<f64>,
|
||||
},
|
||||
}
|
||||
|
||||
/// An image input. Either a data URL string or an explicit base64 object.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum Image {
|
||||
/// A data URL string (must start with `data:`, case-insensitive).
|
||||
DataUrl(
|
||||
/// The data URL, e.g. `data:image/png;base64,...`.
|
||||
String,
|
||||
),
|
||||
/// An explicitly split base64 image.
|
||||
Base64 {
|
||||
/// MIME content type of the image (PNG, JPEG or WebP).
|
||||
content_type: String,
|
||||
/// Base64-encoded image bytes.
|
||||
base64: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Optional request settings.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
pub struct RequestOptions {
|
||||
/// Add 99 percentiles to estimates (default `false`).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub include_quantiles: Option<bool>,
|
||||
/// Return the text that spans index into (default `false`).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub return_state_text: Option<bool>,
|
||||
}
|
||||
|
||||
/// The extraction request body.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ExtractionRequest<Model> {
|
||||
/// The model selector
|
||||
pub model: Model,
|
||||
/// The text to read and predict from. Must be non-empty unless images
|
||||
/// are sent. Text answers are spans of this string.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub state: Option<String>,
|
||||
/// Required. Map of field name to a typed field spec. At least one
|
||||
/// field; no upper limit. Fields that do not fit next to the state in
|
||||
/// one 8,192-token pass are split across parallel passes. Field names
|
||||
/// must match `^[A-Za-z_][A-Za-z0-9_.\-]{0,63}$`.
|
||||
pub fields: HashMap<String, Field>,
|
||||
/// Optional embedded PNG, JPEG or WebP images placed before the state
|
||||
/// (max 4; 4 MiB each, 8 MiB total decoded; request body max 16 MiB).
|
||||
/// Remote URLs are not accepted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub images: Option<Vec<Image>>,
|
||||
/// ISO date that anchors dates written without a year.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reference_date: Option<Date>,
|
||||
/// Optional request settings.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub options: Option<RequestOptions>,
|
||||
}
|
||||
|
||||
/// The value of an answer. Shape depends on the field type; `null` when the
|
||||
/// input does not determine it.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum List {
|
||||
// A list of strings
|
||||
Strings(Vec<String>),
|
||||
// A list of numbers
|
||||
Numbers(Vec<f64>),
|
||||
// A list of dates
|
||||
Dates(Vec<Date>),
|
||||
}
|
||||
|
||||
/// A forecast step: one estimate per step, with median and interval bounds.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ForecastStep {
|
||||
/// The point (median) value for this step.
|
||||
pub value: f64,
|
||||
/// Mean of the distribution for this step.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mean: Option<f64>,
|
||||
/// Lower bound of the interval for this step.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub p10: Option<f64>,
|
||||
/// Upper bound of the interval for this step.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub p90: Option<f64>,
|
||||
}
|
||||
|
||||
/// One answer per requested field, keyed by field name. Tagged by `type`,
|
||||
/// mirroring the field spec that produced it.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "type", rename_all = "lowercase")]
|
||||
pub enum Answer {
|
||||
/// Answer for a text field: an exact span of the state, with character
|
||||
/// offsets.
|
||||
#[serde(rename = "text")]
|
||||
Text {
|
||||
/// The extracted value, or null when the input does not determine it.
|
||||
value: Option<String>,
|
||||
/// The model's best guess before applying the null threshold.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
guess: Option<String>,
|
||||
/// Probability that the field is null.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p_null: Option<f64>,
|
||||
/// Confidence score in `[0, 1]`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
confidence: Option<f64>,
|
||||
/// Character offsets `[start, end]` of the span into the state.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
span: Option<[usize; 2]>,
|
||||
},
|
||||
/// Answer for a number field.
|
||||
#[serde(rename = "number")]
|
||||
Number {
|
||||
/// The extracted value, or null when the input does not determine it.
|
||||
value: Option<f64>,
|
||||
/// The model's best guess before applying the null threshold.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
guess: Option<f64>,
|
||||
/// Probability that the field is null.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p_null: Option<f64>,
|
||||
/// Confidence score in `[0, 1]`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
confidence: Option<f64>,
|
||||
},
|
||||
/// Answer for a date field.
|
||||
#[serde(rename = "date")]
|
||||
Date {
|
||||
/// The computed/extracted ISO date, or null.
|
||||
value: Option<Date>,
|
||||
/// The model's best guess before applying the null threshold.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
guess: Option<Date>,
|
||||
/// Probability that the field is null.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p_null: Option<f64>,
|
||||
/// Confidence score in `[0, 1]`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
confidence: Option<f64>,
|
||||
},
|
||||
/// Answer for a time field.
|
||||
#[serde(rename = "time")]
|
||||
Time {
|
||||
/// The extracted ISO time, or null.
|
||||
value: Option<Time>,
|
||||
/// The model's best guess before applying the null threshold.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
guess: Option<Time>,
|
||||
/// Probability that the field is null.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p_null: Option<f64>,
|
||||
/// Confidence score in `[0, 1]`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
confidence: Option<f64>,
|
||||
},
|
||||
/// Answer for a datetime field.
|
||||
#[serde(rename = "datetime")]
|
||||
Datetime {
|
||||
/// The extracted ISO date-time, or null.
|
||||
value: Option<DateTime>,
|
||||
/// The model's best guess before applying the null threshold.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
guess: Option<DateTime>,
|
||||
/// Probability that the field is null.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p_null: Option<f64>,
|
||||
/// Confidence score in `[0, 1]`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
confidence: Option<f64>,
|
||||
},
|
||||
/// Answer for a duration field.
|
||||
#[serde(rename = "duration")]
|
||||
Duration {
|
||||
/// The extracted duration, or null.
|
||||
value: Option<f64>,
|
||||
/// The model's best guess before applying the null threshold.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
guess: Option<f64>,
|
||||
/// Probability that the field is null.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p_null: Option<f64>,
|
||||
/// Confidence score in `[0, 1]`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
confidence: Option<f64>,
|
||||
},
|
||||
/// Answer for a list field: values with per-item spans.
|
||||
#[serde(rename = "list")]
|
||||
List {
|
||||
/// The extracted items, or null.
|
||||
value: Option<List>,
|
||||
/// Per-item character offsets `[start, end]` into the state.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
spans: Option<Vec<[usize; 2]>>,
|
||||
/// Probability that the field is null.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p_null: Option<f64>,
|
||||
/// Confidence score in `[0, 1]`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
confidence: Option<f64>,
|
||||
},
|
||||
/// Answer for an estimate field: a distribution summary.
|
||||
#[serde(rename = "estimate")]
|
||||
Estimate {
|
||||
/// The point estimate (median).
|
||||
value: NumberOrDate,
|
||||
/// Mean of the estimated distribution.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
mean: Option<f64>,
|
||||
/// Median of the estimated distribution.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
median: Option<f64>,
|
||||
/// 10th percentile of the estimated distribution.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p10: Option<f64>,
|
||||
/// 90th percentile of the estimated distribution.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p90: Option<f64>,
|
||||
/// Probability the true value is below the grid minimum.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p_below: Option<f64>,
|
||||
/// Probability the true value is above the grid maximum.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p_above: Option<f64>,
|
||||
/// 99 percentiles, when `options.include_quantiles` is set.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
quantiles: Option<Vec<f64>>,
|
||||
},
|
||||
/// Answer for a forecast field: one estimate per step.
|
||||
#[serde(rename = "forecast")]
|
||||
Forecast {
|
||||
/// One estimate per step.
|
||||
#[serde(default)]
|
||||
steps: Vec<ForecastStep>,
|
||||
/// Probability that the field is null.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
p_null: Option<f64>,
|
||||
/// Confidence score in `[0, 1]`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
confidence: Option<f64>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Token and pass accounting for a request.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Usage {
|
||||
/// Tokens consumed by the state text.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub state_tokens: Option<u64>,
|
||||
/// Tokens consumed by images.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub image_tokens: Option<u64>,
|
||||
/// Tokens consumed by the field schema.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub schema_tokens: Option<u64>,
|
||||
/// Total input tokens.
|
||||
pub input_tokens: u64,
|
||||
/// Total output tokens.
|
||||
pub output_tokens: u64,
|
||||
/// Number of parallel passes used.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub passes: Option<u64>,
|
||||
}
|
||||
|
||||
/// The extraction response body.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ExtractionResponse<Model> {
|
||||
/// The model that produced this response
|
||||
pub model: Model,
|
||||
/// One answer per field, keyed by field name. `value` is null when the
|
||||
/// input does not determine it.
|
||||
pub answers: HashMap<String, Answer>,
|
||||
/// Whether the input was truncated to fit the context window.
|
||||
pub truncated: bool,
|
||||
/// The text that spans index into, when `options.return_state_text` is set.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub state_text: Option<String>,
|
||||
/// Token and pass accounting for the request.
|
||||
pub usage: Usage,
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod chat;
|
||||
pub mod decisions;
|
||||
pub mod embeddings;
|
||||
pub mod extraction;
|
||||
pub mod models;
|
||||
pub mod system_one;
|
||||
|
||||
@@ -21,8 +21,6 @@ use secrecy::ExposeSecret;
|
||||
use serde::Deserialize;
|
||||
use serde_with::DeserializeFromStr;
|
||||
use serde_with::SerializeDisplay;
|
||||
use tokio::select;
|
||||
use tokio::time::sleep;
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::Infallible;
|
||||
use std::fmt::Display;
|
||||
@@ -31,6 +29,8 @@ use std::time::Duration;
|
||||
use strum::EnumString;
|
||||
use strum::VariantArray;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::select;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[derive(Deserialize, TypedPath)]
|
||||
#[typed_path("/v1/chat/completions")]
|
||||
@@ -201,7 +201,6 @@ impl Shortcut {
|
||||
(P::OpenRouter, "poolside/laguna-s-2.1:free".into()),
|
||||
(P::VercelAiGateway, "stepfun/step-3.7-flash".into()),
|
||||
(P::VercelAiGateway, "inclusionai/ling-3.0-flash-fin".into()),
|
||||
|
||||
(
|
||||
P::VercelAiGateway,
|
||||
"inclusionai/ling-3.0-flash-sante".into(),
|
||||
@@ -259,7 +258,10 @@ impl Shortcut {
|
||||
(P::Ollama, "nemotron-3-ultra".into()),
|
||||
(P::NvidiaNim, "nvidia/nemotron-3-ultra-550b-a55b".into()),
|
||||
(P::Kilo, "nvidia-nemotron-3-ultra-550b-a55b-free".into()),
|
||||
(P::OpenRouter, "nvidia/nemotron-3-ultra-550b-a55b:free".into()),
|
||||
(
|
||||
P::OpenRouter,
|
||||
"nvidia/nemotron-3-ultra-550b-a55b:free".into(),
|
||||
),
|
||||
(P::OpenCode, "nemotron-3-ultra-free".into()),
|
||||
],
|
||||
|
||||
@@ -268,8 +270,14 @@ impl Shortcut {
|
||||
(P::HuggingFace, "zai-org/GLM-5.3".into()),
|
||||
(P::CloudflareWorkersAi, "@cf/zai-org/glm-5.3".into()),
|
||||
(P::Mistral, "zai-glm-5-3".into()),
|
||||
(P::VercelAiGateway, "deepseek/deepseek-v4.1-flash-1017".into()),
|
||||
(P::CloudflareWorkersAi, "@cf/deepseek-ai/deepseek-v4.1-flash-1017".into()),
|
||||
(
|
||||
P::VercelAiGateway,
|
||||
"deepseek/deepseek-v4.1-flash-1017".into(),
|
||||
),
|
||||
(
|
||||
P::CloudflareWorkersAi,
|
||||
"@cf/deepseek-ai/deepseek-v4.1-flash-1017".into(),
|
||||
),
|
||||
(P::OpenCode, "muse-spark-1.3-contributor-free".into()),
|
||||
(P::NvidiaNim, "moonshotai/kimi-k3".into()),
|
||||
(P::HuggingFace, "moonshotai/Kimi-K3".into()),
|
||||
|
||||
@@ -73,8 +73,10 @@ impl Shortcut {
|
||||
|
||||
match self {
|
||||
S::CodestralEmbed2505 => vec![(P::Mistral, "codestral-embed-2505".into())],
|
||||
S::CohereEmbedV4_0 => vec![(P::Cohere, "embed-v4.0".into()),
|
||||
(P::VercelAiGateway, "cohere/embed-v4.0".into())],
|
||||
S::CohereEmbedV4_0 => vec![
|
||||
(P::Cohere, "embed-v4.0".into()),
|
||||
(P::VercelAiGateway, "cohere/embed-v4.0".into()),
|
||||
],
|
||||
S::CohereEmbedV5_0Fast => vec![
|
||||
(P::Cohere, "embed-v5.0-fast".into()),
|
||||
(P::VercelAiGateway, "cohere/embed-v5.0-fast".into()),
|
||||
@@ -209,18 +211,22 @@ pub async fn create_embeddings(
|
||||
.build()
|
||||
})
|
||||
.collect(),
|
||||
Provider::VercelAiGateway => state
|
||||
.app_args
|
||||
.vercel_ai_gateway_api_key
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|api_key| {
|
||||
Upstream::builder()
|
||||
.url("https://ai-gateway.vercel.sh/v1/embeddings".parse().expect("TODO"))
|
||||
.api_key(api_key)
|
||||
.build()
|
||||
})
|
||||
.collect(),
|
||||
Provider::VercelAiGateway => state
|
||||
.app_args
|
||||
.vercel_ai_gateway_api_key
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|api_key| {
|
||||
Upstream::builder()
|
||||
.url(
|
||||
"https://ai-gateway.vercel.sh/v1/embeddings"
|
||||
.parse()
|
||||
.expect("TODO"),
|
||||
)
|
||||
.api_key(api_key)
|
||||
.build()
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
if let Ok(upstreams) = Vec1::try_from(upstreams) {
|
||||
|
||||
@@ -12,8 +12,8 @@ use url::Url;
|
||||
|
||||
mod chat;
|
||||
mod decisions;
|
||||
mod frontend;
|
||||
mod embeddings;
|
||||
mod frontend;
|
||||
mod models;
|
||||
mod system_one;
|
||||
|
||||
|
||||
@@ -53,7 +53,10 @@ impl Shortcut {
|
||||
match self {
|
||||
S::S => {
|
||||
vec![
|
||||
(P::VercelAiGateway, "perplexity/pplx-decider-flash-v1.2".into()),
|
||||
(
|
||||
P::VercelAiGateway,
|
||||
"perplexity/pplx-decider-flash-v1.2".into(),
|
||||
),
|
||||
(P::LiquidAi, "d1:free".into()),
|
||||
(P::CloudflareWorkersAi, "clef-omni".into()),
|
||||
(P::CloudflareWorkersAi, "clef-flash".into()),
|
||||
@@ -74,9 +77,7 @@ impl Shortcut {
|
||||
}
|
||||
|
||||
S::L => {
|
||||
vec![
|
||||
(P::VercelAiGateway, "liquid/d1-ultra".into())
|
||||
]
|
||||
vec![(P::VercelAiGateway, "liquid/d1-ultra".into())]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user