@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / de9c085e17
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 05:30 UTC

[Misc] Add gemma3 chat template with pythonic-style function calling (#17149)

Signed-off-by: Philip Chung <philip.f.chung@gmail.com>

Philip Chung committed Aug 21, 2025 at 21:06 UTC de9c085e17f049c3cea970aa5c5f784231706795
1 file changed +123
examples/tool_chat_template_gemma3_pythonic.jinja new
+123
@@ -0,0 +1,123 @@
1 +{#- Begin-of-sequence token to start the model prompt -#}
2 +{{ bos_token }}
3 +{#- Extracts the system message. Gemma does not support system messages so it will be prepended to first user message. -#}
4 +{%- if messages[0]['role'] == 'system' -%}
5 + {%- if messages[0]['content'] is string -%}
6 + {%- set first_user_prefix = messages[0]['content'] + '\n\n' -%}
7 + {%- else -%}
8 + {%- set first_user_prefix = messages[0]['content'][0]['text'] + '\n\n' -%}
9 + {%- endif -%}
10 + {%- set loop_messages = messages[1:] -%}
11 +{%- else -%}
12 + {%- set first_user_prefix = "" -%}
13 + {%- set loop_messages = messages -%}
14 +{%- endif -%}
15 +{#- Set tools to none if not defined for this ChatCompletion request (helps avoid errors later) -#}
16 +{%- if not tools is defined %}
17 + {%- set tools = none %}
18 +{%- endif %}
19 +{#- Validate alternating user/assistant messages (excluding 'tool' messages and ones with tool_calls) -#}
20 +{%- for message in loop_messages | rejectattr("role", "equalto", "tool") | selectattr("tool_calls", "undefined") -%}
21 + {%- if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}
22 + {{ raise_exception("Conversation roles must alternate user/assistant/user/assistant/...") }}
23 + {%- endif -%}
24 +{%- endfor -%}
25 +
26 +{#- Main loop over all messages in the conversation history -#}
27 +{%- for message in loop_messages -%}
28 + {#- Normalize roles for model prompt formatting -#}
29 + {%- if (message['role'] == 'assistant') -%}
30 + {%- set role = "model" -%}
31 + {%- elif (message['role'] == 'tool') -%}
32 + {%- set role = "user" -%}
33 + {%- else -%}
34 + {%- set role = message['role'] -%}
35 + {%- endif -%}
36 + {#- Mark the start of a message block with the appropriate role -#}
37 + {{ '<start_of_turn>' + role + '\n' -}}
38 +
39 + {#- Insert system message content (if present) at the beginning of the first message. -#}
40 + {%- if loop.first -%}
41 + {{ first_user_prefix }}
42 + {#- Append system message with tool information if using tools in message request. -#}
43 + {%- if tools is not none -%}
44 + {{- "Tools (functions) are available. If you decide to invoke one or more of the tools, you must respond with a python list of the function calls.\n" -}}
45 + {{- "Example Format: [func_name1(params_name1=params_value1, params_name2=params_value2...), func_name2(params)] \n" -}}
46 + {{- "Do not use variables. DO NOT USE MARKDOWN SYNTAX. You SHOULD NOT include any other text in the response if you call a function. If none of the functions can be used, point it out. If you lack the parameters required by the function, also point it out.\n" -}}
47 + {{- "Here is a list of functions in JSON format that you can invoke.\n" -}}
48 + {{- tools | tojson(indent=4) -}}
49 + {{- "\n\n" -}}
50 + {%- endif -%}
51 + {%- endif -%}
52 +
53 + {#- Format model tool calls (turns where model indicates they want to call a tool) -#}
54 + {%- if 'tool_calls' in message -%}
55 + {#- Opening bracket for tool call list. -#}
56 + {{- '[' -}}
57 + {#- For each tool call -#}
58 + {%- for tool_call in message.tool_calls -%}
59 + {#- Get tool call function. -#}
60 + {%- if tool_call.function is defined -%}
61 + {%- set tool_call = tool_call.function -%}
62 + {%- endif -%}
63 + {#- Function name & opening parenthesis. -#}
64 + {{- tool_call.name + '(' -}}
65 +
66 + {#-- Handle arguments as list (positional) or dict (named) --#}
67 + {#-- Named arguments (dict) --#}
68 + {%- if tool_call.arguments is iterable and tool_call.arguments is mapping -%}
69 + {%- set first = true -%}
70 + {%- for key, val in tool_call.arguments.items() -%}
71 + {%- if not first %}, {% endif -%}
72 + {{ key }}={{ val | tojson }}
73 + {%- set first = false -%}
74 + {%- endfor -%}
75 + {#-- Positional arguments (list) --#}
76 + {%- elif tool_call.arguments is iterable -%}
77 + {{- tool_call.arguments | map('tojson') | join(', ') -}}
78 + {#-- Fallback: single positional value --#}
79 + {%- else -%}
80 + {{- tool_call.arguments | tojson -}}
81 + {#-- Closing parenthesis. --#}
82 + {%- endif -%}
83 + {{- ')' -}}
84 + {#-- If more than one tool call, place comma and move to formatting next tool call --#}
85 + {%- if not loop.last -%}, {% endif -%}
86 + {%- endfor -%}
87 + {#- Closing bracket for tool call list. -#}
88 + {{- ']' -}}
89 + {%- endif -%}
90 +
91 + {#- Tool response start tag (for messages from a tool) -#}
92 + {%- if (message['role'] == 'tool') -%}
93 + {{ '<tool_response>\n' -}}
94 + {%- endif -%}
95 +
96 + {#- Render the message content: handle plain string or multimodal content like image/text -#}
97 + {%- if message['content'] is string -%}
98 + {{ message['content'] | trim }}
99 + {%- elif message['content'] is iterable -%}
100 + {%- for item in message['content'] -%}
101 + {%- if item['type'] == 'image' -%}
102 + {{ '<start_of_image>' }}
103 + {%- elif item['type'] == 'text' -%}
104 + {{ item['text'] | trim }}
105 + {%- endif -%}
106 + {%- endfor -%}
107 + {%- else -%}
108 + {{ raise_exception("Invalid content type") }}
109 + {%- endif -%}
110 +
111 + {#- Tool response end tag -#}
112 + {%- if (message['role'] == 'tool') -%}
113 + {{ '</tool_response>' -}}
114 + {%- endif -%}
115 +
116 + {#- Mark end of a single turn -#}
117 + {{ '<end_of_turn>\n' }}
118 +{%- endfor -%}
119 +
120 +{#- If generation is to be triggered, add model prompt prefix -#}
121 +{%- if add_generation_prompt -%}
122 + {{'<start_of_turn>model\n'}}
123 +{%- endif -%}
\ No newline at end of file