diff --git a/docs/en/llm/api_server_tools.md b/docs/en/llm/api_server_tools.md index 56fb1b598a..39e91dbf07 100644 --- a/docs/en/llm/api_server_tools.md +++ b/docs/en/llm/api_server_tools.md @@ -1,6 +1,6 @@ # Tools Calling -LMDeploy supports tools for InternLM2, InternLM2.5 and llama3.1 models. +LMDeploy supports tools for InternLM2, InternLM2.5, llama3.1 and Qwen2.5 models. ## Single Round Invocation @@ -241,3 +241,156 @@ messages += [ assistant_response = request_llama3_1_service(messages) print(assistant_response) ``` + +### Qwen2.5 + +Qwen2.5 supports multi tool calling, which means that multiple tool requests can be initiated in one request + +```python +from openai import OpenAI +import json + +def get_current_temperature(location: str, unit: str = "celsius"): + """Get current temperature at a location. + + Args: + location: The location to get the temperature for, in the format "City, State, Country". + unit: The unit to return the temperature in. Defaults to "celsius". (choices: ["celsius", "fahrenheit"]) + + Returns: + the temperature, the location, and the unit in a dict + """ + return { + "temperature": 26.1, + "location": location, + "unit": unit, + } + + +def get_temperature_date(location: str, date: str, unit: str = "celsius"): + """Get temperature at a location and date. + + Args: + location: The location to get the temperature for, in the format "City, State, Country". + date: The date to get the temperature for, in the format "Year-Month-Day". + unit: The unit to return the temperature in. Defaults to "celsius". (choices: ["celsius", "fahrenheit"]) + + Returns: + the temperature, the location, the date and the unit in a dict + """ + return { + "temperature": 25.9, + "location": location, + "date": date, + "unit": unit, + } + +def get_function_by_name(name): + if name == "get_current_temperature": + return get_current_temperature + if name == "get_temperature_date": + return get_temperature_date + +tools = [{ + 'type': 'function', + 'function': { + 'name': 'get_current_temperature', + 'description': 'Get current temperature at a location.', + 'parameters': { + 'type': 'object', + 'properties': { + 'location': { + 'type': 'string', + 'description': 'The location to get the temperature for, in the format \'City, State, Country\'.' + }, + 'unit': { + 'type': 'string', + 'enum': [ + 'celsius', + 'fahrenheit' + ], + 'description': 'The unit to return the temperature in. Defaults to \'celsius\'.' + } + }, + 'required': [ + 'location' + ] + } + } +}, { + 'type': 'function', + 'function': { + 'name': 'get_temperature_date', + 'description': 'Get temperature at a location and date.', + 'parameters': { + 'type': 'object', + 'properties': { + 'location': { + 'type': 'string', + 'description': 'The location to get the temperature for, in the format \'City, State, Country\'.' + }, + 'date': { + 'type': 'string', + 'description': 'The date to get the temperature for, in the format \'Year-Month-Day\'.' + }, + 'unit': { + 'type': 'string', + 'enum': [ + 'celsius', + 'fahrenheit' + ], + 'description': 'The unit to return the temperature in. Defaults to \'celsius\'.' + } + }, + 'required': [ + 'location', + 'date' + ] + } + } +}] +messages = [{'role': 'user', 'content': 'Today is 2024-11-14, What\'s the temperature in San Francisco now? How about tomorrow?'}] + +client = OpenAI(api_key='YOUR_API_KEY', base_url='http://0.0.0.0:23333/v1') +model_name = client.models.list().data[0].id +response = client.chat.completions.create( + model=model_name, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=False, + tools=tools) +print(response.choices[0].message.tool_calls) +messages.append(response.choices[0].message) + +for tool_call in response.choices[0].message.tool_calls: + tool_call_args = json.loads(tool_call.function.arguments) + tool_call_result = get_function_by_name(tool_call.function.name)(**tool_call_args) + messages.append({ + 'role': 'tool', + 'name': tool_call.function.name, + 'content': tool_call_result, + 'tool_call_id': tool_call.id + }) + +response = client.chat.completions.create( + model=model_name, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=False, + tools=tools) +print(response.choices[0].message.content) + +``` + +Using the Qwen2.5-14B-Instruct, similar results can be obtained as follows + +``` +[ChatCompletionMessageToolCall(id='0', function=Function(arguments='{"location": "San Francisco, California, USA"}', name='get_current_temperature'), type='function'), + ChatCompletionMessageToolCall(id='1', function=Function(arguments='{"location": "San Francisco, California, USA", "date": "2024-11-15"}', name='get_temperature_date'), type='function')] + +The current temperature in San Francisco, California, USA is 26.1°C. For tomorrow, 2024-11-15, the temperature is expected to be 25.9°C. +``` + +It is important to note that in scenarios involving multiple tool calls, the order of the tool call results can affect the response quality. The tool_call_id has not been correctly provided to the LLM. diff --git a/docs/zh_cn/llm/api_server_tools.md b/docs/zh_cn/llm/api_server_tools.md index 643a39d5d2..8688ea35cd 100644 --- a/docs/zh_cn/llm/api_server_tools.md +++ b/docs/zh_cn/llm/api_server_tools.md @@ -1,6 +1,6 @@ # Tools -LMDeploy 支持 InternLM2, InternLM2.5 和 Llama3.1 模型的工具调用。 +LMDeploy 支持 InternLM2, InternLM2.5, Llama3.1 和 Qwen2.5模型的工具调用。 ## 单轮调用 @@ -241,3 +241,156 @@ messages += [ assistant_response = request_llama3_1_service(messages) print(assistant_response) ``` + +### Qwen2.5 + +Qwen2.5 支持了多工具调用,这意味着可以在一次请求中可能发起多个工具请求 + +```python +from openai import OpenAI +import json + +def get_current_temperature(location: str, unit: str = "celsius"): + """Get current temperature at a location. + + Args: + location: The location to get the temperature for, in the format "City, State, Country". + unit: The unit to return the temperature in. Defaults to "celsius". (choices: ["celsius", "fahrenheit"]) + + Returns: + the temperature, the location, and the unit in a dict + """ + return { + "temperature": 26.1, + "location": location, + "unit": unit, + } + + +def get_temperature_date(location: str, date: str, unit: str = "celsius"): + """Get temperature at a location and date. + + Args: + location: The location to get the temperature for, in the format "City, State, Country". + date: The date to get the temperature for, in the format "Year-Month-Day". + unit: The unit to return the temperature in. Defaults to "celsius". (choices: ["celsius", "fahrenheit"]) + + Returns: + the temperature, the location, the date and the unit in a dict + """ + return { + "temperature": 25.9, + "location": location, + "date": date, + "unit": unit, + } + +def get_function_by_name(name): + if name == "get_current_temperature": + return get_current_temperature + if name == "get_temperature_date": + return get_temperature_date + +tools = [{ + 'type': 'function', + 'function': { + 'name': 'get_current_temperature', + 'description': 'Get current temperature at a location.', + 'parameters': { + 'type': 'object', + 'properties': { + 'location': { + 'type': 'string', + 'description': 'The location to get the temperature for, in the format \'City, State, Country\'.' + }, + 'unit': { + 'type': 'string', + 'enum': [ + 'celsius', + 'fahrenheit' + ], + 'description': 'The unit to return the temperature in. Defaults to \'celsius\'.' + } + }, + 'required': [ + 'location' + ] + } + } +}, { + 'type': 'function', + 'function': { + 'name': 'get_temperature_date', + 'description': 'Get temperature at a location and date.', + 'parameters': { + 'type': 'object', + 'properties': { + 'location': { + 'type': 'string', + 'description': 'The location to get the temperature for, in the format \'City, State, Country\'.' + }, + 'date': { + 'type': 'string', + 'description': 'The date to get the temperature for, in the format \'Year-Month-Day\'.' + }, + 'unit': { + 'type': 'string', + 'enum': [ + 'celsius', + 'fahrenheit' + ], + 'description': 'The unit to return the temperature in. Defaults to \'celsius\'.' + } + }, + 'required': [ + 'location', + 'date' + ] + } + } +}] +messages = [{'role': 'user', 'content': 'Today is 2024-11-14, What\'s the temperature in San Francisco now? How about tomorrow?'}] + +client = OpenAI(api_key='YOUR_API_KEY', base_url='http://0.0.0.0:23333/v1') +model_name = client.models.list().data[0].id +response = client.chat.completions.create( + model=model_name, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=False, + tools=tools) +print(response.choices[0].message.tool_calls) +messages.append(response.choices[0].message) + +for tool_call in response.choices[0].message.tool_calls: + tool_call_args = json.loads(tool_call.function.arguments) + tool_call_result = get_function_by_name(tool_call.function.name)(**tool_call_args) + messages.append({ + 'role': 'tool', + 'name': tool_call.function.name, + 'content': tool_call_result, + 'tool_call_id': tool_call.id + }) + +response = client.chat.completions.create( + model=model_name, + messages=messages, + temperature=0.8, + top_p=0.8, + stream=False, + tools=tools) +print(response.choices[0].message.content) + +``` + +使用Qwen2.5-14B-Instruct,可以得到以下类似结果 + +``` +[ChatCompletionMessageToolCall(id='0', function=Function(arguments='{"location": "San Francisco, California, USA"}', name='get_current_temperature'), type='function'), + ChatCompletionMessageToolCall(id='1', function=Function(arguments='{"location": "San Francisco, California, USA", "date": "2024-11-15"}', name='get_temperature_date'), type='function')] + +The current temperature in San Francisco, California, USA is 26.1°C. For tomorrow, 2024-11-15, the temperature is expected to be 25.9°C. +``` + +需要注意的是,多工具调用的情况下,工具调用的结果顺序会影响回答的效果,tool_call_id并没有正确给到LLM. diff --git a/lmdeploy/model.py b/lmdeploy/model.py index 2b3a0a4e1d..b699fe4242 100644 --- a/lmdeploy/model.py +++ b/lmdeploy/model.py @@ -927,7 +927,8 @@ def match(cls, model_path: str) -> Optional[str]: Args: model_path (str): the model path used for matching. """ - if 'qwen' in model_path.lower(): + if 'qwen' in model_path.lower() and 'qwen2.5' not in model_path.lower( + ): return 'qwen' if 'minicpm-v-2_6' in model_path.lower(): return 'minicpmv-2d6' @@ -935,6 +936,113 @@ def match(cls, model_path: str) -> Optional[str]: return 'minicpm3' +@MODELS.register_module(name='qwen2d5') +class Qwen2d5Chat(Qwen7BChat): + """Chat template for Qwen2.5-Instruct series.""" + + def __init__( + self, + system='<|im_start|>system\n', + meta_instruction='You are Qwen, created by Alibaba Cloud. You are a helpful assistant.', + eosys='<|im_end|>\n', + user='<|im_start|>user\n', + eoh='<|im_end|>\n', + assistant='<|im_start|>assistant\n', + eoa='<|im_end|>', + separator='\n', + tools="""\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 XML tags:\n""", + eotools="""\n\n\nFor each function call, return a json object with function name and arguments within XML tags:\n\n{"name": , "arguments": }\n""", + stop_words=['<|im_end|>'], + **kwargs): + + self.tools = tools + self.eotools = eotools + super().__init__(system=system, + meta_instruction=meta_instruction, + eosys=eosys, + user=user, + eoh=eoh, + assistant=assistant, + eoa=eoa, + separator=separator, + stop_words=stop_words, + **kwargs) + + def messages2prompt(self, + messages, + sequence_start=True, + tools=None, + **kwargs): + """Return the prompt that is concatenated with other elements in the + chat template. + + Args: + messages (str | List): user's input prompt + Returns: + str: the concatenated prompt + """ + if isinstance(messages, str): + return self.get_prompt(messages, sequence_start) + box_map = dict(user=self.user, + assistant=self.assistant, + system=self.system) + ret = '' + tool_prompt = '' + if tools is not None and len(tools) > 0: + for tool in tools: + tool_prompt += self.separator + tool_prompt += f'{{"type": "function", "function": {json.dumps(tool, ensure_ascii=False)}}}' + if len(messages) and messages[0]['role'] == 'system': + ret += f"{self.system}{messages[0]['content']}{self.tools}{tool_prompt}{self.eotools}{self.eosys}" + else: + ret += f'{self.system}{self.meta_instruction}{self.tools}{tool_prompt}{self.eotools}{self.eosys}' + else: + if self.meta_instruction is not None and sequence_start: + if len(messages) and messages[0]['role'] == 'system': + ret += f"{self.system}{messages[0]['content']}{self.eosys}" + else: + ret += f'{self.system}{self.meta_instruction}{self.eosys}' + + for index, message in enumerate(messages): + if (message['role'] == 'user' + or (message['role'] == 'system' and index != 0) + or (message['role'] == 'assistant' + and message.get('tool_calls') is None)): + ret += f"{box_map[message['role']]}{message['content']}{self.eosys}" + elif message['role'] == 'assistant': + ret += f'<|im_start|>assistant' + if message.get('content') is not None: + ret += f"{self.separator}{message['content']}" + + if message.get('tool_calls') is not None: + tool_calls = message['tool_calls'] + for tool_call in tool_calls: + if tool_call.get('function') is not None: + tool_call = tool_call['function'] + ret += f'{self.separator}{self.separator}{{"name": "{tool_call["name"]}", "arguments": {json.dumps(tool_call["arguments"], ensure_ascii=False)}}}{self.separator}' + ret += self.eosys + if message['role'] == 'tool': + if index == 0 or messages[index - 1]['role'] != 'tool': + ret += f'<|im_start|>user' + ret += f"{self.separator}{self.separator}{message['content']}{self.separator}" + if index == len(messages) - 1 or messages[index + + 1]['role'] != 'tool': + ret += f'{self.eoh}' + ret += f'{self.assistant}' + return ret + + @classmethod + def match(cls, model_path: str) -> Optional[str]: + """Return the model_name that was registered to MODELS. + + Args: + model_path (str): the model path used for matching. + """ + lower_path = model_path.lower() + if 'qwen2.5' in lower_path or 'qwen2_5' in lower_path: + return 'qwen2d5' + + @MODELS.register_module(name='codellama') class CodeLlama(Llama2): diff --git a/lmdeploy/serve/async_engine.py b/lmdeploy/serve/async_engine.py index 3c8f193cd5..f3c3432328 100644 --- a/lmdeploy/serve/async_engine.py +++ b/lmdeploy/serve/async_engine.py @@ -4,6 +4,7 @@ import json import os import random +import re from contextlib import asynccontextmanager from copy import deepcopy from itertools import count @@ -648,14 +649,37 @@ def parse_tool_response(self, text, tools, **kwargs): name, parameters = action['name'], json.dumps(action.get( 'parameters', action.get('arguments', {})), ensure_ascii=False) + call_info_list = [(name, parameters)] elif '') parameters = action[action.find('{'):] name = action.split('{')[0] + call_info_list = [(name, parameters)] + elif '' in text and '' in text: # qwen2.5 + # get tool_call in text + pattern = r'(.*?)' + match_result_list = re.findall(pattern, text, re.DOTALL) + call_info_list = [] + for match_result in match_result_list: + action = json.loads(match_result) + call_info_list.append((action['name'], + json.dumps(action['arguments'], + ensure_ascii=False))) + # get text outside of tags + if not text.startswith(''): + text = text[:text.find('')] + elif not text.endswith(''): + text = text[text.rfind('') + len(''):] + else: + text = '' + else: raise RuntimeError(f'Unexpected model response: {text}') - action_id = [tool.function.name for tool in tools].index(name) - return text, action_id, name, parameters + + call_info_list = [([tool.function.name for tool in tools + ].index(call_info[0]), call_info[0], call_info[1]) + for call_info in call_info_list] + return text, call_info_list def chat(self, prompt: str, diff --git a/lmdeploy/serve/openai/api_server.py b/lmdeploy/serve/openai/api_server.py index a12cadaa7d..2d0560720d 100644 --- a/lmdeploy/serve/openai/api_server.py +++ b/lmdeploy/serve/openai/api_server.py @@ -495,17 +495,18 @@ async def completion_stream_generator() -> AsyncGenerator[str, None]: final_logprobs.extend(res.logprobs) tool_calls = None - if request.tool_choice != 'none' and ('<|plugin|>' in text - or '' in text or '' in text): if final_res.finish_reason == 'stop': final_res.finish_reason = 'tool_calls' try: # TODO add json_schema guidance to turbomind - text, action_id, name, parameters = VariableInterface.async_engine.parse_tool_response( # noqa + text, call_info_list = VariableInterface.async_engine.parse_tool_response( # noqa text, request.tools) tool_calls = [ - ToolCall(id=str(action_id), - function=FunctionResponse(name=name, - arguments=parameters)) + ToolCall(id=str(call_info[0]), + function=FunctionResponse(name=call_info[1], + arguments=call_info[2])) + for call_info in call_info_list ] except Exception as e: logger.error(f'Exception: {e}') diff --git a/tests/test_lmdeploy/test_model.py b/tests/test_lmdeploy/test_model.py index a38971e4d0..ae21053308 100644 --- a/tests/test_lmdeploy/test_model.py +++ b/tests/test_lmdeploy/test_model.py @@ -9,6 +9,7 @@ ('internlm/internlm2-1_8b', ['base']), ('models--internlm--internlm-chat-7b/snapshots/1234567', ['internlm']), ('Qwen/Qwen-7B-Chat', ['qwen']), + ('Qwen/Qwen2.5-7B-Instruct', ['qwen2d5']), ('codellama/CodeLlama-7b-hf', ['codellama']), ('upstage/SOLAR-0-70b', ['solar', 'solar-70b']), ('meta-llama/Llama-2-7b-chat-hf', ['llama2']), @@ -283,6 +284,291 @@ def test_qwen(): assert _prompt is None +def test_qwen2d5(): + prompt = 'hello, can u introduce yourself' + model = MODELS.get('qwen2d5')(capability='completion') + assert model.get_prompt(prompt, sequence_start=True) == prompt + assert model.get_prompt(prompt, sequence_start=False) == prompt + + model = MODELS.get('qwen2d5')(capability='chat') + + # No tool call + messages = [ + dict(role='user', + content='What\'s the temperature in San Francisco now?') + ] + no_tool_prompt = ('<|im_start|>system\nYou are Qwen, created by Alibaba ' + 'Cloud. You are a helpful ' + "assistant.<|im_end|>\n<|im_start|>user\nWhat's the " + 'temperature in San Francisco ' + 'now?<|im_end|>\n<|im_start|>assistant\n') + assert model.messages2prompt(messages) == no_tool_prompt + assert model.messages2prompt(messages, tools=[]) == no_tool_prompt + + messages.append({'role': 'assistant', 'content': 'I don\'t know.'}) + no_tool_prompt = ('<|im_start|>system\nYou are Qwen, created by Alibaba ' + 'Cloud. You are a helpful ' + "assistant.<|im_end|>\n<|im_start|>user\nWhat's the " + 'temperature in San Francisco ' + "now?<|im_end|>\n<|im_start|>assistant\nI don't " + 'know.<|im_end|>\n<|im_start|>assistant\n') + assert model.messages2prompt(messages) == no_tool_prompt + # Single tool call + tools = [{ + 'name': 'get_current_temperature', + 'description': 'Get current temperature at a location.', + 'parameters': { + 'type': 'object', + 'properties': { + 'location': { + 'type': + 'string', + 'description': + 'The location to get the temperature for,' + ' in the format \'City, State, Country\'.' + }, + 'unit': { + 'type': + 'string', + 'enum': ['celsius', 'fahrenheit'], + 'description': + 'The unit to return the temperature in. Defaults to ' + '\'celsius\'.' + } + }, + 'required': ['location'] + } + }] + + messages = [ + dict(role='user', + content='What\'s the temperature in San Francisco now?') + ] + tool_prompt = ('<|im_start|>system\nYou are Qwen, created by Alibaba ' + 'Cloud. You are a helpful assistant.\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 XML tags:\n\n{\"type\": " + "\"function\", \"function\": {\"name\": " + "\"get_current_temperature\", \"description\": \"Get " + "current temperature at a location.\", \"parameters\": {" + "\"type\": \"object\", \"properties\": {\"location\": {" + "\"type\": \"string\", \"description\": \"The location to " + "get the temperature for, in the format 'City, State, " + "Country'.\"}, \"unit\": {\"type\": \"string\", \"enum\": " + "[\"celsius\", \"fahrenheit\"], \"description\": \"The " + 'unit to return the temperature in. Defaults to ' + "'celsius'.\"}}, \"required\": [" + "\"location\"]}}}\n\n\nFor each function call, " + 'return a json object with function name and arguments ' + 'within XML tags:\n\n{' + "\"name\": , \"arguments\": " + '}\n<|im_end|>\n<|im_start' + "|>user\nWhat's the temperature in San Francisco " + 'now?<|im_end|>\n<|im_start|>assistant\n') + assert model.messages2prompt(messages, tools=tools) == tool_prompt + + messages.append( + dict(role='tool', + name='get_current_temperature', + content={ + 'temperature': 26.1, + 'location': 'San Francisco, California, USA', + 'unit': 'celsius' + }, + tool_call_id='0')) + tool_prompt = ('<|im_start|>system\nYou are Qwen, created by Alibaba ' + 'Cloud. You are a helpful assistant.\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 XML tags:\n\n{\"type\": " + "\"function\", \"function\": {\"name\": " + "\"get_current_temperature\", \"description\": \"Get " + "current temperature at a location.\", \"parameters\": {" + "\"type\": \"object\", \"properties\": {\"location\": {" + "\"type\": \"string\", \"description\": \"The location to " + "get the temperature for, in the format 'City, State, " + "Country'.\"}, \"unit\": {\"type\": \"string\", \"enum\": " + "[\"celsius\", \"fahrenheit\"], \"description\": \"The " + 'unit to return the temperature in. Defaults to ' + "'celsius'.\"}}, \"required\": [" + "\"location\"]}}}\n\n\nFor each function call, " + 'return a json object with function name and arguments ' + 'within XML tags:\n\n{' + "\"name\": , \"arguments\": " + '}\n<|im_end|>\n<|im_start' + "|>user\nWhat's the temperature in San Francisco " + 'now?<|im_end|>\n<|im_start|>user\n\n{' + "'temperature': 26.1, 'location': 'San Francisco, " + "California, USA', 'unit': " + "'celsius'}\n<|im_end|>\n<|im_start" + '|>assistant\n') + assert model.messages2prompt(messages, tools=tools) == tool_prompt + # Multi tool calling + tools = [{ + 'name': 'get_current_temperature', + 'description': 'Get current temperature at a location.', + 'parameters': { + 'type': 'object', + 'properties': { + 'location': { + 'type': + 'string', + 'description': + 'The location to get the temperature for, in the format ' + '\'City, State, Country\'.' + }, + 'unit': { + 'type': + 'string', + 'enum': ['celsius', 'fahrenheit'], + 'description': + 'The unit to return the temperature in.' + ' Defaults to \'celsius\'.' + } + }, + 'required': ['location'] + } + }, { + 'name': 'get_temperature_date', + 'description': 'Get temperature at a location and date.', + 'parameters': { + 'type': 'object', + 'properties': { + 'location': { + 'type': + 'string', + 'description': + 'The location to get the temperature for,' + ' in the format \'City, State, Country\'.' + }, + 'date': { + 'type': + 'string', + 'description': + 'The date to get the temperature for,' + ' in the format \'Year-Month-Day\'.' + }, + 'unit': { + 'type': + 'string', + 'enum': ['celsius', 'fahrenheit'], + 'description': + 'The unit to return the temperature in.' + ' Defaults to \'celsius\'.' + } + }, + 'required': ['location', 'date'] + } + }] + messages = [ + dict(role='user', + content='Today is 2024-11-14, What\'s the temperature in' + ' San Francisco now? How about tomorrow?') + ] + tool_prompt = ('<|im_start|>system\nYou are Qwen, created by Alibaba ' + 'Cloud. You are a helpful assistant.\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 XML tags:\n\n{\"type\": " + "\"function\", \"function\": {\"name\": " + "\"get_current_temperature\", \"description\": \"Get " + "current temperature at a location.\", \"parameters\": {" + "\"type\": \"object\", \"properties\": {\"location\": {" + "\"type\": \"string\", \"description\": \"The location to " + "get the temperature for, in the format 'City, State, " + "Country'.\"}, \"unit\": {\"type\": \"string\", \"enum\": " + "[\"celsius\", \"fahrenheit\"], \"description\": \"The " + 'unit to return the temperature in. Defaults to ' + "'celsius'.\"}}, \"required\": [\"location\"]}}}\n{" + "\"type\": \"function\", \"function\": {\"name\": " + "\"get_temperature_date\", \"description\": \"Get " + "temperature at a location and date.\", \"parameters\": {" + "\"type\": \"object\", \"properties\": {\"location\": {" + "\"type\": \"string\", \"description\": \"The location to " + "get the temperature for, in the format 'City, State, " + "Country'.\"}, \"date\": {\"type\": \"string\", " + "\"description\": \"The date to get the temperature for, " + "in the format 'Year-Month-Day'.\"}, \"unit\": {\"type\": " + "\"string\", \"enum\": [\"celsius\", \"fahrenheit\"], " + "\"description\": \"The unit to return the temperature " + "in. Defaults to 'celsius'.\"}}, \"required\": [" + "\"location\", \"date\"]}}}\n\n\nFor each " + 'function call, return a json object with function name ' + 'and arguments within XML ' + "tags:\n\n{\"name\": , " + "\"arguments\": " + '}\n<|im_end|>\n<|im_start' + "|>user\nToday is 2024-11-14, What's the temperature in " + 'San Francisco now? How about ' + 'tomorrow?<|im_end|>\n<|im_start|>assistant\n') + assert model.messages2prompt(messages, tools=tools) == tool_prompt + + messages.append( + dict(role='tool', + name='get_current_temperature', + content={ + 'temperature': 26.1, + 'location': 'San Francisco, California, USA', + 'unit': 'celsius' + }, + tool_call_id='0')) + messages.append( + dict(role='tool', + name='get_temperature_date', + content={ + 'temperature': 25.9, + 'location': 'San Francisco, California, USA', + 'date': '2024-11-15', + 'unit': 'celsius' + }, + tool_call_id='1')) + tool_prompt = ('<|im_start|>system\nYou are Qwen, created by Alibaba ' + 'Cloud. You are a helpful assistant.\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 XML tags:\n\n{\"type\": " + "\"function\", \"function\": {\"name\": " + "\"get_current_temperature\", \"description\": \"Get " + "current temperature at a location.\", \"parameters\": {" + "\"type\": \"object\", \"properties\": {\"location\": {" + "\"type\": \"string\", \"description\": \"The location to " + "get the temperature for, in the format 'City, State, " + "Country'.\"}, \"unit\": {\"type\": \"string\", \"enum\": " + "[\"celsius\", \"fahrenheit\"], \"description\": \"The " + 'unit to return the temperature in. Defaults to ' + "'celsius'.\"}}, \"required\": [\"location\"]}}}\n{" + "\"type\": \"function\", \"function\": {\"name\": " + "\"get_temperature_date\", \"description\": \"Get " + "temperature at a location and date.\", \"parameters\": {" + "\"type\": \"object\", \"properties\": {\"location\": {" + "\"type\": \"string\", \"description\": \"The location to " + "get the temperature for, in the format 'City, State, " + "Country'.\"}, \"date\": {\"type\": \"string\", " + "\"description\": \"The date to get the temperature for, " + "in the format 'Year-Month-Day'.\"}, \"unit\": {\"type\": " + "\"string\", \"enum\": [\"celsius\", \"fahrenheit\"], " + "\"description\": \"The unit to return the temperature " + "in. Defaults to 'celsius'.\"}}, \"required\": [" + "\"location\", \"date\"]}}}\n\n\nFor each " + 'function call, return a json object with function name ' + 'and arguments within XML ' + "tags:\n\n{\"name\": , " + "\"arguments\": " + '}\n<|im_end|>\n<|im_start' + "|>user\nToday is 2024-11-14, What's the temperature in " + 'San Francisco now? How about ' + 'tomorrow?<|im_end|>\n<|im_start|>user\n\n{'temperature': 26.1, 'location': 'San Francisco, " + "California, USA', 'unit': " + "'celsius'}\n\n\n{" + "'temperature': 25.9, 'location': 'San Francisco, " + "California, USA', 'date': '2024-11-15', 'unit': " + "'celsius'}\n<|im_end|>\n<|im_start" + '|>assistant\n') + assert model.messages2prompt(messages, tools=tools) == tool_prompt + + def test_codellama_completion(): model = MODELS.get('codellama')(capability='completion') prompt = """\