Spaces:
Paused
Paused
| import re | |
| import json | |
| import time | |
| from src.config import ( | |
| TEMPERATURE, | |
| LONG_ANSWER_USER_TEMPLATE, | |
| SENTENCE_SPLIT_SYSTEM, | |
| SENTENCE_SPLIT_USER_TEMPLATE, | |
| FACTOID_SYSTEM, | |
| FACTOID_USER_TEMPLATE, | |
| Q_QUESTIONS_SYSTEM, | |
| Q_QUESTIONS_USER_TEMPLATE, | |
| M_ANSWERS_SYSTEM, | |
| M_ANSWERS_USER_TEMPLATE, | |
| ) | |
| def _chat(client, model_name, system_prompt, user_prompt, max_tokens, logprobs=False): | |
| messages = [] | |
| if system_prompt: | |
| messages.append({"role": "system", "content": system_prompt}) | |
| messages.append({"role": "user", "content": user_prompt}) | |
| return client.chat.completions.create( | |
| model=model_name, | |
| messages=messages, | |
| max_tokens=int(max_tokens), | |
| temperature=TEMPERATURE, | |
| logprobs=logprobs, | |
| top_logprobs=1 if logprobs else None, | |
| ) | |
| def query_model(system_prompt, user_prompt, model_name, max_tokens, client): | |
| """Returns {"answer": str}.""" | |
| resp = _chat(client, model_name, system_prompt, user_prompt, max_tokens, logprobs=False) | |
| return {"answer": resp.choices[0].message.content or ""} | |
| def query_model_with_logprobs(question, model_name, max_tokens, client): | |
| """ | |
| Generates a 3-sentence answer with logprobs captured. | |
| Returns (answer_str, token_logprobs) where token_logprobs is | |
| a list of {"token": str, "logprob": float}. | |
| """ | |
| user_prompt = LONG_ANSWER_USER_TEMPLATE.format(question=question) | |
| resp = _chat(client, model_name, "", user_prompt, max_tokens, logprobs=True) | |
| answer = (resp.choices[0].message.content or "").strip() | |
| token_logprobs = [] | |
| lp = resp.choices[0].logprobs | |
| if lp and lp.content: | |
| for t in lp.content: | |
| token_logprobs.append({"token": t.token, "logprob": t.logprob}) | |
| return answer, token_logprobs | |
| def _clean_json(raw): | |
| return re.sub(r"```json|```", "", raw).strip() | |
| def split_answers_into_json_strings(answer, model_name, max_tokens, client): | |
| """ | |
| Splits answer into sentences, then decomposes each into sentence_parts + factoids. | |
| Returns (master_json_output, sentences). | |
| """ | |
| user_prompt = SENTENCE_SPLIT_USER_TEMPLATE.format(answer=answer) | |
| try: | |
| res = query_model(SENTENCE_SPLIT_SYSTEM, user_prompt, model_name, max_tokens, client) | |
| sentences = json.loads(_clean_json(res["answer"])) | |
| except Exception as e: | |
| print(f"Sentence split error: {e}") | |
| return [], [] | |
| master_json_output = [] | |
| for i, sentence in enumerate(sentences, 1): | |
| user_prompt2 = FACTOID_USER_TEMPLATE.format(sentence=sentence) | |
| try: | |
| res2 = query_model(FACTOID_SYSTEM, user_prompt2, model_name, max_tokens, client) | |
| parsed = json.loads(_clean_json(res2["answer"])) | |
| master_json_output.append(parsed) | |
| except Exception as e: | |
| print(f"Factoid extraction error for sentence {i}: {e}") | |
| master_json_output.append([]) | |
| return master_json_output, sentences | |
| def parse_json_to_dictionaries(master_json_output, original_sentences): | |
| sentence_part_to_factoids = {} | |
| factoid_to_sentence_part = {} | |
| sentence_parts = [] | |
| factoids_list = [] | |
| for sentence_data, original_sentence in zip(master_json_output, original_sentences): | |
| if not isinstance(sentence_data, list): | |
| print(f"WARNING: skipping non-list factoid data: {type(sentence_data).__name__}") | |
| continue | |
| for item in sentence_data[:2]: | |
| if not isinstance(item, dict): | |
| print(f"WARNING: skipping non-dict sentence part: {item!r}") | |
| continue | |
| s_part = str(item.get("sentence_part", "") or "").strip() | |
| extracted_factoids = item.get("factoids", []) | |
| if not isinstance(extracted_factoids, list): | |
| extracted_factoids = [] | |
| if not s_part: | |
| continue | |
| if s_part not in original_sentence: | |
| print(f"WARNING: sentence_part not substring of original: '{s_part}'") | |
| constrained_factoids = [str(f).strip() for f in extracted_factoids if str(f).strip()][:2] | |
| if not constrained_factoids: | |
| continue | |
| sentence_parts.append(s_part) | |
| sentence_part_to_factoids[s_part] = constrained_factoids | |
| for factoid in constrained_factoids: | |
| factoids_list.append(factoid) | |
| factoid_to_sentence_part[factoid] = s_part | |
| return sentence_part_to_factoids, factoid_to_sentence_part, sentence_parts, factoids_list | |
| def generate_Q_questions(factoid, original_answer, Q, model_name, max_tokens, client, enable_sleep=True): | |
| user_prompt = Q_QUESTIONS_USER_TEMPLATE.format( | |
| Q=Q, original_answer=original_answer, factoid=factoid | |
| ) | |
| max_retries = 3 | |
| for attempt in range(max_retries): | |
| try: | |
| res = query_model(Q_QUESTIONS_SYSTEM, user_prompt, model_name, max_tokens, client) | |
| questions = json.loads(_clean_json(res["answer"])) | |
| return [str(q).strip() for q in questions][:Q] | |
| except Exception as e: | |
| err = str(e).lower() | |
| if enable_sleep and any(x in err for x in ("429", "rate limit", "too many requests")): | |
| print(f"Rate limit, sleeping 20s (attempt {attempt+1}/{max_retries})") | |
| time.sleep(20) | |
| else: | |
| print(f"Q-question error: {e}") | |
| if attempt == max_retries - 1 or not enable_sleep: | |
| return [] | |
| return [] | |
| def generate_M_answers(question, M, model_name, max_tokens, client, enable_sleep=True): | |
| user_prompt = M_ANSWERS_USER_TEMPLATE.format(question=question) | |
| sub_answers = [] | |
| for i in range(M): | |
| max_retries = 3 | |
| for attempt in range(max_retries): | |
| try: | |
| res = query_model(M_ANSWERS_SYSTEM, user_prompt, model_name, max_tokens, client) | |
| sub_answers.append(res["answer"].strip()) | |
| break | |
| except Exception as e: | |
| err = str(e).lower() | |
| if enable_sleep and any(x in err for x in ("429", "rate limit", "too many requests")): | |
| print(f"Rate limit, sleeping 20s (attempt {attempt+1}/{max_retries})") | |
| time.sleep(20) | |
| else: | |
| print(f"M-answer error (call {i+1}): {e}") | |
| if attempt == max_retries - 1 or not enable_sleep: | |
| sub_answers.append("ERROR") | |
| break | |
| return sub_answers | |