| 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 -%} |