1use lellm_core::{CacheControl, ChatRequest, LlmError, Message, Prompt, ToolDefinition};
8use lellm_provider::ResolvedModel;
9
10use super::context::ContextBudget;
11use super::fallback::FallbackStrategy;
12use super::request_opts::RequestOptions;
13use super::retry::RetryPolicy;
14use std::sync::Arc;
15
16#[derive(Debug, Clone)]
24pub struct ToolUseConfig {
25 pub system: Option<Prompt>,
31 pub max_iterations: usize,
33 pub max_output_tokens: u32,
41 pub max_total_output_tokens: Option<u32>,
50 pub max_total_reasoning_tokens: Option<u32>,
57 pub context_budget: ContextBudget,
64 pub request_options: RequestOptions,
72 pub stream_thinking: bool,
80 pub tool_cache_policy: ToolCachePolicy,
87 pub retry_policy: RetryPolicy,
89}
90
91#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
96pub enum ToolCachePolicy {
97 #[default]
100 Auto,
101 Preserve,
103 Disabled,
105}
106
107impl Default for ToolUseConfig {
108 fn default() -> Self {
109 Self {
110 system: None,
111 max_iterations: 10,
112 max_output_tokens: 4_000,
113 max_total_output_tokens: None,
114 max_total_reasoning_tokens: None,
115 context_budget: ContextBudget::default(),
116 request_options: RequestOptions::default(),
117 stream_thinking: false,
118 tool_cache_policy: ToolCachePolicy::default(),
119 retry_policy: RetryPolicy::default(),
120 }
121 }
122}
123
124#[derive(Clone)]
130pub struct ToolUseDeps {
131 pub fallback: Arc<dyn FallbackStrategy>,
133}
134
135impl Default for ToolUseDeps {
136 fn default() -> Self {
137 Self {
138 fallback: Arc::new(super::fallback::DefaultFallback::default()),
139 }
140 }
141}
142
143pub(super) fn has_system_message(messages: &[Message]) -> bool {
147 messages.iter().any(|m| matches!(m, Message::System { .. }))
148}
149
150pub(super) fn build_request_messages_inner(
152 config: &ToolUseConfig,
153 messages: &[Message],
154) -> Result<Vec<Message>, LlmError> {
155 if let Some(ref system) = config.system {
156 if has_system_message(messages) {
157 return Err(LlmError::DuplicateSystemPrompt);
158 }
159 let mut result = vec![Message::System {
160 content: system.to_content_blocks(),
161 }];
162 result.extend(messages.iter().cloned());
163 Ok(result)
164 } else {
165 Ok(messages.to_vec())
166 }
167}
168
169pub(super) fn build_request_inner(
177 model: &ResolvedModel,
178 messages: &[Message],
179 max_output_tokens: u32,
180 request_options: &RequestOptions,
181 definitions: &[ToolDefinition],
182 tool_cache_policy: ToolCachePolicy,
183) -> ChatRequest {
184 let tools = match tool_cache_policy {
185 ToolCachePolicy::Auto => {
186 if definitions.is_empty() {
187 None
188 } else {
189 Some(
190 definitions
191 .iter()
192 .map(|d| {
193 if d.cache_control.is_none() {
194 let mut cloned = d.clone();
195 cloned.cache_control = Some(CacheControl::Breakpoint);
196 cloned
197 } else {
198 d.clone()
199 }
200 })
201 .collect(),
202 )
203 }
204 }
205 ToolCachePolicy::Preserve => {
206 if definitions.is_empty() {
207 None
208 } else {
209 Some(definitions.to_vec())
210 }
211 }
212 ToolCachePolicy::Disabled => {
213 if definitions.is_empty() {
214 None
215 } else {
216 Some(
217 definitions
218 .iter()
219 .map(|d| {
220 let mut cloned = d.clone();
221 cloned.cache_control = None;
222 cloned
223 })
224 .collect(),
225 )
226 }
227 }
228 };
229
230 let mut req = ChatRequest {
231 model: model.model.clone(),
232 messages: messages.to_vec(),
233 tools,
234 max_tokens: Some(max_output_tokens),
235 temperature: None,
236 top_p: None,
237 seed: None,
238 tool_choice: None,
239 stop_sequences: None,
240 prefill: None,
241 reasoning: None,
242 max_reasoning_tokens: None,
243 extra: None,
244 };
245
246 request_options.apply(&mut req);
248
249 req
250}
251
252pub(super) fn build_request_inner_with_round(
260 model: &ResolvedModel,
261 messages: &[Message],
262 max_output_tokens: u32,
263 request_options: &RequestOptions,
264 iteration: usize,
265 definitions: &[ToolDefinition],
266 tool_cache_policy: ToolCachePolicy,
267) -> ChatRequest {
268 let mut req = build_request_inner(
269 model,
270 messages,
271 max_output_tokens,
272 request_options,
273 definitions,
274 tool_cache_policy,
275 );
276
277 if iteration > 0 && request_options.tool_choice.is_some() {
280 req.tool_choice = None;
281 }
282
283 req
284}
285
286pub(super) fn empty_response() -> lellm_core::ChatResponse {
288 lellm_core::ChatResponse::new(
289 lellm_core::text_block(String::new()),
290 lellm_core::TokenUsage::default(),
291 serde_json::Value::Null,
292 )
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use lellm_core::Prompt;
299
300 #[test]
301 fn test_build_request_messages_with_prompt() {
302 let config = ToolUseConfig {
303 system: Some(
304 Prompt::builder()
305 .layer_cached("核心身份")
306 .layer_cached("工具指南")
307 .layer_dynamic("会话上下文")
308 .build(),
309 ),
310 ..Default::default()
311 };
312
313 let messages: Vec<Message> = vec![Message::user_text("你好")];
314 let result = build_request_messages_inner(&config, &messages).unwrap();
315
316 assert_eq!(result.len(), 2);
318
319 if let Message::System { content } = &result[0] {
321 assert_eq!(content.len(), 3);
322
323 if let lellm_core::ContentBlock::Text(t) = &content[0] {
325 assert_eq!(t.text, "核心身份");
326 assert!(t.cache_control.is_some());
327 } else {
328 panic!("expected Text block");
329 }
330
331 if let lellm_core::ContentBlock::Text(t) = &content[1] {
333 assert_eq!(t.text, "工具指南");
334 assert!(t.cache_control.is_some());
335 } else {
336 panic!("expected Text block");
337 }
338
339 if let lellm_core::ContentBlock::Text(t) = &content[2] {
341 assert_eq!(t.text, "会话上下文");
342 assert!(t.cache_control.is_none());
343 } else {
344 panic!("expected Text block");
345 }
346 } else {
347 panic!("expected System message");
348 }
349 }
350
351 #[test]
352 fn test_build_request_messages_with_plain_prompt() {
353 let config = ToolUseConfig {
354 system: Some("简单提示".into()),
355 ..Default::default()
356 };
357
358 let messages: Vec<Message> = vec![Message::user_text("你好")];
359 let result = build_request_messages_inner(&config, &messages).unwrap();
360
361 assert_eq!(result.len(), 2);
362 if let Message::System { content } = &result[0] {
363 assert_eq!(content.len(), 1);
364 assert_eq!(content[0].as_text(), Some("简单提示"));
365 } else {
366 panic!("expected System message");
367 }
368 }
369
370 #[test]
371 fn test_build_request_messages_no_system() {
372 let config = ToolUseConfig::default();
373 let messages: Vec<Message> = vec![Message::user_text("你好")];
374 let result = build_request_messages_inner(&config, &messages).unwrap();
375
376 assert_eq!(result.len(), 1);
377 assert!(matches!(result[0], Message::User { .. }));
378 }
379
380 #[test]
381 fn test_duplicate_system_prompt_error() {
382 let config = ToolUseConfig {
383 system: Some("系统提示".into()),
384 ..Default::default()
385 };
386
387 let messages: Vec<Message> =
388 vec![Message::system_text("已有系统"), Message::user_text("你好")];
389 let result = build_request_messages_inner(&config, &messages);
390
391 assert!(result.is_err());
392 }
393}