Checklist / 检查清单
Bug Description / Bug 描述
我们目前需要将一些“环境信息”反馈给模型。
- 如果将该信息放入 user 角色,容易引起模型指令混淆;
- 如果放 system 角色,不利于多轮对话维护。
因此,这类背景信息最适合放入 tool 角色中,作为客观观测结果(Observation)提供给模型。
但在实际使用中,如果给定的messages包含单独的 role: "tool" 消息,但在其之前没有对应的 assistant 工具调用消息,会直接报错。
事实上,Qwen、GLM 等主流模型的底层模板(Chat Template)均原生支持解析无前置调用的独立 tool 消息(会自动将其转换为 <tool_response> 或 <|observation|> 结构块渲染给模型)。
How to Reproduce / 如何复现
import json
from swift import get_processor, get_template
准备工具返回结果
tr_sh = json.dumps(dict(city='上海', aqi='72', unit='fahrenheit'), ensure_ascii=False)
tool = {
'type': 'function',
'function': {
'name': 'realtime_aqi',
'description': '天气预报。获取实时空气质量。',
'parameters': {
'type': 'object',
'properties': {'city': {'type': 'string', 'description': '城市名'}},
'required': ['city'],
},
},
}
data = {
'tools': [tool],
'messages': [
{'role': 'user', 'content': '上海今天的天气情况'},
{'role': 'tool', 'content': '这是一个环境信息'},
# 1. 中间过程:模型发起的工具调用(必须是 assistant 角色,且携带 tool_calls 字段)
{
'role': 'assistant',
'tool_calls': [
{
'type': 'function',
'function': {
'name': 'realtime_aqi',
'arguments': {"city": "上海"}
}
}
]
},
# 2. 中间过程:外部环境/系统返回的工具结果(角色名称必须是 'tool')
{'role': 'tool', 'name': 'realtime_aqi', 'content': tr_sh},
# 3. 最终解答
{'role': 'assistant', 'content': '北京AQI为10,良好;上海AQI为72,轻度污染。'},
],
}
template = get_template(
get_processor('Qwen3-8B'),
agent_template='react_en',
add_non_thinking_prefix=False,
)
template.set_mode('train')
inputs = template.encode(data)
print(template.safe_decode(inputs['labels']))
print(inputs['loss_scale'])
报错:
KeyError: 'content'
KeyError Traceback (most recent call last)
Cell In[6], line 95
91 agent_template='react_en',
92 add_non_thinking_prefix=False,
93 )
94 template.set_mode('train')
---> 95 encoded = template.encode(data)
96
97 print('[INPUT_IDS]')
98 print(template.safe_decode(encoded['input_ids']))
File /usr/local/lib/python3.12/dist-packages/torch/utils/_contextlib.py:124, in context_decorator..decorate_context(*args, **kwargs)
120 @functools.wraps(func)
121 def decorate_context(*args, **kwargs):
122 # pyrefly: ignore [bad-context-manager]
123 with ctx_factory():
--> 124 return func(*args, **kwargs)
File /usr/local/lib/python3.12/dist-packages/swift/utils/utils.py:445, in retry_decorator.._retry..new_func(*args, **kwargs)
443 while True:
444 try:
--> 445 return func(*args, **kwargs)
446 except Exception:
447 if i == retry:
File /usr/local/lib/python3.12/dist-packages/swift/template/base.py:615, in Template.encode(self, inputs, return_template_inputs, return_length)
612 inputs = asdict(inputs)
614 if isinstance(inputs, dict):
--> 615 inputs = TemplateInputs.from_dict(inputs)
616 elif isinstance(inputs, TemplateInputs):
617 inputs = deepcopy(inputs)
File /usr/local/lib/python3.12/dist-packages/swift/template/template_inputs.py:219, in TemplateInputs.from_dict(cls, inputs)
216 if chosen_v is not None and rejected_v is None:
217 rejected[k] = chosen_v
--> 219 return cls(**kwargs)
File :7, in create_fn..init(self, chosen, rejected, positive, negative)
File /usr/local/lib/python3.12/dist-packages/swift/template/template_inputs.py:152, in TemplateInputs.post_init(self)
150 continue
151 if key in {'chosen', 'rejected'}:
--> 152 setattr(self, key, StdTemplateInputs.from_dict(value_dict))
153 else:
154 res = []
File /usr/local/lib/python3.12/dist-packages/swift/template/template_inputs.py:82, in StdTemplateInputs.from_dict(cls, inputs)
79 if message['role'] in {'tool_call', 'tool'} and not isinstance(message['content'], str):
80 message['content'] = json.dumps(message['content'], ensure_ascii=False)
---> 82 media_kwargs = StdTemplateInputs.remove_messages_media(messages)
83 for k in list(media_kwargs.keys()):
84 mm_data = media_kwargs[k]
File /usr/local/lib/python3.12/dist-packages/swift/template/template_inputs.py:111, in StdTemplateInputs.remove_messages_media(messages)
109 res = {'images': [], 'audios': [], 'videos': []}
110 for message in messages:
--> 111 content = message['content']
112 if isinstance(content, str):
113 continue
KeyError: 'content'
Additional Information / 补充信息
No response
Checklist / 检查清单
Bug Description / Bug 描述
我们目前需要将一些“环境信息”反馈给模型。
因此,这类背景信息最适合放入 tool 角色中,作为客观观测结果(Observation)提供给模型。
但在实际使用中,如果给定的messages包含单独的 role: "tool" 消息,但在其之前没有对应的 assistant 工具调用消息,会直接报错。
事实上,Qwen、GLM 等主流模型的底层模板(Chat Template)均原生支持解析无前置调用的独立 tool 消息(会自动将其转换为 <tool_response> 或 <|observation|> 结构块渲染给模型)。
How to Reproduce / 如何复现
import json
from swift import get_processor, get_template
准备工具返回结果
tr_sh = json.dumps(dict(city='上海', aqi='72', unit='fahrenheit'), ensure_ascii=False)
tool = {
'type': 'function',
'function': {
'name': 'realtime_aqi',
'description': '天气预报。获取实时空气质量。',
'parameters': {
'type': 'object',
'properties': {'city': {'type': 'string', 'description': '城市名'}},
'required': ['city'],
},
},
}
data = {
'tools': [tool],
'messages': [
{'role': 'user', 'content': '上海今天的天气情况'},
{'role': 'tool', 'content': '这是一个环境信息'},
# 1. 中间过程:模型发起的工具调用(必须是 assistant 角色,且携带 tool_calls 字段)
{
'role': 'assistant',
'tool_calls': [
{
'type': 'function',
'function': {
'name': 'realtime_aqi',
'arguments': {"city": "上海"}
}
}
]
},
}
template = get_template(
get_processor('Qwen3-8B'),
agent_template='react_en',
add_non_thinking_prefix=False,
)
template.set_mode('train')
inputs = template.encode(data)
print(template.safe_decode(inputs['labels']))
print(inputs['loss_scale'])
报错:
KeyError: 'content'
KeyError Traceback (most recent call last)
Cell In[6], line 95
91 agent_template='react_en',
92 add_non_thinking_prefix=False,
93 )
94 template.set_mode('train')
---> 95 encoded = template.encode(data)
96
97 print('[INPUT_IDS]')
98 print(template.safe_decode(encoded['input_ids']))
File /usr/local/lib/python3.12/dist-packages/torch/utils/_contextlib.py:124, in context_decorator..decorate_context(*args, **kwargs)
120 @functools.wraps(func)
121 def decorate_context(*args, **kwargs):
122 # pyrefly: ignore [bad-context-manager]
123 with ctx_factory():
--> 124 return func(*args, **kwargs)
File /usr/local/lib/python3.12/dist-packages/swift/utils/utils.py:445, in retry_decorator.._retry..new_func(*args, **kwargs)
443 while True:
444 try:
--> 445 return func(*args, **kwargs)
446 except Exception:
447 if i == retry:
File /usr/local/lib/python3.12/dist-packages/swift/template/base.py:615, in Template.encode(self, inputs, return_template_inputs, return_length)
612 inputs = asdict(inputs)
614 if isinstance(inputs, dict):
--> 615 inputs = TemplateInputs.from_dict(inputs)
616 elif isinstance(inputs, TemplateInputs):
617 inputs = deepcopy(inputs)
File /usr/local/lib/python3.12/dist-packages/swift/template/template_inputs.py:219, in TemplateInputs.from_dict(cls, inputs)
216 if chosen_v is not None and rejected_v is None:
217 rejected[k] = chosen_v
--> 219 return cls(**kwargs)
File :7, in create_fn..init(self, chosen, rejected, positive, negative)
File /usr/local/lib/python3.12/dist-packages/swift/template/template_inputs.py:152, in TemplateInputs.post_init(self)
150 continue
151 if key in {'chosen', 'rejected'}:
--> 152 setattr(self, key, StdTemplateInputs.from_dict(value_dict))
153 else:
154 res = []
File /usr/local/lib/python3.12/dist-packages/swift/template/template_inputs.py:82, in StdTemplateInputs.from_dict(cls, inputs)
79 if message['role'] in {'tool_call', 'tool'} and not isinstance(message['content'], str):
80 message['content'] = json.dumps(message['content'], ensure_ascii=False)
---> 82 media_kwargs = StdTemplateInputs.remove_messages_media(messages)
83 for k in list(media_kwargs.keys()):
84 mm_data = media_kwargs[k]
File /usr/local/lib/python3.12/dist-packages/swift/template/template_inputs.py:111, in StdTemplateInputs.remove_messages_media(messages)
109 res = {'images': [], 'audios': [], 'videos': []}
110 for message in messages:
--> 111 content = message['content']
112 if isinstance(content, str):
113 continue
KeyError: 'content'
Additional Information / 补充信息
No response