from copy import deepcopy
import json
import re
import random
import math
from .base import ToolEvalEvaluator
from typing import List, Union, Dict, Any, Callable
from .utils import register_evaluator,OpenaiPoolRequest
from tenacity import retry, stop_after_attempt
@register_evaluator
class OpenAIEvaluator(ToolEvalEvaluator):
def __init__(self,
cfg_path: str = None,
):
super().__init__(cfg_path)
self.opr = OpenaiPoolRequest(self.eval_config['apis_json'])
self.conversation_template = []
for message in re.findall(r"(.*?)", self.template,re.DOTALL):
message = {
'role':re.findall(r"(.*?)",message,re.DOTALL)[0],
'content':re.findall(r"(.*?)",message,re.DOTALL)[0]
}
self.conversation_template.append(message)
def openai_completions(self,task_description:Dict,answers:Dict)->int:
conversation = deepcopy(self.conversation_template)
for msg in conversation:
if msg['role'] == 'user':
msg['content'] = msg['content'].format(
task_description=json.dumps(task_description),
answers=json.dumps(answers)
)
res = self.opr(messages=conversation,**self.eval_config['completions_kwargs'])
prefers = []
for choice in res.choices:
prefers.append(int(json.loads(choice.message.function_call.arguments)['preference']))
return random.choice(prefers)
@register_evaluator
class OpenAINormalizedEvaluator(ToolEvalEvaluator):
def __init__(self,
cfg_path: str = None,
):
super().__init__(cfg_path)
self.opr = OpenaiPoolRequest(self.eval_config['apis_json'])
# setting up the function templates
self.parsed_function_templates = {}
for function in re.findall(r"(.*?)", self.template,re.DOTALL):
name = re.findall(r"(.*?)",function,re.DOTALL)[0]
description = re.findall(r"(.*?)",function,re.DOTALL)[0]
self.parsed_function_templates[name] = description
self.functions = {}
for function in self.eval_config['completions_kwargs']['functions']:
self.functions[function['name']] = function
# @retry(stop=stop_after_attempt(3),reraise=True)
def function_call(self,
func_name,
func_args:Dict,
*,
return_reason=False,
return_content=False):
completion_kwargs = deepcopy(self.eval_config['completions_kwargs'])
func_description = deepcopy(self.functions[func_name])
if return_reason:
func_description['parameters']['required'].append('reason')
func_description['parameters']['properties']['reason'] = {
'type':'string',
'description':'explain your answer.'
}
completion_kwargs['function_call'] = {'name':func_name}
completion_kwargs['functions'] = [func_description]
completion_kwargs['messages'] = [{
'role':'user',
'content':str(self.parsed_function_templates[func_name]).format(**func_args)
}]
res = self.opr.request(**completion_kwargs)
# print(res)
ret = json.loads(res.choices[0].message.function_call.arguments)
# check required items
required_args = getattr(func_description['parameters'],'required',None)
if required_args is not None:
ret_args = set(ret.keys())
for arg in required_args:
if arg not in ret_args:
raise KeyError(f"Arg {arg} not found in reply!")
if return_content:
ret['content'] = dict(res.choices[0].message).get('content','')
ret['tokens'] = res.usage.total_tokens
return ret
def select_best_final_answer(self,query,final_answers:List[str])->int:
hashed_ans = list(map(hash,final_answers))
all_same = True
for item in hashed_ans[1:]:
if item != hashed_ans[0]:
all_same = False
if all_same:
return random.choice(range(len(final_answers)))
while True:
selected = int(self.function_call('select_best_final_answer',{'query':query,'final_answers':final_answers})['best_answer_index'])
if selected=0:
break
return selected
def check_solve_query(self,query,final_answer:str)->bool:
return bool(self.function_call('check_solve_query',{'query':query,'final_answer':final_answer})['is_solved'])
def compare_answer_details(self,answer:List)->List[int]:
parsed_answers = []
for ans in answer:
parsed_ans = self.function_call('parse_answer_details',{'answer_details':ans['answer_details']})
parsed_ans['total_steps'] = ans['total_steps']
parsed_answers.append(parsed_ans)
# calculate socre and return one with highest score
scores = []
for ans in parsed_answers:
score = 0
score += int(ans['succeed_tool_calling'])*10
score += int(ans['used_tool_types'])*5
if int(ans['total_steps'])<=0:
score -= int(1e5)
else:
score += -5*math.log(ans['total_steps'])
scores.append(score)
# return index of highest score
highest_score = max(scores)
highest_idx = [idx for idx,score in enumerate(scores) if score==highest_score]
return random.choice(highest_idx)
def normalized_openai_completions(self,task_description:Dict,answers:List[Dict[Any,Any]])->int:
all_empty = True
all_nonempty = True
is_nonempty = []
for ans in answers:
status = ans['final_answer']!=''
if status:
all_empty = False
else:
all_nonempty = False
is_nonempty.append(status)
# print(is_nonempty)
if all_nonempty:
all_solved = True
all_failed = True
is_solved = []
for ans in answers:
status = self.check_solve_query(task_description['query'],ans['final_answer'])
# print(ans['final_answer'])
if status:
all_failed = False
else:
all_solved = False
is_solved.append(status)
# print(is_solved)
if all_solved:
steps = [int(ans['total_steps']) for ans in answers]
shortest_steps = min(steps)
ans_idxs = [idx for idx,step in enumerate(steps) if step==shortest_steps]
# return only one idx
if len(ans_idxs)>1:
return ans_idxs[self.select_best_final_answer(
task_description['query'],
[answers[idx]['final_answer'] for idx in ans_idxs]
)]
else:
return ans_idxs[0]
elif all_failed:
return self.compare_answer_details(answers)
else:
return random.choice([index for index,solve in enumerate(is_solved) if solve])
elif all_empty:
return self.compare_answer_details(answers)
else:
return random.choice([index for index,nonempty in enumerate(is_nonempty) if nonempty])