use std::{future::Future, pin::Pin, sync::Arc};
use serde::{Serialize, de::DeserializeOwned};
use serde_json::Value;
use crate::{Cancellation, Error, middleware::Middleware};
type HookFuture = Pin<Box<dyn Future<Output = Result<Value, Error>> + Send + 'static>>;
#[derive(Clone, Copy)]
pub(crate) enum Stage {
Execute,
Model,
SystemPrompt,
Permission,
Compression,
}
trait Entry: Send + Sync {
fn apply(
&self,
stage: Stage,
before: bool,
value: Value,
cancellation: Cancellation,
) -> HookFuture;
}
struct Typed<M>(Arc<M>);
impl<M: Middleware + 'static> Entry for Typed<M> {
fn apply(
&self,
stage: Stage,
before: bool,
mut value: Value,
cancellation: Cancellation,
) -> HookFuture {
let middleware = self.0.clone();
Box::pin(async move {
match (stage, before) {
(Stage::Execute, true) => {
middleware.before_execute(&mut value, &cancellation).await?
}
(Stage::Execute, false) => {
middleware.after_execute(&mut value, &cancellation).await?
}
(Stage::Model, true) => middleware.before_model(&mut value, &cancellation).await?,
(Stage::Model, false) => middleware.after_model(&mut value, &cancellation).await?,
(Stage::SystemPrompt, true) => {
middleware
.before_system_prompt(&mut value, &cancellation)
.await?
}
(Stage::SystemPrompt, false) => {
middleware
.after_system_prompt(&mut value, &cancellation)
.await?
}
(Stage::Permission, true) => {
middleware
.before_permission(&mut value, &cancellation)
.await?
}
(Stage::Permission, false) => {
middleware
.after_permission(&mut value, &cancellation)
.await?
}
(Stage::Compression, true) => {
middleware
.before_compress_context(&mut value, &cancellation)
.await?
}
(Stage::Compression, false) => {
middleware
.after_compress_context(&mut value, &cancellation)
.await?
}
}
Ok(value)
})
}
}
#[derive(Clone, Default)]
pub(crate) struct Hooks(Vec<Arc<dyn Entry>>);
impl Hooks {
pub(crate) fn len(&self) -> usize {
self.0.len()
}
pub(crate) fn add<M: Middleware + 'static>(&mut self, middleware: M) {
self.0.push(Arc::new(Typed(Arc::new(middleware))));
}
async fn apply<T: Clone + Serialize + DeserializeOwned>(
&self,
stage: Stage,
before: bool,
value: T,
cancellation: &Cancellation,
) -> Result<T, Error> {
Box::pin(self.apply_from(stage, before, value, 0, |_, _| Ok(()), cancellation)).await
}
pub(crate) async fn apply_from<T: Clone + Serialize + DeserializeOwned>(
&self,
stage: Stage,
before: bool,
value: T,
idx: usize,
mut commit: impl FnMut(usize, T) -> Result<(), Error>,
cancellation: &Cancellation,
) -> Result<T, Error> {
self.apply_from_async(
stage,
before,
value,
idx,
|idx, value| std::future::ready(commit(idx, value)),
cancellation,
)
.await
}
pub(crate) async fn apply_from_async<T, K, KF>(
&self,
stage: Stage,
before: bool,
mut value: T,
idx: usize,
mut commit: K,
cancellation: &Cancellation,
) -> Result<T, Error>
where
T: Clone + Serialize + DeserializeOwned,
K: FnMut(usize, T) -> KF,
KF: Future<Output = Result<(), Error>>,
{
if idx > self.0.len() {
return Err(Error::new("INVALID_STATE", "invalid middleware cursor"));
}
let reverse = !before && !matches!(stage, Stage::SystemPrompt);
for offset in idx..self.0.len() {
let index = if reverse {
self.0.len() - 1 - offset
} else {
offset
};
value = Box::pin(crate::core::lifecycle::prepare(
crate::core::lifecycle::Phase::Before,
value,
|value| async {
let value = serde_json::to_value(value).map_err(|_| {
Error::new("INVALID_ARGUMENTS", "cannot encode middleware input")
})?;
let usage = (!before && matches!(stage, Stage::Model | Stage::Execute))
.then(|| value.get("usage").cloned())
.flatten();
let mut result = self.0[index]
.apply(stage, before, value, cancellation.clone())
.await?;
if let Some(usage) = usage
&& let Some(object) = result.as_object_mut()
{
object.insert("usage".into(), usage);
}
serde_json::from_value(result).map_err(|_| {
Error::new(
"INVALID_ARGUMENTS",
"middleware returned an invalid stage value",
)
})
},
|value| commit(offset + 1, value),
cancellation,
))
.await?;
}
Ok(value)
}
pub(crate) async fn after_from(
&self,
stage: Stage,
message: crate::Message,
idx: usize,
mut commit: impl FnMut(usize, crate::Message) -> Result<(), Error>,
cancellation: &Cancellation,
) -> Result<crate::Message, Error> {
let usage = message.usage.clone();
let mut result = Box::pin(self.apply_from(
stage,
false,
message,
idx,
|idx, mut message: crate::Message| {
message.usage = usage.clone();
commit(idx, message)
},
cancellation,
))
.await?;
result.usage = usage;
Ok(result)
}
pub(crate) async fn before_compress(
&self,
messages: crate::Messages,
cancellation: &Cancellation,
) -> Result<crate::Messages, Error> {
self.apply(Stage::Compression, true, messages, cancellation)
.await
}
pub(crate) async fn after_compress(
&self,
messages: crate::Messages,
cancellation: &Cancellation,
) -> Result<crate::Messages, Error> {
self.apply(Stage::Compression, false, messages, cancellation)
.await
}
}
pub(crate) async fn cancellable<T>(
future: impl Future<Output = Result<T, Error>>,
cancellation: &Cancellation,
) -> Result<T, Error> {
crate::runtime::cancellation::cancellable(future, cancellation).await
}
#[cfg(test)]
mod tests {
use super::{Hooks, Stage};
use crate::{Cancellation, Error, middleware::Middleware};
use serde_json::{Value, json};
use std::sync::{
Arc,
atomic::{AtomicBool, AtomicUsize, Ordering},
};
struct Transform {
key: &'static str,
calls: Arc<AtomicUsize>,
fail: Arc<AtomicBool>,
}
impl Transform {
fn apply(&self, value: &mut Value) -> Result<(), Error> {
self.calls.fetch_add(1, Ordering::SeqCst);
value["values"][self.key] = json!(true);
if self.fail.load(Ordering::SeqCst) {
return Err(Error::new("HOOK_FAILED", "controlled hook failure"));
}
Ok(())
}
}
impl Middleware for Transform {
async fn after_system_prompt(
&self,
value: &mut Value,
_: &Cancellation,
) -> Result<(), Error> {
self.apply(value)
}
}
#[tokio::test]
async fn only_acknowledged_hook_transformations_advance_the_cursor() {
let first_calls = Arc::new(AtomicUsize::new(0));
let second_calls = Arc::new(AtomicUsize::new(0));
let fail_second = Arc::new(AtomicBool::new(true));
let mut hooks = Hooks::default();
hooks.add(Transform {
key: "first",
calls: Arc::clone(&first_calls),
fail: Arc::new(AtomicBool::new(false)),
});
hooks.add(Transform {
key: "second",
calls: Arc::clone(&second_calls),
fail: Arc::clone(&fail_second),
});
let mut acknowledged = Vec::new();
let error = hooks
.apply_from(
Stage::SystemPrompt,
false,
json!({"values": {}, "usage": {"total": 15}}),
0,
|idx, value| {
acknowledged.push((idx, value));
Ok(())
},
&Cancellation::new(),
)
.await
.expect_err("second hook is configured to fail");
assert_eq!(error.code, "HOOK_FAILED");
assert_eq!(acknowledged.len(), 1);
assert_eq!(acknowledged[0].0, 1);
assert_eq!(acknowledged[0].1["values"]["first"], true);
assert!(acknowledged[0].1["values"].get("second").is_none());
assert_eq!(acknowledged[0].1["usage"]["total"], 15);
assert_eq!(first_calls.load(Ordering::SeqCst), 1);
assert_eq!(second_calls.load(Ordering::SeqCst), 1);
fail_second.store(false, Ordering::SeqCst);
let (cursor, saved_value) = acknowledged.pop().unwrap();
let mut restored_cursor = 0;
let mut restored_value = Value::Null;
let final_value = hooks
.apply_from(
Stage::SystemPrompt,
false,
saved_value,
cursor,
|idx, value| {
restored_cursor = idx;
restored_value = value;
Ok(())
},
&Cancellation::new(),
)
.await
.expect("resume from the acknowledged cursor");
assert_eq!(restored_cursor, 2);
assert_eq!(restored_value, final_value);
assert_eq!(final_value["values"]["first"], true);
assert_eq!(final_value["values"]["second"], true);
assert_eq!(final_value["usage"]["total"], 15);
assert_eq!(first_calls.load(Ordering::SeqCst), 1);
assert_eq!(second_calls.load(Ordering::SeqCst), 2);
}
}