@@ -4376,34 +4376,15 @@ class Gemma4ChatHandler(MTMDChatHandler):
43764376 " description:<|\" |>{{ value['description'] }}<|\" |>\n "
43774377 " {%- set add_comma = true -%}\n "
43784378 " {%- endif -%}\n "
4379- " {%- if value['nullable'] %}\n "
4380- " {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n "
4381- " nullable:true\n "
4382- " {%- endif -%}\n "
43834379 " {%- if value['type'] | upper == 'STRING' -%}\n "
43844380 " {%- if value['enum'] -%}\n "
43854381 " {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n "
43864382 " enum:{{ format_argument(value['enum']) }}\n "
43874383 " {%- endif -%}\n "
4388- " {%- elif value['type'] | upper == 'OBJECT' -%}\n "
4389- " ,properties:{\n "
4390- " {%- if value['properties'] is defined and value['properties'] is mapping -%}\n "
4391- " {{- format_parameters(value['properties'], value['required'] | default([])) -}}\n "
4392- " {%- elif value is mapping -%}\n "
4393- " {{- format_parameters(value, value['required'] | default([])) -}}\n "
4394- " {%- endif -%}\n "
4395- " }\n "
4396- " {%- if value['required'] -%}\n "
4397- " ,required:[\n "
4398- " {%- for item in value['required'] | default([]) -%}\n "
4399- " <|\" |>{{- item -}}<|\" |>\n "
4400- " {%- if not loop.last %},{% endif -%}\n "
4401- " {%- endfor -%}\n "
4402- " ]\n "
4403- " {%- endif -%}\n "
44044384 " {%- elif value['type'] | upper == 'ARRAY' -%}\n "
44054385 " {%- if value['items'] is mapping and value['items'] -%}\n "
4406- " ,items:{\n "
4386+ " {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n "
4387+ " items:{\n "
44074388 " {%- set ns_items = namespace(found_first=false) -%}\n "
44084389 " {%- for item_key, item_value in value['items'] | dictsort -%}\n "
44094390 " {%- if item_value is not none -%}\n "
@@ -4436,6 +4417,32 @@ class Gemma4ChatHandler(MTMDChatHandler):
44364417 " }\n "
44374418 " {%- endif -%}\n "
44384419 " {%- endif -%}\n "
4420+ " {%- if value['nullable'] %}\n "
4421+ " {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n "
4422+ " nullable:true\n "
4423+ " {%- endif -%}\n "
4424+ " {%- if value['type'] | upper == 'OBJECT' -%}\n "
4425+ " {%- if value['properties'] is defined and value['properties'] is mapping -%}\n "
4426+ " {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n "
4427+ " properties:{\n "
4428+ " {{- format_parameters(value['properties'], value['required'] | default([])) -}}\n "
4429+ " }\n "
4430+ " {%- elif value is mapping -%}\n "
4431+ " {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n "
4432+ " properties:{\n "
4433+ " {{- format_parameters(value, value['required'] | default([])) -}}\n "
4434+ " }\n "
4435+ " {%- endif -%}\n "
4436+ " {%- if value['required'] -%}\n "
4437+ " {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n "
4438+ " required:[\n "
4439+ " {%- for item in value['required'] | default([]) -%}\n "
4440+ " <|\" |>{{- item -}}<|\" |>\n "
4441+ " {%- if not loop.last %},{% endif -%}\n "
4442+ " {%- endfor -%}\n "
4443+ " ]\n "
4444+ " {%- endif -%}\n "
4445+ " {%- endif -%}\n "
44394446 " {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}\n "
44404447 " type:<|\" |>{{ value['type'] | upper }}<|\" |>}\n "
44414448 " {%- endif -%}\n "
@@ -4514,25 +4521,35 @@ class Gemma4ChatHandler(MTMDChatHandler):
45144521 " {%- endfor -%}\n "
45154522 " {{- ns.result | trim -}}\n "
45164523 "{%- endmacro -%}\n "
4517- "\n "
4524+ "{%- macro format_tool_response_block(tool_name, response) -%}\n "
4525+ " {{- '<|tool_response>' -}}\n "
4526+ " {%- if response is mapping -%}\n "
4527+ " {{- 'response:' + tool_name + '{' -}}\n "
4528+ " {%- for key, value in response | dictsort -%}\n "
4529+ " {{- key -}}:{{- format_argument(value, escape_keys=False) -}}\n "
4530+ " {%- if not loop.last %},{% endif -%}\n "
4531+ " {%- endfor -%}\n "
4532+ " {{- '}' -}}\n "
4533+ " {%- else -%}\n "
4534+ " {{- 'response:' + tool_name + '{value:' + format_argument(response, escape_keys=False) + '}' -}}\n "
4535+ " {%- endif -%}\n "
4536+ " {{- '<tool_response|>' -}}\n "
4537+ "{%- endmacro -%}\n "
45184538 "{%- set ns = namespace(prev_message_type=None) -%}\n "
45194539 "{%- set loop_messages = messages -%}\n "
4520- "{{ bos_token }}\n "
4540+ "{{- bos_token - }}\n "
45214541 "{#- Handle System/Tool Definitions Block -#}\n "
45224542 "{%- if (enable_thinking is defined and enable_thinking) or tools or messages[0]['role'] in ['system', 'developer'] -%}\n "
45234543 " {{- '<|turn>system\\ n' -}}\n "
4524- "\n "
45254544 " {#- Inject Thinking token at the very top of the FIRST system turn -#}\n "
45264545 " {%- if enable_thinking is defined and enable_thinking -%}\n "
4527- " {{- '<|think|>' -}}\n "
4546+ " {{- '<|think|>\\ n ' -}}\n "
45284547 " {%- set ns.prev_message_type = 'think' -%}\n "
45294548 " {%- endif -%}\n "
4530- "\n "
45314549 " {%- if messages[0]['role'] in ['system', 'developer'] -%}\n "
45324550 " {{- messages[0]['content'] | trim -}}\n "
45334551 " {%- set loop_messages = messages[1:] -%}\n "
45344552 " {%- endif -%}\n "
4535- "\n "
45364553 " {%- if tools -%}\n "
45374554 " {%- for tool in tools %}\n "
45384555 " {{- '<|tool>' -}}\n "
@@ -4541,16 +4558,41 @@ class Gemma4ChatHandler(MTMDChatHandler):
45414558 " {%- endfor %}\n "
45424559 " {%- set ns.prev_message_type = 'tool' -%}\n "
45434560 " {%- endif -%}\n "
4544- "\n "
45454561 " {{- '<turn|>\\ n' -}}\n "
45464562 "{%- endif %}\n "
4547- "\n "
4563+ "{#- Pre-scan: find last user message index for reasoning guard -#}\n "
4564+ "{%- set ns_turn = namespace(last_user_idx=-1) -%}\n "
4565+ "{%- for i in range(loop_messages | length) -%}\n "
4566+ " {%- if loop_messages[i]['role'] == 'user' -%}\n "
4567+ " {%- set ns_turn.last_user_idx = i -%}\n "
4568+ " {%- endif -%}\n "
4569+ "{%- endfor -%}\n "
45484570 "{#- Loop through messages -#}\n "
45494571 "{%- for message in loop_messages -%}\n "
4572+ " {%- if message['role'] != 'tool' -%}\n "
45504573 " {%- set ns.prev_message_type = None -%}\n "
45514574 " {%- set role = 'model' if message['role'] == 'assistant' else message['role'] -%}\n "
4575+ " {#- Detect continuation: suppress duplicate <|turn>model when previous non-tool message was also assistant -#}\n "
4576+ " {%- set prev_nt = namespace(role=None, found=false) -%}\n "
4577+ " {%- if loop.index0 > 0 -%}\n "
4578+ " {%- for j in range(loop.index0 - 1, -1, -1) -%}\n "
4579+ " {%- if not prev_nt.found -%}\n "
4580+ " {%- if loop_messages[j]['role'] != 'tool' -%}\n "
4581+ " {%- set prev_nt.role = loop_messages[j]['role'] -%}\n "
4582+ " {%- set prev_nt.found = true -%}\n "
4583+ " {%- endif -%}\n "
4584+ " {%- endif -%}\n "
4585+ " {%- endfor -%}\n "
4586+ " {%- endif -%}\n "
4587+ " {%- set continue_same_model_turn = (role == 'model' and prev_nt.role == 'assistant') -%}\n "
4588+ " {%- if not continue_same_model_turn -%}\n "
45524589 " {{- '<|turn>' + role + '\\ n' }}\n "
4553- "\n "
4590+ " {%- endif -%}\n "
4591+ " {#- Render reasoning/reasoning_content as thinking channel -#}\n "
4592+ " {%- set thinking_text = message.get('reasoning') or message.get('reasoning_content') -%}\n "
4593+ " {%- if thinking_text and loop.index0 > ns_turn.last_user_idx and message.get('tool_calls') -%}\n "
4594+ " {{- '<|channel>thought\\ n' + thinking_text + '\\ n<channel|>' -}}\n "
4595+ " {%- endif -%}\n "
45544596 " {%- if message['tool_calls'] -%}\n "
45554597 " {%- for tool_call in message['tool_calls'] -%}\n "
45564598 " {%- set function = tool_call['function'] -%}\n "
@@ -4569,26 +4611,50 @@ class Gemma4ChatHandler(MTMDChatHandler):
45694611 " {%- endfor -%}\n "
45704612 " {%- set ns.prev_message_type = 'tool_call' -%}\n "
45714613 " {%- endif -%}\n "
4572- "\n "
4573- " {%- if message[ 'tool_responses'] -%}\n "
4574- " {#- Tool Response handling -#}\n "
4614+ " {%- set ns_tr_out = namespace(flag=false) -%} \n "
4615+ " {%- if message.get( 'tool_responses') -%}\n "
4616+ " {#- Legacy: tool_responses embedded on the assistant message (Google/Gemma native) -#}\n "
45754617 " {%- for tool_response in message['tool_responses'] -%}\n "
4576- " {{- '<|tool_response>' -}}\n "
4577- " {%- if tool_response['response'] is mapping -%}\n "
4578- " {{- 'response:' + tool_response['name'] | default('unknown') + '{' -}}\n "
4579- " {%- for key, value in tool_response['response'] | dictsort -%}\n "
4580- " {{- key -}}:{{- format_argument(value, escape_keys=False) -}}\n "
4581- " {%- if not loop.last %},{% endif -%}\n "
4582- " {%- endfor -%}\n "
4583- " {{- '}' -}}\n "
4618+ " {{- format_tool_response_block(tool_response['name'] | default('unknown'), tool_response['response']) -}}\n "
4619+ " {%- set ns_tr_out.flag = true -%}\n "
4620+ " {%- set ns.prev_message_type = 'tool_response' -%}\n "
4621+ " {%- endfor -%}\n "
4622+ " {%- elif message.get('tool_calls') -%}\n "
4623+ " {#- OpenAI Chat Completions: forward-scan consecutive role:tool messages -#}\n "
4624+ " {%- set ns_tool_scan = namespace(stopped=false) -%}\n "
4625+ " {%- for k in range(loop.index0 + 1, loop_messages | length) -%}\n "
4626+ " {%- if ns_tool_scan.stopped -%}\n "
4627+ " {%- elif loop_messages[k]['role'] != 'tool' -%}\n "
4628+ " {%- set ns_tool_scan.stopped = true -%}\n "
45844629 " {%- else -%}\n "
4585- " {{- 'response:' + tool_response['name'] | default('unknown') + '{value:' + format_argument(tool_response['response'], escape_keys=False) + '}' -}}\n "
4630+ " {%- set follow = loop_messages[k] -%}\n "
4631+ " {#- Resolve tool_call_id to function name -#}\n "
4632+ " {%- set ns_tname = namespace(name=follow.get('name') | default('unknown')) -%}\n "
4633+ " {%- for tc in message['tool_calls'] -%}\n "
4634+ " {%- if tc.get('id') == follow.get('tool_call_id') -%}\n "
4635+ " {%- set ns_tname.name = tc['function']['name'] -%}\n "
4636+ " {%- endif -%}\n "
4637+ " {%- endfor -%}\n "
4638+ " {#- Handle content as string or content-parts array -#}\n "
4639+ " {%- set tool_body = follow.get('content') -%}\n "
4640+ " {%- if tool_body is string -%}\n "
4641+ " {{- format_tool_response_block(ns_tname.name, tool_body) -}}\n "
4642+ " {%- elif tool_body is sequence and tool_body is not string -%}\n "
4643+ " {%- set ns_txt = namespace(s='') -%}\n "
4644+ " {%- for part in tool_body -%}\n "
4645+ " {%- if part.get('type') == 'text' -%}\n "
4646+ " {%- set ns_txt.s = ns_txt.s + (part.get('text') | default('')) -%}\n "
4647+ " {%- endif -%}\n "
4648+ " {%- endfor -%}\n "
4649+ " {{- format_tool_response_block(ns_tname.name, ns_txt.s) -}}\n "
4650+ " {%- else -%}\n "
4651+ " {{- format_tool_response_block(ns_tname.name, tool_body) -}}\n "
4652+ " {%- endif -%}\n "
4653+ " {%- set ns_tr_out.flag = true -%}\n "
4654+ " {%- set ns.prev_message_type = 'tool_response' -%}\n "
45864655 " {%- endif -%}\n "
4587- " {{- '<tool_response|>' -}}\n "
45884656 " {%- endfor -%}\n "
4589- " {%- set ns.prev_message_type = 'tool_response' -%}\n "
45904657 " {%- endif -%}\n "
4591- "\n "
45924658 " {%- if message['content'] is string -%}\n "
45934659 " {%- if role == 'model' -%}\n "
45944660 " {{- strip_thinking(message['content']) -}}\n "
@@ -4605,35 +4671,36 @@ class Gemma4ChatHandler(MTMDChatHandler):
46054671 " {%- endif -%}\n "
46064672 " {%- elif item['type'] == 'image_url' -%}\n "
46074673 " {%- set url_val = item['image_url'] if item['image_url'] is string else item['image_url']['url'] -%}\n "
4608- " {{- '\\ n \\ n <|image|>' + url_val + ' \\ n \\ n' -}}\n "
4674+ " {{- '<|image|>' + url_val -}}\n "
46094675 " {%- set ns.prev_message_type = 'image' -%}\n "
46104676 " {%- elif item['type'] == 'audio_url' -%}\n "
46114677 " {%- set audio_val = item['audio_url'] if item['audio_url'] is string else item['audio_url']['url'] -%}\n "
4612- " {{- '\\ n \\ n <|audio|>' + audio_val + ' \\ n \\ n' -}}\n "
4678+ " {{- '<|audio|>' + audio_val -}}\n "
46134679 " {%- set ns.prev_message_type = 'audio' -%}\n "
46144680 " {%- elif item['type'] == 'input_audio' -%}\n "
46154681 " {%- set audio_val = item['input_audio'] if item['input_audio'] is string else ('data:audio/' + item['input_audio']['format'] + ';base64,' + item['input_audio']['data']) -%}\n "
4616- " {{- '\\ n \\ n <|audio|>' + audio_val + ' \\ n \\ n' -}}\n "
4682+ " {{- '<|audio|>' + audio_val -}}\n "
46174683 " {%- set ns.prev_message_type = 'audio' -%}\n "
46184684 # " {%- elif item['type'] == 'video_url' -%}\n"
46194685 # " {%- set video_val = item['video_url'] if item['video_url'] is string else item['video_url']['url'] -%}\n"
4620- # " {{- '\\n\\n <|video|>' + video_val + '\\n\\n' -}}\n"
4686+ # " {{- '<|video|>' + video_val -}}\n"
46214687 # " {%- set ns.prev_message_type = 'video' -%}\n"
46224688 " {%- endif -%}\n "
46234689 " {%- endfor -%}\n "
46244690 " {%- endif -%}\n "
4625- "\n "
4626- " {%- if not (message['tool_responses'] and not message['content']) -%}\n "
4691+ " {%- if ns.prev_message_type == 'tool_call' and not ns_tr_out.flag -%}\n "
4692+ " {{- '<|tool_response>' -}}\n "
4693+ " {%- elif not (ns_tr_out.flag and not message.get('content')) -%}\n "
46274694 " {{- '<turn|>\\ n' -}}\n "
46284695 " {%- endif -%}\n "
4696+ " {%- endif -%}\n "
46294697 "{%- endfor -%}\n "
4630- "\n "
46314698 "{%- if add_generation_prompt -%}\n "
4632- " {%- if ns.prev_message_type != 'tool_response' -%}\n "
4699+ " {%- if ns.prev_message_type != 'tool_response' and ns.prev_message_type != 'tool_call' -%}\n "
46334700 " {{- '<|turn>model\\ n' -}}\n "
4634- " {%- endif -%}\n "
4635- " {%- if not enable_thinking | default(false) -% }\n "
4636- " {{- '<|channel>thought \\ n<channel|>' -} }\n "
4701+ " {%- if not enable_thinking | default(false) -%}\n "
4702+ " {{- '<|channel>thought \\ n<channel|>' -} }\n "
4703+ " {%- endif -% }\n "
46374704 " {%- endif -%}\n "
46384705 "{%- endif -%}\n "
46394706 )
0 commit comments