starweaver-runtime 0.6.0

Agent-loop graph and runtime executor primitives for Starweaver
Documentation
//! Output function and final output validation helpers.

use starweaver_context::AgentContext;

use crate::{
    agent::Agent,
    capability::CapabilityError,
    output::{OutputFunctionContext, OutputValue, parse_output},
    run::AgentRunState,
};

impl Agent {
    pub(in crate::agent) async fn try_call_output_function(
        &self,
        state: &mut AgentRunState,
        context: &mut AgentContext,
        calls: &[starweaver_model::ToolCallPart],
    ) -> Result<Option<(String, Option<serde_json::Value>)>, CapabilityError> {
        let Some(call) = calls.iter().find(|call| {
            self.output_functions
                .iter()
                .any(|function| function.definition().name == call.name)
        }) else {
            return Ok(None);
        };
        let function = self
            .output_functions
            .iter()
            .find(|function| function.definition().name == call.name)
            .ok_or_else(|| {
                CapabilityError::Failed(format!("missing output function {}", call.name))
            })?;
        match function
            .call(
                OutputFunctionContext {
                    state: state.clone(),
                },
                call.arguments.execution_value(),
            )
            .await
            .map_err(Self::output_validation_error)
        {
            Ok(output) => {
                self.call_before_output_validation(state, context, &output.as_text())
                    .await?;
                self.validate_output_value(state, context, output)
                    .await
                    .map(Some)
            }
            Err(error) => Err(error),
        }
    }

    pub(in crate::agent) async fn validate_final_output(
        &self,
        state: &mut AgentRunState,
        context: &mut AgentContext,
        output: &str,
    ) -> Result<(), CapabilityError> {
        self.call_before_output_validation(state, context, output)
            .await?;
        let parsed = parse_output(output, self.output_schema.as_ref())
            .map_err(Self::output_validation_error)?;
        self.validate_output_value(state, context, parsed).await?;
        Ok(())
    }

    pub(in crate::agent) async fn validate_output_value(
        &self,
        state: &mut AgentRunState,
        context: &mut AgentContext,
        output: OutputValue,
    ) -> Result<(String, Option<serde_json::Value>), CapabilityError> {
        let output_text = output.as_text();
        state.structured_output = output.as_json().cloned();
        self.call_output_validators(state, &output).await?;
        self.call_validate_output(state, context, &output_text)
            .await?;
        self.call_after_output_validation(state, context, &output_text)
            .await?;
        Ok((output_text, state.structured_output.clone()))
    }

    pub(in crate::agent) async fn call_before_output_validation(
        &self,
        state: &mut AgentRunState,
        context: &mut AgentContext,
        output: &str,
    ) -> Result<(), CapabilityError> {
        for capability in &self.ordered_capabilities_for_validation()? {
            capability
                .before_output_validation_with_context(state, context, output)
                .await?;
        }
        Ok(())
    }

    pub(in crate::agent) async fn call_output_validators(
        &self,
        state: &mut AgentRunState,
        output: &OutputValue,
    ) -> Result<(), CapabilityError> {
        for validator in &self.output_validators {
            validator
                .validate(state, output)
                .await
                .map_err(Self::output_validation_error)?;
        }
        Ok(())
    }

    pub(in crate::agent) async fn call_validate_output(
        &self,
        state: &mut AgentRunState,
        context: &mut AgentContext,
        output: &str,
    ) -> Result<(), CapabilityError> {
        for capability in &self.ordered_capabilities_for_validation()? {
            capability
                .validate_output_with_context(state, context, output)
                .await?;
        }
        Ok(())
    }

    pub(in crate::agent) async fn call_after_output_validation(
        &self,
        state: &mut AgentRunState,
        context: &mut AgentContext,
        output: &str,
    ) -> Result<(), CapabilityError> {
        for capability in &self.ordered_capabilities_for_validation()? {
            capability
                .after_output_validation_with_context(state, context, output)
                .await?;
        }
        Ok(())
    }
}