Skip to main content

bamboo_engine/runtime/task_evaluation/
executor.rs

1use 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
37/// 执行 TaskList 评估
38pub 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    // When to evaluate is owned entirely by the caller (the loop spawns this only
60    // on a Task-tool write); this function just decides how. The single remaining
61    // guard above skips when there is nothing in progress to assess.
62    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    // Use model from parameter (passed from config), not from session.
81    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}