use std::{
path::PathBuf,
time::{Duration, SystemTime},
};
use crate::model::{ChatRole, SourceRef};
#[cfg(feature = "grobid")]
use crate::references::{
CitationAttachment, ReferenceEntry, ReferenceOptions, ReferenceProgress, ReferenceReport,
};
use super::RagSetup;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RequestId(u64);
impl RequestId {
pub(crate) fn new(value: u64) -> Self {
Self(value)
}
pub fn get(self) -> u64 {
self.0
}
}
impl std::fmt::Display for RequestId {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "#{}", self.0)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AttachmentSource {
Text(String),
File(PathBuf),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingAttachment {
pub id: String,
pub label: String,
pub source: AttachmentSource,
}
impl PendingAttachment {
pub fn file(id: impl Into<String>, label: impl Into<String>, path: impl Into<PathBuf>) -> Self {
Self {
id: id.into(),
label: label.into(),
source: AttachmentSource::File(path.into()),
}
}
pub fn text(id: impl Into<String>, label: impl Into<String>, text: impl Into<String>) -> Self {
Self {
id: id.into(),
label: label.into(),
source: AttachmentSource::Text(text.into()),
}
}
}
#[derive(Clone, Debug)]
pub enum RagCommand {
Ask {
id: RequestId,
prompt: String,
attachments: Vec<PendingAttachment>,
},
Stop {
id: RequestId,
},
NewSession,
LoadSession {
id: String,
},
RefreshSessions,
RefreshLibrary,
SyncCorpus {
id: RequestId,
name: String,
folder: PathBuf,
},
StopSync {
id: RequestId,
},
#[cfg(feature = "grobid")]
ExtractReferences {
id: RequestId,
folder: PathBuf,
options: ReferenceOptions,
},
#[cfg(feature = "grobid")]
StopReferences {
id: RequestId,
},
#[cfg(feature = "grobid")]
RefreshReferences {
folder: PathBuf,
},
Apply {
setup: Box<RagSetup>,
},
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct IngestProgress {
pub corpus: String,
pub document: Option<String>,
pub index: usize,
pub total: usize,
pub chunks: Option<usize>,
pub embedded: usize,
pub total_chunks: usize,
pub phase: IngestPhase,
pub failed: Option<String>,
}
impl IngestProgress {
pub fn fraction(&self) -> Option<f32> {
(self.total_chunks > 0).then(|| self.embedded as f32 / self.total_chunks as f32)
}
pub fn summary(&self) -> String {
let corpus = if self.corpus.is_empty() {
"corpus".to_owned()
} else {
self.corpus.clone()
};
let document = self.document.as_deref().unwrap_or("documents");
if self.total_chunks > 0 {
format!(
"{corpus} · {document} · {}/{} chunks",
self.embedded, self.total_chunks
)
} else if self.total > 0 {
format!(
"{corpus} · {document} · {}/{} documents",
(self.index + 1).min(self.total),
self.total
)
} else {
format!("{corpus} · {document}")
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum IngestPhase {
#[default]
Starting,
Parsed,
Embedding,
Stored,
Failed,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SyncSummary {
pub corpus: String,
pub documents: usize,
pub chunks: usize,
pub failed: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LibraryDocument {
pub corpus: String,
pub name: String,
pub parser: String,
pub chunker: String,
pub path: Option<PathBuf>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct LibrarySnapshot {
pub documents: Vec<LibraryDocument>,
pub chunks: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SessionInfo {
pub id: String,
pub turn_count: usize,
pub summary: Option<String>,
pub updated: SystemTime,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SessionTurn {
pub role: ChatRole,
pub text: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RagReply {
pub answer: String,
pub model: String,
pub chunks: Option<usize>,
pub sources: Vec<SourceRef>,
pub elapsed: Option<Duration>,
pub rewritten_query: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AgentInfo {
pub workspace: PathBuf,
pub chat_backend: String,
pub chat_model: String,
pub embed_backend: String,
pub embed_model: String,
pub memory: String,
pub pdf_backend: String,
pub top_k: usize,
pub chunk_size: usize,
pub chunk_overlap: usize,
pub chunks: usize,
pub documents: usize,
}
#[derive(Clone, Debug)]
pub enum RagEvent {
AgentReady(AgentInfo),
AgentFailed {
message: String,
},
SessionStarted {
id: String,
},
SessionLoaded {
id: String,
turns: Vec<SessionTurn>,
},
Sessions(Vec<SessionInfo>),
Started {
id: RequestId,
},
Chunk {
id: RequestId,
delta: String,
},
Finished {
id: RequestId,
reply: RagReply,
},
Stopped {
id: RequestId,
partial: String,
},
Failed {
id: RequestId,
message: String,
},
Library(LibrarySnapshot),
SyncStarted {
id: RequestId,
corpus: String,
},
SyncProgress {
id: RequestId,
progress: IngestProgress,
},
SyncFinished {
id: RequestId,
summary: SyncSummary,
},
SyncStopped {
id: RequestId,
},
SyncFailed {
id: RequestId,
message: String,
},
#[cfg(feature = "grobid")]
ReferencesStarted {
id: RequestId,
folder: PathBuf,
},
#[cfg(feature = "grobid")]
ReferencesProgress {
id: RequestId,
progress: ReferenceProgress,
},
#[cfg(feature = "grobid")]
ReferencesLoaded {
entries: Vec<ReferenceEntry>,
bibliography: PathBuf,
},
#[cfg(feature = "grobid")]
ReferencesFinished {
id: RequestId,
report: ReferenceReport,
},
#[cfg(feature = "grobid")]
ReferencesStopped {
id: RequestId,
},
#[cfg(feature = "grobid")]
ReferencesFailed {
id: RequestId,
message: String,
},
#[cfg(feature = "grobid")]
Citations {
resolved: Vec<CitationAttachment>,
missed: Vec<String>,
},
Notice(String),
}