Run the agentic tool-use loop. The LLM can invoke tools up to `config.max_turns` times before a final text response is forced.
(
provider: &LlmProvider,
executor: &dyn ToolExecutor,
query: &str,
config: &AgentConfig,
)
| 529 | /// Run the agentic tool-use loop. The LLM can invoke tools up to |
| 530 | /// `config.max_turns` times before a final text response is forced. |
| 531 | pub async fn run_tool_loop( |
| 532 | provider: &LlmProvider, |
| 533 | executor: &dyn ToolExecutor, |
| 534 | query: &str, |
| 535 | config: &AgentConfig, |
| 536 | ) -> Result<AgentResult, AiError> { |
| 537 | let tool_defs = executor.tool_definitions(); |
| 538 | |
| 539 | let mut messages: Vec<ToolMessage> = Vec::new(); |
| 540 | if !config.system_prompt.is_empty() { |
| 541 | messages.push(ToolMessage::System { |
| 542 | content: config.system_prompt.clone(), |
| 543 | }); |
| 544 | } |
| 545 | messages.push(ToolMessage::User { |
| 546 | content: query.to_string(), |
| 547 | }); |
| 548 | |
| 549 | let mut total_usage = TokenUsage::default(); |
| 550 | let mut tool_trace: Vec<ToolTraceEntry> = Vec::new(); |
| 551 | let mut turn: u8 = 0; |
| 552 | let mut last_text = String::new(); // accumulate text across turns |
| 553 | |
| 554 | loop { |
| 555 | turn += 1; |
| 556 | let at_limit = turn > config.max_turns; |
| 557 | |
| 558 | // If we've exhausted turns, inject a prompt telling the LLM to |
| 559 | // answer now with what it has, rather than removing tools (which |
| 560 | // confuses the conversation flow after tool_use/tool_result pairs). |
| 561 | if at_limit { |
| 562 | messages.push(ToolMessage::User { |
| 563 | content: "You have used all available tool turns. Please provide your \ |
| 564 | answer now based on what you have found so far. Be concise \ |
| 565 | and cite file paths and line numbers." |
| 566 | .to_string(), |
| 567 | }); |
| 568 | } |
| 569 | let tools_for_call = &tool_defs; // always provide tools |
| 570 | let tokens_for_call = if at_limit { |
| 571 | config.max_tokens.max(8192) |
| 572 | } else { |
| 573 | config.max_tokens |
| 574 | }; |
| 575 | |
| 576 | debug!( |
| 577 | "tool loop turn {turn}/{} (tools={})", |
| 578 | config.max_turns, |
| 579 | tools_for_call.len() |
| 580 | ); |
| 581 | |
| 582 | let resp = provider |
| 583 | .chat_with_tools(&messages, tools_for_call, tokens_for_call) |
| 584 | .await?; |
| 585 | |
| 586 | total_usage.input_tokens += resp.usage.input_tokens; |
| 587 | total_usage.output_tokens += resp.usage.output_tokens; |
| 588 | total_usage.cache_read_tokens += resp.usage.cache_read_tokens; |
no test coverage detected