# 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 from .base import BaseAgentTemplate class ChatGLM4AgentTemplate(BaseAgentTemplate): is_glm4_0414 = False @staticmethod def _find_function_call(single_content: str) -> Optional[Function]: single_content = single_content.replace('<|observation|>', '') pattern = re.compile(r'([^\n`]*?)\n({.*?})(?=\w*\n|$)', re.DOTALL) matches = pattern.findall(single_content) if not matches: return name, arguments = matches[0] return Function(name=name, arguments=arguments) def get_toolcall(self, response: str) -> List[Function]: toolcall_list = response.split('<|assistant|>') functions = [] for toolcall in toolcall_list: function = self._find_function_call(toolcall) if function: functions.append(function) if len(functions) == 0: # compat react_en return super().get_toolcall(response) return functions def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str: tool_descs = [] for tool in tools: tool = self.unwrap_tool(tool) name = self._get_tool_name(tool) tool_descs.append(f'## {name}\n\n{json.dumps(tool, ensure_ascii=False, indent=4)}\n' '在调用上述函数时,请使用 Json 格式表示调用的参数。') glm4_system = '你是一个名为 GLM-4 的人工智能助手。你是基于智谱AI训练的语言模型 GLM-4 模型开发的,你的任务是针对用户的问题和要求提供适当的答复和支持。\n\n' # noqa return ('' if self.is_glm4_0414 else glm4_system) + """# 可用工具 """ + '\n'.join(tool_descs) def _format_tool_responses( self, assistant_content: str, tool_messages, ) -> Tuple[str, 'Prompt']: with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content if with_action: return super()._format_tool_responses(assistant_content, tool_messages) res = ['\n'] for i, tool_message in enumerate(tool_messages): tool_content = tool_message['content'] if i > 0: res.append('<|observation|>\n') res.append(tool_content) res.append('<|assistant|>\n') return assistant_content, res 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["name"]}\n{tool_call["arguments"]}') return '<|assistant|>'.join(tool_calls) + '<|observation|>' class GLM4AgentTemplate(ChatGLM4AgentTemplate): is_glm4_0414 = True class GLM4_5AgentTemplate(BaseAgentTemplate): model_type = 'glm4_5' @staticmethod def _find_function_call(single_content: str) -> Optional[Function]: single_content = single_content.strip() func_name_match = re.match(r'^([^\n<]+)', single_content) if not func_name_match: return None func_name = func_name_match.group(1).strip() keys = re.findall(r'(.*?)', single_content, re.DOTALL) values = re.findall(r'(.*?)', single_content, re.DOTALL) if len(keys) != len(values): return None args = {k.strip(): v.strip() for k, v in zip(keys, values)} return Function(name=func_name, arguments=json.dumps(args, ensure_ascii=False)) def get_toolcall(self, response: str) -> List[Function]: toolcall_list = re.findall(r'(.*?)', response, re.DOTALL) functions = [] for toolcall in toolcall_list: function = self._find_function_call(toolcall) if function: functions.append(function) if len(functions) == 0: # compat react_en return super().get_toolcall(response) return functions def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str: tool_descs = [ '# Tools\n\nYou may call one or more functions to assist with the user query.\n\n' 'You are provided with function signatures within XML tags:\n' ] for tool in tools: if self.model_type == 'glm5_1': tool = self.unwrap_tool(tool) tool_descs.append(f'{json.dumps(tool, ensure_ascii=False)}') if self.model_type == 'glm4_5': tool_desc = ('\n\nFor each function call, output the function name and arguments within ' 'the following XML format:\n{function-name}\n{arg-key-1}\n' '{arg-value-1}\n{arg-key-2}\n' '{arg-value-2}\n...\n') elif self.model_type in {'glm4_7', 'glm5_1'}: tool_desc = ('\n\nFor each function call, output the function name and arguments within ' 'the following XML format:\n{function-name}{arg-key-1}' '{arg-value-1}{arg-key-2}' '{arg-value-2}...') else: raise ValueError("model_type must be one of 'glm4_5', 'glm4_7', or 'glm5_1'.") tool_descs.append(tool_desc) tool_descs = '\n'.join(tool_descs) if system is not None and system.strip(): tool_descs += '<|system|>\n' + system.strip() elif self.model_type in {'glm4_7', 'glm5_1'} and not tool_descs.startswith('\n'): tool_descs = '\n' + tool_descs return tool_descs def _format_tool_responses( self, assistant_content: str, tool_messages, ) -> Tuple[str, 'Prompt']: with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content if with_action: return super()._format_tool_responses(assistant_content, tool_messages) if self.model_type == 'glm4_5': res = [] for tool_message in tool_messages: tool_content = tool_message['content'] res.append(f'\n\n{tool_content}\n') res.append('<|assistant|>\n') elif self.model_type in {'glm4_7', 'glm5_1'}: res = [] for tool_message in tool_messages: tool_content = tool_message['content'] res.append(f'{tool_content}') res.append('<|assistant|>') return assistant_content, res 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['name']}") for arg_key, arg_value in tool_call['arguments'].items(): tool_calls.append(f'{arg_key}') tool_calls.append(f'{arg_value}') tool_calls.append('') if self.model_type == 'glm4_5': sep = '\n' elif self.model_type in {'glm4_7', 'glm5_1'}: sep = '' return sep.join(tool_calls) + '<|observation|>' class GLM4_7AgentTemplate(GLM4_5AgentTemplate): model_type = 'glm4_7' class GLM5_1AgentTemplate(GLM4_5AgentTemplate): model_type = 'glm5_1'