feat(grid-agent): add chat and IM interactions (#123)
This commit is contained in:
@@ -6,6 +6,8 @@ use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
#[cfg(feature = "live-grid")]
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "live-grid")]
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
const MAX_ARGUMENTS: usize = 8;
|
||||
|
||||
@@ -20,6 +22,24 @@ impl fmt::Display for CliError {
|
||||
|
||||
impl Error for CliError {}
|
||||
|
||||
#[cfg(feature = "live-grid")]
|
||||
#[derive(Debug)]
|
||||
struct DenyUnregisteredTools;
|
||||
|
||||
#[cfg(feature = "live-grid")]
|
||||
impl metacrate_grid_agent::AuthorizedToolBackend for DenyUnregisteredTools {
|
||||
fn apply(
|
||||
&self,
|
||||
_action: metacrate_grid_agent::AuthorizedAction,
|
||||
_cancellation: libremetaverse_types::compat::CancellationToken,
|
||||
) -> metacrate_grid_agent::BackendFuture<
|
||||
'_,
|
||||
Result<metacrate_grid_agent::ToolCallOutcome, metacrate_grid_agent::BackendError>,
|
||||
> {
|
||||
Box::pin(async { Err(metacrate_grid_agent::BackendError::RejectedMutation) })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Options {
|
||||
config: Option<PathBuf>,
|
||||
@@ -139,6 +159,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "live-grid")]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn run_live(
|
||||
config: metacrate_grid_agent::AgentConfig,
|
||||
run_once: bool,
|
||||
@@ -152,7 +173,14 @@ async fn run_live(
|
||||
CliError("validated live configuration did not contain a grid connection".into())
|
||||
})?;
|
||||
let owner = LibremetaverseClientOwner::new()?;
|
||||
let backend = owner.session_backend(connection)?;
|
||||
let mut interaction = start_live_interactions(&config, &owner)?;
|
||||
let backend = match owner.session_backend_with_interaction(connection, interaction.ingress()) {
|
||||
Ok(backend) => backend,
|
||||
Err(error) => {
|
||||
interaction.shutdown().await?;
|
||||
return Err(error.into());
|
||||
}
|
||||
};
|
||||
let erased: Arc<dyn GridSessionBackend> = Arc::new(backend);
|
||||
let mut handle = SessionSupervisor::new(
|
||||
erased,
|
||||
@@ -196,10 +224,16 @@ async fn run_live(
|
||||
Err(_) => Err(CliError("timed out waiting for full grid readiness".into())),
|
||||
};
|
||||
if let Err(error) = readiness {
|
||||
handle.shutdown().await?;
|
||||
let session_result = handle.shutdown().await;
|
||||
let interaction_result = interaction.shutdown().await;
|
||||
session_result?;
|
||||
interaction_result?;
|
||||
return Err(error.into());
|
||||
}
|
||||
handle.shutdown().await?;
|
||||
let session_result = handle.shutdown().await;
|
||||
let interaction_result = interaction.shutdown().await;
|
||||
session_result?;
|
||||
interaction_result?;
|
||||
println!("grid agent completed one supervised login/logout cycle");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -226,12 +260,81 @@ async fn run_live(
|
||||
);
|
||||
}
|
||||
}
|
||||
event = interaction.next_observation() => {
|
||||
let Some(event) = event else { break; };
|
||||
println!("grid interaction event={event:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
handle.shutdown().await?;
|
||||
let session_result = handle.shutdown().await;
|
||||
let interaction_result = interaction.shutdown().await;
|
||||
session_result?;
|
||||
interaction_result?;
|
||||
if let Some(error) = signal_error {
|
||||
return Err(error.into());
|
||||
}
|
||||
println!("grid agent stopped cleanly");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "live-grid")]
|
||||
fn start_live_interactions(
|
||||
config: &metacrate_grid_agent::AgentConfig,
|
||||
owner: &metacrate_grid_agent::LibremetaverseClientOwner,
|
||||
) -> Result<metacrate_grid_agent::InteractionHandle, Box<dyn Error>> {
|
||||
use metacrate_grid_agent::{
|
||||
ConversationStore, InteractionCoordinator, LlmClient, LlmTransportLimits,
|
||||
MemoryPolicyAudit, PolicyGateway, PolicyLimits, PolicyLlmResponder, ToolLoopLimits,
|
||||
};
|
||||
|
||||
let transport_limits = LlmTransportLimits {
|
||||
request_timeout: config.timeouts.request,
|
||||
total_timeout: config.interaction.model_timeout,
|
||||
max_prompt_bytes: config.limits.max_body_bytes,
|
||||
max_response_bytes: config.limits.max_body_bytes,
|
||||
max_concurrent_requests: config.interaction.max_concurrent_inference,
|
||||
..LlmTransportLimits::default()
|
||||
};
|
||||
let client = Arc::new(LlmClient::new(config.llm.clone(), transport_limits)?);
|
||||
let audit = Arc::new(MemoryPolicyAudit::new(config.limits.observable_queue)?);
|
||||
let gateway = Arc::new(PolicyGateway::new(
|
||||
config.authorized_avatar_uuids.clone(),
|
||||
Vec::new(),
|
||||
PolicyLimits::default(),
|
||||
audit,
|
||||
)?);
|
||||
let loop_limits = ToolLoopLimits {
|
||||
max_tool_calls_per_turn: config.limits.max_tool_calls,
|
||||
max_tool_calls_per_session: config.limits.max_tool_calls,
|
||||
max_history_messages: config.conversation.limits.max_turns_per_session,
|
||||
max_history_bytes: config.conversation.limits.max_session_bytes,
|
||||
wall_clock_timeout: config.interaction.model_timeout,
|
||||
..ToolLoopLimits::default()
|
||||
};
|
||||
let now = Arc::new(|| {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_or(0, |duration| duration.as_secs())
|
||||
});
|
||||
let responder = Arc::new(PolicyLlmResponder::new(
|
||||
client,
|
||||
gateway,
|
||||
Arc::new(DenyUnregisteredTools),
|
||||
loop_limits,
|
||||
now,
|
||||
)?);
|
||||
let conversation = Arc::new(ConversationStore::from_config(config)?);
|
||||
let sink = Arc::new(owner.interaction_sink());
|
||||
Ok(InteractionCoordinator::new(
|
||||
config.interaction.clone(),
|
||||
libremetaverse_types::UUID::zero(),
|
||||
config.authorized_avatar_uuids.clone(),
|
||||
conversation,
|
||||
responder,
|
||||
sink,
|
||||
config.limits.grid_event_queue,
|
||||
config.limits.observable_queue,
|
||||
config.timeouts.shutdown,
|
||||
)?
|
||||
.start())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user