bamboo_engine/runtime/task_evaluation/
executor.rs1use std::sync::Arc;
2
3use tokio::sync::mpsc;
4
5use bamboo_agent_core::{AgentError, AgentEvent, Session};
6use bamboo_domain::ReasoningEffort;
7use bamboo_domain::TaskItemStatus;
8use bamboo_llm::{LLMProvider, LLMRequestOptions};
9
10use super::super::task_context::TaskLoopContext;
11use super::message_builder::build_task_evaluation_messages;
12use super::schema::get_task_evaluation_tools;
13use super::token_estimation::estimate_prompt_tokens;
14use super::TaskEvaluationResult;
15
16mod outcomes;
17
18fn skipped_evaluation(reasoning: &str) -> TaskEvaluationResult {
19 TaskEvaluationResult {
20 needs_evaluation: false,
21 updates: Vec::new(),
22 reasoning: reasoning.to_string(),
23 prompt_tokens: 0,
24 completion_tokens: 0,
25 }
26}
27
28fn normalize_lightweight_reasoning_effort(
29 reasoning_effort: Option<ReasoningEffort>,
30) -> Option<ReasoningEffort> {
31 reasoning_effort.map(|effort| match effort {
32 ReasoningEffort::Xhigh | ReasoningEffort::Max => ReasoningEffort::High,
33 other => other,
34 })
35}
36
37pub async fn evaluate_task_progress(
39 ctx: &TaskLoopContext,
40 session: &Session,
41 llm: Arc<dyn LLMProvider>,
42 event_tx: &mpsc::Sender<AgentEvent>,
43 session_id: &str,
44 model: &str,
45 reasoning_effort: Option<ReasoningEffort>,
46) -> Result<TaskEvaluationResult, AgentError> {
47 use crate::runtime::stream::handler::consume_llm_stream_silent;
48
49 let in_progress_count = ctx
50 .items
51 .iter()
52 .filter(|item| matches!(item.status, TaskItemStatus::InProgress))
53 .count();
54
55 if in_progress_count == 0 {
56 return Ok(skipped_evaluation("No in-progress tasks to evaluate"));
57 }
58
59 tracing::info!(
63 "[{}] Evaluating {} in-progress task items",
64 session_id,
65 in_progress_count
66 );
67
68 let _ = event_tx
69 .send(AgentEvent::TaskEvaluationStarted {
70 session_id: session_id.to_string(),
71 items_count: in_progress_count,
72 generation: Some(ctx.version),
73 })
74 .await;
75
76 let messages = build_task_evaluation_messages(ctx, session);
77 let prompt_tokens = estimate_prompt_tokens(&messages);
78 let tools = get_task_evaluation_tools();
79
80 tracing::debug!("[{}] Task evaluation using model: {}", session_id, model);
82
83 let request_reasoning_effort = normalize_lightweight_reasoning_effort(reasoning_effort);
84 if request_reasoning_effort != reasoning_effort {
85 tracing::debug!(
86 "[{}] Task evaluation downgraded reasoning effort from {:?} to {:?} for lightweight request",
87 session_id,
88 reasoning_effort,
89 request_reasoning_effort
90 );
91 }
92
93 let request_options = LLMRequestOptions {
94 session_id: Some(session_id.to_string()),
95 reasoning_effort: request_reasoning_effort,
96 parallel_tool_calls: None,
97 responses: None,
98 request_purpose: Some("task_evaluation".to_string()),
99 cache: None,
100 };
101 match llm
102 .chat_stream_with_options(&messages, &tools, Some(8192), model, Some(&request_options))
103 .await
104 {
105 Ok(stream) => {
106 let stream_output = consume_llm_stream_silent(
107 stream,
108 &tokio_util::sync::CancellationToken::new(),
109 session_id,
110 )
111 .await
112 .map_err(|error| AgentError::LLM(error.to_string()))?;
113
114 Ok(outcomes::build_success_result(
115 stream_output,
116 event_tx,
117 session_id,
118 prompt_tokens,
119 ctx.version,
120 )
121 .await)
122 }
123 Err(error) => {
124 tracing::warn!("[{}] Task evaluation failed: {}", session_id, error);
125 Ok(skipped_evaluation(&format!("Evaluation failed: {}", error)))
126 }
127 }
128}