Veblen34's picture
Upload folder using huggingface_hub
cd6775d verified
Raw
History Blame Contribute Delete
12.2 kB
from socratic_node import SocraticNode
import os
import json
import re
import pdb
import torch
import time
from openai import OpenAI
import requests
client = OpenAI(
api_key=os.environ.get("CHATANYWHERE_API_KEY", ""),
base_url='https://api.chatanywhere.tech/v1',
)
# client = OpenAI(api_key=os.environ.get("DEEPSEEK_API_KEY", ""), base_url="https://api.deepseek.com")
# [ragqa] 可插拔 LLM:设为 callable(system_define:str, prompt:str)->str 即用它替代默认的 gpt-4o-mini。
_LLM_OVERRIDE = None
def set_llm(fn):
"""注入自定义 LLM(如本地模型)。传 None 恢复默认 gpt-4o-mini。"""
global _LLM_OVERRIDE
_LLM_OVERRIDE = fn
# [ragqa] 可插拔检索器:设为 callable(query:str)->str(top-1 passage 文本)即用它替代默认 HTTP(8895)。
# 进程内函数版默认注入空检索桩,从而完全不发 HTTP;纯查询分解不需要真实检索。
_RETRIEVER_OVERRIDE = None
def set_retriever(fn):
"""注入自定义检索器。传 None 恢复默认 HTTP ColBERT。"""
global _RETRIEVER_OVERRIDE
_RETRIEVER_OVERRIDE = fn
def get_retrieval(query_item):
if _RETRIEVER_OVERRIDE is not None:
return _RETRIEVER_OVERRIDE(query_item)
url = 'http://localhost:8895/api/search?query='+query_item+'&k=1'
response = requests.get(url=url)
res_dic = response.json()
corpus_list_topk = res_dic['topk']
top1_passage = corpus_list_topk[0]['text']
return top1_passage
class SocraticTree:
def __init__(self, backbone, api, prompt_map, dataset, num_question, max_turn, max_depth, save_dir):
self.backbone = backbone
if backbone == 'gpt':
pass
elif backbone == 'falcon':
# host model by text-generation:
from text_generation import Client
self.pipeline = Client("http://127.0.0.1:8080")
self.prompt_map = prompt_map
self.dataset = dataset
self.num_question = num_question
self.max_turn = max_turn
self.max_depth = max_depth
self.root_node = None
self.save_dir = save_dir
self.log_path = None
def select_prompt(self, isMultipleChoice, type):
try:
# question_type = 'multipleChoice' if isMultipleChoice else 'normalQA'
question_type = 'multipleChoice_cot' if isMultipleChoice else 'normalQA'
prompt = self.prompt_map[type][question_type][self.dataset]['prompt']
tip = self.prompt_map[type][question_type][self.dataset]['tip']
system_define = self.prompt_map[type][question_type][self.dataset]['system_define']
except Exception as e:
print(e)
pdb.set_trace()
return prompt, tip, system_define
# while True:
# try:
# global client
# response = client.chat.completions.create(
# model='deepseek-chat',
# messages=[
# {"role": "system", "content": system_define},
# {"role": "user", "content": prompt},
# ]
# )
# break
# except Exception as e:
# # current time in human-readable format
# obj = time.localtime()
# t = time.asctime(obj)
# print(t, 'Request failed. Retrying...')
# pdb.set_trace()
# # time.sleep(10)
def request_gpt(self, system_define, prompt):
# [ragqa] 若注入了自定义 LLM,则用它(进程内本地模型等),否则走默认 gpt-4o-mini。
if _LLM_OVERRIDE is not None:
return _LLM_OVERRIDE(system_define, prompt).strip()
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[
{"role": "system", "content": system_define},
{"role": "user", "content": prompt},
]
)
return response.choices[0].message.content.strip()
def request_falcon(self, system_define, prompt):
input_str = system_define + '\n\n\n' + prompt
answer = self.pipeline.generate(input_str, max_new_tokens=300).generated_text
return answer.strip()
def request(self, system_define, prompt):
if self.backbone == 'gpt':
return self.request_gpt(system_define, prompt)
elif self.backbone == 'falcon':
return self.request_falcon(system_define, prompt)
def qa2hint(self, question, answer):
# request chatGPT
system_define = """Imagine you are an editor. You are given a question-and-answer pair. You need to merge the question and answer into a statement sentence."""
prompt = "Question: " + question + "\nAnswer: " + answer
return self.request(system_define, prompt)
def get_clean_answer(self, raw_answer):
answer = raw_answer
# remove " in the raw_answer string
while '"' in answer:
answer = answer.replace('"', '')
if ': ' in answer:
answer = answer.split(': ')[1]
if 'the answer is ' in answer:
idx = answer.index('the answer is ')
answer = answer[idx+14]
if 'Option ' in answer:
answer = answer.split('Option ')[1][0]
if len(answer) > 0 and answer[-1] == '.':
answer = answer[:-1]
if 'A. ' in answer:
answer = 'A'
elif 'B. ' in answer:
answer = 'B'
elif 'C. ' in answer:
answer = 'C'
elif 'D. ' in answer:
answer = 'D'
elif '. ' in raw_answer:
answer = raw_answer.split('. ')[1]
if len(answer) == 2 and answer[1] == '.':
answer = answer[0]
return answer
def answer_question(self, node):
passage = get_retrieval(node.question)
system_define = "You are an expert in open domain Q&A and your task is to assess how relevant the retrieved passages are to the question. According to the degree of help of the retrieved corpus to answer the question, it can be divided into three correlation degrees: high, middle and low."
tip = "Only allowed to answer high or middle or low. If you are not sure, choose the one you think is more likely."
prompt = ''
prompt += 'Question: '
prompt += node.question + '\n'
prompt += 'Passage: '
prompt += passage + '\n'
prompt += tip + '\n'
prompt += 'Answer: '
relevant = self.request(system_define, prompt)
if 'high' in relevant or 'High' in relevant:
confidence = 'high'
elif 'middle' in relevant or 'Middle' in relevant:
confidence = 'middle'
elif 'low' in relevant or 'Low' in relevant:
confidence = 'low'
else:
confidence = 'middle'
return confidence
def raise_question(self, node):
system_define = "Imagine that you are a thoughtful and logical problem solver. You will get a question. However, this question is too complex or lacking information to answer. You need to break down the original question into several simpler subquestions to help the information retrieval system retrieve the relevant information. Important: Do not use pronouns or indefinite pronoun phrases in generated questions. The questions asked must be self-contained questions. Each question can contain only one parameter. Don't just ask yes/no questions."
prompt = ''
prompt += "For example, Question: Could the Great Wall of China connect the Dodgers to the White Sox? Note: The raised question has to be a self-contained question. Do not use pronouns or indefinite pronoun phrases in the generated questions. Copy context from the original question if needed. Deep Questions: 1. What is the most commonly cited figure for the total length of the Great Wall? 2. What is the straight-line distance between Chicago, Illinois, and Los Angeles, California?" + '\n'
prompt += 'Question: '
prompt += node.question + '\n'
prompt += 'Deep Questions: '
raw_answer = self.request(system_define, prompt)
# post-process answer
ls_questions = raw_answer.split('\n')
ls_questions = [re.sub(r'^\d+\.\s+', '', q) for q in ls_questions]
ls_questions = ls_questions[:self.num_question] if len(ls_questions) > self.num_question else ls_questions
return ls_questions
def self_questioning(self, node):
confidence = self.answer_question(node)
if (confidence == 'high' or node.isLeaf) and (node.hasHint() or node.depth != 1):
# self.log(node, confidence)
return False, confidence
else:
ls_deepQuestion = self.raise_question(node)
# self.log(node, confidence, ls_deepQuestion=ls_deepQuestion)
return True, ls_deepQuestion
def run(self, node, collected_questions=None):
if collected_questions is None:
collected_questions = []
# self questioning
continue_deeper, output = self.self_questioning(node)
if continue_deeper: # output is subquestion list
for deep_question in output:
# Collect the deep question
collected_questions.append(deep_question)
deep_node = SocraticNode(
deep_question,
node.turn,
node.depth + 1,
self.max_turn,
self.max_depth,
context=node.context
)
deep_answer = self.run(deep_node, collected_questions)
node.add_hint(1)
node.update_turn_num()
return self.run(node, collected_questions)
else: # output is answer
return collected_questions
def start(self, id, question, options, context=None):
self.log_path = self.save_dir + '/log/' + str(id) + '.txt'
if os.path.exists(self.log_path):
os.remove(self.log_path)
self.root_node = SocraticNode(question, 1, 1, self.max_turn, self.max_depth, context=context, options=options)
collected_questions = self.run(self.root_node)
return collected_questions
def log(self, node, raw_answer, ls_deepQuestion=None):
turn = node.turn
depth = node.depth
isLeaf = node.isLeaf
context = node.context
question = node.question
options = None
if node.isMultipleChoice():
options = node.add_optionID_toText()
hints = node.get_textHints()
raw_answer = raw_answer
answer = str(node.answer)
ls_deepQuestion = ls_deepQuestion
with open(self.log_path, 'a') as f:
f.write('=========================================================================================================\n')
f.write('Turn: ' + str(turn) + ', Depth: ' + str(depth) + ', isLeaf: ' + str(isLeaf) + '\n')
if context is not None:
f.write('Context:\t' + context + '\n')
f.write('Question:\t' + question + '\n')
if options is not None:
f.write('Options:\t' + options + '\n')
if len(hints) > 0:
f.write('Hints:\t' + hints + '\n')
f.write('Raw Answer:\t' + raw_answer + '\n')
f.write('Answer:\t' + answer + '\n')
if ls_deepQuestion is not None:
f.write('\nDeep Questions:\n')
for i, deepQuestion in enumerate(ls_deepQuestion):
f.write(str(i+1) + '. ' + deepQuestion + '\n')
f.write('\n')