File size: 6,535 Bytes
8fcbdf9
 
 
 
aa899cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8fcbdf9
 
 
aa899cf
 
 
 
8fcbdf9
 
aa899cf
8fcbdf9
aa899cf
 
 
8fcbdf9
aa899cf
 
 
 
 
 
 
 
 
8fcbdf9
 
aa899cf
 
8fcbdf9
 
aa899cf
 
 
 
 
 
8fcbdf9
aa899cf
 
8fcbdf9
aa899cf
8fcbdf9
 
aa899cf
8fcbdf9
aa899cf
8fcbdf9
aa899cf
 
 
8fcbdf9
aa899cf
 
 
8fcbdf9
 
aa899cf
8fcbdf9
 
 
 
 
aa899cf
8fcbdf9
93974bc
 
 
aa899cf
93974bc
 
 
 
aa899cf
93974bc
 
aa899cf
8fcbdf9
 
 
aa899cf
8fcbdf9
93974bc
aa899cf
8fcbdf9
 
aa899cf
8fcbdf9
 
 
 
 
 
 
 
 
aa899cf
 
 
8fcbdf9
 
 
aa899cf
 
 
8fcbdf9
aa899cf
 
 
 
8fcbdf9
aa899cf
8fcbdf9
 
 
 
aa899cf
8fcbdf9
aa899cf
8fcbdf9
 
 
 
 
aa899cf
 
 
8fcbdf9
aa899cf
 
 
 
8fcbdf9
aa899cf
8fcbdf9
aa899cf
8fcbdf9
aa899cf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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