import json import os import time from typing import Any from bfcl_eval.constants.enums import ModelStyle from bfcl_eval.constants.type_mappings import GORILLA_TO_OPENAPI from bfcl_eval.model_handler.base_handler import BaseHandler from bfcl_eval.model_handler.utils import ( convert_to_function_call, convert_to_tool, default_decode_ast_prompting, default_decode_execute_prompting, format_execution_results_prompting, retry_with_backoff, system_prompt_pre_processing_chat_model, ) from mistralai import Mistral class MistralHandler(BaseHandler): def __init__( self, model_name, temperature, registry_name, is_fc_model, **kwargs, ) -> None: super().__init__(model_name, temperature, registry_name, is_fc_model, **kwargs) self.model_style = ModelStyle.MISTRAL self.client = Mistral(api_key=os.getenv("MISTRAL_API_KEY")) def decode_ast(self, result, language, has_tool_call_tag): if self.is_fc_model: decoded_output = [] for invoked_function in result: name = list(invoked_function.keys())[0] params = json.loads(invoked_function[name]) decoded_output.append({name: params}) return decoded_output else: return default_decode_ast_prompting(result, language, has_tool_call_tag) def decode_execute(self, result, has_tool_call_tag): if self.is_fc_model: function_call = convert_to_function_call(result) return function_call else: return default_decode_execute_prompting(result, has_tool_call_tag) @retry_with_backoff(error_message_pattern=r".*Status 429.*") def generate_with_backoff(self, **kwargs): start_time = time.time() api_response = self.client.chat.complete(**kwargs) end_time = time.time() return api_response, end_time - start_time #### FC methods #### def _query_FC(self, inference_data: dict): message = inference_data["message"] tool = inference_data["tools"] inference_data["inference_input_log"] = { "message": message, "tools": tool, } return self.generate_with_backoff( model=self.model_name, messages=message, tools=tool, temperature=self.temperature, ) def _pre_query_processing_FC(self, inference_data: dict, test_entry: dict) -> dict: inference_data["message"] = [] return inference_data def _compile_tools(self, inference_data: dict, test_entry: dict) -> dict: functions: list = test_entry["function"] tools = convert_to_tool(functions, GORILLA_TO_OPENAPI, self.model_style) inference_data["tools"] = tools return inference_data def _parse_query_response_FC(self, api_response: Any) -> dict: try: model_responses = [ {func_call.function.name: func_call.function.arguments} for func_call in api_response.choices[0].message.tool_calls ] tool_call_func_names = [ func_call.function.name for func_call in api_response.choices[0].message.tool_calls ] tool_call_ids = [ func_call.id for func_call in api_response.choices[0].message.tool_calls ] except: model_responses = api_response.choices[0].message.content tool_call_func_names = [] tool_call_ids = [] return { "model_responses": model_responses, "model_responses_message_for_chat_history": api_response.choices[0].message, "tool_call_func_names": tool_call_func_names, "tool_call_ids": tool_call_ids, "input_token": api_response.usage.prompt_tokens, "output_token": api_response.usage.completion_tokens, } def add_first_turn_message_FC( self, inference_data: dict, first_turn_message: list[dict] ) -> dict: inference_data["message"].extend(first_turn_message) return inference_data def _add_next_turn_user_message_FC( self, inference_data: dict, user_message: list[dict] ) -> dict: inference_data["message"].extend(user_message) return inference_data def _add_assistant_message_FC( self, inference_data: dict, model_response_data: dict ) -> dict: inference_data["message"].append( model_response_data["model_responses_message_for_chat_history"] ) return inference_data def _add_execution_results_FC( self, inference_data: dict, execution_results: list[str], model_response_data: dict ) -> dict: for execution_result, func_name, tool_call_id in zip( execution_results, model_response_data["tool_call_func_names"], model_response_data["tool_call_ids"], ): tool_message = { "role": "tool", "name": func_name, "content": execution_result, "tool_call_id": tool_call_id, } inference_data["message"].append(tool_message) return inference_data #### Prompting methods #### def _query_prompting(self, inference_data: dict): message = inference_data["message"] inference_data["inference_input_log"] = {"message": message} return self.generate_with_backoff( model=self.model_name, messages=message, temperature=self.temperature, ) def _pre_query_processing_prompting(self, test_entry: dict) -> dict: functions: list = test_entry["function"] test_entry_id: str = test_entry["id"] test_entry["question"][0] = system_prompt_pre_processing_chat_model( test_entry["question"][0], functions, test_entry_id ) return {"message": []} def _parse_query_response_prompting(self, api_response: Any) -> dict: return { "model_responses": api_response.choices[0].message.content, "model_responses_message_for_chat_history": api_response.choices[0].message, "input_token": api_response.usage.prompt_tokens, "output_token": api_response.usage.completion_tokens, } def add_first_turn_message_prompting( self, inference_data: dict, first_turn_message: list[dict] ) -> dict: inference_data["message"].extend(first_turn_message) return inference_data def _add_next_turn_user_message_prompting( self, inference_data: dict, user_message: list[dict] ) -> dict: inference_data["message"].extend(user_message) return inference_data def _add_assistant_message_prompting( self, inference_data: dict, model_response_data: dict ) -> dict: inference_data["message"].append( model_response_data["model_responses_message_for_chat_history"] ) return inference_data def _add_execution_results_prompting( self, inference_data: dict, execution_results: list[str], model_response_data: dict ) -> dict: formatted_results_message = format_execution_results_prompting( inference_data, execution_results, model_response_data ) inference_data["message"].append( {"role": "user", "content": formatted_results_message} ) return inference_data