Skip to content

Commit 60a70de

Browse files
kossumokaris
authored andcommitted
feat: Add Gemma3 chat handler (#1976)
1 parent 7e893b5 commit 60a70de

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

llama_cpp/llama_chat_format.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3520,6 +3520,95 @@ def __call__(self, **kwargs):
35203520
return super().__call__(**kwargs)
35213521

35223522

3523+
class Gemma3ChatHandler(Llava15ChatHandler):
3524+
# Chat Format:
3525+
# '<bos><start_of_turn>user\n{system_prompt}\n\n{prompt}<end_of_turn>\n<start_of_turn>model\n'
3526+
3527+
DEFAULT_SYSTEM_MESSAGE = None
3528+
3529+
CHAT_FORMAT = (
3530+
"{{ '<bos>' }}"
3531+
"{%- if messages[0]['role'] == 'system' -%}"
3532+
"{%- if messages[0]['content'] is string -%}"
3533+
"{%- set first_user_prefix = messages[0]['content'] + '\n\n' -%}"
3534+
"{%- else -%}"
3535+
"{%- set first_user_prefix = messages[0]['content'][0]['text'] + '\n\n' -%}"
3536+
"{%- endif -%}"
3537+
"{%- set loop_messages = messages[1:] -%}"
3538+
"{%- else -%}"
3539+
"{%- set first_user_prefix = \"\" -%}"
3540+
"{%- set loop_messages = messages -%}"
3541+
"{%- endif -%}"
3542+
"{%- for message in loop_messages -%}"
3543+
"{%- if (message['role'] == 'user') != (loop.index0 % 2 == 0) -%}"
3544+
"{{ raise_exception(\"Conversation roles must alternate user/assistant/user/assistant/...\") }}"
3545+
"{%- endif -%}"
3546+
"{%- if (message['role'] == 'assistant') -%}"
3547+
"{%- set role = \"model\" -%}"
3548+
"{%- else -%}"
3549+
"{%- set role = message['role'] -%}"
3550+
"{%- endif -%}"
3551+
"{{ '<start_of_turn>' + role + '\n' + (first_user_prefix if loop.first else \"\") }}"
3552+
"{%- if message['content'] is string -%}"
3553+
"{{ message['content'] | trim }}"
3554+
"{%- elif message['content'] is iterable -%}"
3555+
"{%- for item in message['content'] -%}"
3556+
"{%- if item['type'] == 'image' -%}"
3557+
"{{ '<start_of_image>' }}"
3558+
"{%- elif item['type'] == 'text' -%}"
3559+
"{{ item['text'] | trim }}"
3560+
"{%- endif -%}"
3561+
"{%- endfor -%}"
3562+
"{%- else -%}"
3563+
"{{ raise_exception(\"Invalid content type\") }}"
3564+
"{%- endif -%}"
3565+
"{{ '<end_of_turn>\n' }}"
3566+
"{%- endfor -%}"
3567+
"{%- if add_generation_prompt -%}"
3568+
"{{ '<start_of_turn>model\n' }}"
3569+
"{%- endif -%}"
3570+
)
3571+
3572+
@staticmethod
3573+
def split_text_on_image_urls(text: str, image_urls: List[str]):
3574+
split_text: List[Tuple[Literal["text", "image_url"], str]] = []
3575+
copied_urls = image_urls[:]
3576+
remaining = text
3577+
image_placeholder = "<start_of_image>"
3578+
3579+
while remaining:
3580+
# Find placeholder
3581+
pos = remaining.find(image_placeholder)
3582+
if pos != -1:
3583+
assert len(copied_urls) > 0
3584+
if pos > 0:
3585+
split_text += [("text", remaining[:pos])]
3586+
split_text += [("text", "\n\n<start_of_image>")]
3587+
split_text += [("image_url", copied_urls.pop(0))]
3588+
split_text += [("text", "<end_of_image>\n\n")]
3589+
remaining = remaining[pos + len(image_placeholder):]
3590+
else:
3591+
assert len(copied_urls) == 0
3592+
split_text.append(("text", remaining))
3593+
remaining = ""
3594+
return split_text
3595+
3596+
@staticmethod
3597+
def get_image_urls(messages: List[llama_types.ChatCompletionRequestMessage]):
3598+
image_urls: List[str] = []
3599+
for message in messages:
3600+
if message["role"] == "user":
3601+
if message.get("content") is None:
3602+
continue
3603+
for content in message["content"]:
3604+
if isinstance(content, dict) and content.get("type") == "image":
3605+
if isinstance(content.get("image"), dict) and isinstance(content["image"].get("url"), str):
3606+
image_urls.append(content["image"]["url"])
3607+
elif isinstance(content.get("url"), str):
3608+
image_urls.append(content["url"])
3609+
return image_urls
3610+
3611+
35233612
def _accumulate_chunks(
35243613
chunks_iterator: Iterator[llama_types.CreateCompletionStreamResponse],
35253614
chunks_list: List[llama_types.CreateCompletionStreamResponse],

0 commit comments

Comments
 (0)