Skip to content

Commit f33dde3

Browse files
committed
feat: Add Gemma3 chat handler (abetlen#1976)
1 parent 37eb5f0 commit f33dde3

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

llama_cpp/llama_chat_format.py

+89
Original file line numberDiff line numberDiff line change
@@ -3373,6 +3373,95 @@ class MiniCPMv26ChatHandler(Llava15ChatHandler):
33733373
)
33743374

33753375

3376+
class Gemma3ChatHandler(Llava15ChatHandler):
3377+
# Chat Format:
3378+
# '<bos><start_of_turn>user\n{system_prompt}\n\n{prompt}<end_of_turn>\n<start_of_turn>model\n'
3379+
3380+
DEFAULT_SYSTEM_MESSAGE = None
3381+
3382+
CHAT_FORMAT = (
3383+
"{{ '<bos>' }}"
3384+
"{%- if messages[0]['role'] == 'system' -%}"
3385+
"{%- if messages[0]['content'] is string -%}"
3386+
"{%- set first_user_prefix = messages[0]['content'] + '\n\n' -%}"
3387+
"{%- else -%}"
3388+
"{%- set first_user_prefix = messages[0]['content'][0]['text'] + '\n\n' -%}"
3389+
"{%- endif -%}"
3390+
"{%- set loop_messages = messages[1:] -%}"
3391+
"{%- else -%}"
3392+
"{%- set first_user_prefix = \"\" -%}"
3393+
"{%- set loop_messages = messages -%}"
3394+
"{%- endif -%}"
3395+
"{%- for message in loop_messages -%}"
3396+
"{%- if (message['role'] == 'user') != (loop.index0 % 2 == 0) -%}"
3397+
"{{ raise_exception(\"Conversation roles must alternate user/assistant/user/assistant/...\") }}"
3398+
"{%- endif -%}"
3399+
"{%- if (message['role'] == 'assistant') -%}"
3400+
"{%- set role = \"model\" -%}"
3401+
"{%- else -%}"
3402+
"{%- set role = message['role'] -%}"
3403+
"{%- endif -%}"
3404+
"{{ '<start_of_turn>' + role + '\n' + (first_user_prefix if loop.first else \"\") }}"
3405+
"{%- if message['content'] is string -%}"
3406+
"{{ message['content'] | trim }}"
3407+
"{%- elif message['content'] is iterable -%}"
3408+
"{%- for item in message['content'] -%}"
3409+
"{%- if item['type'] == 'image' -%}"
3410+
"{{ '<start_of_image>' }}"
3411+
"{%- elif item['type'] == 'text' -%}"
3412+
"{{ item['text'] | trim }}"
3413+
"{%- endif -%}"
3414+
"{%- endfor -%}"
3415+
"{%- else -%}"
3416+
"{{ raise_exception(\"Invalid content type\") }}"
3417+
"{%- endif -%}"
3418+
"{{ '<end_of_turn>\n' }}"
3419+
"{%- endfor -%}"
3420+
"{%- if add_generation_prompt -%}"
3421+
"{{ '<start_of_turn>model\n' }}"
3422+
"{%- endif -%}"
3423+
)
3424+
3425+
@staticmethod
3426+
def split_text_on_image_urls(text: str, image_urls: List[str]):
3427+
split_text: List[Tuple[Literal["text", "image_url"], str]] = []
3428+
copied_urls = image_urls[:]
3429+
remaining = text
3430+
image_placeholder = "<start_of_image>"
3431+
3432+
while remaining:
3433+
# Find placeholder
3434+
pos = remaining.find(image_placeholder)
3435+
if pos != -1:
3436+
assert len(copied_urls) > 0
3437+
if pos > 0:
3438+
split_text += [("text", remaining[:pos])]
3439+
split_text += [("text", "\n\n<start_of_image>")]
3440+
split_text += [("image_url", copied_urls.pop(0))]
3441+
split_text += [("text", "<end_of_image>\n\n")]
3442+
remaining = remaining[pos + len(image_placeholder):]
3443+
else:
3444+
assert len(copied_urls) == 0
3445+
split_text.append(("text", remaining))
3446+
remaining = ""
3447+
return split_text
3448+
3449+
@staticmethod
3450+
def get_image_urls(messages: List[llama_types.ChatCompletionRequestMessage]):
3451+
image_urls: List[str] = []
3452+
for message in messages:
3453+
if message["role"] == "user":
3454+
if message.get("content") is None:
3455+
continue
3456+
for content in message["content"]:
3457+
if isinstance(content, dict) and content.get("type") == "image":
3458+
if isinstance(content.get("image"), dict) and isinstance(content["image"].get("url"), str):
3459+
image_urls.append(content["image"]["url"])
3460+
elif isinstance(content.get("url"), str):
3461+
image_urls.append(content["url"])
3462+
return image_urls
3463+
3464+
33763465
@register_chat_completion_handler("chatml-function-calling")
33773466
def chatml_function_calling(
33783467
llama: llama.Llama,

0 commit comments

Comments
 (0)