use futures::StreamExt;
use misanthropic::model::{ModelInfo, Models};
use misanthropic::prompt::Prompt;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
mod agent;
pub use agent::cache;
#[cfg(feature = "seed")]
pub use agent::seed;
pub use agent::{Agent, Control, Outcome, State, default_handle};
mod backend;
pub use backend::{AgentNotFound, Inference, SaveError, Storage};
#[cfg(feature = "fs-storage")]
pub mod storage;
#[cfg(feature = "fs-storage")]
pub use storage::FsStorage;
pub mod inference;
mod orchestrator;
pub use orchestrator::{Orchestrator, OrchestratorReport};
#[cfg(feature = "client")]
pub mod anthropic;
#[cfg(feature = "client")]
pub use anthropic::Client;
use crate::ids::{AgentId, ReactorId};
#[cfg(test)]
mod tests;
pub trait Error:
std::error::Error + Send + Sync + RetryAfter + 'static
{
}
impl<T: std::error::Error + Send + Sync + RetryAfter + 'static> Error for T {}
pub trait RetryAfter {
fn retry_after(&self) -> Option<std::time::Duration> {
None
}
fn is_fatal(&self) -> bool {
self.retry_after().is_none()
}
}
#[derive(Debug, thiserror::Error)]
pub enum ReactorError<I: Inference, S: Storage, A: Agent> {
#[error("inference: {0}")]
InferenceError(I::Error),
#[error("agent: {0}")]
AgentError(A::Error),
#[error("storage: {0}")]
StorageError(S::Error),
#[error("{}", .0.message)]
Shared(ErrorReport),
}
impl<I: Inference, S: Storage, A: Agent> ReactorError<I, S, A> {
pub fn kind(&self) -> ErrorKind {
match self {
ReactorError::InferenceError(_) => ErrorKind::Inference,
ReactorError::AgentError(_) => ErrorKind::Agent,
ReactorError::StorageError(_) => ErrorKind::Storage,
ReactorError::Shared(report) => report.kind,
}
}
}
impl<I: Inference, S: Storage, A: Agent> RetryAfter for ReactorError<I, S, A> {
fn retry_after(&self) -> Option<std::time::Duration> {
match self {
ReactorError::InferenceError(e) => e.retry_after(),
ReactorError::AgentError(e) => e.retry_after(),
ReactorError::StorageError(e) => e.retry_after(),
ReactorError::Shared(report) => report.retry_after,
}
}
}
type Persist<I, S, A> = (A, Result<Outcome, ReactorError<I, S, A>>);
const MAX_BATCH_ITEM_RETRIES: usize = 3;
pub(crate) const MAX_INFER_RETRIES: u32 = 5;
enum Admission<'a> {
Batch(&'a ModelInfo),
Sequential(&'a ModelInfo),
Rejected,
}
fn negotiate<'a>(offered: &'a Models, requested: &ModelInfo) -> Admission<'a> {
let batch = requested.capabilities.batch.supported;
for model in offered.iter() {
if model.satisfies(requested) {
if batch {
return Admission::Batch(model);
} else {
return Admission::Sequential(model);
}
}
}
Admission::Rejected
}
pub struct Reactor<I: Inference, S: Storage, A: Agent> {
id: ReactorId,
inference: I,
storage: S,
agents: VecDeque<A>,
done: BTreeMap<AgentId, A>,
failed: BTreeMap<AgentId, A>,
errors: BTreeMap<AgentId, ReactorError<I, S, A>>,
unsaved: BTreeMap<AgentId, serde_json::Value>,
rejected: BTreeMap<AgentId, serde_json::Value>,
}
#[cfg(feature = "client")]
pub type AnthropicReactor<S, A> = Reactor<anthropic::Client, S, A>;
impl<I, S, A> Default for Reactor<I, S, A>
where
I: Inference + Default,
S: Storage + Default,
A: Agent,
{
fn default() -> Self {
Self::new(I::default(), S::default(), Vec::<A>::new())
}
}
impl<I, S, A, Ai, As> From<As> for Reactor<I, S, A>
where
I: Inference,
S: Storage,
A: Agent,
Self: Default + FromIterator<Ai>,
As: IntoIterator<Item = Ai>,
{
fn from(value: As) -> Self {
value.into_iter().collect()
}
}
impl<I, S, A, Ai> FromIterator<Ai> for Reactor<I, S, A>
where
I: Inference,
S: Storage,
Self: Default,
Ai: Into<A>,
A: Agent,
{
fn from_iter<T: IntoIterator<Item = Ai>>(iter: T) -> Self {
Self::default().with_agents(iter)
}
}
impl<I: Inference, S: Storage, A: Agent> Reactor<I, S, A> {
pub const MAX_STALLS: usize = 3;
pub fn new<Ai>(
inference: I,
storage: S,
agents: impl IntoIterator<Item = Ai>,
) -> Self
where
Ai: Into<A>,
{
Self {
inference,
storage,
id: ReactorId::new(),
agents: VecDeque::new(),
done: BTreeMap::new(),
failed: BTreeMap::new(),
errors: BTreeMap::new(),
unsaved: BTreeMap::new(),
rejected: BTreeMap::new(),
}
.with_agents(agents)
}
pub fn with_agents<Ai>(
mut self,
agents: impl IntoIterator<Item = Ai>,
) -> Self
where
Ai: Into<A>,
{
self.extend(agents);
self
}
pub fn extend<Ai>(&mut self, agents: impl IntoIterator<Item = Ai>)
where
Ai: Into<A>,
{
self.agents.extend(agents.into_iter().map(Into::into));
}
pub fn report(&self) -> Report {
Report {
done: self.done.len(),
failed: self.failed.len(),
errors: self
.errors
.iter()
.map(|(id, e)| (*id, ErrorReport::from(e)))
.collect(),
unsaved: self.unsaved.clone(),
rejected: self.rejected.clone(),
}
}
async fn drive_one(
inference: &I,
agent: &mut A,
) -> Result<Outcome, ReactorError<I, S, A>> {
let driven = Self::drive_inner(inference, agent).await;
let teardown =
agent.on_teardown().await.map_err(ReactorError::AgentError);
driven.and_then(|outcome| teardown.map(|()| outcome))
}
async fn drive_inner(
inference: &I,
agent: &mut A,
) -> Result<Outcome, ReactorError<I, S, A>> {
agent.on_init().await.map_err(ReactorError::AgentError)?;
let mut stalls = 0usize;
loop {
agent.on_turn().await.map_err(ReactorError::AgentError)?;
let mut attempt: u32 = 0;
let response = loop {
match inference.infer(agent.prompt()).await {
Ok(response) => break response,
Err(e) => match e.retry_after() {
Some(wait) if attempt < MAX_INFER_RETRIES => {
attempt += 1;
let wait = wait * attempt;
tracing::warn!(
agent_id = %agent.id(),
attempt,
wait_secs = wait.as_secs(),
error = %e,
"retryable inference error"
);
tokio::time::sleep(wait).await;
}
_ => {
return Err(ReactorError::InferenceError(e));
}
},
}
};
match agent
.handle(response)
.await
.map_err(ReactorError::AgentError)?
{
Control::Done(outcome) => break Ok(outcome),
Control::Continue => stalls = 0,
Control::Stalled => {
stalls += 1;
if stalls >= Self::MAX_STALLS {
break Ok(Outcome::Failed);
}
}
}
}
}
async fn run_agent_major(
inference: &I,
agents: Vec<A>,
) -> Vec<Persist<I, S, A>> {
let limit = inference.max_concurrency().get();
futures::stream::iter(agents)
.map(|mut agent| async move {
let result = Self::drive_one(inference, &mut agent).await;
(agent, result)
})
.buffer_unordered(limit)
.collect()
.await
}
async fn run_round_major(
inference: &I,
mut agents: Vec<A>,
) -> Vec<Persist<I, S, A>> {
let mut errors: HashMap<usize, ReactorError<I, S, A>> = HashMap::new();
let mut finished: HashMap<usize, Outcome> = HashMap::new();
let mut stalls: HashMap<usize, usize> = HashMap::new();
let mut item_failures: HashMap<usize, usize> = HashMap::new();
for (i, agent) in agents.iter_mut().enumerate() {
if let Err(e) = agent.on_init().await {
errors.insert(i, ReactorError::AgentError(e));
}
}
let primes: Vec<Prompt> = {
let mut primed: BTreeSet<String> = BTreeSet::new();
agents
.iter()
.enumerate()
.filter(|(i, _)| !errors.contains_key(i))
.filter_map(|(_, agent)| agent.prime_prompt())
.filter(|p| primed.insert(p.model.name().to_string()))
.collect()
};
if !primes.is_empty() {
let prompts: Vec<&Prompt> = primes.iter().collect();
match inference.infer_batch(&prompts).await {
Ok(_) => tracing::info!(models = primes.len(), "cache primed"),
Err(e) => tracing::warn!(
error = %e,
"cache prime failed"
),
}
}
let mut live: Vec<usize> = Vec::new();
loop {
live.clear();
live.extend((0..agents.len()).filter(|i| {
!errors.contains_key(i) && !finished.contains_key(i)
}));
if live.is_empty() {
break;
}
for &i in &live {
if let Err(e) = agents[i].on_turn().await {
errors.insert(i, ReactorError::AgentError(e));
}
}
live.retain(|i| !errors.contains_key(i));
if live.is_empty() {
continue;
}
let resps = {
let prompts: Vec<&Prompt> =
live.iter().map(|&i| agents[i].prompt()).collect();
match inference.infer_batch(&prompts).await {
Ok(resps) => resps,
Err(e) => {
let report = ErrorReport::from(
&ReactorError::<I, S, A>::InferenceError(e),
);
for &i in &live {
errors.insert(
i,
ReactorError::Shared(report.clone()),
);
}
break;
}
}
};
for (&i, resp) in live.iter().zip(resps) {
match resp {
Ok(message) => {
item_failures.remove(&i);
match agents[i].handle(message).await {
Err(e) => {
errors.insert(i, ReactorError::AgentError(e));
}
Ok(Control::Done(outcome)) => {
finished.insert(i, outcome);
}
Ok(Control::Continue) => {
stalls.remove(&i);
}
Ok(Control::Stalled) => {
let n = stalls.entry(i).or_insert(0);
*n += 1;
if *n >= Self::MAX_STALLS {
finished.insert(i, Outcome::Failed);
}
}
}
}
Err(e) if e.is_fatal() => {
errors.insert(i, ReactorError::InferenceError(e));
}
Err(e) => {
let n = item_failures.entry(i).or_insert(0);
*n += 1;
if *n >= MAX_BATCH_ITEM_RETRIES {
errors.insert(i, ReactorError::InferenceError(e));
}
}
}
}
}
for (i, agent) in agents.iter_mut().enumerate() {
if let Err(e) = agent.on_teardown().await {
errors.entry(i).or_insert(ReactorError::AgentError(e));
}
}
agents
.into_iter()
.enumerate()
.map(|(i, agent)| {
let result = match errors.remove(&i) {
Some(e) => Err(e),
None => Ok(finished.remove(&i).unwrap_or(Outcome::Failed)),
};
(agent, result)
})
.collect()
}
async fn persist_all(&mut self, agent_results: Vec<Persist<I, S, A>>) {
let mut values: Vec<(AgentId, serde_json::Value)> =
Vec::with_capacity(agent_results.len());
for (agent, _) in &agent_results {
let id = agent.id();
match serde_json::to_value(agent.state()) {
Ok(v) => values.push((id, v)),
Err(e) => {
self.errors.insert(
id,
ReactorError::StorageError(S::Error::from(e)),
);
}
}
}
let attempted: BTreeSet<AgentId> =
values.iter().map(|(id, _)| *id).collect();
let (saved, mut save_err) =
match self.storage.save_all_raw(values.clone().into_iter()).await {
Ok(()) => (attempted.clone(), None),
Err(SaveError { saved, inner }) => (saved, Some(inner)),
};
for (id, value) in values {
if !saved.contains(&id) {
self.unsaved.insert(id, value);
}
}
for (agent, result) in agent_results {
let id = agent.id();
let done =
matches!(result, Ok(Outcome::Complete)) && saved.contains(&id);
match result {
Err(e) => {
self.errors.insert(id, e);
}
Ok(_) => {
if attempted.contains(&id)
&& !saved.contains(&id)
&& let Some(e) = save_err.take()
{
self.errors.insert(id, ReactorError::StorageError(e));
}
}
}
if done {
self.done.insert(id, agent);
} else {
self.failed.insert(id, agent);
}
}
}
}
pub async fn load_agents<S: Storage, A: Agent>(
storage: &S,
context: A::Context,
ids: impl ExactSizeIterator<Item = AgentId> + Send,
) -> Result<(Vec<A>, Vec<(AgentId, A::Error)>), S::Error> {
let raw = storage.load_all::<_, A>(ids).await?;
let mut agents = Vec::with_capacity(raw.len());
let mut failures = Vec::new();
for (id, result) in raw {
match result {
Ok(state) => match A::new(id, state, context.clone()) {
Ok(agent) => agents.push(agent),
Err(e) => failures.push((id, e)),
},
Err(e) => failures.push((
id,
A::Error::from(
Box::new(e) as Box<dyn std::error::Error + Send + Sync>
),
)),
}
}
Ok((agents, failures))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ErrorKind {
Inference,
Agent,
Storage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorReport {
pub kind: ErrorKind,
pub retry_after: Option<std::time::Duration>,
pub message: String,
}
impl<I: Inference, S: Storage, A: Agent> From<&ReactorError<I, S, A>>
for ErrorReport
{
fn from(e: &ReactorError<I, S, A>) -> Self {
if let ReactorError::Shared(report) = e {
return report.clone();
}
ErrorReport {
kind: e.kind(),
retry_after: e.retry_after(),
message: e.to_string(),
}
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Report {
pub done: usize,
pub failed: usize,
pub errors: BTreeMap<AgentId, ErrorReport>,
pub unsaved: BTreeMap<AgentId, serde_json::Value>,
#[serde(default)]
pub rejected: BTreeMap<AgentId, serde_json::Value>,
}
impl std::ops::Add<Report> for Report {
type Output = Report;
fn add(mut self, rhs: Report) -> Self::Output {
self += rhs;
self
}
}
impl std::ops::AddAssign<Report> for Report {
fn add_assign(&mut self, rhs: Report) {
self.done += rhs.done;
self.failed += rhs.failed;
self.errors.extend(rhs.errors);
self.unsaved.extend(rhs.unsaved);
self.rejected.extend(rhs.rejected);
}
}
#[derive(Debug, thiserror::Error)]
#[error("{kind:?} error: {inner}")]
pub struct RunError {
pub kind: ErrorKind,
pub retry_after: Option<std::time::Duration>,
pub inner: anyhow::Error,
}
impl RetryAfter for RunError {
fn retry_after(&self) -> Option<std::time::Duration> {
self.retry_after
}
}
impl<I, S, A> From<ReactorError<I, S, A>> for RunError
where
I: Inference,
S: Storage,
A: Agent,
{
fn from(value: ReactorError<I, S, A>) -> Self {
let kind = value.kind();
let retry_after = value.retry_after();
let inner = match value {
ReactorError::InferenceError(e) => anyhow::Error::new(e),
ReactorError::AgentError(e) => anyhow::Error::new(e),
ReactorError::StorageError(e) => anyhow::Error::new(e),
ReactorError::Shared(report) => anyhow::anyhow!(report.message),
};
RunError {
kind,
retry_after,
inner,
}
}
}
#[async_trait::async_trait]
pub trait Run: Send {
fn id(&self) -> ReactorId;
async fn run(&mut self) -> Result<Report, RunError>;
}
static_assertions::assert_obj_safe!(Run);
#[async_trait::async_trait]
impl<I: Inference, S: Storage, A: Agent> Run for Reactor<I, S, A> {
fn id(&self) -> ReactorId {
self.id
}
async fn run(&mut self) -> Result<Report, RunError> {
let offered = self
.inference
.models()
.await
.map_err(ReactorError::<I, S, A>::InferenceError)?;
let quirks = self.inference.quirks();
let agents = std::mem::take(&mut self.agents);
let mut batch: Vec<A> = Vec::new();
let mut sequential: Vec<A> = Vec::new();
let mut rejected: Vec<A> = Vec::new();
for mut agent in agents {
match negotiate(&offered, &agent.model()) {
Admission::Batch(model) => {
debug_assert_eq!(
agent.prompt().model.name(),
model.id.name(),
"prompt model diverges from the negotiated model"
);
agent.on_admit(model, &quirks);
batch.push(agent);
}
Admission::Sequential(model) => {
debug_assert_eq!(
agent.prompt().model.name(),
model.id.name(),
"prompt model diverges from the negotiated model"
);
agent.on_admit(model, &quirks);
sequential.push(agent);
}
Admission::Rejected => rejected.push(agent),
}
}
for agent in rejected {
match serde_json::to_value(agent.state()) {
Ok(value) => {
self.rejected.insert(agent.id(), value);
}
Err(e) => {
self.errors.insert(
agent.id(),
ReactorError::StorageError(S::Error::from(e)),
);
}
}
}
let inference = &self.inference;
let (mut to_persist, seq_persist) = futures::join!(
Self::run_round_major(inference, batch),
Self::run_agent_major(inference, sequential),
);
to_persist.extend(seq_persist);
self.persist_all(to_persist).await;
Ok(self.report())
}
}