use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::{Result, TinyAgentsError};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum InterpreterSpec {
#[default]
Rhai,
Python {
#[serde(default, skip_serializing_if = "Option::is_none")]
binary: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
args: Vec<String>,
},
Javascript {
#[serde(default, skip_serializing_if = "Option::is_none")]
binary: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
args: Vec<String>,
},
Command {
binary: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
args: Vec<String>,
},
}
impl InterpreterSpec {
pub fn language(&self) -> &'static str {
match self {
InterpreterSpec::Rhai => "rhai",
InterpreterSpec::Python { .. } => "python",
InterpreterSpec::Javascript { .. } => "javascript",
InterpreterSpec::Command { .. } => "python",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct RlmPolicy {
pub max_cells: usize,
pub max_script_bytes: usize,
pub max_output_bytes: usize,
pub max_llm_calls: usize,
pub max_tool_calls: usize,
pub max_agent_calls: usize,
pub max_depth: usize,
#[serde(with = "humantime_millis")]
pub cell_timeout: Option<Duration>,
pub max_operations: u64,
}
impl Default for RlmPolicy {
fn default() -> Self {
Self {
max_cells: 16,
max_script_bytes: 64 * 1024,
max_output_bytes: 256 * 1024,
max_llm_calls: 64,
max_tool_calls: 128,
max_agent_calls: 32,
max_depth: 8,
cell_timeout: Some(Duration::from_secs(120)),
max_operations: 5_000_000,
}
}
}
mod humantime_millis {
use std::time::Duration;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(
value: &Option<Duration>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match value {
Some(duration) => serializer.serialize_some(&(duration.as_millis() as u64)),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<Duration>, D::Error> {
Ok(Option::<u64>::deserialize(deserializer)?.map(Duration::from_millis))
}
}
#[derive(Clone, Debug, Default)]
pub struct RlmCancelFlag(Arc<AtomicBool>);
impl RlmCancelFlag {
pub fn new() -> Self {
Self(Arc::new(AtomicBool::new(false)))
}
pub fn cancel(&self) {
self.0.store(true, Ordering::SeqCst);
}
pub fn is_cancelled(&self) -> bool {
self.0.load(Ordering::SeqCst)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "capability", rename_all = "snake_case")]
pub enum HostCall {
Llm {
#[serde(default, skip_serializing_if = "Option::is_none")]
model: Option<String>,
prompt: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
system: Option<String>,
},
Tool {
tool: String,
#[serde(default)]
arguments: Value,
},
Agent {
agent: String,
input: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
data: Option<Value>,
},
FinalAnswer {
answer: String,
},
}
impl HostCall {
pub fn name(&self) -> String {
match self {
HostCall::Llm { model, .. } => model.clone().unwrap_or_else(|| "default".to_string()),
HostCall::Tool { tool, .. } => tool.clone(),
HostCall::Agent { agent, .. } => agent.clone(),
HostCall::FinalAnswer { .. } => "final_answer".to_string(),
}
}
pub fn kind(&self) -> RlmCallKind {
match self {
HostCall::Llm { .. } => RlmCallKind::Llm,
HostCall::Tool { .. } => RlmCallKind::Tool,
HostCall::Agent { .. } => RlmCallKind::Agent,
HostCall::FinalAnswer { .. } => RlmCallKind::FinalAnswer,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RlmCallKind {
Llm,
Tool,
Agent,
FinalAnswer,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RlmCallRecord {
pub kind: RlmCallKind,
pub name: String,
pub detail: Value,
pub elapsed: Duration,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CellOutcome {
pub stdout: String,
pub value: Option<Value>,
pub error: Option<String>,
pub calls: Vec<RlmCallRecord>,
pub final_answer: Option<String>,
pub elapsed: Duration,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RlmStopReason {
Answered,
ModelAnswered,
CellBudgetExhausted,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RlmStep {
pub code: String,
pub outcome: CellOutcome,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RlmOutcome {
pub answer: Option<String>,
pub stop_reason: RlmStopReason,
pub steps: Vec<RlmStep>,
pub driver_calls: usize,
pub sub_llm_calls: usize,
pub tool_calls: usize,
pub agent_calls: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RlmTemplate {
pub name: String,
pub system_prompt: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TemplateSpec {
Named(String),
Inline(RlmTemplate),
}
impl Default for TemplateSpec {
fn default() -> Self {
TemplateSpec::Named("general".to_string())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct RlmConfig {
pub interpreter: InterpreterSpec,
#[serde(skip_serializing_if = "Option::is_none")]
pub driver_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sub_model: Option<String>,
pub policy: RlmPolicy,
pub template: TemplateSpec,
}
impl RlmConfig {
pub fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json).map_err(TinyAgentsError::Serialization)
}
pub fn to_json(&self) -> Result<String> {
serde_json::to_string_pretty(self).map_err(TinyAgentsError::Serialization)
}
}