Files
Aether/crates/aether-ai-serving/src/attempt_loop.rs

97 lines
2.6 KiB
Rust
Raw Normal View History

2026-05-02 13:23:54 +08:00
use async_trait::async_trait;
pub trait AiExecutionAttempt {
fn execution_plan(&self) -> &aether_contracts::ExecutionPlan;
fn report_kind(&self) -> Option<String>;
fn report_context(&self) -> Option<serde_json::Value>;
}
#[derive(Debug)]
pub enum AiAttemptLoopOutcome<Response, Exhaustion> {
Responded(Response),
Exhausted(Exhaustion),
NoPath,
}
#[async_trait]
pub trait AiAttemptLoopPort<Attempt>: Send + Sync
where
Attempt: AiExecutionAttempt + Send + Sync + 'static,
{
type Response: Send;
type Exhaustion: Send;
type Error: Send;
async fn execute_attempt(
&self,
attempt: &Attempt,
) -> Result<Option<Self::Response>, Self::Error>;
async fn mark_unused_attempts(&self, attempts: Vec<Attempt>) -> Result<(), Self::Error>;
async fn build_exhaustion(
&self,
last_plan: aether_contracts::ExecutionPlan,
last_report_context: Option<serde_json::Value>,
) -> Result<Self::Exhaustion, Self::Error>;
}
pub async fn run_ai_attempt_loop<Port, Attempt>(
port: &Port,
attempts: Vec<Attempt>,
) -> Result<AiAttemptLoopOutcome<Port::Response, Port::Exhaustion>, Port::Error>
where
Port: AiAttemptLoopPort<Attempt>,
Attempt: AiExecutionAttempt + Send + Sync + 'static,
{
let mut remaining = attempts.into_iter();
let mut last_attempted = None;
while let Some(attempt) = remaining.next() {
last_attempted = Some((attempt.execution_plan().clone(), attempt.report_context()));
if let Some(response) = port.execute_attempt(&attempt).await? {
port.mark_unused_attempts(remaining.collect()).await?;
return Ok(AiAttemptLoopOutcome::Responded(response));
}
}
let Some((last_plan, last_report_context)) = last_attempted else {
return Ok(AiAttemptLoopOutcome::NoPath);
};
Ok(AiAttemptLoopOutcome::Exhausted(
port.build_exhaustion(last_plan, last_report_context)
.await?,
))
}
impl AiExecutionAttempt for crate::dto::AiSyncAttempt {
fn execution_plan(&self) -> &aether_contracts::ExecutionPlan {
&self.plan
}
fn report_kind(&self) -> Option<String> {
self.report_kind.clone()
}
fn report_context(&self) -> Option<serde_json::Value> {
self.report_context.clone()
}
}
impl AiExecutionAttempt for crate::dto::AiStreamAttempt {
fn execution_plan(&self) -> &aether_contracts::ExecutionPlan {
&self.plan
}
fn report_kind(&self) -> Option<String> {
self.report_kind.clone()
}
fn report_context(&self) -> Option<serde_json::Value> {
self.report_context.clone()
}
}