1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! Unified tool-calling policy for all LLM call paths.
//!
//! This module is the single source of truth for two related concerns that
//! used to be reimplemented in every call path:
//!
//! 1. **Attaching** tool definitions to outgoing chat-completion requests.
//! The native OpenAI-style `tools: [...]` shape is sent when the model
//! advertises native function calling; otherwise tools are exposed
//! through the system prompt and the model emits XML.
//!
//! 2. **Extracting** tool calls from a returned `Message`. When native FC
//! is enabled the model is *expected* to use the `message.tool_calls`
//! field, but in practice some backends (sglang on certain quants,
//! older vLLM, etc.) leave that field empty and embed `<tool>...</tool>`
//! blocks (or other text formats) in `content` instead. The unified
//! extractor falls back to the multi-format text parser whenever the
//! native field is empty, regardless of whether the request was made
//! with native FC on or off.
//!
//! Both behaviours are now identical for `chat()`, `chat_stream()`,
//! `chat_with_profile()`, and the SWL runtime.
use ;
use crate;
/// Attach tool definitions to a chat-completion request body.
///
/// When `native_function_calling` is true and tools are present, sets:
/// - `body.tools` = the JSON-serialized tool definitions
/// - `body.tool_choice` = `"auto"`
///
/// When `native_function_calling` is false, tools are still attached to
/// the request body so backends that auto-detect them can use them, but
/// `tool_choice` is omitted (the model is expected to emit XML in
/// `content` instead, prompted by the system message).
///
/// When `tools` is `None`, this is a no-op.
/// Extract tool calls from a returned assistant message.
///
/// Behaviour:
/// - If `message.tool_calls` is present and non-empty, return those
/// directly (the native path).
/// - Otherwise, parse `message.content` text for XML/JSON-formatted tool
/// calls. This is the *fallback* path and runs even when
/// `native_function_calling` is true — some backends populate text
/// despite the request flag.
/// - The fallback also runs when `native_function_calling` is false (the
/// prompt-based path).
///
/// In all cases the returned `ToolCall` values are normalized OpenAI-style
/// objects (id/type/function) so downstream code never needs to distinguish.
/// Convert a [`ParsedToolCall`] (from the text parser) into an OpenAI-style
/// [`ToolCall`] so downstream code only ever sees one shape.
///
/// The synthetic id `parsed_<uuid>` matches the convention already in use
/// by the streaming agent fallback (see `src/agent/assistant_response.rs`).
/// Same as [`extract_tool_calls`] but takes raw text content. Useful when
/// the caller has already split content/reasoning out of the message
/// (e.g. the streaming path in `agent/assistant_response.rs`, which
/// receives the assembled text directly).