Skip to content
Open
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
3 changes: 3 additions & 0 deletions docs/source/Instruction/Supported-models-and-datasets.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@
|[moonshotai/Kimi-K2-Instruct](https://modelscope.cn/models/moonshotai/Kimi-K2-Instruct)|deepseek_v3|kimi_k2||transformers>=4.39.3|✔|-|[moonshotai/Kimi-K2-Instruct](https://huggingface.co/moonshotai/Kimi-K2-Instruct)|
|[moonshotai/Kimi-K2-Instruct-0905](https://modelscope.cn/models/moonshotai/Kimi-K2-Instruct-0905)|deepseek_v3|kimi_k2||transformers>=4.39.3|✔|-|[moonshotai/Kimi-K2-Instruct-0905](https://huggingface.co/moonshotai/Kimi-K2-Instruct-0905)|
|[moonshotai/Kimi-K2-Thinking](https://modelscope.cn/models/moonshotai/Kimi-K2-Thinking)|deepseek_v3|kimi_k2||transformers>=4.39.3|✔|-|[moonshotai/Kimi-K2-Thinking](https://huggingface.co/moonshotai/Kimi-K2-Thinking)|
|[TeleAI/TeleChat3-105B-A4.7B-Thinking](https://modelscope.cn/models/TeleAI/TeleChat3-105B-A4.7B-Thinking)|deepseek_v3|telechat3|telechat3|transformers>=4.46.3|✘|-|-|
|[deepseek-ai/DeepSeek-V3.1-Base](https://modelscope.cn/models/deepseek-ai/DeepSeek-V3.1-Base)|deepseek_v3|deepseek_v3_1|deepseek_v3_1|transformers>=4.39.3|✔|-|[deepseek-ai/DeepSeek-V3.1-Base](https://huggingface.co/deepseek-ai/DeepSeek-V3.1-Base)|
|[deepseek-ai/DeepSeek-V3.1](https://modelscope.cn/models/deepseek-ai/DeepSeek-V3.1)|deepseek_v3|deepseek_v3_1|deepseek_v3_1|transformers>=4.39.3|✔|-|[deepseek-ai/DeepSeek-V3.1](https://huggingface.co/deepseek-ai/DeepSeek-V3.1)|
|[deepseek-ai/DeepSeek-V3.1-Terminus](https://modelscope.cn/models/deepseek-ai/DeepSeek-V3.1-Terminus)|deepseek_v3|deepseek_v3_1|deepseek_v3_1|transformers>=4.39.3|✔|-|[deepseek-ai/DeepSeek-V3.1-Terminus](https://huggingface.co/deepseek-ai/DeepSeek-V3.1-Terminus)|
Expand Down Expand Up @@ -547,6 +548,8 @@
|[TeleAI/TeleChat2-7B-32K](https://modelscope.cn/models/TeleAI/TeleChat2-7B-32K)|telechat2|telechat2||-|✘|-|[Tele-AI/TeleChat2-7B-32K](https://huggingface.co/Tele-AI/TeleChat2-7B-32K)|
|[TeleAI/TeleChat2-35B-32K](https://modelscope.cn/models/TeleAI/TeleChat2-35B-32K)|telechat2|telechat2||-|✘|-|[Tele-AI/TeleChat2-35B-32K](https://huggingface.co/Tele-AI/TeleChat2-35B-32K)|
|[TeleAI/TeleChat2-35B-Nov](https://modelscope.cn/models/TeleAI/TeleChat2-35B-Nov)|telechat2|telechat2||-|✘|-|[Tele-AI/TeleChat2-35B-Nov](https://huggingface.co/Tele-AI/TeleChat2-35B-Nov)|
|[TeleAI/TeleChat3-36B-Thinking](https://modelscope.cn/models/TeleAI/TeleChat3-36B-Thinking)|telechat3|telechat3|telechat3|transformers>=4.53.2|✘|-|[Tele-AI/TeleChat3-36B-Thinking](https://huggingface.co/Tele-AI/TeleChat3-36B-Thinking)|
|[TeleAI/TeleChat3-Coder-36B-Thinking](https://modelscope.cn/models/TeleAI/TeleChat3-Coder-36B-Thinking)|telechat3|telechat3_coder|telechat3_coder|transformers>=4.53.2|✘|coding|[Tele-AI/TeleChat3-Coder-36B-Thinking](https://huggingface.co/Tele-AI/TeleChat3-Coder-36B-Thinking)|
|[AI-ModelScope/Mistral-7B-Instruct-v0.1](https://modelscope.cn/models/AI-ModelScope/Mistral-7B-Instruct-v0.1)|mistral|llama||transformers>=4.34|✘|-|[mistralai/Mistral-7B-Instruct-v0.1](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.1)|
|[AI-ModelScope/Mistral-7B-Instruct-v0.2](https://modelscope.cn/models/AI-ModelScope/Mistral-7B-Instruct-v0.2)|mistral|llama||transformers>=4.34|✘|-|[mistralai/Mistral-7B-Instruct-v0.2](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2)|
|[LLM-Research/Mistral-7B-Instruct-v0.3](https://modelscope.cn/models/LLM-Research/Mistral-7B-Instruct-v0.3)|mistral|llama||transformers>=4.34|✘|-|[mistralai/Mistral-7B-Instruct-v0.3](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.3)|
Expand Down
3 changes: 3 additions & 0 deletions docs/source_en/Instruction/Supported-models-and-datasets.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ The table below introduces the models integrated with ms-swift:
|[moonshotai/Kimi-K2-Instruct](https://modelscope.cn/models/moonshotai/Kimi-K2-Instruct)|deepseek_v3|kimi_k2||transformers>=4.39.3|✔|-|[moonshotai/Kimi-K2-Instruct](https://huggingface.co/moonshotai/Kimi-K2-Instruct)|
|[moonshotai/Kimi-K2-Instruct-0905](https://modelscope.cn/models/moonshotai/Kimi-K2-Instruct-0905)|deepseek_v3|kimi_k2||transformers>=4.39.3|✔|-|[moonshotai/Kimi-K2-Instruct-0905](https://huggingface.co/moonshotai/Kimi-K2-Instruct-0905)|
|[moonshotai/Kimi-K2-Thinking](https://modelscope.cn/models/moonshotai/Kimi-K2-Thinking)|deepseek_v3|kimi_k2||transformers>=4.39.3|✔|-|[moonshotai/Kimi-K2-Thinking](https://huggingface.co/moonshotai/Kimi-K2-Thinking)|
|[TeleAI/TeleChat3-105B-A4.7B-Thinking](https://modelscope.cn/models/TeleAI/TeleChat3-105B-A4.7B-Thinking)|deepseek_v3|telechat3|telechat3|transformers>=4.46.3|✘|-|-|
|[deepseek-ai/DeepSeek-V3.1-Base](https://modelscope.cn/models/deepseek-ai/DeepSeek-V3.1-Base)|deepseek_v3|deepseek_v3_1|deepseek_v3_1|transformers>=4.39.3|✔|-|[deepseek-ai/DeepSeek-V3.1-Base](https://huggingface.co/deepseek-ai/DeepSeek-V3.1-Base)|
|[deepseek-ai/DeepSeek-V3.1](https://modelscope.cn/models/deepseek-ai/DeepSeek-V3.1)|deepseek_v3|deepseek_v3_1|deepseek_v3_1|transformers>=4.39.3|✔|-|[deepseek-ai/DeepSeek-V3.1](https://huggingface.co/deepseek-ai/DeepSeek-V3.1)|
|[deepseek-ai/DeepSeek-V3.1-Terminus](https://modelscope.cn/models/deepseek-ai/DeepSeek-V3.1-Terminus)|deepseek_v3|deepseek_v3_1|deepseek_v3_1|transformers>=4.39.3|✔|-|[deepseek-ai/DeepSeek-V3.1-Terminus](https://huggingface.co/deepseek-ai/DeepSeek-V3.1-Terminus)|
Expand Down Expand Up @@ -548,6 +549,8 @@ The table below introduces the models integrated with ms-swift:
|[TeleAI/TeleChat2-7B-32K](https://modelscope.cn/models/TeleAI/TeleChat2-7B-32K)|telechat2|telechat2||-|✘|-|[Tele-AI/TeleChat2-7B-32K](https://huggingface.co/Tele-AI/TeleChat2-7B-32K)|
|[TeleAI/TeleChat2-35B-32K](https://modelscope.cn/models/TeleAI/TeleChat2-35B-32K)|telechat2|telechat2||-|✘|-|[Tele-AI/TeleChat2-35B-32K](https://huggingface.co/Tele-AI/TeleChat2-35B-32K)|
|[TeleAI/TeleChat2-35B-Nov](https://modelscope.cn/models/TeleAI/TeleChat2-35B-Nov)|telechat2|telechat2||-|✘|-|[Tele-AI/TeleChat2-35B-Nov](https://huggingface.co/Tele-AI/TeleChat2-35B-Nov)|
|[TeleAI/TeleChat3-36B-Thinking](https://modelscope.cn/models/TeleAI/TeleChat3-36B-Thinking)|telechat3|telechat3|telechat3|transformers>=4.53.2|✘|-|[Tele-AI/TeleChat3-36B-Thinking](https://huggingface.co/Tele-AI/TeleChat3-36B-Thinking)|
|[TeleAI/TeleChat3-Coder-36B-Thinking](https://modelscope.cn/models/TeleAI/TeleChat3-Coder-36B-Thinking)|telechat3|telechat3_coder|telechat3_coder|transformers>=4.53.2|✘|coding|[Tele-AI/TeleChat3-Coder-36B-Thinking](https://huggingface.co/Tele-AI/TeleChat3-Coder-36B-Thinking)|
|[AI-ModelScope/Mistral-7B-Instruct-v0.1](https://modelscope.cn/models/AI-ModelScope/Mistral-7B-Instruct-v0.1)|mistral|llama||transformers>=4.34|✘|-|[mistralai/Mistral-7B-Instruct-v0.1](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.1)|
|[AI-ModelScope/Mistral-7B-Instruct-v0.2](https://modelscope.cn/models/AI-ModelScope/Mistral-7B-Instruct-v0.2)|mistral|llama||transformers>=4.34|✘|-|[mistralai/Mistral-7B-Instruct-v0.2](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2)|
|[LLM-Research/Mistral-7B-Instruct-v0.3](https://modelscope.cn/models/LLM-Research/Mistral-7B-Instruct-v0.3)|mistral|llama||transformers>=4.34|✘|-|[mistralai/Mistral-7B-Instruct-v0.3](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.3)|
Expand Down
6 changes: 5 additions & 1 deletion swift/agent_template/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from typing import Any, Dict, List, Literal, Optional, Tuple, Union

from swift.infer_engine import Function
from swift.template import Prompt, split_str_parts_by
from swift.template import Prompt, Tool, split_str_parts_by


@dataclass
Expand Down Expand Up @@ -168,6 +168,10 @@ def _add_tool_call_prefix(self, tool_content: str, pre_message=None) -> str:
"""
return tool_content

def get_toolcall_with_tools(self, response: str, tools: Optional[List[Tool]] = None) -> List[Function]:
"""Parse tool calls with request tools; legacy parsers ignore the schemas."""
return self.get_toolcall(response)

@staticmethod
def _get_tool_name(tool):
return tool.get('name_for_model') or tool.get('name')
Expand Down
3 changes: 3 additions & 0 deletions swift/agent_template/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .qwen3_coder import Qwen3_5AgentTemplate, Qwen3CoderAgentTemplate
from .react import ReactEnAgentTemplate, ReactZnAgentTemplate
from .seed_oss import SeedAgentTemplate
from .telechat3 import TeleChat3AgentTemplate, TeleChat3CoderAgentTemplate
from .toolbench import ToolBenchAgentTemplate
from .youtu import YoutuAgentTemplate

Expand Down Expand Up @@ -50,6 +51,8 @@
'minimax_m2': MinimaxM2AgentTemplate,
'minimax_m3': MinimaxM3AgentTemplate,
'seed_oss': SeedAgentTemplate,
'telechat3': TeleChat3AgentTemplate,
'telechat3_coder': TeleChat3CoderAgentTemplate,
# ref: https://modelscope.cn/models/google/gemma-4-12B-it
'gemma4': Gemma4AgentTemplate,
# extra
Expand Down
152 changes: 152 additions & 0 deletions swift/agent_template/telechat3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import json
import re
from typing import List, Optional, Tuple, Union

from swift.infer_engine import Function
from swift.template import Prompt, Tool
from .base import BaseAgentTemplate


class TeleChat3AgentTemplate(BaseAgentTemplate):

@staticmethod
def _dump_tool(tool) -> str:
return json.dumps(tool, ensure_ascii=False)

@staticmethod
def _dump_arg_value(value) -> str:
return value if isinstance(value, str) else json.dumps(value, ensure_ascii=False)

def get_toolcall(self, response: str) -> List[Function]:
functions = []
for tool_call in re.findall(r'<tool_call>\s*(.*?)\s*</tool_call>', response, re.DOTALL):
tool_call = self._parse_json(tool_call.strip())
if isinstance(tool_call, dict) and 'name' in tool_call:
functions.append(Function(name=tool_call['name'], arguments=tool_call.get('arguments') or {}))
return functions

def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
tool_descs = '\n'.join(self._dump_tool(tool) for tool in tools)
tool_instruction = ('\n\n# 可用工具\n'
'你可以调用<tools></tools>标签中包含的一个或多个工具来辅助你回答问题,以下是可用工具详情:\n'
'<tools>\n'
f'{tool_descs}\n'
'</tools>\n\n'
'# 调用方法\n'
'你需要遵循工具的要求,使用json格式返回工具名称及参数,并用<tool_call></tool_call>包含。下方是一个调用模板:\n'
'<tool_call>\n'
'{"name": <function-name>, "arguments": <args-json-object>}\n'
'</tool_call>\n')
return (system or '') + tool_instruction

def _format_tool_calls(self, tool_call_messages) -> str:
tool_calls = []
for message in tool_call_messages:
tool_call = self._parse_tool_call(message['content'])
tool_calls.append(f'<tool_call>\n{json.dumps(tool_call, ensure_ascii=False)}\n</tool_call>')
return '\n'.join(tool_calls)

@staticmethod
def _to_prompt(content: Union[str, Prompt]) -> Prompt:
return content if isinstance(content, list) else [content]

def _format_tool_responses(self, assistant_content: Union[str, Prompt], tool_messages) -> Tuple[Prompt, Prompt]:
res: Prompt = []
for i, tool_message in enumerate(tool_messages):
tool_content = tool_message['content']
if i == 0:
res.append('<_user><tool_response>\n')
else:
res.append('\n<tool_response>\n')
res.append(tool_content)
res.append('\n</tool_response>')
res.append('<_bot>')
return self._to_prompt(assistant_content) + ['<_end>\n'], res


class TeleChat3CoderAgentTemplate(TeleChat3AgentTemplate):

def _add_tool_call_prefix(self, tool_content: str, pre_message=None) -> str:
if pre_message is None or pre_message['role'] != 'assistant':
return '</think>' + tool_content
return tool_content

def get_toolcall(self, response: str) -> List[Function]:
return self.get_toolcall_with_tools(response)

def get_toolcall_with_tools(self, response: str, tools: Optional[List[Tool]] = None) -> List[Function]:
functions = []
for block in re.findall(r'<tool_call>(.*?)</tool_call>', response, re.DOTALL):
name, args = self._parse_coder_tool_call(block, tools)
if name:
functions.append(Function(name=name, arguments=args))
return functions

def _parse_coder_tool_call(self, block: str, tools: Optional[List[Tool]] = None):
name = re.split(r'<param_key>', block, maxsplit=1)[0].strip()
args = {}
pattern = re.compile(r'<param_key>(.*?)</param_key>\s*<param_value>(.*?)</param_value>', re.DOTALL)
for key, value in pattern.findall(block):
key = key.strip()
value = value.strip()
if self._is_string_arg(name, key, tools):
args[key] = value
else:
parsed_value = self._parse_json(value)
args[key] = parsed_value if parsed_value is not None or value in {'null', 'None'} else value
return name, args

def _is_string_arg(self, tool_name, arg_name, tools):
# Match the model's official parser: only an exact schema type of "string" keeps the raw value.
for tool in tools or []:
if isinstance(tool, str):
tool = self._parse_json(tool)
if not isinstance(tool, dict):
continue
tool = self.unwrap_tool(tool)
if self._get_tool_name(tool) != tool_name:
continue
Comment on lines +107 to +109

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a defensive check to ensure tool is a dictionary after calling unwrap_tool. If unwrap_tool returns None or a non-dictionary object, calling _get_tool_name(tool) will raise an AttributeError.

Suggested change
tool = self.unwrap_tool(tool)
if self._get_tool_name(tool) != tool_name:
continue
tool = self.unwrap_tool(tool)
if not isinstance(tool, dict):
continue
if self._get_tool_name(tool) != tool_name:
continue

parameters = self._parse_json(tool.get('parameters') or {})
if not isinstance(parameters, dict):
return False
properties = parameters.get('properties') or {}
if not isinstance(properties, dict):
return False
arg_schema = properties.get(arg_name) or {}
return isinstance(arg_schema, dict) and arg_schema.get('type') == 'string'
return False

def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
tool_descs = '\n'.join(self._dump_tool(tool) for tool in tools)
tool_instruction = (
'\n# Tools\n\n'
'You may call one or more functions to assist with the user query.\n\n'
'You are provided with function signatures within <tools></tools> XML tags:\n'
'<tools>\n'
f'{tool_descs}\n'
'</tools>\n\n'
'For each function call, output the function name and arguments within the following XML format:\n'
'<tool_call>{function-name}<param_key>{param-key-1}</param_key><param_value>{param-value-1}</param_value>'
'<param_key>{param-key-2}</param_key><param_value>{param-value-2}</param_value>...</tool_call>')
return (system or '') + tool_instruction

def _format_tool_calls(self, tool_call_messages) -> str:
tool_calls = []
for message in tool_call_messages:
tool_call = self._parse_tool_call(message['content'])
arguments = tool_call.get('arguments') or {}
parts = [f'<tool_call>{tool_call["name"]}']
for key, value in arguments.items():
parts.append(f'<param_key>{key}</param_key><param_value>{self._dump_arg_value(value)}</param_value>')
parts.append('</tool_call>')
tool_calls.append(''.join(parts))
return ''.join(tool_calls)

def _format_tool_responses(self, assistant_content: Union[str, Prompt], tool_messages) -> Tuple[Prompt, Prompt]:
res: Prompt = ['<_observation>']
for tool_message in tool_messages:
tool_content = tool_message['content']
res += ['<tool_response>', tool_content, '</tool_response>']
res.append('<_bot>')
return self._to_prompt(assistant_content) + ['<_end>'], res
2 changes: 1 addition & 1 deletion swift/infer_engine/grpo_vllm_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def _create_chat_completion_response(self, result, inputs, request_config, reque
output.token_ids = list(output.token_ids)
response = self.template.decode_generate_ids(output.token_ids, template_inputs=inputs['template_inputs'])
logprobs = self._get_logprobs(output.logprobs, output.token_ids, request_config.top_logprobs)
toolcall = self._get_toolcall(response)
toolcall = self._get_toolcall(response, inputs['template_inputs'].tools)

token_ids = output.token_ids if request_config.return_details else None
choice = ChatCompletionResponseChoice(
Expand Down
8 changes: 5 additions & 3 deletions swift/infer_engine/infer_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from swift.metrics import Metric
from swift.model import get_ckpt_dir
from swift.template import Template, get_template
from swift.template import Template, Tool, get_template
from swift.utils import Processor, ProcessorMixin, get_logger
from .base import BaseInferEngine
from .protocol import (ChatCompletionMessageToolCall, ChatCompletionResponse, ChatCompletionStreamResponse,
Expand Down Expand Up @@ -187,9 +187,11 @@ def infer(self,
use_tqdm = not request_config.stream and len(infer_requests) > 1
return self._batch_infer_stream(tasks, request_config.stream, use_tqdm, metrics)

def _get_toolcall(self, response: str) -> Optional[List[ChatCompletionMessageToolCall]]:
def _get_toolcall(self,
response: str,
tools: Optional[List[Tool]] = None) -> Optional[List[ChatCompletionMessageToolCall]]:
try:
functions = self.template.agent_template.get_toolcall(response)
functions = self.template.agent_template.get_toolcall_with_tools(response, tools)
except Exception:
functions = None
if functions:
Expand Down
5 changes: 3 additions & 2 deletions swift/infer_engine/lmdeploy_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ async def _infer_stream_async(
toolcall = None
if is_finished:
toolcall = self._get_toolcall(
self.template.decode_generate_ids(output.token_ids, template_inputs=inputs['template_inputs']))
self.template.decode_generate_ids(output.token_ids, template_inputs=inputs['template_inputs']),
inputs['template_inputs'].tools)
finish_reason = self._get_finish_reason(generation_config.max_new_tokens, output.num_token,
output.status.name == 'FINISH')
choices = [
Expand Down Expand Up @@ -265,7 +266,7 @@ async def _infer_full_async(
logprobs = self._get_logprobs(output.logprobs, output.token_ids, request_config.top_logprobs)

usage_info = self._get_usage_info(len(inputs['input_ids']), output.num_token)
toolcall = self._get_toolcall(response)
toolcall = self._get_toolcall(response, inputs['template_inputs'].tools)
finish_reason = self._get_finish_reason(generation_config.max_new_tokens, output.num_token,
output.status.name == 'FINISH')
token_ids = output.token_ids if request_config.return_details else None
Expand Down
Loading