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        })
73        .await;
74
75    let messages = build_task_evaluation_messages(ctx, session);
76    let prompt_tokens = estimate_prompt_tokens(&messages);
77    let tools = get_task_evaluation_tools();
78
79    // Use model from parameter (passed from config), not from session.
80    tracing::debug!("[{}] Task evaluation using model: {}", session_id, model);
81
82    let request_reasoning_effort = normalize_lightweight_reasoning_effort(reasoning_effort);
83    if request_reasoning_effort != reasoning_effort {
84        tracing::debug!(
85            "[{}] Task evaluation downgraded reasoning effort from {:?} to {:?} for lightweight request",
86            session_id,
87            reasoning_effort,
88            request_reasoning_effort
89        );
90    }
91
92    let request_options = LLMRequestOptions {
93        session_id: Some(session_id.to_string()),
94        reasoning_effort: request_reasoning_effort,
95        parallel_tool_calls: None,
96        responses: None,
97        request_purpose: Some("task_evaluation".to_string()),
98        cache: None,
99    };
100    match llm
101        .chat_stream_with_options(&messages, &tools, Some(8192), model, Some(&request_options))
102        .await
103    {
104        Ok(stream) => {
105            let stream_output = consume_llm_stream_silent(
106                stream,
107                &tokio_util::sync::CancellationToken::new(),
108                session_id,
109            )
110            .await
111            .map_err(|error| AgentError::LLM(error.to_string()))?;
112
113            Ok(
114                outcomes::build_success_result(stream_output, event_tx, session_id, prompt_tokens)
115                    .await,
116            )
117        }
118        Err(error) => {
119            tracing::warn!("[{}] Task evaluation failed: {}", session_id, error);
120            Ok(skipped_evaluation(&format!("Evaluation failed: {}", error)))
121        }
122    }
123}