Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/cli/groups/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@
type=float,
help='Confidence threshold for accepting draft tokens.')
@click.option('--tool-call-parser',
type=click.Choice(['qwen35', 'hermes', 'gemma4']),
type=click.Choice(['qwen35', 'hermes', 'gemma4', 'museglimmer']),
required=False,
default=None,
help='Tool-call output format for this model (qwen35 XML, hermes JSON, or gemma4 call syntax). llm/vlm only; required for tool calling.')
help='Tool-call output format for this model (qwen35 XML, hermes JSON, gemma4 call syntax, or museglimmer Harmony atem). llm/vlm only; required for tool calling.')
@click.pass_context
def add(ctx, model_path, model_name, engine, model_type, device, runtime_config, scheduler_config, cache_dir, draft_model_path, draft_device, num_assistant_tokens, assistant_confidence_threshold, tool_call_parser):
"""- Add a model configuration to the config file."""
Expand Down
9 changes: 6 additions & 3 deletions src/engine/ov_genai/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,12 @@ async def generate_text(self, gen_config: OVGenAI_GenConfig) -> AsyncIterator[Un

perf_metrics = result.perf_metrics
decoder_tokenizer = self.model.get_tokenizer()
# gemma4 protocol tags are special=True: keep them in the decoded text
# so the route-level parse_generation can split reasoning/tool calls.
keep_special = getattr(gen_config, "tool_call_parser", None) == "gemma4"
# gemma4/museglimmer protocol tags are special=True: keep them in the
# decoded text so the route-level parse_generation can split
# reasoning/tool calls.
keep_special = getattr(gen_config, "tool_call_parser", None) in (
"gemma4", "museglimmer",
)
text = (
decoder_tokenizer.decode(result.tokens, skip_special_tokens=not keep_special)[0]
if getattr(result, "tokens", None) else ""
Expand Down
8 changes: 7 additions & 1 deletion src/engine/ov_genai/streamers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from openvino_genai import StreamerBase
from src.server.schemas.modeling.contract_ovgenai_llm_and_vlm import OVGenAI_GenConfig
from src.engine.ov_genai.tool_parse import gemma4 as gemma4_tool_parse
from src.engine.ov_genai.tool_parse import museglimmer as museglimmer_tool_parse
from src.engine.ov_genai.tool_parse import qwen35 as qwen35_tool_parse


Expand Down Expand Up @@ -101,7 +102,10 @@ def select_streamer(tokenizer, gen_config: OVGenAI_GenConfig) -> StreamerBase:
(token-ID block boundaries, parsed OpenAI deltas). gemma4 requests use
Gemma4ToolCallStreamer whenever tools are requested OR thinking is enabled
(its thought-channel tags are special=True, so the plain text path cannot
split reasoning); everything else uses ChunkStreamer. All of them enqueue
split reasoning). museglimmer requests ALWAYS use MuseGlimmerToolCallStreamer:
its Harmony 'to=' channel routing is token-ID-only, so even a plain content
response opens with ' to=user<|message|>' header text that only the engine
streamer can strip. Everything else uses ChunkStreamer. All of them enqueue
on .text_queue, so consumers are unaffected.
"""
parser_name = getattr(gen_config, "tool_call_parser", None)
Expand All @@ -113,4 +117,6 @@ def select_streamer(tokenizer, gen_config: OVGenAI_GenConfig) -> StreamerBase:
thinking = bool(gen_config.chat_template_kwargs.get("enable_thinking", True))
if gen_config.tools or thinking:
return gemma4_tool_parse.Gemma4ToolCallStreamer(tokenizer, gen_config)
if parser_name == "museglimmer":
return museglimmer_tool_parse.MuseGlimmerToolCallStreamer(tokenizer, gen_config)
return ChunkStreamer(tokenizer, gen_config)
Loading
Loading