use serde::{Deserialize, Serialize};
use systemprompt_identifiers::{AiRequestId, DocumentNodeId};
use super::candidate::FactCandidate;
use super::envelope::BrainEnvelope;
use super::kind_decl::KindDecl;
use super::source::{FactSource, SourceSpan};
pub const BRAIN_EXTRACT_PURPOSE: &str = "brain.extract";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceNode {
pub node_id: DocumentNodeId,
pub hash8: String,
pub text: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceText {
pub text: String,
pub nodes: Vec<SourceNode>,
}
impl SourceText {
#[must_use]
pub fn has_span(&self, span: &SourceSpan) -> bool {
self.nodes.iter().any(|node| {
node.node_id == span.node_id
&& node.hash8 == span.hash8
&& node.text.matches(span.find.as_str()).count() > span.nth as usize
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ExtractionInput {
pub envelope: BrainEnvelope,
pub kind: KindDecl,
pub source: FactSource,
pub text: Option<SourceText>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ExtractionRecord {
pub extractor: String,
pub model: Option<String>,
pub prompt_version: Option<String>,
pub raw_output: serde_json::Value,
pub input_tokens: u32,
pub output_tokens: u32,
pub ai_request_id: Option<AiRequestId>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ExtractionOutput {
pub candidates: Vec<FactCandidate>,
pub record: ExtractionRecord,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BrainPrompt {
pub system: String,
pub user: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StrictJsonSchema {
pub name: String,
pub schema: serde_json::Value,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BrainModelRequest {
pub system: String,
pub user: String,
pub json_schema: StrictJsonSchema,
pub max_output_tokens: u32,
pub prompt_version: String,
pub purpose: String,
}
impl BrainModelRequest {
pub const DEFAULT_MAX_OUTPUT_TOKENS: u32 = 2048;
#[must_use]
pub fn new(prompt: BrainPrompt, json_schema: StrictJsonSchema) -> Self {
Self {
system: prompt.system,
user: prompt.user,
json_schema,
max_output_tokens: Self::DEFAULT_MAX_OUTPUT_TOKENS,
prompt_version: String::new(),
purpose: BRAIN_EXTRACT_PURPOSE.to_owned(),
}
}
#[must_use]
pub const fn with_max_output_tokens(mut self, max_output_tokens: u32) -> Self {
self.max_output_tokens = max_output_tokens;
self
}
#[must_use]
pub fn with_prompt_version(mut self, prompt_version: impl Into<String>) -> Self {
self.prompt_version = prompt_version.into();
self
}
#[must_use]
pub fn with_purpose(mut self, purpose: impl Into<String>) -> Self {
self.purpose = purpose.into();
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BrainModelResponse {
pub model: String,
pub text: String,
pub structured: Option<serde_json::Value>,
pub input_tokens: u32,
pub output_tokens: u32,
pub ai_request_id: Option<AiRequestId>,
}