use lellm_core::{ChatRequest, LlmError, Message};
use lellm_provider::ResolvedModel;
use super::context::ContextBudget;
use super::fallback::FallbackStrategy;
use super::request_opts::RequestOptions;
use super::tools::ToolExecutor;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct ToolUseConfig {
pub system_prompt: Option<String>,
pub max_iterations: usize,
pub max_output_tokens: u32,
pub max_total_output_tokens: Option<u32>,
pub max_total_reasoning_tokens: Option<u32>,
pub context_budget: ContextBudget,
pub request_options: RequestOptions,
pub stream_thinking: bool,
}
impl Default for ToolUseConfig {
fn default() -> Self {
Self {
system_prompt: None,
max_iterations: 10,
max_output_tokens: 4_000,
max_total_output_tokens: None,
max_total_reasoning_tokens: None,
context_budget: ContextBudget::default(),
request_options: RequestOptions::default(),
stream_thinking: false,
}
}
}
#[derive(Clone)]
pub struct ToolUseDeps {
pub fallback: Arc<dyn FallbackStrategy>,
}
impl Default for ToolUseDeps {
fn default() -> Self {
Self {
fallback: Arc::new(super::fallback::DefaultFallback::default()),
}
}
}
pub(super) fn has_system_message(messages: &[Message]) -> bool {
messages.iter().any(|m| matches!(m, Message::System { .. }))
}
pub(super) fn build_request_messages_inner(
config: &ToolUseConfig,
messages: &[Message],
) -> Result<Vec<Message>, LlmError> {
if let Some(ref sp) = config.system_prompt {
if has_system_message(messages) {
return Err(LlmError::DuplicateSystemPrompt);
}
let mut result = vec![Message::System {
content: lellm_core::text_block(sp.clone()),
}];
result.extend(messages.iter().cloned());
Ok(result)
} else {
Ok(messages.to_vec())
}
}
pub(super) fn build_request_inner(
model: &ResolvedModel,
executor: &ToolExecutor,
messages: &[Message],
max_output_tokens: u32,
request_options: &RequestOptions,
) -> ChatRequest {
let mut req = ChatRequest {
model: model.model.clone(),
messages: messages.to_vec(),
tools: executor.has_tools().then(|| executor.definitions()),
max_tokens: Some(max_output_tokens),
temperature: None,
top_p: None,
seed: None,
tool_choice: None,
stop_sequences: None,
prefill: None,
reasoning: None,
max_reasoning_tokens: None,
extra: None,
};
request_options.apply(&mut req);
req
}
pub(super) fn build_request_inner_with_round(
model: &ResolvedModel,
executor: &ToolExecutor,
messages: &[Message],
max_output_tokens: u32,
request_options: &RequestOptions,
iteration: usize,
) -> ChatRequest {
let mut req = build_request_inner(
model,
executor,
messages,
max_output_tokens,
request_options,
);
if iteration > 0 && request_options.tool_choice.is_some() {
req.tool_choice = None;
}
req
}
pub(super) fn empty_response() -> lellm_core::ChatResponse {
lellm_core::ChatResponse::new(
lellm_core::text_block(String::new()),
lellm_core::TokenUsage::default(),
serde_json::Value::Null,
)
}