-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcode.py
More file actions
132 lines (103 loc) · 4.65 KB
/
Copy pathcode.py
File metadata and controls
132 lines (103 loc) · 4.65 KB
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
"""Reasoning Techniques — different ways of spending thinking tokens.
Four approaches to the same problem, so the difference is visible rather than
asserted:
direct - just answer. Fast, cheap, wrong on anything multi-step.
chain-of-thought - think step by step in one pass.
self-consistency - sample several chains, take the majority answer.
decomposition - split into sub-questions, solve each, combine.
They are not ranked. Self-consistency costs N times as much as CoT for a
single answer, so it earns its keep only where being wrong is expensive.
The problem below has a deliberate trap: a rate that changes partway through,
which direct answers tend to miss.
"""
import re
from collections import Counter
from resources.agent import llm
from resources.helper import show_response
PROBLEM = (
"A courier charges $4 per package for the first 20 packages of the day, "
"then $3 per package after that. On Monday she delivered 34 packages. "
"On Tuesday she delivered 12. She pays $25 per day in fuel. "
"What was her total profit across both days?"
)
# Worked by hand: Monday 20*4 + 14*3 = 80 + 42 = 122. Tuesday 12*4 = 48.
# Revenue 170, fuel 50, profit 120.
CORRECT = 120
def final_number(text):
"""Pull the last dollar figure out of a reply - that's the answer."""
matches = re.findall(r"\$?\s*(\d[\d,]*(?:\.\d+)?)", text.replace(",", ""))
return float(matches[-1]) if matches else None
def direct():
"""No reasoning budget at all. The baseline."""
return llm.invoke(f"{PROBLEM}\n\nReply with the number only, no working.")
def chain_of_thought():
"""One pass, thinking allowed. The cheapest real improvement."""
return llm.invoke(
f"{PROBLEM}\n\nWork through this step by step, then state the final "
f"answer on its own line as 'ANSWER: $X'."
)
def self_consistency(samples=3):
"""Sample several independent chains and take the majority answer.
Independent runs make different arithmetic slips, but they tend to make
*different* ones, so the correct answer is usually the modal answer. This
trades money for reliability and nothing else.
"""
answers = []
for i in range(samples):
reply = chain_of_thought()
value = final_number(reply.content)
answers.append(value)
print(f" sample {i + 1}: {value}")
winner, count = Counter(answers).most_common(1)[0]
print(f" majority: {winner} ({count}/{samples} agreed)")
return winner
def decomposition():
"""Split into sub-questions, solve each, then combine the answers.
Differs from chain-of-thought in that each sub-answer is a separate call
with a narrow question, so a mistake in one doesn't contaminate the
reasoning of the next.
"""
subs = [
"How much did the courier earn on Monday for 34 packages, at $4 each "
"for the first 20 and $3 each thereafter? Reply with the number only.",
"How much did she earn on Tuesday for 12 packages, at $4 each for the "
"first 20 and $3 each thereafter? Reply with the number only.",
"What is her total fuel cost for 2 days at $25 per day? "
"Reply with the number only.",
]
parts = []
for sub in subs:
answer = llm.invoke(sub).content.strip()
print(f" {sub[:50]}... -> {answer}")
parts.append(f"{sub}\n-> {answer}")
joined = "\n\n".join(parts)
return llm.invoke(
f"Given these sub-answers, what is total profit (earnings minus "
f"fuel)?\n\n{joined}\n\nState it as 'ANSWER: $X'."
)
if __name__ == "__main__":
print(f"PROBLEM: {PROBLEM}")
print(f"(worked by hand, the answer is ${CORRECT})\n")
print(f"{'=' * 60}\n1. DIRECT\n{'=' * 60}")
reply = direct()
show_response(reply, show_usage=False)
direct_answer = final_number(reply.content)
print(f"\n{'=' * 60}\n2. CHAIN OF THOUGHT\n{'=' * 60}")
reply = chain_of_thought()
show_response(reply, show_usage=False)
cot_answer = final_number(reply.content)
print(f"\n{'=' * 60}\n3. SELF-CONSISTENCY (3 samples)\n{'=' * 60}")
sc_answer = self_consistency()
print(f"\n{'=' * 60}\n4. DECOMPOSITION\n{'=' * 60}")
reply = decomposition()
show_response(reply, show_usage=False)
decomp_answer = final_number(reply.content)
print(f"\n{'=' * 60}\nSCOREBOARD (correct = ${CORRECT})\n{'=' * 60}")
for name, value, calls in [
("direct", direct_answer, 1),
("chain-of-thought", cot_answer, 1),
("self-consistency", sc_answer, 3),
("decomposition", decomp_answer, 4),
]:
mark = "ok " if value == CORRECT else "WRONG"
print(f" [{mark}] {name:<18} {str(value):>10} ({calls} call(s))")