Files
wehub-resource-sync a203934033
Lint test / lint (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:34:58 +08:00

154 lines
6.4 KiB
Python

# 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 SeedAgentTemplate(BaseAgentTemplate):
TOOL_CALL_START = '<seed:tool_call>'
TOOL_CALL_END = '</seed:tool_call>'
FUNCTION_TAG = 'function'
PARAMETER_TAG = 'parameter'
_PY_TYPE_MAPPING = {
'string': 'str',
'number': 'int',
'integer': 'int',
'boolean': 'bool',
'array': 'list',
}
@staticmethod
def _py_type(t: str) -> str:
return SeedAgentTemplate._PY_TYPE_MAPPING.get(t, 'Any')
def get_toolcall(self, response: str) -> List[Function]:
res_list = re.findall(rf'{self.TOOL_CALL_START}(.+?){self.TOOL_CALL_END}', response, re.DOTALL)
if not res_list:
return super().get_toolcall(response)
functions = []
for res in res_list:
func_name_match = re.search(rf'<{self.FUNCTION_TAG}=([^>]+)>', res)
if not func_name_match:
continue
func_name = func_name_match.group(1)
param_matches = re.findall(rf'<{self.PARAMETER_TAG}=([^>]+)>(.*?)</{self.PARAMETER_TAG}>', res, re.DOTALL)
arguments = {name: value for name, value in param_matches}
functions.append(Function(name=func_name, arguments=arguments))
return functions
def _get_tool_responses(self, tool_messages: List[dict]) -> str:
responses = [f"<seed:bos>tool\n{tool_message['content']}<seed:eos>" for tool_message in tool_messages]
return ''.join(responses) + '<seed:bos>assistant\n'
def _format_tool_responses(
self,
assistant_content: str,
tool_messages: List[dict],
) -> 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)
formatted_tool_responses = self._get_tool_responses(tool_messages)
return assistant_content, ['<seed:eos>', formatted_tool_responses]
def _build_tool_def_string(self, tool: dict) -> str:
"""Helper to build a single tool definition string."""
func = tool.get('function', {})
func_name = func.get('name')
if not func_name:
return ''
parameters = func.get('parameters', {})
properties = parameters.get('properties', {})
params = [
f"{name}: {self._py_type(spec.get('type', 'any'))}" for name, spec in properties.items()
if isinstance(spec, dict)
]
param_str = ','.join(params)
docstring_parts = [' """', f' {func.get("description", "").strip()}']
if properties:
docstring_parts.append('\n Args:')
required_params = parameters.get('required', [])
for name, spec in properties.items():
if isinstance(spec, dict):
req_tag = '[必填]' if name in required_params else '[选填]'
desc = spec.get('description', '')
type_str = self._py_type(spec.get('type', 'any'))
docstring_parts.append(f' - {name} ({type_str}) {req_tag}: {desc}')
returns_props = func.get('returns', {}).get('properties', {})
if returns_props:
docstring_parts.append('\n Returns:')
for name, spec in returns_props.items():
desc = spec.get('description', '')
type_str = self._py_type(spec.get('type', 'any'))
docstring_parts.append(f' - {name} ({type_str}): {desc}')
docstring_parts.append('\n """')
docstring = '\n'.join(docstring_parts)
return f'Function:\ndef {func_name}({param_str}):\n{docstring}'
def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
if not tools:
return system or ''
tool_defs = [
tool_def for tool in tools if (wrapped_tool := self.wrap_tool(tool)).get('type') == 'function' and (
tool_def := self._build_tool_def_string(wrapped_tool)) != ''
]
tool_defs_joined = '\n\n'.join(tool_defs)
tool_call_format_instruction = (
'工具调用请遵循如下格式:\n'
f'{self.TOOL_CALL_START}\n'
f'<{self.FUNCTION_TAG}=example_function_name>\n'
f'<{self.PARAMETER_TAG}=example_parameter_1>value_1</{self.PARAMETER_TAG}>\n'
f'<{self.PARAMETER_TAG}=example_parameter_2>This is the value for the second parameter\n'
'that can span\n'
f'multiple lines</{self.PARAMETER_TAG}>\n'
f'</{self.FUNCTION_TAG}>\n'
f'{self.TOOL_CALL_END}')
split_token = '<seed:eos><seed:bos>system'
if system and split_token in system:
parts = system.split(split_token, 1)
return f'{parts[0]}\n\n{tool_defs_joined}\n{tool_call_format_instruction}\n{split_token}{parts[1]}'
else:
doubao_prompt = ('You are Doubao, a helpful AI assistant. '
'You may call one or more functions to assist with the user query.')
return (f'{doubao_prompt}\n\n{tool_defs_joined}\n{tool_call_format_instruction}\n'
f'{split_token}\n{system or ""}')
def _format_tool_calls(self, tool_call_messages: List[dict]) -> str:
formatted_calls = []
for message in tool_call_messages:
tool_call = self._parse_tool_call(message['content'])
func_name = tool_call['name']
arguments = tool_call.get('arguments', {})
call_parts = [f'<{self.FUNCTION_TAG}={func_name}>']
for arg_name, arg_value in arguments.items():
arg_value_str = arg_value if isinstance(arg_value, str) else json.dumps(arg_value, ensure_ascii=False)
call_parts.append(f'<{self.PARAMETER_TAG}={arg_name}>{arg_value_str}</{self.PARAMETER_TAG}>')
call_parts.append(f'</{self.FUNCTION_TAG}>')
call_parts_joined = '\n'.join(call_parts)
full_call = f'{self.TOOL_CALL_START}\n{call_parts_joined}\n{self.TOOL_CALL_END}'
formatted_calls.append(full_call)
return '\n'.join(formatted_calls)