use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
use core::future::Future;
use futures_core::Stream;
use futures_lite::{StreamExt, pin};
#[derive(Clone, Debug)]
pub struct ResearchRequest {
pub query: String,
pub instructions: Option<String>,
pub sources: Vec<ResearchSource>,
pub options: ResearchOptions,
}
impl ResearchRequest {
#[must_use]
pub fn new(query: impl Into<String>) -> Self {
Self {
query: query.into(),
instructions: None,
sources: Vec::new(),
options: ResearchOptions::default(),
}
}
#[must_use]
pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = Some(instructions.into());
self
}
#[must_use]
pub fn with_source(mut self, source: ResearchSource) -> Self {
self.sources.push(source);
self
}
pub fn push_source(&mut self, source: ResearchSource) {
self.sources.push(source);
}
#[must_use]
pub const fn options(mut self, options: ResearchOptions) -> Self {
self.options = options;
self
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ResearchOptions {
pub max_interactions: Option<u16>,
pub allow_web_browsing: bool,
pub allow_code_execution: bool,
pub temperature: Option<f32>,
}
impl ResearchOptions {
#[must_use]
pub fn max_interactions(mut self, value: impl Into<Option<u16>>) -> Self {
self.max_interactions = value.into();
self
}
#[must_use]
pub const fn web_browsing(mut self, allow: bool) -> Self {
self.allow_web_browsing = allow;
self
}
#[must_use]
pub const fn code_execution(mut self, allow: bool) -> Self {
self.allow_code_execution = allow;
self
}
#[must_use]
pub fn temperature(mut self, temperature: impl Into<Option<f32>>) -> Self {
self.temperature = temperature.into();
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ResearchSource {
Url {
url: String,
label: Option<String>,
},
File {
path: String,
},
Note {
title: String,
content: String,
},
}
impl ResearchSource {
#[must_use]
pub fn url(url: impl Into<String>) -> Self {
Self::Url {
url: url.into(),
label: None,
}
}
#[must_use]
pub fn labeled_url(url: impl Into<String>, label: impl Into<String>) -> Self {
Self::Url {
url: url.into(),
label: Some(label.into()),
}
}
#[must_use]
pub fn file(path: impl Into<String>) -> Self {
Self::File { path: path.into() }
}
#[must_use]
pub fn note(title: impl Into<String>, content: impl Into<String>) -> Self {
Self::Note {
title: title.into(),
content: content.into(),
}
}
}
#[derive(Clone, Debug)]
pub enum ResearchEvent {
Stage {
stage: ResearchStage,
message: String,
},
Finding(ResearchFinding),
Citation(ResearchCitation),
Finalized(ResearchReport),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ResearchStage {
Planning,
Searching,
Reading,
Writing,
Completed,
}
#[derive(Clone, Debug)]
pub struct ResearchFinding {
pub title: String,
pub summary: String,
pub confidence: Option<f32>,
pub citations: Vec<ResearchCitation>,
}
impl ResearchFinding {
#[must_use]
pub fn new(title: impl Into<String>, summary: impl Into<String>) -> Self {
Self {
title: title.into(),
summary: summary.into(),
confidence: None,
citations: Vec::new(),
}
}
#[must_use]
pub const fn confidence(mut self, confidence: f32) -> Self {
self.confidence = Some(confidence);
self
}
#[must_use]
pub fn citation(mut self, citation: ResearchCitation) -> Self {
self.citations.push(citation);
self
}
}
#[derive(Clone, Debug)]
pub struct ResearchCitation {
pub url: String,
pub title: Option<String>,
pub snippet: Option<String>,
}
impl ResearchCitation {
#[must_use]
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
title: None,
snippet: None,
}
}
#[must_use]
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
#[must_use]
pub fn snippet(mut self, snippet: impl Into<String>) -> Self {
self.snippet = Some(snippet.into());
self
}
}
#[derive(Clone, Debug, Default)]
pub struct ResearchReport {
pub summary: String,
pub findings: Vec<ResearchFinding>,
pub citations: Vec<ResearchCitation>,
}
impl ResearchReport {
#[must_use]
pub fn summary(mut self, summary: impl Into<String>) -> Self {
self.summary = summary.into();
self
}
pub fn push_finding(&mut self, finding: ResearchFinding) {
self.findings.push(finding);
}
pub fn push_citation(&mut self, citation: ResearchCitation) {
self.citations.push(citation);
}
}
#[derive(Clone, Debug)]
pub struct ResearcherProfile {
pub name: String,
pub supports_streaming: bool,
pub supports_web_browsing: bool,
pub supports_code_execution: bool,
}
pub trait Researcher: Sized + Send + Sync {
type Error: core::error::Error + Send + Sync + 'static;
fn research(
&self,
request: &ResearchRequest,
) -> impl Stream<Item = Result<ResearchEvent, Self::Error>> + Send;
fn report(
&self,
request: &ResearchRequest,
) -> impl Future<Output = crate::Result<ResearchReport>> + Send {
research_report(self, request)
}
fn profile(&self) -> impl Future<Output = ResearcherProfile> + Send;
}
macro_rules! impl_researcher {
($($name:ident),*) => {
$(
impl<T: Researcher> Researcher for $name<T> {
type Error = T::Error;
fn research(
&self,
request: &ResearchRequest,
) -> impl Stream<Item = Result<ResearchEvent, Self::Error>> + Send {
T::research(self, request)
}
fn profile(&self) -> impl Future<Output = ResearcherProfile> + Send {
T::profile(self)
}
}
)*
};
}
impl_researcher!(Arc, Box);
impl<T: Researcher> Researcher for &T {
type Error = T::Error;
fn research(
&self,
request: &ResearchRequest,
) -> impl Stream<Item = Result<ResearchEvent, Self::Error>> + Send {
T::research(self, request)
}
fn profile(&self) -> impl Future<Output = ResearcherProfile> + Send {
T::profile(self)
}
}
async fn research_report<R: Researcher>(
researcher: &R,
request: &ResearchRequest,
) -> crate::Result<ResearchReport> {
let stream = researcher.research(request);
pin!(stream);
let mut report = ResearchReport::default();
while let Some(event) = stream.try_next().await.map_err(anyhow::Error::new)? {
match event {
ResearchEvent::Finding(finding) => report.push_finding(finding),
ResearchEvent::Citation(citation) => report.push_citation(citation),
ResearchEvent::Finalized(final_report) => return Ok(final_report),
ResearchEvent::Stage { .. } => {}
}
}
Ok(report)
}