use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Weak};
use super::callbacks::{LlmRequestContext, LlmResponseContext};
use crate::api::llm::LlmRequest;
use crate::codec::request::AnnotatedLlmRequest;
use crate::codec::response::AnnotatedLlmResponse;
use crate::codec::traits::{LlmCodec, LlmResponseCodec};
use crate::error::{FlowError, Result};
use crate::json::Json;
fn inactive_execution_codec_error() -> FlowError {
FlowError::InvalidArgument("LLM execution codec capability is no longer active".into())
}
fn upgrade_active_codec<T: ?Sized>(codec: &Weak<T>, gate: &ExecutionCodecGate) -> Result<Arc<T>> {
let codec = codec.upgrade().ok_or_else(inactive_execution_codec_error)?;
gate.ensure_active()?;
Ok(codec)
}
#[derive(Debug)]
struct ExecutionCodecGate {
active: AtomicBool,
}
impl ExecutionCodecGate {
fn new() -> Self {
Self {
active: AtomicBool::new(true),
}
}
fn ensure_active(&self) -> Result<()> {
if self.active.load(Ordering::Acquire) {
Ok(())
} else {
Err(inactive_execution_codec_error())
}
}
fn revoke(&self) {
self.active.store(false, Ordering::Release);
}
}
pub(crate) struct LlmExecutionCodecLeaseGuard {
gate: Arc<ExecutionCodecGate>,
request_codec: Option<Arc<dyn LlmCodec>>,
response_codec: Option<Arc<dyn LlmResponseCodec>>,
}
impl Drop for LlmExecutionCodecLeaseGuard {
fn drop(&mut self) {
self.gate.revoke();
drop(self.request_codec.take());
drop(self.response_codec.take());
}
}
struct RevocableRequestCodec {
codec: Weak<dyn LlmCodec>,
identity: super::LlmCodecIdentity,
gate: Arc<ExecutionCodecGate>,
}
impl LlmCodec for RevocableRequestCodec {
fn codec_identity(&self) -> super::LlmCodecIdentity {
self.identity.clone()
}
fn decode(&self, request: &LlmRequest) -> Result<AnnotatedLlmRequest> {
upgrade_active_codec(&self.codec, &self.gate)?.decode(request)
}
fn encode(&self, annotated: &AnnotatedLlmRequest, original: &LlmRequest) -> Result<LlmRequest> {
upgrade_active_codec(&self.codec, &self.gate)?.encode(annotated, original)
}
}
struct RevocableResponseCodec {
codec: Weak<dyn LlmResponseCodec>,
identity: super::LlmCodecIdentity,
gate: Arc<ExecutionCodecGate>,
}
impl LlmResponseCodec for RevocableResponseCodec {
fn codec_identity(&self) -> super::LlmCodecIdentity {
self.identity.clone()
}
fn decode_response(&self, response: &Json) -> Result<AnnotatedLlmResponse> {
upgrade_active_codec(&self.codec, &self.gate)?.decode_response(response)
}
fn allows_estimated_cost(&self, response: &Json) -> bool {
upgrade_active_codec(&self.codec, &self.gate)
.is_ok_and(|codec| codec.allows_estimated_cost(response))
}
}
#[derive(Clone, Debug, Default)]
pub struct LlmExecutionContext {
request_codec: LlmRequestContext,
response_codec: Option<LlmResponseContext>,
}
impl LlmExecutionContext {
#[must_use]
pub fn new(
request_codec: LlmRequestContext,
response_codec: Option<LlmResponseContext>,
) -> Self {
Self {
request_codec,
response_codec,
}
}
pub(crate) fn for_non_streaming(
request_codec: Option<Arc<dyn LlmCodec>>,
response_codec: Option<Arc<dyn LlmResponseCodec>>,
) -> Self {
Self::new(
LlmRequestContext::for_request_codec(request_codec),
Some(LlmResponseContext::for_response_codec(response_codec)),
)
}
pub(crate) fn for_streaming(request_codec: Option<Arc<dyn LlmCodec>>) -> Self {
Self::new(LlmRequestContext::for_request_codec(request_codec), None)
}
pub(crate) fn lease(&self) -> (Self, LlmExecutionCodecLeaseGuard) {
let gate = Arc::new(ExecutionCodecGate::new());
let leased_request_codec = self.request_codec.resolve_codec();
let request_codec = match leased_request_codec.as_ref() {
Some(codec) => {
LlmRequestContext::for_request_codec(Some(Arc::new(RevocableRequestCodec {
codec: Arc::downgrade(codec),
identity: self.request_codec.codec().clone(),
gate: Arc::clone(&gate),
})))
}
None => LlmRequestContext::with_identity(self.request_codec.codec().clone()),
};
let leased_response_codec = self
.response_codec
.as_ref()
.and_then(LlmResponseContext::resolve_codec);
let response_codec =
self.response_codec
.as_ref()
.map(|context| match leased_response_codec.as_ref() {
Some(codec) => LlmResponseContext::for_response_codec(Some(Arc::new(
RevocableResponseCodec {
codec: Arc::downgrade(codec),
identity: context.codec().clone(),
gate: Arc::clone(&gate),
},
))),
None => LlmResponseContext::with_identity(context.codec().clone()),
});
(
Self::new(request_codec, response_codec),
LlmExecutionCodecLeaseGuard {
gate,
request_codec: leased_request_codec,
response_codec: leased_response_codec,
},
)
}
#[must_use]
pub fn request_codec(&self) -> &LlmRequestContext {
&self.request_codec
}
#[must_use]
pub fn response_codec(&self) -> Option<&LlmResponseContext> {
self.response_codec.as_ref()
}
}
#[cfg(test)]
#[path = "../../../tests/unit/llm_execution_context_tests.rs"]
mod tests;