jiaqiz commited on
Commit
5b92cf7
·
verified ·
1 Parent(s): 595095f

Add files using upload-large-folder tool

Browse files
README.md ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ license: other
4
+ license_name: nvidia-open-model-license
5
+ license_link: >-
6
+ https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-open-model-license/
7
+
8
+ pipeline_tag: text-generation
9
+ language:
10
+ - en
11
+ tags:
12
+ - nvidia
13
+ - llama-3
14
+ - pytorch
15
+ ---
16
+
17
+ # Llama-3.3-Nemotron-Super-49B-v1.5-FP8
18
+
19
+ ![image](./accuracy_chart.png)
20
+
21
+ ## Model Overview
22
+
23
+ Llama-3.3-Nemotron-Super-49B-v1.5-FP8 is a significantly upgraded version of Llama-3.3-Nemotron-Super-49B-v1 and is a large language model (LLM) which is a derivative of Meta Llama-3.3-70B-Instruct (AKA the reference model). It is a reasoning model that is post trained for reasoning, human chat preferences, and agentic tasks, such as RAG and tool calling. The model supports a context length of 128K tokens.
24
+
25
+ Llama-3.3-Nemotron-Super-49B-v1.5-FP8 is a model which offers a great tradeoff between model accuracy and efficiency. Efficiency (throughput) directly translates to savings. Using a novel Neural Architecture Search (NAS) approach, we greatly reduce the model’s memory footprint, enabling larger workloads, as well as fitting the model on a single GPU at high workloads (H200). This NAS approach enables the selection of a desired point in the accuracy-efficiency tradeoff. For more information on the NAS approach, please refer to [this paper](https://arxiv.org/abs/2411.19146)
26
+
27
+ The model underwent a multi-phase post-training process to enhance both its reasoning and non-reasoning capabilities. This includes a supervised fine-tuning stage for Math, Code, Science, and Tool Calling. Additionally, the model went through multiple stages of Reinforcement Learning (RL) including Reward-aware Preference Optimization (RPO) for chat, Reinforcement Learning with Verifiable Rewards (RLVR) for reasoning, and iterative Direct Preference Optimization (DPO) for Tool Calling capability enhancements. The final checkpoint was achieved after merging several RL and DPO checkpoints.
28
+
29
+ This model is part of the Llama Nemotron Collection. You can find the other model(s) in this family here:
30
+ - [Llama-3.1-Nemotron-Nano-4B-v1.1](https://huggingface.co/nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1)
31
+ - [Llama-3.1-Nemotron-Ultra-253B-v1](https://huggingface.co/nvidia/Llama-3_1-Nemotron-Ultra-253B-v1)
32
+
33
+ This model is ready for commercial use.
34
+
35
+ ## License/Terms of Use
36
+
37
+ GOVERNING TERMS: Your use of this model is governed by the [NVIDIA Open Model License.](https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-open-model-license/) Additional Information: [Llama 3.3 Community License Agreement](https://www.llama.com/llama3_3/license/). Built with Llama.
38
+
39
+ **Model Developer:** NVIDIA
40
+
41
+ **Model Dates:** Trained between November 2024 and July 2025
42
+
43
+ **Data Freshness:** The pretraining data has a cutoff of 2023 per Meta Llama 3.3 70B
44
+
45
+ ## Deployment Geography
46
+ Global
47
+
48
+ ### Use Case: <br>
49
+ Developers designing AI Agent systems, chatbots, RAG systems, and other AI-powered applications. Also suitable for typical instruction-following tasks. <br>
50
+
51
+ ### Release Date: <br>
52
+ - Hugging Face 7/25/2025 via [Llama-3_3-Nemotron-Super-49B-v1_5](https://huggingface.co/nvidia/Llama-3_3-Nemotron-Super-49B-v1_5)
53
+ - build.nvidia.com 7/25/2025 [Llama-3_3-Nemotron-Super-49B-v1_5](https://build.nvidia.com/nvidia/llama-3_3-nemotron-super-49b-v1_5)
54
+
55
+ ## References
56
+
57
+ * [\[2505.00949\] Llama-Nemotron: Efficient Reasoning Models](https://arxiv.org/abs/2505.00949)
58
+ * [\[2502.00203\] Reward-aware Preference Optimization: A Unified Mathematical Framework for Model Alignment](https://arxiv.org/abs/2502.00203)
59
+ * [\[2411.19146\]Puzzle: Distillation-Based NAS for Inference-Optimized LLMs](https://arxiv.org/abs/2411.19146)
60
+
61
+ ## Model Architecture
62
+
63
+ **Architecture Type:** Dense decoder-only Transformer model
64
+
65
+ **Network Architecture:** Llama 3.3 70B Instruct, customized through Neural Architecture Search (NAS)
66
+
67
+ The model is a derivative of Meta’s Llama-3.3-70B-Instruct, using Neural Architecture Search (NAS). The NAS algorithm results in non-standard and non-repetitive blocks. This includes the following:
68
+
69
+ Skip attention: In some blocks, the attention is skipped entirely, or replaced with a single linear layer.
70
+ Variable FFN: The expansion/compression ratio in the FFN layer is different between blocks.
71
+
72
+ We utilize a block-wise distillation of the reference model, where for each block we create multiple variants providing different tradeoffs of quality vs. computational complexity, discussed in more depth below. We then search over the blocks to create a model which meets the required throughput and memory (optimized for a single H100-80GB GPU) while minimizing the quality degradation. The model then undergoes knowledge distillation (KD), with a focus on English single and multi-turn chat use-cases. The KD step included 40 billion tokens consisting of a mixture of 3 datasets - FineWeb, Buzz-V1.2 and Dolma.
73
+
74
+ ## Intended use
75
+
76
+ Llama-3.3-Nemotron-Super-49B-v1.5-FP8 is a general purpose reasoning and chat model intended to be used in English and coding languages. Other non-English languages (German, French, Italian, Portuguese, Hindi, Spanish, and Thai) are also supported.
77
+
78
+ ## Input
79
+ - **Input Type:** Text
80
+ - **Input Format:** String
81
+ - **Input Parameters:** One-Dimensional (1D)
82
+ - **Other Properties Related to Input:** Context length up to 131,072 tokens
83
+
84
+ ## Output
85
+ - **Output Type:** Text
86
+ - **Output Format:** String
87
+ - **Output Parameters:** One-Dimensional (1D)
88
+ - **Other Properties Related to Output:** Context length up to 131,072 tokens
89
+
90
+ Our AI models are designed and/or optimized to run on NVIDIA GPU-accelerated systems. By leveraging NVIDIA’s hardware (e.g. GPU cores) and software frameworks (e.g., CUDA libraries), the model achieves faster training and inference times compared to CPU-only solutions.
91
+
92
+ ## Model Version
93
+ 1.5 (07/25/2025)
94
+
95
+ ## Software Integration
96
+ - **Runtime Engine:** Transformers
97
+
98
+ - **Recommended Hardware Microarchitecture Compatibility:**
99
+ - NVIDIA Ampere
100
+ - NVIDIA Hopper
101
+ - **Preferred Operating System(s):** Linux
102
+
103
+ ## Quick Start and Usage Recommendations:
104
+
105
+ 1. By default (empty system prompt) the model will respond in reasoning ON mode. Setting `/no_think` in the system prompt will enable reasoning OFF mode.
106
+ 2. We recommend setting temperature to `0.6`, and Top P to `0.95` for Reasoning ON mode
107
+ 3. We recommend using greedy decoding for Reasoning OFF mode
108
+
109
+ You can try this model out through the preview API, using this link: [Llama-3_3-Nemotron-Super-49B-v1_5](https://build.nvidia.com/nvidia/llama-3_3-nemotron-super-49b-v1_5).
110
+
111
+ ## Use It with vLLM
112
+
113
+ ```pip install vllm==0.9.2```
114
+
115
+ An example on how to serve with vLLM:
116
+
117
+ ```console
118
+ $ python3 -m vllm.entrypoints.openai.api_server \
119
+ --model "nvidia/Llama-3_3-Nemotron-Super-49B-v1_5-FP8" \
120
+ --trust-remote-code \
121
+ --seed=1 \
122
+ --host="0.0.0.0" \
123
+ --port=5000 \
124
+ --served-model-name "Llama-3_3-Nemotron-Super-49B-v1_5-FP8" \
125
+ --tensor-parallel-size=8 \
126
+ --max-model-len=65536 \
127
+ --gpu-memory-utilization 0.95 \
128
+ --enforce-eager \
129
+ --quantization=modelopt
130
+ ```
131
+
132
+ ### Running a vLLM Server with Tool-call Support
133
+ To enable tool calling usage with this model, we provide a tool parser in the repository. Here is an example on how to use it:
134
+
135
+
136
+ ```console
137
+ $ git clone https://huggingface.co/nvidia/Llama-3_3-Nemotron-Super-49B-v1_5-FP8
138
+
139
+ $ conda create -n vllm python=3.12 -y
140
+ $ conda activate vllm
141
+ $ pip install vllm==0.9.2
142
+
143
+ $ python3 -m vllm.entrypoints.openai.api_server \
144
+ --model Llama-3_3-Nemotron-Super-49B-v1_5-FP8 \
145
+ --trust-remote-code \
146
+ --seed=1 \
147
+ --host="0.0.0.0" \
148
+ --port=5000 \
149
+ --served-model-name "Llama-3_3-Nemotron-Super-49B-v1_5-FP8" \
150
+ --tensor-parallel-size=8 \
151
+ --max-model-len=65536 \
152
+ --gpu-memory-utilization 0.95 \
153
+ --enforce-eager \
154
+ --quantization=modelopt \
155
+ --enable-auto-tool-choice \
156
+ --tool-parser-plugin "Llama-3_3-Nemotron-Super-49B-v1_5-FP8/llama_nemotron_toolcall_parser_no_streaming.py" \
157
+ --tool-call-parser "llama_nemotron_json"
158
+ ```
159
+
160
+ After launching a vLLM server, you can call the server with tool-call support using a Python script like below.
161
+
162
+ ```python
163
+ from openai import OpenAI
164
+ client = OpenAI(
165
+ base_url="http://0.0.0.0:5000/v1",
166
+ api_key="dummy",
167
+ )
168
+ completion = client.chat.completions.create(
169
+ model="Llama-3_3-Nemotron-Super-49B-v1_5-FP8",
170
+ messages=[
171
+ {"role": "system", "content": ""},
172
+ {"role": "user", "content": "My bill is $100. What will be the amount for 18% tip?"}
173
+ ],
174
+ tools=[
175
+ {
176
+ "type": "function",
177
+ "function": {
178
+ "name": "calculate_tip",
179
+ "parameters": {
180
+ "type": "object",
181
+ "properties": {
182
+ "bill_total": {
183
+ "type": "integer",
184
+ "description": "The total amount of the bill"
185
+ },
186
+ "tip_percentage": {
187
+ "type": "integer",
188
+ "description": "The percentage of tip to be applied"
189
+ }
190
+ },
191
+ "required": ["bill_total", "tip_percentage"]
192
+ }
193
+ }
194
+ },
195
+ {
196
+ "type": "function",
197
+ "function": {
198
+ "name": "convert_currency",
199
+ "parameters": {
200
+ "type": "object",
201
+ "properties": {
202
+ "amount": {
203
+ "type": "integer",
204
+ "description": "The amount to be converted"
205
+ },
206
+ "from_currency": {
207
+ "type": "string",
208
+ "description": "The currency code to convert from"
209
+ },
210
+ "to_currency": {
211
+ "type": "string",
212
+ "description": "The currency code to convert to"
213
+ }
214
+ },
215
+ "required": ["from_currency", "amount", "to_currency"]
216
+ }
217
+ }
218
+ }
219
+ ],
220
+ temperature=0.6,
221
+ top_p=0.95,
222
+ max_tokens=32768,
223
+ stream=False
224
+ )
225
+ print(completion.choices[0].message.content)
226
+ '''
227
+ <think>
228
+ Okay, let's see. The user has a bill of $100 and wants to know the amount for an 18% tip. Hmm, I need to calculate the tip based on the bill total and the percentage. The tools provided include calculate_tip, which takes bill_total and tip_percentage as parameters. So the bill_total here is 100, and the tip_percentage is 18. I should call the calculate_tip function with these values. Wait, do I need to check if the parameters are integers? The bill is $100, which is an integer, and 18% is also an integer. So that fits the function's requirements. I don't need to convert any currency here because the user is asking about a tip in the same currency. So the correct tool to use is calculate_tip with those parameters.
229
+ </think>
230
+ '''
231
+ print(completion.choices[0].message.tool_calls)
232
+ '''
233
+ [ChatCompletionMessageToolCall(id='chatcmpl-tool-e341c6954d2c48c2a0e9071c7bdefd8b', function=Function(arguments='{"bill_total": 100, "tip_percentage": 18}', name='calculate_tip'), type='function')]
234
+ '''
235
+ ```
236
+
237
+ ## Training and Evaluation Datasets
238
+
239
+ ## Training Datasets
240
+
241
+ A large variety of training data was used for the knowledge distillation phase before post-training pipeline, 3 of which included: FineWeb, Buzz-V1.2, and Dolma.
242
+
243
+ The data for the multi-stage post-training phases for improvements in Code, Math, and Reasoning is a compilation of SFT and RL data that supports improvements of math, code, general reasoning, and instruction following capabilities of the original Llama instruct model.
244
+
245
+ Prompts have been sourced from either public and open corpus or synthetically generated. Responses were synthetically generated by a variety of models, with some prompts containing responses for both reasoning on and off modes, to train the model to distinguish between two modes.
246
+
247
+ NVIDIA will be releasing the post-training dataset in the coming weeks.
248
+
249
+ **Data Collection for Training Datasets:**
250
+ Hybrid: Automated, Human, Synthetic
251
+
252
+ **Data Labeling for Training Datasets:**
253
+ Hybrid: Automated, Human, Synthetic
254
+
255
+ ## Evaluation Datasets
256
+
257
+ We used the datasets listed below to evaluate Llama-3.3-Nemotron-Super-49B-v1.5-FP8.
258
+
259
+ Data Collection for Evaluation Datasets:
260
+ - Hybrid: Human. Synthetic
261
+
262
+ Data Labeling for Evaluation Datasets:
263
+ - Hybrid: Human, Synthetic, Automatic
264
+
265
+ ## Evaluation Results
266
+ We evaluate the model using temperature=`0.6`, top_p=`0.95`, and 64k sequence length. We run the benchmarks up to 16 times and average the scores to be more accurate.
267
+
268
+ ### MATH500
269
+
270
+ | Reasoning Mode | pass@1 (avg. over 4 runs) |
271
+ |--------------|------------|
272
+ | Reasoning On | 97.8 |
273
+
274
+ ### AIME 2024
275
+
276
+ | Reasoning Mode | pass@1 (avg. over 16 runs) |
277
+ |--------------|------------|
278
+ | Reasoning On | 88.54 |
279
+
280
+ ### AIME 2025
281
+
282
+ | Reasoning Mode | pass@1 (avg. over 16 runs) |
283
+ |--------------|------------|
284
+ | Reasoning On | 83.75 |
285
+
286
+ ### GPQA
287
+
288
+ | Reasoning Mode | pass@1 (avg. over 4 runs) |
289
+ |--------------|------------|
290
+ | Reasoning On | 69.57 |
291
+
292
+ ### LiveCodeBench 24.10-25.02
293
+
294
+ | Reasoning Mode | pass@1 (avg. over 4 runs) |
295
+ |--------------|------------|
296
+ | Reasoning On | 69.52 |
297
+
298
+ ### BFCL v3
299
+
300
+ | Reasoning Mode | pass@1 (avg. over 2 runs) |
301
+ |--------------|------------|
302
+ | Reasoning On | 71.11 |
303
+
304
+ ### IFEval
305
+
306
+ | Reasoning Mode | Strict:Instruction |
307
+ |--------------|------------|
308
+ | Reasoning On | 87.05 |
309
+
310
+ ### ArenaHard
311
+
312
+ | Reasoning Mode | pass@1 (avg. over 1 runs) |
313
+ |--------------|------------|
314
+ | Reasoning On | 88.7 |
315
+
316
+
317
+ All evaluations were done using the [NeMo-Skills](https://github.com/NVIDIA/NeMo-Skills) repository.
318
+
319
+ ## Inference:
320
+
321
+ **Engine:**
322
+ - Transformers
323
+
324
+ **Test Hardware:**
325
+ - 1x NVIDIA H100-80GB GPU
326
+
327
+ ## Ethical Considerations:
328
+
329
+ NVIDIA believes Trustworthy AI is a shared responsibility and we have established policies and practices to enable development for a wide array of AI applications. When downloaded or used in accordance with our terms of service, developers should work with their internal model team to ensure this model meets requirements for the relevant industry and use case and addresses unforeseen product misuse.
330
+
331
+ For more detailed information on ethical considerations for this model, please see the Model Card++ [Explainability](./EXPLAINABILITY.md), [Bias](./BIAS.md), [Safety & Security](./SAFETY&SECURITY.md), and [Privacy](./PRIVACY.md) Subcards.
332
+
333
+ Please report security vulnerabilities or NVIDIA AI Concerns [here](https://www.nvidia.com/en-us/support/submit-security-vulnerability/).
334
+
335
+ ## Citation
336
+
337
+ ```
338
+ @misc{bercovich2025llamanemotronefficientreasoningmodels,
339
+ title={Llama-Nemotron: Efficient Reasoning Models},
340
+ author={Akhiad Bercovich and Itay Levy and Izik Golan and Mohammad Dabbah and Ran El-Yaniv and Omri Puny and Ido Galil and Zach Moshe and Tomer Ronen and Najeeb Nabwani and Ido Shahaf and Oren Tropp and Ehud Karpas and Ran Zilberstein and Jiaqi Zeng and Soumye Singhal and Alexander Bukharin and Yian Zhang and Tugrul Konuk and Gerald Shen and Ameya Sunil Mahabaleshwarkar and Bilal Kartal and Yoshi Suhara and Olivier Delalleau and Zijia Chen and Zhilin Wang and David Mosallanezhad and Adi Renduchintala and Haifeng Qian and Dima Rekesh and Fei Jia and Somshubra Majumdar and Vahid Noroozi and Wasi Uddin Ahmad and Sean Narenthiran and Aleksander Ficek and Mehrzad Samadi and Jocelyn Huang and Siddhartha Jain and Igor Gitman and Ivan Moshkov and Wei Du and Shubham Toshniwal and George Armstrong and Branislav Kisacanin and Matvei Novikov and Daria Gitman and Evelina Bakhturina and Jane Polak Scowcroft and John Kamalu and Dan Su and Kezhi Kong and Markus Kliegl and Rabeeh Karimi and Ying Lin and Sanjeev Satheesh and Jupinder Parmar and Pritam Gundecha and Brandon Norick and Joseph Jennings and Shrimai Prabhumoye and Syeda Nahida Akter and Mostofa Patwary and Abhinav Khattar and Deepak Narayanan and Roger Waleffe and Jimmy Zhang and Bor-Yiing Su and Guyue Huang and Terry Kong and Parth Chadha and Sahil Jain and Christine Harvey and Elad Segal and Jining Huang and Sergey Kashirsky and Robert McQueen and Izzy Putterman and George Lam and Arun Venkatesan and Sherry Wu and Vinh Nguyen and Manoj Kilaru and Andrew Wang and Anna Warno and Abhilash Somasamudramath and Sandip Bhaskar and Maka Dong and Nave Assaf and Shahar Mor and Omer Ullman Argov and Scot Junkin and Oleksandr Romanenko and Pedro Larroy and Monika Katariya and Marco Rovinelli and Viji Balas and Nicholas Edelman and Anahita Bhiwandiwalla and Muthu Subramaniam and Smita Ithape and Karthik Ramamoorthy and Yuting Wu and Suguna Varshini Velury and Omri Almog and Joyjit Daw and Denys Fridman and Erick Galinkin and Michael Evans and Katherine Luna and Leon Derczynski and Nikki Pope and Eileen Long and Seth Schneider and Guillermo Siman and Tomasz Grzegorzek and Pablo Ribalta and Monika Katariya and Joey Conway and Trisha Saar and Ann Guan and Krzysztof Pawelec and Shyamala Prayaga and Oleksii Kuchaiev and Boris Ginsburg and Oluwatobi Olabiyi and Kari Briski and Jonathan Cohen and Bryan Catanzaro and Jonah Alben and Yonatan Geifman and Eric Chung and Chris Alexiuk},
341
+ year={2025},
342
+ eprint={2505.00949},
343
+ archivePrefix={arXiv},
344
+ primaryClass={cs.CL},
345
+ url={https://arxiv.org/abs/2505.00949},
346
+ }
347
+ ```
348
+
llama_nemotron_toolcall_parser_no_streaming.py ADDED
@@ -0,0 +1,470 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ import ast
4
+ import json
5
+ import re
6
+ from collections.abc import Sequence
7
+ from typing import Union
8
+
9
+ import partial_json_parser
10
+ from partial_json_parser.core.options import Allow
11
+
12
+ from vllm.entrypoints.openai.protocol import (
13
+ ChatCompletionRequest,
14
+ DeltaFunctionCall, DeltaMessage,
15
+ DeltaToolCall,
16
+ ExtractedToolCallInformation,
17
+ FunctionCall,
18
+ ToolCall,
19
+ )
20
+ from vllm.entrypoints.openai.tool_parsers.abstract_tool_parser import (
21
+ ToolParser,
22
+ ToolParserManager,
23
+ )
24
+ from vllm.logger import init_logger
25
+ from vllm.transformers_utils.tokenizer import AnyTokenizer
26
+ from vllm.utils import random_uuid
27
+
28
+ logger = init_logger(__name__)
29
+
30
+
31
+ @ToolParserManager.register_module("llama_nemotron_xml")
32
+ class LlamaNemotronXMLToolParser(ToolParser):
33
+
34
+ def __init__(self, tokenizer: AnyTokenizer):
35
+ super().__init__(tokenizer)
36
+
37
+ self.current_tool_name_sent: bool = False
38
+ self.prev_tool_call_arr: list[dict] = []
39
+ self.current_tool_id: int = -1 # Potentially for streaming
40
+ self.streamed_args_for_tool: list[str] = [] # Potentially for streaming
41
+
42
+ self.tool_call_start_token: str = "<tool_call>"
43
+ self.tool_call_end_token: str = "</tool_call>"
44
+
45
+ # Regex to find full <tool_call>...</tool_call> blocks and capture their content
46
+ self.tool_call_block_regex = re.compile(r"<tool_call>(.*?)</tool_call>", re.DOTALL)
47
+ # Regex to find <tool>...</tool> within a tool_call block content
48
+ self.name_regex = re.compile(r"<tool>(.*?)</tool>", re.DOTALL)
49
+ # Regex to find <key>value</key> pairs within the tool_call block content (excluding <tool> tags)
50
+ self.param_regex = re.compile(r"<([^/>\s]+)>(.*?)</\1>", re.DOTALL)
51
+
52
+ def extract_tool_calls(
53
+ self,
54
+ model_output: str,
55
+ request: ChatCompletionRequest,
56
+ ) -> ExtractedToolCallInformation:
57
+
58
+ tool_call_start_index = model_output.find(self.tool_call_start_token)
59
+
60
+ if tool_call_start_index == -1:
61
+ return ExtractedToolCallInformation(
62
+ tools_called=False,
63
+ tool_calls=[],
64
+ content=model_output,
65
+ )
66
+
67
+ content = model_output[:tool_call_start_index].strip()
68
+ tool_calls_str_content = model_output[tool_call_start_index:]
69
+
70
+ parsed_tool_calls = []
71
+
72
+ try:
73
+ # Find all occurrences of <tool_call>...</tool_call>
74
+ xml_tool_call_contents = self.tool_call_block_regex.findall(tool_calls_str_content)
75
+
76
+ for tool_content_str in xml_tool_call_contents:
77
+ name_match = self.name_regex.search(tool_content_str)
78
+ if not name_match:
79
+ logger.warning(f"Could not find tool name in XML block: {tool_content_str}")
80
+ continue
81
+ tool_name = name_match.group(1).strip()
82
+
83
+ parsed_arguments = {}
84
+
85
+ # Find all parameter tags in the tool_call content, excluding the <tool> tag
86
+ param_matches = self.param_regex.finditer(tool_content_str)
87
+
88
+ for match in param_matches:
89
+ param_name = match.group(1).strip()
90
+ param_value_str = match.group(2).strip()
91
+
92
+ # Skip the <tool> tag since it's not a parameter
93
+ if param_name == "tool":
94
+ continue
95
+
96
+ target_type = None
97
+ # Try to get type from request.tools schema
98
+ if request.tools:
99
+ for tool_def in request.tools:
100
+ if tool_def.function.name == tool_name:
101
+ if tool_def.function.parameters and \
102
+ isinstance(tool_def.function.parameters, dict) and \
103
+ "properties" in tool_def.function.parameters and \
104
+ isinstance(tool_def.function.parameters["properties"], dict) and \
105
+ param_name in tool_def.function.parameters["properties"] and \
106
+ isinstance(tool_def.function.parameters["properties"][param_name], dict):
107
+ target_type = tool_def.function.parameters["properties"][param_name].get("type")
108
+ break
109
+
110
+ typed_param_value = param_value_str # Default to string
111
+ if target_type:
112
+ try:
113
+ if target_type == "string":
114
+ typed_param_value = param_value_str
115
+ elif target_type == "integer":
116
+ typed_param_value = int(param_value_str)
117
+ elif target_type == "number":
118
+ typed_param_value = float(param_value_str)
119
+ elif target_type == "boolean":
120
+ typed_param_value = param_value_str.lower() == 'true'
121
+ elif target_type in ["object", "array"]:
122
+ try:
123
+ typed_param_value = json.loads(param_value_str)
124
+ except json.JSONDecodeError:
125
+ # Fallback for non-strict JSON like Python dict/list string
126
+ typed_param_value = ast.literal_eval(param_value_str)
127
+ else: # Unknown type, keep as string
128
+ typed_param_value = param_value_str
129
+ except (ValueError, SyntaxError, json.JSONDecodeError) as e:
130
+ logger.warning(
131
+ f"Could not convert param '{param_name}' with value '{param_value_str}' "
132
+ f"to type '{target_type}'. Error: {e}. Using string value."
133
+ )
134
+ typed_param_value = param_value_str
135
+ else: # No schema type, try ast.literal_eval
136
+ try:
137
+ # For values like "true", "123", "['a', 'b']"
138
+ # ast.literal_eval('some_string_without_quotes') will raise SyntaxError
139
+ if (param_value_str.startswith("'") and param_value_str.endswith("'")) or \
140
+ (param_value_str.startswith('"') and param_value_str.endswith('"')) or \
141
+ (param_value_str.startswith('[') and param_value_str.endswith(']')) or \
142
+ (param_value_str.startswith('{') and param_value_str.endswith('}')) or \
143
+ param_value_str.lower() in ['true', 'false', 'none'] or \
144
+ param_value_str.replace('.', '', 1).isdigit() or \
145
+ (param_value_str.startswith('-') and param_value_str[1:].replace('.', '', 1).isdigit()):
146
+ typed_param_value = ast.literal_eval(param_value_str)
147
+ else: # It's likely a plain string not meant for ast.literal_eval
148
+ typed_param_value = param_value_str
149
+ except (ValueError, SyntaxError):
150
+ typed_param_value = param_value_str # Keep as string if ast.literal_eval fails
151
+
152
+ parsed_arguments[param_name] = typed_param_value
153
+
154
+ parsed_tool_calls.append(ToolCall(
155
+ id=f"call_{random_uuid()}",
156
+ type="function",
157
+ function=FunctionCall(
158
+ name=tool_name,
159
+ arguments=json.dumps(parsed_arguments, ensure_ascii=False),
160
+ ),
161
+ ))
162
+
163
+ return ExtractedToolCallInformation(
164
+ tools_called=len(parsed_tool_calls) > 0,
165
+ tool_calls=parsed_tool_calls,
166
+ content=content if content else None,
167
+ )
168
+
169
+ except Exception:
170
+ logger.exception(f"Error in extracting XML tool call from response. Response: {model_output}")
171
+ # Fallback to original model output if parsing fails catastrophically
172
+ return ExtractedToolCallInformation(
173
+ tools_called=False,
174
+ tool_calls=[],
175
+ content=model_output,
176
+ )
177
+
178
+ def extract_tool_calls_streaming(
179
+ self,
180
+ previous_text: str,
181
+ current_text: str,
182
+ delta_text: str,
183
+ previous_token_ids: Sequence[int],
184
+ current_token_ids: Sequence[int],
185
+ delta_token_ids: Sequence[int],
186
+ request: ChatCompletionRequest,
187
+ ) -> Union[DeltaMessage, None]:
188
+
189
+ raise NotImplementedError("Tool calling is not supported in streaming mode!")
190
+
191
+
192
+ @ToolParserManager.register_module("llama_nemotron_json")
193
+ class LlamaNemotronJSONToolParser(ToolParser):
194
+
195
+ def __init__(self, tokenizer: AnyTokenizer):
196
+ super().__init__(tokenizer)
197
+
198
+ self.current_tool_name_sent: bool = False
199
+ self.prev_tool_call_arr: list[dict] = []
200
+ self.current_tool_id: int = -1
201
+ self.streamed_args_for_tool: list[str] = []
202
+
203
+ self.tool_call_start_token: str = "<TOOLCALL>"
204
+ self.tool_call_end_token: str = "</TOOLCALL>"
205
+
206
+ self.tool_call_regex = re.compile(r"<TOOLCALL>(.*?)</TOOLCALL>", re.DOTALL)
207
+
208
+ def extract_tool_calls(
209
+ self,
210
+ model_output: str,
211
+ request: ChatCompletionRequest,
212
+ ) -> ExtractedToolCallInformation:
213
+
214
+ if self.tool_call_start_token not in model_output:
215
+ return ExtractedToolCallInformation(
216
+ tools_called=False,
217
+ tool_calls=[],
218
+ content=model_output,
219
+ )
220
+
221
+ else:
222
+
223
+ try:
224
+ str_tool_calls = self.tool_call_regex.findall(model_output)[0].strip()
225
+ if not str_tool_calls.startswith("["):
226
+ str_tool_calls = "[" + str_tool_calls
227
+ if not str_tool_calls.endswith("]"):
228
+ str_tool_calls = "]" + str_tool_calls
229
+ json_tool_calls = json.loads(str_tool_calls)
230
+ tool_calls = []
231
+ for tool_call in json_tool_calls:
232
+ try:
233
+ tool_calls.append(ToolCall(
234
+ type="function",
235
+ function=FunctionCall(
236
+ name=tool_call["name"],
237
+ arguments=json.dumps(tool_call["arguments"], ensure_ascii=False) \
238
+ if isinstance(tool_call["arguments"], dict) else tool_call["arguments"],
239
+ ),
240
+ ))
241
+ except:
242
+ continue
243
+
244
+ content = model_output[:model_output.rfind(self.tool_call_start_token)]
245
+
246
+ return ExtractedToolCallInformation(
247
+ tools_called=True,
248
+ tool_calls=tool_calls,
249
+ content=content if content else None,
250
+ )
251
+
252
+ except Exception:
253
+ logger.exception(f"Error in extracting tool call from response. Response: {model_output}")
254
+ return ExtractedToolCallInformation(
255
+ tools_called=False,
256
+ tool_calls=[],
257
+ content=model_output,
258
+ )
259
+
260
+ def extract_tool_calls_streaming(
261
+ self,
262
+ previous_text: str,
263
+ current_text: str,
264
+ delta_text: str,
265
+ previous_token_ids: Sequence[int],
266
+ current_token_ids: Sequence[int],
267
+ delta_token_ids: Sequence[int],
268
+ request: ChatCompletionRequest,
269
+ ) -> Union[DeltaMessage, None]:
270
+
271
+ raise NotImplementedError("Tool calling is not supported in streaming mode!")
272
+
273
+
274
+ @ToolParserManager.register_module("llama_nemotron_pythonic")
275
+ class LlamaNemotronPythonicToolParser(ToolParser):
276
+
277
+ def __init__(self, tokenizer: AnyTokenizer):
278
+ super().__init__(tokenizer)
279
+
280
+ self.current_tool_name_sent: bool = False
281
+ self.prev_tool_call_arr: list[dict] = []
282
+ self.current_tool_id: int = -1
283
+ self.streamed_args_for_tool: list[str] = []
284
+
285
+ self.tool_call_start_token: str = "<TOOLCALL>"
286
+ self.tool_call_end_token: str = "</TOOLCALL>"
287
+
288
+ self.tool_call_regex = re.compile(r"<TOOLCALL>(.*?)</TOOLCALL>", re.DOTALL)
289
+ # Regex to parse pythonic function calls: function_name(arg1="value1", arg2=123, arg3=True)
290
+ self.function_call_regex = re.compile(r"(\w+)\((.*?)\)$", re.DOTALL)
291
+
292
+ def parse_function_arguments(self, args_str: str) -> dict:
293
+ """Parse pythonic function arguments string into a dictionary"""
294
+ if not args_str.strip():
295
+ return {}
296
+
297
+ # Use ast.parse to safely parse the function call arguments
298
+ # We'll construct a temporary function call and parse it
299
+ try:
300
+ # Create a dummy function call to parse arguments
301
+ dummy_code = f"dummy_func({args_str})"
302
+ parsed = ast.parse(dummy_code, mode='eval')
303
+
304
+ # Extract arguments from the AST
305
+ call_node = parsed.body
306
+ if not isinstance(call_node, ast.Call):
307
+ return {}
308
+
309
+ arguments = {}
310
+
311
+ # Handle keyword arguments
312
+ for keyword in call_node.keywords:
313
+ if keyword.arg is None: # **kwargs
314
+ continue
315
+
316
+ # Convert AST value to Python value
317
+ try:
318
+ value = ast.literal_eval(keyword.value)
319
+ arguments[keyword.arg] = value
320
+ except (ValueError, TypeError):
321
+ # If literal_eval fails, try to get the raw value
322
+ if isinstance(keyword.value, ast.Name):
323
+ arguments[keyword.arg] = keyword.value.id
324
+ elif isinstance(keyword.value, ast.Constant):
325
+ arguments[keyword.arg] = keyword.value.value
326
+ else:
327
+ # Fallback: convert to string
328
+ arguments[keyword.arg] = ast.unparse(keyword.value)
329
+
330
+ # Handle positional arguments (less common in tool calls but supported)
331
+ for i, arg in enumerate(call_node.args):
332
+ try:
333
+ value = ast.literal_eval(arg)
334
+ arguments[f"arg_{i}"] = value
335
+ except (ValueError, TypeError):
336
+ if isinstance(arg, ast.Name):
337
+ arguments[f"arg_{i}"] = arg.id
338
+ elif isinstance(arg, ast.Constant):
339
+ arguments[f"arg_{i}"] = arg.value
340
+ else:
341
+ arguments[f"arg_{i}"] = ast.unparse(arg)
342
+
343
+ return arguments
344
+
345
+ except (SyntaxError, ValueError) as e:
346
+ logger.warning(f"Failed to parse function arguments '{args_str}': {e}")
347
+ return {}
348
+
349
+ def extract_tool_calls(
350
+ self,
351
+ model_output: str,
352
+ request: ChatCompletionRequest,
353
+ ) -> ExtractedToolCallInformation:
354
+
355
+ if self.tool_call_start_token not in model_output:
356
+ return ExtractedToolCallInformation(
357
+ tools_called=False,
358
+ tool_calls=[],
359
+ content=model_output,
360
+ )
361
+
362
+ tool_call_start_index = model_output.find(self.tool_call_start_token)
363
+ content = model_output[:tool_call_start_index].strip()
364
+
365
+ try:
366
+ # Extract content between <TOOLCALL> tags
367
+ tool_call_matches = self.tool_call_regex.findall(model_output)
368
+ if not tool_call_matches:
369
+ return ExtractedToolCallInformation(
370
+ tools_called=False,
371
+ tool_calls=[],
372
+ content=model_output,
373
+ )
374
+
375
+ tool_calls_content = tool_call_matches[0].strip()
376
+
377
+ # Split by lines to get individual function calls
378
+ function_lines = [line.strip() for line in tool_calls_content.split('\n') if line.strip()]
379
+
380
+ parsed_tool_calls = []
381
+
382
+ for func_line in function_lines:
383
+ # Parse each function call
384
+ match = self.function_call_regex.match(func_line)
385
+ if not match:
386
+ logger.warning(f"Could not parse function call: {func_line}")
387
+ continue
388
+
389
+ function_name = match.group(1)
390
+ args_str = match.group(2)
391
+
392
+ # Parse arguments
393
+ parsed_arguments = self.parse_function_arguments(args_str)
394
+
395
+ # Apply type conversion based on schema if available
396
+ if request.tools:
397
+ for tool_def in request.tools:
398
+ if tool_def.function.name == function_name:
399
+ schema_properties = {}
400
+ if (tool_def.function.parameters and
401
+ isinstance(tool_def.function.parameters, dict) and
402
+ "properties" in tool_def.function.parameters and
403
+ isinstance(tool_def.function.parameters["properties"], dict)):
404
+ schema_properties = tool_def.function.parameters["properties"]
405
+
406
+ # Convert arguments based on schema types
407
+ for arg_name, arg_value in parsed_arguments.items():
408
+ if arg_name in schema_properties:
409
+ param_info = schema_properties[arg_name]
410
+ target_type = param_info.get("type")
411
+
412
+ try:
413
+ if target_type == "string" and not isinstance(arg_value, str):
414
+ parsed_arguments[arg_name] = str(arg_value)
415
+ elif target_type == "integer" and not isinstance(arg_value, int):
416
+ parsed_arguments[arg_name] = int(arg_value)
417
+ elif target_type == "number" and not isinstance(arg_value, (int, float)):
418
+ parsed_arguments[arg_name] = float(arg_value)
419
+ elif target_type == "boolean" and not isinstance(arg_value, bool):
420
+ if isinstance(arg_value, str):
421
+ parsed_arguments[arg_name] = arg_value.lower() in ['true', '1', 'yes']
422
+ else:
423
+ parsed_arguments[arg_name] = bool(arg_value)
424
+ elif target_type in ["object", "array"]:
425
+ if isinstance(arg_value, str):
426
+ try:
427
+ parsed_arguments[arg_name] = json.loads(arg_value)
428
+ except json.JSONDecodeError:
429
+ # Keep as string if JSON parsing fails
430
+ pass
431
+ except (ValueError, TypeError) as e:
432
+ logger.warning(f"Type conversion failed for {arg_name}: {e}")
433
+ # Keep original value if conversion fails
434
+ break
435
+
436
+ parsed_tool_calls.append(ToolCall(
437
+ id=f"call_{random_uuid()}",
438
+ type="function",
439
+ function=FunctionCall(
440
+ name=function_name,
441
+ arguments=json.dumps(parsed_arguments, ensure_ascii=False),
442
+ ),
443
+ ))
444
+
445
+ return ExtractedToolCallInformation(
446
+ tools_called=len(parsed_tool_calls) > 0,
447
+ tool_calls=parsed_tool_calls,
448
+ content=content if content else None,
449
+ )
450
+
451
+ except Exception:
452
+ logger.exception(f"Error in extracting pythonic tool call from response. Response: {model_output}")
453
+ return ExtractedToolCallInformation(
454
+ tools_called=False,
455
+ tool_calls=[],
456
+ content=model_output,
457
+ )
458
+
459
+ def extract_tool_calls_streaming(
460
+ self,
461
+ previous_text: str,
462
+ current_text: str,
463
+ delta_text: str,
464
+ previous_token_ids: Sequence[int],
465
+ current_token_ids: Sequence[int],
466
+ delta_token_ids: Sequence[int],
467
+ request: ChatCompletionRequest,
468
+ ) -> Union[DeltaMessage, None]:
469
+
470
+ raise NotImplementedError("Tool calling is not supported in streaming mode!")