use lellm_core::{ChatResponse, LlmError, Message};
use lellm_graph::{Graph, NoopStepCallback};
use std::sync::Arc;
use super::config::{ToolUseConfig, build_request_messages_inner, empty_response};
use super::event::{AgentEvent, AgentStream, StopReason};
use super::tools::ToolSnapshot;
use super::typed_state::{AgentState, AgentStateMerge};
#[derive(Clone)]
pub struct ResolvedRound {
pub snapshot: Arc<ToolSnapshot>,
pub definitions: Vec<lellm_core::ToolDefinition>,
}
impl ResolvedRound {
pub fn new(snapshot: Arc<ToolSnapshot>) -> Self {
Self {
definitions: snapshot.definitions().to_vec(),
snapshot,
}
}
}
#[derive(Debug, Clone)]
pub struct ToolUseResult {
pub stop_reason: StopReason,
pub response: ChatResponse,
pub messages: Vec<Message>,
pub iterations: usize,
pub tool_calls_executed: usize,
}
impl ToolUseResult {
pub fn is_success(&self) -> bool {
matches!(self.stop_reason, StopReason::Complete)
}
}
#[derive(Clone)]
pub struct ToolUseLoop {
graph: Arc<Graph<AgentState, AgentStateMerge>>,
config: ToolUseConfig,
}
impl ToolUseLoop {
pub fn new(graph: Arc<Graph<AgentState, AgentStateMerge>>, config: ToolUseConfig) -> Self {
Self { graph, config }
}
pub fn graph(&self) -> &Graph<AgentState, AgentStateMerge> {
&self.graph
}
pub fn config(&self) -> &ToolUseConfig {
&self.config
}
pub async fn invoke(&self, messages: Vec<Message>) -> Result<ToolUseResult, LlmError> {
let initial_messages = build_request_messages_inner(&self.config, &messages)?;
let max_steps = self.config.max_iterations * 4 + 1;
let mut agent_state_init = super::typed_state::AgentState::from_messages(initial_messages);
let mut exec_ctx = lellm_graph::ExecutionContext::new(
&mut agent_state_init,
None,
lellm_graph::CancellationToken::new(),
None,
None,
);
let mut cb = NoopStepCallback;
self.graph
.run_inline(&mut exec_ctx, max_steps, &mut cb)
.await
.map_err(|e| lellm_core::LlmError::Provider {
provider: "react_graph".into(),
status: None,
code: None,
message: e.to_string(),
})?;
let agent_state = exec_ctx.state();
let stop_reason = agent_state
.stop_reason
.clone()
.unwrap_or(StopReason::Complete);
let last_response = agent_state
.last_response
.clone()
.unwrap_or_else(empty_response);
Ok(ToolUseResult {
stop_reason,
response: last_response,
messages: agent_state.messages.clone(),
iterations: agent_state.iterations,
tool_calls_executed: agent_state.total_tool_calls,
})
}
pub fn invoke_stream(&self, messages: Vec<Message>) -> AgentStream {
let (tx, rx) = tokio::sync::mpsc::channel(32);
let graph = self.graph.clone();
let config = self.config.clone();
tokio::spawn(async move {
let initial_messages = match build_request_messages_inner(&config, &messages) {
Ok(m) => m,
Err(e) => {
let _ = tx
.send(AgentEvent::LoopError {
error: e,
iterations: 0,
})
.await;
return;
}
};
let max_steps = config.max_iterations * 4 + 1;
let mut agent_state = super::typed_state::AgentState::from_messages(initial_messages);
let event_sink = super::event_bridge::AgentEventSink::new(tx.clone());
let sink: std::sync::Arc<dyn lellm_graph::StreamSink> = std::sync::Arc::new(event_sink);
let mut exec_ctx = lellm_graph::ExecutionContext::new(
&mut agent_state,
Some(sink),
lellm_graph::CancellationToken::new(),
None,
None,
);
let mut cb = NoopStepCallback;
match graph.run_inline(&mut exec_ctx, max_steps, &mut cb).await {
Ok(()) => {
let state = exec_ctx.state();
let stop_reason = state.stop_reason.clone().unwrap_or(StopReason::Complete);
let last_response = state.last_response.clone().unwrap_or_else(empty_response);
let _ = tx
.send(AgentEvent::LoopEnd {
result: ToolUseResult {
stop_reason,
response: last_response,
messages: state.messages.clone(),
iterations: state.iterations,
tool_calls_executed: state.total_tool_calls,
},
})
.await;
}
Err(e) => {
let state = exec_ctx.state();
let error = LlmError::Provider {
provider: "react_graph".into(),
status: None,
code: None,
message: e.to_string(),
};
let _ = tx
.send(AgentEvent::LoopError {
error,
iterations: state.iterations,
})
.await;
}
}
});
rx
}
}