feat(model-meta): register qwen-plus family with its own tokenizer and template
request_model values like qwen-plus matched no registered family, so resolve_model_family fell through to its "glm5" default and the formatter rendered canonical_prompt with the GLM-5 chat template and tokenized release blocks with the GLM-5 tokenizer. The fallback is silent, so nothing surfaced it. Register the family, ship Qwen3-plus tokenizer/chat_template (the template was embedded in tokenizer_config.json upstream and is extracted here), and add a QwenPlusTraceAdapter ahead of the GLM catch-all in get_raw_adapter -- the registry entry alone is not enough, since the adapter is what picks the template. The shared message/tool normalization is factored out of build_glm5_canonical_prompt so both builders use one copy. Measured against provider-reported usage.input_tokens on a 400-request sample, median tokenized/reported ratio goes 0.9449 -> 1.0000, and unique release block count on a 2190-request sample moves 5886 -> 5701. Note deepseek-v4-flash is still unregistered and still resolves to glm5. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,10 @@ def _load_qwen3_coder_chat_template():
|
||||
return _load_chat_template(str(resolve_chat_template_path("qwen3-coder")))
|
||||
|
||||
|
||||
def _load_qwen_plus_chat_template():
|
||||
return _load_chat_template(str(resolve_chat_template_path("qwen-plus")))
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_qwen3_coder_tool_parser_spec() -> Qwen3CoderToolParserSpec:
|
||||
defaults = Qwen3CoderToolParserSpec()
|
||||
@@ -101,6 +105,10 @@ def _stringify_message_content_for_template(content):
|
||||
|
||||
|
||||
def _normalize_message_content_for_template(content, role=""):
|
||||
if content is None:
|
||||
return ""
|
||||
if role == "tool" and not isinstance(content, (list, dict, str)):
|
||||
return str(content)
|
||||
if role == "tool" and isinstance(content, (list, dict)):
|
||||
has_named_tool_refs = False
|
||||
if isinstance(content, list):
|
||||
@@ -261,7 +269,35 @@ def _normalize_qwen_message_with_tool_parser(message, spec: Qwen3CoderToolParser
|
||||
return normalized_message
|
||||
|
||||
|
||||
def build_glm5_canonical_prompt(payload):
|
||||
_FALLBACK_COUNT = 0
|
||||
|
||||
|
||||
def _fallback_canonical_prompt(messages):
|
||||
global _FALLBACK_COUNT
|
||||
_FALLBACK_COUNT += 1
|
||||
parts = []
|
||||
for m in messages:
|
||||
if not isinstance(m, dict):
|
||||
parts.append(str(m))
|
||||
continue
|
||||
role = str(m.get("role", ""))
|
||||
content = _stringify_message_content_for_template(m.get("content"))
|
||||
seg = "<|" + role + "|>" + content
|
||||
tool_calls = m.get("tool_calls") or []
|
||||
if isinstance(tool_calls, list):
|
||||
for tc in tool_calls:
|
||||
function = tc.get("function", {}) if isinstance(tc, dict) else {}
|
||||
if not isinstance(function, dict):
|
||||
function = {}
|
||||
name = str(function.get("name", ""))
|
||||
arguments = function.get("arguments", {})
|
||||
seg += "<tool_call>" + name + compact_json(arguments) + "</tool_call>"
|
||||
parts.append(seg)
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def _normalize_payload_for_template(payload):
|
||||
"""Normalize one request payload into (messages, tools) ready for a chat template."""
|
||||
input_payload = payload.get("input", {}) if isinstance(payload, dict) else {}
|
||||
parameters = payload.get("parameters", {}) if isinstance(payload, dict) else {}
|
||||
messages = []
|
||||
@@ -289,11 +325,33 @@ def build_glm5_canonical_prompt(payload):
|
||||
elif not isinstance(tools_payload, list):
|
||||
tools_payload = [tools_payload]
|
||||
tools = [_normalize_tool_spec_for_template(tool) for tool in tools_payload if isinstance(tool, dict)]
|
||||
return messages, tools
|
||||
|
||||
|
||||
def build_glm5_canonical_prompt(payload):
|
||||
messages, tools = _normalize_payload_for_template(payload)
|
||||
try:
|
||||
return _load_glm5_chat_template().render(
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
add_generation_prompt=True,
|
||||
)
|
||||
except Exception:
|
||||
return _fallback_canonical_prompt(messages)
|
||||
|
||||
|
||||
def build_qwen_plus_canonical_prompt(payload):
|
||||
messages, tools = _normalize_payload_for_template(payload)
|
||||
if not messages:
|
||||
messages = [{"role": "system", "content": ""}]
|
||||
try:
|
||||
return _load_qwen_plus_chat_template().render(
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
add_generation_prompt=True,
|
||||
)
|
||||
except Exception:
|
||||
return _fallback_canonical_prompt(messages)
|
||||
|
||||
|
||||
def build_qwen3_coder_canonical_prompt(payload):
|
||||
@@ -408,8 +466,20 @@ class QwenTraceAdapter(GLMTraceAdapter):
|
||||
return build_qwen3_coder_canonical_prompt(payload)
|
||||
|
||||
|
||||
class QwenPlusTraceAdapter(GLMTraceAdapter):
|
||||
name = "qwen-plus"
|
||||
|
||||
def detect(self, raw):
|
||||
if "request_params" not in raw:
|
||||
return False
|
||||
return infer_model_family_from_request_model(raw.get("request_model")) == "qwen-plus"
|
||||
|
||||
def build_canonical_prompt(self, payload):
|
||||
return build_qwen_plus_canonical_prompt(payload)
|
||||
|
||||
|
||||
def get_raw_adapter(raw: dict[str, Any]):
|
||||
for adapter in [QwenTraceAdapter(), GLMTraceAdapter()]:
|
||||
for adapter in [QwenTraceAdapter(), QwenPlusTraceAdapter(), GLMTraceAdapter()]:
|
||||
if adapter.detect(raw):
|
||||
return adapter
|
||||
raise ValueError("Unsupported raw trace format for trace_formatter.")
|
||||
|
||||
62
trace_model_meta/Qwen/Qwen3-plus/chat_template.jinja
Normal file
62
trace_model_meta/Qwen/Qwen3-plus/chat_template.jinja
Normal file
@@ -0,0 +1,62 @@
|
||||
{%- if tools %}
|
||||
{{- '<|im_start|>system\n' }}
|
||||
{%- if messages[0]['role'] == 'system' %}
|
||||
{{- messages[0]['content'] }}
|
||||
{%- else %}
|
||||
{{- '' }}
|
||||
{%- endif %}
|
||||
{{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
|
||||
{%- for tool in tools %}
|
||||
{{- "\n" }}
|
||||
{{- tool | tojson }}
|
||||
{%- endfor %}
|
||||
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
|
||||
{%- else %}
|
||||
{%- if messages[0]['role'] == 'system' %}
|
||||
{{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }}
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- for message in messages %}
|
||||
{%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
|
||||
{{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
|
||||
{%- elif message.role == "assistant" and not message.tool_calls %}
|
||||
{%- set content = message.content %}
|
||||
{%- if not loop.last %}
|
||||
{%- set content = message.content.split('</think>')[-1].lstrip('\n') %}
|
||||
{%- endif %}
|
||||
{{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
|
||||
{%- elif message.role == "assistant" %}
|
||||
{%- set content = message.content %}
|
||||
{%- if not loop.last %}
|
||||
{%- set content = message.content.split('</think>')[-1].lstrip('\n') %}
|
||||
{%- endif %}
|
||||
{{- '<|im_start|>' + message.role }}
|
||||
{%- if message.content %}
|
||||
{{- '\n' + content }}
|
||||
{%- endif %}
|
||||
{%- for tool_call in message.tool_calls %}
|
||||
{%- if tool_call.function is defined %}
|
||||
{%- set tool_call = tool_call.function %}
|
||||
{%- endif %}
|
||||
{{- '\n<tool_call>\n{"name": "' }}
|
||||
{{- tool_call.name }}
|
||||
{{- '", "arguments": ' }}
|
||||
{{- tool_call.arguments | tojson }}
|
||||
{{- '}\n</tool_call>' }}
|
||||
{%- endfor %}
|
||||
{{- '<|im_end|>\n' }}
|
||||
{%- elif message.role == "tool" %}
|
||||
{%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %}
|
||||
{{- '<|im_start|>user' }}
|
||||
{%- endif %}
|
||||
{{- '\n<tool_response>\n' }}
|
||||
{{- message.content }}
|
||||
{{- '\n</tool_response>' }}
|
||||
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
|
||||
{{- '<|im_end|>\n' }}
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- if add_generation_prompt %}
|
||||
{{- '<|im_start|>assistant\n' }}
|
||||
{%- endif %}
|
||||
42
trace_model_meta/Qwen/Qwen3-plus/config.json
Normal file
42
trace_model_meta/Qwen/Qwen3-plus/config.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"architectures": [
|
||||
"Qwen3MoeForCausalLM"
|
||||
],
|
||||
"attention_bias": false,
|
||||
"attention_dropout": 0.0,
|
||||
"attn_output_gate": false,
|
||||
"bos_token_id": 151643,
|
||||
"decoder_sparse_step": 1,
|
||||
"eos_token_id": 151645,
|
||||
"head_dim": 128,
|
||||
"hidden_act": "silu",
|
||||
"hidden_size": 4096,
|
||||
"initializer_range": 0.02,
|
||||
"intermediate_size": 12288,
|
||||
"max_position_embeddings": 35840,
|
||||
"max_window_layers": 60,
|
||||
"mlp_only_layers": [],
|
||||
"model_type": "qwen3_moe",
|
||||
"moe_intermediate_size": 1536,
|
||||
"norm_topk_prob": true,
|
||||
"num_attention_heads": 64,
|
||||
"num_experts": 128,
|
||||
"num_experts_per_tok": 8,
|
||||
"num_hidden_layers": 60,
|
||||
"num_key_value_heads": 4,
|
||||
"output_router_logits": false,
|
||||
"partial_rotary_factor": 1.0,
|
||||
"rms_norm_eps": 1e-06,
|
||||
"rope_scaling": null,
|
||||
"rope_theta": 10000000,
|
||||
"router_aux_loss_coef": 0.001,
|
||||
"shared_expert_intermediate_size": 0,
|
||||
"sliding_window": null,
|
||||
"tie_word_embeddings": false,
|
||||
"torch_dtype": "bfloat16",
|
||||
"transformers_version": "4.52.3",
|
||||
"use_cache": true,
|
||||
"use_gemma_rms_norm": false,
|
||||
"use_sliding_window": false,
|
||||
"vocab_size": 151936
|
||||
}
|
||||
303318
trace_model_meta/Qwen/Qwen3-plus/tokenizer.json
Normal file
303318
trace_model_meta/Qwen/Qwen3-plus/tokenizer.json
Normal file
File diff suppressed because it is too large
Load Diff
239
trace_model_meta/Qwen/Qwen3-plus/tokenizer_config.json
Normal file
239
trace_model_meta/Qwen/Qwen3-plus/tokenizer_config.json
Normal file
@@ -0,0 +1,239 @@
|
||||
{
|
||||
"add_bos_token": false,
|
||||
"add_prefix_space": false,
|
||||
"added_tokens_decoder": {
|
||||
"151643": {
|
||||
"content": "<|endoftext|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151644": {
|
||||
"content": "<|im_start|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151645": {
|
||||
"content": "<|im_end|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151646": {
|
||||
"content": "<|object_ref_start|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151647": {
|
||||
"content": "<|object_ref_end|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151648": {
|
||||
"content": "<|box_start|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151649": {
|
||||
"content": "<|box_end|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151650": {
|
||||
"content": "<|quad_start|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151651": {
|
||||
"content": "<|quad_end|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151652": {
|
||||
"content": "<|vision_start|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151653": {
|
||||
"content": "<|vision_end|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151654": {
|
||||
"content": "<|vision_pad|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151655": {
|
||||
"content": "<|image_pad|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151656": {
|
||||
"content": "<|video_pad|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": true
|
||||
},
|
||||
"151657": {
|
||||
"content": "<tool_call>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151658": {
|
||||
"content": "</tool_call>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151659": {
|
||||
"content": "<|fim_prefix|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151660": {
|
||||
"content": "<|fim_middle|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151661": {
|
||||
"content": "<|fim_suffix|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151662": {
|
||||
"content": "<|fim_pad|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151663": {
|
||||
"content": "<|repo_name|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151664": {
|
||||
"content": "<|file_sep|>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151665": {
|
||||
"content": "<tool_response>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151666": {
|
||||
"content": "</tool_response>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151667": {
|
||||
"content": "<think>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
},
|
||||
"151668": {
|
||||
"content": "</think>",
|
||||
"lstrip": false,
|
||||
"normalized": false,
|
||||
"rstrip": false,
|
||||
"single_word": false,
|
||||
"special": false
|
||||
}
|
||||
},
|
||||
"additional_special_tokens": [
|
||||
"<|im_start|>",
|
||||
"<|im_end|>",
|
||||
"<|object_ref_start|>",
|
||||
"<|object_ref_end|>",
|
||||
"<|box_start|>",
|
||||
"<|box_end|>",
|
||||
"<|quad_start|>",
|
||||
"<|quad_end|>",
|
||||
"<|vision_start|>",
|
||||
"<|vision_end|>",
|
||||
"<|vision_pad|>",
|
||||
"<|image_pad|>",
|
||||
"<|video_pad|>"
|
||||
],
|
||||
"bos_token": null,
|
||||
"chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- '' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" and not message.tool_calls %}\n {%- set content = message.content %}\n {%- if not loop.last %}\n {%- set content = message.content.split('</think>')[-1].lstrip('\\n') %}\n {%- endif %}\n {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {%- set content = message.content %}\n {%- if not loop.last %}\n {%- set content = message.content.split('</think>')[-1].lstrip('\\n') %}\n {%- endif %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n",
|
||||
"clean_up_tokenization_spaces": false,
|
||||
"eos_token": "<|im_end|>",
|
||||
"errors": "replace",
|
||||
"model_max_length": 131072,
|
||||
"pad_token": "<|endoftext|>",
|
||||
"split_special_tokens": false,
|
||||
"tokenizer_class": "Qwen2Tokenizer",
|
||||
"unk_token": null
|
||||
}
|
||||
1
trace_model_meta/Qwen/Qwen3-plus/vocab.json
Normal file
1
trace_model_meta/Qwen/Qwen3-plus/vocab.json
Normal file
File diff suppressed because one or more lines are too long
@@ -42,6 +42,12 @@ MODEL_REGISTRY = {
|
||||
model_name="Qwen3-Coder-480B-A35B-Instruct",
|
||||
request_model_hints=("qwen3-coder", "qwen3 coder", "qwen3_coder"),
|
||||
),
|
||||
"qwen-plus": ModelMeta(
|
||||
family="qwen-plus",
|
||||
provider="Qwen",
|
||||
model_name="Qwen3-plus",
|
||||
request_model_hints=("qwen-plus", "qwen3-plus", "qwen_plus"),
|
||||
),
|
||||
}
|
||||
|
||||
MODEL_ALIASES = {
|
||||
@@ -54,6 +60,10 @@ MODEL_ALIASES = {
|
||||
"qwen3-coder": "qwen3-coder",
|
||||
"qwen3_coder": "qwen3-coder",
|
||||
"coder": "qwen3-coder",
|
||||
"qwen-plus": "qwen-plus",
|
||||
"qwen3-plus": "qwen-plus",
|
||||
"qwen_plus": "qwen-plus",
|
||||
"plus": "qwen-plus",
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +83,8 @@ def _infer_model_family_from_path(input_path: str | Path | None) -> str | None:
|
||||
return None
|
||||
if "qwen3-coder" in text or "qwen3_coder" in text:
|
||||
return "qwen3-coder"
|
||||
if "qwen-plus" in text or "qwen3-plus" in text or "qwen_plus" in text:
|
||||
return "qwen-plus"
|
||||
if "glm5" in text or "trace-glm" in text:
|
||||
return "glm5"
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user