treequest 0.3.1__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {treequest-0.3.1 → treequest-0.3.3}/PKG-INFO +37 -14
- {treequest-0.3.1 → treequest-0.3.3}/README.md +35 -12
- {treequest-0.3.1 → treequest-0.3.3}/pyproject.toml +9 -3
- treequest-0.3.3/src/treequest/__init__.py +40 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/ab_mcts_a/prob_state.py +24 -12
- treequest-0.3.3/src/treequest/algos/ab_mcts_m/_ab_mcts_m_imports.py +58 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/ab_mcts_m/algo.py +49 -1
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/ab_mcts_m/pymc_interface.py +35 -6
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/tree.py +9 -1
- treequest-0.3.1/src/treequest/__init__.py +0 -33
- treequest-0.3.1/src/treequest/algos/ab_mcts_m/_ab_mcts_m_imports.py +0 -29
- {treequest-0.3.1 → treequest-0.3.3}/.gitignore +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/LICENSE +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/ab_mcts_a/algo.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/ab_mcts_m/numpyro_utils.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/base.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/best_first_search.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/multi_armed_bandit_ucb.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/standard_mcts.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/algos/tree_of_thought_bfs.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/imports.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/py.typed +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/ranker.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/trial.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/types.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/__init__.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/algo_adapters/__init__.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/algo_adapters/ab_mcts_a.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/algo_adapters/ab_mcts_m.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/algo_adapters/base.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/algo_adapters/best_first_search.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/algo_adapters/multi_armed_bandit_ucb.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/algo_adapters/standard_mcts.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/algo_adapters/tree_of_thoughts_bfs.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/assets/colormaps.json +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/assets/d3.LICENSE.txt +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/assets/d3.v7.min.js +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/assets/d3_tree.html.jinja2 +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/build_snapshot.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/errors.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/render.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/renderers/__init__.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/renderers/color_utils.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/renderers/graphviz_renderer.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/renderers/html.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/renderers/json_yaml.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/renderers/mermaid.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/snapshot.py +0 -0
- {treequest-0.3.1 → treequest-0.3.3}/src/treequest/visualization.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: treequest
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: A Tree Search Library with Flexible API for LLM Inference-Time Scaling
|
|
5
5
|
Author-email: Kou Misaki <kou.misaki@sakana.ai>, Takuya Akiba <takiba@sakana.ai>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -57,24 +57,27 @@ import treequest as tq
|
|
|
57
57
|
# Each node is associated with a user-definable `state`.
|
|
58
58
|
State = str
|
|
59
59
|
|
|
60
|
+
|
|
60
61
|
# 1. Define a function to be used for node generation.
|
|
61
62
|
def generate(parent_state: State | None) -> tuple[State, float]:
|
|
62
63
|
"""Generates new states and scores based on the parent state."""
|
|
63
|
-
if parent_state is None:
|
|
64
|
+
if parent_state is None: # None represents the expansion from root.
|
|
64
65
|
new_state = "Initial state"
|
|
65
66
|
else:
|
|
66
67
|
new_state = f"State after {parent_state}"
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
# A score for the new state; It should be normalized to the [0, 1] range.
|
|
70
|
+
score = random.random()
|
|
69
71
|
return new_state, score
|
|
70
72
|
|
|
73
|
+
|
|
71
74
|
# 2. Instantiate the algorithm and a search tree object.
|
|
72
75
|
algo = tq.ABMCTSA()
|
|
73
76
|
search_tree = algo.init_tree()
|
|
74
77
|
|
|
75
78
|
# 3. Run the search with a generation budget (10 in this case).
|
|
76
79
|
for _ in range(10):
|
|
77
|
-
search_tree = algo.step(search_tree, {
|
|
80
|
+
search_tree = algo.step(search_tree, {"Action A": generate})
|
|
78
81
|
|
|
79
82
|
# 4. Extract the best score and state.
|
|
80
83
|
best_state, best_node_score = tq.top_k(search_tree, algo, k=1)[0]
|
|
@@ -82,7 +85,8 @@ print(f"Best state: {best_state}, Score: {best_node_score}")
|
|
|
82
85
|
|
|
83
86
|
# 5. Visualize the search tree.
|
|
84
87
|
output_file_basename = Path("ab_mcts_a_search_tree")
|
|
85
|
-
|
|
88
|
+
# Generates `ab_mcts_a_search_tree.html`
|
|
89
|
+
tq.render(search_tree, output_file_basename, format="html")
|
|
86
90
|
```
|
|
87
91
|
|
|
88
92
|
Alternatively, you can use an ask–tell interface with batched AB-MCTS sampling steps:
|
|
@@ -92,8 +96,9 @@ import treequest as tq
|
|
|
92
96
|
|
|
93
97
|
State = str
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
|
|
100
|
+
def generate(parent_state: State | None) -> tuple[State, float]: ...
|
|
101
|
+
|
|
97
102
|
|
|
98
103
|
generate_fns = {"Action A": generate}
|
|
99
104
|
actions = list(generate_fns.keys())
|
|
@@ -160,11 +165,13 @@ import dataclasses
|
|
|
160
165
|
|
|
161
166
|
import treequest as tq
|
|
162
167
|
|
|
168
|
+
|
|
163
169
|
@dataclasses.dataclass
|
|
164
170
|
class State:
|
|
165
171
|
llm_answer: str
|
|
166
172
|
score: float
|
|
167
173
|
|
|
174
|
+
|
|
168
175
|
def generate(parent_state: State | None) -> tuple[State, float]:
|
|
169
176
|
"""Generate a new node by calling an LLM."""
|
|
170
177
|
if parent_state is None:
|
|
@@ -174,12 +181,14 @@ def generate(parent_state: State | None) -> tuple[State, float]:
|
|
|
174
181
|
|
|
175
182
|
return state, state.score
|
|
176
183
|
|
|
184
|
+
|
|
177
185
|
def initial_generation() -> State:
|
|
178
186
|
"""
|
|
179
187
|
Call LLM API to generate an initial answer.
|
|
180
188
|
"""
|
|
181
189
|
...
|
|
182
190
|
|
|
191
|
+
|
|
183
192
|
def refine_answer(llm_answer: str, score: float) -> State:
|
|
184
193
|
"""
|
|
185
194
|
Call LLM API to refine an answer.
|
|
@@ -190,11 +199,11 @@ def refine_answer(llm_answer: str, score: float) -> State:
|
|
|
190
199
|
algo = tq.ABMCTSM()
|
|
191
200
|
search_tree = algo.init_tree()
|
|
192
201
|
for i in range(20):
|
|
193
|
-
search_tree = algo.step(search_tree, {
|
|
202
|
+
search_tree = algo.step(search_tree, {"Action Label": generate})
|
|
194
203
|
# Logging best node during the search.
|
|
195
204
|
if (i + 1) % 5 == 0:
|
|
196
205
|
best_interim_state, _ = tq.top_k(search_tree, algo, k=1)[0]
|
|
197
|
-
print(f"Iteration {i+1}: Best state so far = {best_interim_state}")
|
|
206
|
+
print(f"Iteration {i + 1}: Best state so far = {best_interim_state}")
|
|
198
207
|
|
|
199
208
|
best_state, _ = tq.top_k(search_tree, algo, k=1)[0]
|
|
200
209
|
print(f"Best Answer: {best_state.llm_answer}, Best Score: {best_state.score}")
|
|
@@ -209,6 +218,7 @@ from functools import partial
|
|
|
209
218
|
|
|
210
219
|
import treequest as tq
|
|
211
220
|
|
|
221
|
+
|
|
212
222
|
def generate(llm_name: str, parent_state=None):
|
|
213
223
|
"""
|
|
214
224
|
Call LLM API using litellm, vllm, etc., to generate a new node
|
|
@@ -216,9 +226,12 @@ def generate(llm_name: str, parent_state=None):
|
|
|
216
226
|
...
|
|
217
227
|
return new_state, new_score
|
|
218
228
|
|
|
229
|
+
|
|
219
230
|
llm_names = ["o4-mini", "gemini-2.5-pro"]
|
|
220
231
|
# Create dict of different actions backed by different LLMs.
|
|
221
|
-
generate_fns = {
|
|
232
|
+
generate_fns = {
|
|
233
|
+
llm_name: partial(generate, llm_name=llm_name) for llm_name in llm_names
|
|
234
|
+
}
|
|
222
235
|
|
|
223
236
|
algo = tq.StandardMCTS()
|
|
224
237
|
search_tree = algo.init_tree()
|
|
@@ -237,6 +250,7 @@ The variation is not limited to LLM types; you can use different prompts, action
|
|
|
237
250
|
- Idempotent: calling `tell` twice on the same `trial_id` does not add extra nodes.
|
|
238
251
|
- For queue-based algorithms, over-told Trials beyond possible number of childs from a parent node (e.g., `(# actions)*samples_per_action` for StandardMCTS) become `INVALID` and are not reflected.
|
|
239
252
|
- Scores are expected to be normalized to the `[0, 1]` range.
|
|
253
|
+
- `ABMCTSM` exposes an internal `_advanced_config` for advanced knobs (e.g., bypassing the runtime check or tuning Bayesian priors). In most cases, the defaults are sufficient; only change it if you understand the impact.
|
|
240
254
|
|
|
241
255
|
## Algorithms
|
|
242
256
|
|
|
@@ -285,10 +299,12 @@ from pathlib import Path
|
|
|
285
299
|
import treequest as tq
|
|
286
300
|
from PIL import Image
|
|
287
301
|
|
|
302
|
+
|
|
288
303
|
class State:
|
|
289
304
|
text: str
|
|
290
305
|
image: Image.Image
|
|
291
306
|
|
|
307
|
+
|
|
292
308
|
def state_formatter_html(state: State) -> str:
|
|
293
309
|
"""Formats the state for HTML visualization."""
|
|
294
310
|
buffer = BytesIO()
|
|
@@ -298,13 +314,15 @@ def state_formatter_html(state: State) -> str:
|
|
|
298
314
|
# In case state.text cannot be trusted, we recommend e.g., html.escape(state.text) to avoid html injection.
|
|
299
315
|
return f'<p>{state.text}</p><br/><img src="data:image/webp;base64,{img_str}" width=100% />'
|
|
300
316
|
|
|
317
|
+
|
|
301
318
|
generate_fns = {
|
|
302
319
|
"action_1": ... # Define your node generation functions (actions) here
|
|
303
320
|
}
|
|
304
321
|
|
|
305
322
|
algo = tq.ABMCTSA()
|
|
306
323
|
search_tree = algo.init_tree()
|
|
307
|
-
output_dir = Path("search_tree")
|
|
324
|
+
output_dir = Path("search_tree")
|
|
325
|
+
output_dir.mkdir(exist_ok=True)
|
|
308
326
|
for i in range(10):
|
|
309
327
|
search_tree = algo.step(search_tree, generate_fns)
|
|
310
328
|
# Rendering "latest.html" for checking search progress on the fly
|
|
@@ -313,11 +331,16 @@ for i in range(10):
|
|
|
313
331
|
search_tree,
|
|
314
332
|
output_basename=output_dir / "latest",
|
|
315
333
|
format="html",
|
|
316
|
-
state_formatter=state_formatter_html,
|
|
334
|
+
state_formatter=state_formatter_html, # Use html custom formetter
|
|
317
335
|
)
|
|
318
336
|
|
|
319
337
|
tq.render(search_tree, output_basename=output_dir / "final", format="pdf")
|
|
320
|
-
tq.render(
|
|
338
|
+
tq.render(
|
|
339
|
+
search_tree,
|
|
340
|
+
output_basename=output_dir / "final",
|
|
341
|
+
format="html",
|
|
342
|
+
state_formatter=state_formatter_html,
|
|
343
|
+
)
|
|
321
344
|
```
|
|
322
345
|
|
|
323
346
|
> **IMPORTANT:** When using HTML format, ensure that the HTML file is securely handled, especially if the state formatter includes raw HTML content. Avoid opening untrusted HTML files in your browser. For example, XSS (cross site scripting) attacks can occur if the state includes malicious HTML/JavaScript code.
|
|
@@ -21,24 +21,27 @@ import treequest as tq
|
|
|
21
21
|
# Each node is associated with a user-definable `state`.
|
|
22
22
|
State = str
|
|
23
23
|
|
|
24
|
+
|
|
24
25
|
# 1. Define a function to be used for node generation.
|
|
25
26
|
def generate(parent_state: State | None) -> tuple[State, float]:
|
|
26
27
|
"""Generates new states and scores based on the parent state."""
|
|
27
|
-
if parent_state is None:
|
|
28
|
+
if parent_state is None: # None represents the expansion from root.
|
|
28
29
|
new_state = "Initial state"
|
|
29
30
|
else:
|
|
30
31
|
new_state = f"State after {parent_state}"
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
# A score for the new state; It should be normalized to the [0, 1] range.
|
|
34
|
+
score = random.random()
|
|
33
35
|
return new_state, score
|
|
34
36
|
|
|
37
|
+
|
|
35
38
|
# 2. Instantiate the algorithm and a search tree object.
|
|
36
39
|
algo = tq.ABMCTSA()
|
|
37
40
|
search_tree = algo.init_tree()
|
|
38
41
|
|
|
39
42
|
# 3. Run the search with a generation budget (10 in this case).
|
|
40
43
|
for _ in range(10):
|
|
41
|
-
search_tree = algo.step(search_tree, {
|
|
44
|
+
search_tree = algo.step(search_tree, {"Action A": generate})
|
|
42
45
|
|
|
43
46
|
# 4. Extract the best score and state.
|
|
44
47
|
best_state, best_node_score = tq.top_k(search_tree, algo, k=1)[0]
|
|
@@ -46,7 +49,8 @@ print(f"Best state: {best_state}, Score: {best_node_score}")
|
|
|
46
49
|
|
|
47
50
|
# 5. Visualize the search tree.
|
|
48
51
|
output_file_basename = Path("ab_mcts_a_search_tree")
|
|
49
|
-
|
|
52
|
+
# Generates `ab_mcts_a_search_tree.html`
|
|
53
|
+
tq.render(search_tree, output_file_basename, format="html")
|
|
50
54
|
```
|
|
51
55
|
|
|
52
56
|
Alternatively, you can use an ask–tell interface with batched AB-MCTS sampling steps:
|
|
@@ -56,8 +60,9 @@ import treequest as tq
|
|
|
56
60
|
|
|
57
61
|
State = str
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
|
|
64
|
+
def generate(parent_state: State | None) -> tuple[State, float]: ...
|
|
65
|
+
|
|
61
66
|
|
|
62
67
|
generate_fns = {"Action A": generate}
|
|
63
68
|
actions = list(generate_fns.keys())
|
|
@@ -124,11 +129,13 @@ import dataclasses
|
|
|
124
129
|
|
|
125
130
|
import treequest as tq
|
|
126
131
|
|
|
132
|
+
|
|
127
133
|
@dataclasses.dataclass
|
|
128
134
|
class State:
|
|
129
135
|
llm_answer: str
|
|
130
136
|
score: float
|
|
131
137
|
|
|
138
|
+
|
|
132
139
|
def generate(parent_state: State | None) -> tuple[State, float]:
|
|
133
140
|
"""Generate a new node by calling an LLM."""
|
|
134
141
|
if parent_state is None:
|
|
@@ -138,12 +145,14 @@ def generate(parent_state: State | None) -> tuple[State, float]:
|
|
|
138
145
|
|
|
139
146
|
return state, state.score
|
|
140
147
|
|
|
148
|
+
|
|
141
149
|
def initial_generation() -> State:
|
|
142
150
|
"""
|
|
143
151
|
Call LLM API to generate an initial answer.
|
|
144
152
|
"""
|
|
145
153
|
...
|
|
146
154
|
|
|
155
|
+
|
|
147
156
|
def refine_answer(llm_answer: str, score: float) -> State:
|
|
148
157
|
"""
|
|
149
158
|
Call LLM API to refine an answer.
|
|
@@ -154,11 +163,11 @@ def refine_answer(llm_answer: str, score: float) -> State:
|
|
|
154
163
|
algo = tq.ABMCTSM()
|
|
155
164
|
search_tree = algo.init_tree()
|
|
156
165
|
for i in range(20):
|
|
157
|
-
search_tree = algo.step(search_tree, {
|
|
166
|
+
search_tree = algo.step(search_tree, {"Action Label": generate})
|
|
158
167
|
# Logging best node during the search.
|
|
159
168
|
if (i + 1) % 5 == 0:
|
|
160
169
|
best_interim_state, _ = tq.top_k(search_tree, algo, k=1)[0]
|
|
161
|
-
print(f"Iteration {i+1}: Best state so far = {best_interim_state}")
|
|
170
|
+
print(f"Iteration {i + 1}: Best state so far = {best_interim_state}")
|
|
162
171
|
|
|
163
172
|
best_state, _ = tq.top_k(search_tree, algo, k=1)[0]
|
|
164
173
|
print(f"Best Answer: {best_state.llm_answer}, Best Score: {best_state.score}")
|
|
@@ -173,6 +182,7 @@ from functools import partial
|
|
|
173
182
|
|
|
174
183
|
import treequest as tq
|
|
175
184
|
|
|
185
|
+
|
|
176
186
|
def generate(llm_name: str, parent_state=None):
|
|
177
187
|
"""
|
|
178
188
|
Call LLM API using litellm, vllm, etc., to generate a new node
|
|
@@ -180,9 +190,12 @@ def generate(llm_name: str, parent_state=None):
|
|
|
180
190
|
...
|
|
181
191
|
return new_state, new_score
|
|
182
192
|
|
|
193
|
+
|
|
183
194
|
llm_names = ["o4-mini", "gemini-2.5-pro"]
|
|
184
195
|
# Create dict of different actions backed by different LLMs.
|
|
185
|
-
generate_fns = {
|
|
196
|
+
generate_fns = {
|
|
197
|
+
llm_name: partial(generate, llm_name=llm_name) for llm_name in llm_names
|
|
198
|
+
}
|
|
186
199
|
|
|
187
200
|
algo = tq.StandardMCTS()
|
|
188
201
|
search_tree = algo.init_tree()
|
|
@@ -201,6 +214,7 @@ The variation is not limited to LLM types; you can use different prompts, action
|
|
|
201
214
|
- Idempotent: calling `tell` twice on the same `trial_id` does not add extra nodes.
|
|
202
215
|
- For queue-based algorithms, over-told Trials beyond possible number of childs from a parent node (e.g., `(# actions)*samples_per_action` for StandardMCTS) become `INVALID` and are not reflected.
|
|
203
216
|
- Scores are expected to be normalized to the `[0, 1]` range.
|
|
217
|
+
- `ABMCTSM` exposes an internal `_advanced_config` for advanced knobs (e.g., bypassing the runtime check or tuning Bayesian priors). In most cases, the defaults are sufficient; only change it if you understand the impact.
|
|
204
218
|
|
|
205
219
|
## Algorithms
|
|
206
220
|
|
|
@@ -249,10 +263,12 @@ from pathlib import Path
|
|
|
249
263
|
import treequest as tq
|
|
250
264
|
from PIL import Image
|
|
251
265
|
|
|
266
|
+
|
|
252
267
|
class State:
|
|
253
268
|
text: str
|
|
254
269
|
image: Image.Image
|
|
255
270
|
|
|
271
|
+
|
|
256
272
|
def state_formatter_html(state: State) -> str:
|
|
257
273
|
"""Formats the state for HTML visualization."""
|
|
258
274
|
buffer = BytesIO()
|
|
@@ -262,13 +278,15 @@ def state_formatter_html(state: State) -> str:
|
|
|
262
278
|
# In case state.text cannot be trusted, we recommend e.g., html.escape(state.text) to avoid html injection.
|
|
263
279
|
return f'<p>{state.text}</p><br/><img src="data:image/webp;base64,{img_str}" width=100% />'
|
|
264
280
|
|
|
281
|
+
|
|
265
282
|
generate_fns = {
|
|
266
283
|
"action_1": ... # Define your node generation functions (actions) here
|
|
267
284
|
}
|
|
268
285
|
|
|
269
286
|
algo = tq.ABMCTSA()
|
|
270
287
|
search_tree = algo.init_tree()
|
|
271
|
-
output_dir = Path("search_tree")
|
|
288
|
+
output_dir = Path("search_tree")
|
|
289
|
+
output_dir.mkdir(exist_ok=True)
|
|
272
290
|
for i in range(10):
|
|
273
291
|
search_tree = algo.step(search_tree, generate_fns)
|
|
274
292
|
# Rendering "latest.html" for checking search progress on the fly
|
|
@@ -277,11 +295,16 @@ for i in range(10):
|
|
|
277
295
|
search_tree,
|
|
278
296
|
output_basename=output_dir / "latest",
|
|
279
297
|
format="html",
|
|
280
|
-
state_formatter=state_formatter_html,
|
|
298
|
+
state_formatter=state_formatter_html, # Use html custom formetter
|
|
281
299
|
)
|
|
282
300
|
|
|
283
301
|
tq.render(search_tree, output_basename=output_dir / "final", format="pdf")
|
|
284
|
-
tq.render(
|
|
302
|
+
tq.render(
|
|
303
|
+
search_tree,
|
|
304
|
+
output_basename=output_dir / "final",
|
|
305
|
+
format="html",
|
|
306
|
+
state_formatter=state_formatter_html,
|
|
307
|
+
)
|
|
285
308
|
```
|
|
286
309
|
|
|
287
310
|
> **IMPORTANT:** When using HTML format, ensure that the HTML file is securely handled, especially if the state formatter includes raw HTML content. Avoid opening untrusted HTML files in your browser. For example, XSS (cross site scripting) attacks can occur if the state includes malicious HTML/JavaScript code.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "treequest"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.3"
|
|
4
4
|
description = "A Tree Search Library with Flexible API for LLM Inference-Time Scaling"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -44,16 +44,22 @@ build-backend = "hatchling.build"
|
|
|
44
44
|
[dependency-groups]
|
|
45
45
|
dev = [
|
|
46
46
|
"line-profiler>=4.2.0",
|
|
47
|
-
|
|
47
|
+
# Pin ruff and mypy so that their new releases do not change CI results.
|
|
48
|
+
# Keep ruff in sync with .pre-commit-config.yaml.
|
|
49
|
+
"mypy==2.4.0",
|
|
48
50
|
"pre-commit>=4.2.0",
|
|
49
51
|
"pytest>=8.3.4",
|
|
50
52
|
"pytest-xdist>=3.6.1",
|
|
51
|
-
"ruff
|
|
53
|
+
"ruff==0.17.1",
|
|
52
54
|
"tqdm>=4.64.0",
|
|
53
55
|
"types-tqdm>=4.64.0",
|
|
54
56
|
"types-PyYAML>=6.0.0",
|
|
55
57
|
]
|
|
56
58
|
|
|
59
|
+
[tool.ruff.lint]
|
|
60
|
+
# The rules Ruff enabled by default before 0.16, which expanded its default rule set.
|
|
61
|
+
select = ["E4", "E7", "E9", "F"]
|
|
62
|
+
|
|
57
63
|
[tool.hatch.build.targets.wheel]
|
|
58
64
|
packages = ["src/treequest"]
|
|
59
65
|
include = [
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
# Expose the real symbol to static type checkers / IDEs even when optional
|
|
5
|
+
# dependencies for ABMCTSM are not installed at runtime.
|
|
6
|
+
from .algos.ab_mcts_m.algo import ABMCTSM
|
|
7
|
+
else:
|
|
8
|
+
from .algos.ab_mcts_m._ab_mcts_m_imports import _import as _ab_mcts_m_import
|
|
9
|
+
|
|
10
|
+
if not _ab_mcts_m_import.is_successful():
|
|
11
|
+
# Create a placeholder that raises an informative error when accessed
|
|
12
|
+
class _ABMCTSMPlaceholder:
|
|
13
|
+
def __getattr__(self, name): # type: ignore
|
|
14
|
+
_ab_mcts_m_import.check()
|
|
15
|
+
raise ImportError("ABMCTSM import failed.")
|
|
16
|
+
|
|
17
|
+
def __call__(self, *args, **kwargs): # type: ignore
|
|
18
|
+
_ab_mcts_m_import.check()
|
|
19
|
+
raise ImportError("ABMCTSM import failed.")
|
|
20
|
+
|
|
21
|
+
ABMCTSM = _ABMCTSMPlaceholder() # type: ignore
|
|
22
|
+
else:
|
|
23
|
+
from .algos.ab_mcts_m.algo import ABMCTSM # type: ignore[assignment]
|
|
24
|
+
|
|
25
|
+
from .algos.ab_mcts_a.algo import ABMCTSA
|
|
26
|
+
from .algos.base import Algorithm
|
|
27
|
+
from .algos.standard_mcts import StandardMCTS
|
|
28
|
+
from .algos.tree_of_thought_bfs import TreeOfThoughtsBFSAlgo
|
|
29
|
+
from .ranker import top_k
|
|
30
|
+
from .vis import render
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"StandardMCTS",
|
|
34
|
+
"top_k",
|
|
35
|
+
"TreeOfThoughtsBFSAlgo",
|
|
36
|
+
"ABMCTSA",
|
|
37
|
+
"ABMCTSM",
|
|
38
|
+
"Algorithm",
|
|
39
|
+
"render",
|
|
40
|
+
]
|
|
@@ -13,6 +13,24 @@ from treequest.algos.tree import Node
|
|
|
13
13
|
T = TypeVar("T")
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def _child_index_by_identity(parent: Node, child: Node) -> int:
|
|
17
|
+
"""
|
|
18
|
+
Return the index of `child` in `parent.children` using identity comparison.
|
|
19
|
+
|
|
20
|
+
NOTE: Avoid `list.index()` here because it relies on `__eq__`, and user-defined
|
|
21
|
+
states may include objects (e.g., numpy arrays / torch tensors) whose equality
|
|
22
|
+
returns non-bool values and can raise when coerced to `bool`.
|
|
23
|
+
"""
|
|
24
|
+
for i, n in enumerate(parent.children):
|
|
25
|
+
if n is child:
|
|
26
|
+
return i
|
|
27
|
+
raise ValueError(
|
|
28
|
+
"Child node not found in parent.children by identity. "
|
|
29
|
+
f"child.expand_idx={getattr(child, 'expand_idx', None)}, "
|
|
30
|
+
f"parent.expand_idx={getattr(parent, 'expand_idx', None)}"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
16
34
|
@dataclasses.dataclass
|
|
17
35
|
class BetaPrior:
|
|
18
36
|
"""
|
|
@@ -278,18 +296,15 @@ class NodeProbState:
|
|
|
278
296
|
assert node.parent is not None
|
|
279
297
|
|
|
280
298
|
# Update for continuing with existing nodes
|
|
299
|
+
child_idx = _child_index_by_identity(node.parent, node)
|
|
281
300
|
if self.model_selection_strategy.startswith("multiarm_bandit_"):
|
|
282
301
|
# Update the node's probability
|
|
283
|
-
self.node_probas["shared"][
|
|
284
|
-
node.parent.children.index(node)
|
|
285
|
-
].tell_observation(reward)
|
|
302
|
+
self.node_probas["shared"][child_idx].tell_observation(reward)
|
|
286
303
|
# For multiarm bandit strategies, update shared CONT distribution
|
|
287
304
|
self.gen_vs_cont_probas["shared"]["CONT"].tell_observation(reward)
|
|
288
305
|
else:
|
|
289
306
|
# Update the node's probability
|
|
290
|
-
self.node_probas[action][
|
|
291
|
-
reward
|
|
292
|
-
)
|
|
307
|
+
self.node_probas[action][child_idx].tell_observation(reward)
|
|
293
308
|
# For stack strategy, update action-specific CONT distribution
|
|
294
309
|
self.gen_vs_cont_probas[action]["CONT"].tell_observation(reward)
|
|
295
310
|
|
|
@@ -444,14 +459,11 @@ class NodeProbState:
|
|
|
444
459
|
|
|
445
460
|
# Initialize the node's probability distribution with its score
|
|
446
461
|
assert node.parent is not None
|
|
462
|
+
child_idx = _child_index_by_identity(node.parent, node)
|
|
447
463
|
if model_selection_strategy == "stack":
|
|
448
|
-
self.node_probas[action][
|
|
449
|
-
node.score
|
|
450
|
-
)
|
|
464
|
+
self.node_probas[action][child_idx].tell_observation(node.score)
|
|
451
465
|
else:
|
|
452
|
-
self.node_probas["shared"][
|
|
453
|
-
node.parent.children.index(node)
|
|
454
|
-
].tell_observation(node.score)
|
|
466
|
+
self.node_probas["shared"][child_idx].tell_observation(node.score)
|
|
455
467
|
|
|
456
468
|
def get_action_for_child(self, child: Node) -> str:
|
|
457
469
|
"""
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from treequest.imports import try_import
|
|
5
|
+
|
|
6
|
+
_ARVIZ_IMPORT_ATTEMPTS = 3
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _import_arviz() -> None:
|
|
10
|
+
# TODO: Remove this workaround after arviz fixes the race in its daily warning
|
|
11
|
+
# https://github.com/arviz-devs/arviz/issues/2530
|
|
12
|
+
# arviz 0.23.3 and 0.23.4 update the warning's stamp file through a fixed
|
|
13
|
+
# temporary file, so importing arviz from several processes at once can raise
|
|
14
|
+
# FileNotFoundError. The process that won has already updated the stamp, so
|
|
15
|
+
# importing again normally succeeds.
|
|
16
|
+
for attempt in range(1, _ARVIZ_IMPORT_ATTEMPTS + 1):
|
|
17
|
+
try:
|
|
18
|
+
importlib.import_module("arviz")
|
|
19
|
+
return
|
|
20
|
+
except FileNotFoundError as e:
|
|
21
|
+
# The process that lost the race fails to rename the temporary file.
|
|
22
|
+
lost_race = Path(e.filename or "").name == "daily_warning.tmp"
|
|
23
|
+
if not lost_race or attempt == _ARVIZ_IMPORT_ATTEMPTS:
|
|
24
|
+
raise
|
|
25
|
+
# Leave sys.modules as it is: Python has already dropped the failed
|
|
26
|
+
# package, the leftover arviz.preview is safe to reuse, and another
|
|
27
|
+
# thread may be importing arviz by now.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
with try_import() as _import:
|
|
31
|
+
import jax
|
|
32
|
+
from packaging.version import Version
|
|
33
|
+
|
|
34
|
+
# TODO: Remove this hotfix after numpyro fixes incompatibility with jax>=0.7
|
|
35
|
+
# https://github.com/pyro-ppl/numpyro/issues/2051
|
|
36
|
+
if Version(jax.__version__) >= Version("0.7.0"):
|
|
37
|
+
import jax.experimental.pjit as _pjit
|
|
38
|
+
from jax.extend.core.primitives import jit_p
|
|
39
|
+
|
|
40
|
+
_pjit.pjit_p = jit_p # type: ignore
|
|
41
|
+
import numpy as np
|
|
42
|
+
import numpyro # type: ignore
|
|
43
|
+
import pandas as pd # type: ignore
|
|
44
|
+
|
|
45
|
+
_import_arviz() # Import arviz before pymc does; see _import_arviz.
|
|
46
|
+
import pymc as pm # type: ignore
|
|
47
|
+
from pymc.sampling.jax import sample_numpyro_nuts # type: ignore
|
|
48
|
+
from xarray import DataArray
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"jax",
|
|
52
|
+
"np",
|
|
53
|
+
"numpyro",
|
|
54
|
+
"pd",
|
|
55
|
+
"pm",
|
|
56
|
+
"sample_numpyro_nuts",
|
|
57
|
+
"DataArray",
|
|
58
|
+
]
|
|
@@ -47,6 +47,37 @@ class ABMCTSMState(Generic[StateT]):
|
|
|
47
47
|
trial_store: TrialStore[StateT] = field(default_factory=TrialStore[StateT])
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class ABMCTSMAdvancedConfig:
|
|
52
|
+
"""
|
|
53
|
+
Advanced/experimental ABMCTSM configuration.
|
|
54
|
+
|
|
55
|
+
Most users should rely on the defaults.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
# If True, validate that rewards/scores are in [0, 1].
|
|
59
|
+
validate_reward_range: bool = True
|
|
60
|
+
|
|
61
|
+
# Prior sigmas for the underlying Bayesian model.
|
|
62
|
+
prior_mu_alpha_sigma: float = 0.2
|
|
63
|
+
prior_sigma_alpha_sigma: float = 0.2
|
|
64
|
+
prior_sigma_y_sigma: float = 0.3
|
|
65
|
+
|
|
66
|
+
def __post_init__(self) -> None:
|
|
67
|
+
if self.prior_mu_alpha_sigma <= 0:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"prior_mu_alpha_sigma must be > 0, got {self.prior_mu_alpha_sigma}"
|
|
70
|
+
)
|
|
71
|
+
if self.prior_sigma_alpha_sigma <= 0:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"prior_sigma_alpha_sigma must be > 0, got {self.prior_sigma_alpha_sigma}"
|
|
74
|
+
)
|
|
75
|
+
if self.prior_sigma_y_sigma <= 0:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"prior_sigma_y_sigma must be > 0, got {self.prior_sigma_y_sigma}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
50
81
|
class ABMCTSM(Algorithm[StateT, ABMCTSMState[StateT]]):
|
|
51
82
|
"""
|
|
52
83
|
Monte Carlo Tree Search algorithm using AB-MCTS-M algorithm.
|
|
@@ -63,6 +94,7 @@ class ABMCTSM(Algorithm[StateT, ABMCTSMState[StateT]]):
|
|
|
63
94
|
min_subtree_size_for_pruning: int = 4,
|
|
64
95
|
same_score_proportion_threshold: float = 0.75,
|
|
65
96
|
max_process_workers: int = os.cpu_count() or 1,
|
|
97
|
+
_advanced_config: ABMCTSMAdvancedConfig | None = None,
|
|
66
98
|
is_worker: bool = False,
|
|
67
99
|
):
|
|
68
100
|
"""
|
|
@@ -79,7 +111,13 @@ class ABMCTSM(Algorithm[StateT, ABMCTSMState[StateT]]):
|
|
|
79
111
|
min_subtree_size_for_pruning: Pruning Config, see PruningConfig class.
|
|
80
112
|
same_score_proportion_threshold: Pruning Config, see PruningConfig class.
|
|
81
113
|
max_process_workers: Maximum number of parallel processes used for running PyMC sampling.
|
|
114
|
+
_advanced_config: Advanced/experimental configuration. Defaults are recommended.
|
|
82
115
|
"""
|
|
116
|
+
self._advanced_config = (
|
|
117
|
+
_advanced_config
|
|
118
|
+
if _advanced_config is not None
|
|
119
|
+
else ABMCTSMAdvancedConfig()
|
|
120
|
+
)
|
|
83
121
|
self.enable_pruning = enable_pruning
|
|
84
122
|
self.pruning_config = PruningConfig(
|
|
85
123
|
min_subtree_size_for_pruning=min_subtree_size_for_pruning,
|
|
@@ -94,6 +132,9 @@ class ABMCTSM(Algorithm[StateT, ABMCTSMState[StateT]]):
|
|
|
94
132
|
enable_pruning=self.enable_pruning,
|
|
95
133
|
pruning_config=self.pruning_config,
|
|
96
134
|
reward_average_priors=self.reward_average_priors,
|
|
135
|
+
prior_mu_alpha_sigma=self._advanced_config.prior_mu_alpha_sigma,
|
|
136
|
+
prior_sigma_alpha_sigma=self._advanced_config.prior_sigma_alpha_sigma,
|
|
137
|
+
prior_sigma_y_sigma=self._advanced_config.prior_sigma_y_sigma,
|
|
97
138
|
model_selection_strategy=self.model_selection_strategy,
|
|
98
139
|
)
|
|
99
140
|
|
|
@@ -195,6 +236,9 @@ class ABMCTSM(Algorithm[StateT, ABMCTSMState[StateT]]):
|
|
|
195
236
|
def _get_generation_action(
|
|
196
237
|
self, state: ABMCTSMState[StateT], node: Node[StateT], actions: list[str]
|
|
197
238
|
) -> str:
|
|
239
|
+
if len(actions) == 1:
|
|
240
|
+
return actions[0]
|
|
241
|
+
|
|
198
242
|
observations = Observation.collect_all_observations_of_descendant(
|
|
199
243
|
node, state.all_observations
|
|
200
244
|
)
|
|
@@ -255,6 +299,7 @@ class ABMCTSM(Algorithm[StateT, ABMCTSMState[StateT]]):
|
|
|
255
299
|
min_subtree_size_for_pruning=self.pruning_config.min_subtree_size_for_pruning,
|
|
256
300
|
same_score_proportion_threshold=self.pruning_config.same_score_proportion_threshold,
|
|
257
301
|
max_process_workers=1,
|
|
302
|
+
_advanced_config=self._advanced_config,
|
|
258
303
|
is_worker=True,
|
|
259
304
|
)
|
|
260
305
|
|
|
@@ -298,7 +343,10 @@ class ABMCTSM(Algorithm[StateT, ABMCTSMState[StateT]]):
|
|
|
298
343
|
|
|
299
344
|
# Add new node to the tree
|
|
300
345
|
new_node = state.tree.add_node(
|
|
301
|
-
result,
|
|
346
|
+
result,
|
|
347
|
+
parent_node,
|
|
348
|
+
trial_id=finished_trial.trial_id,
|
|
349
|
+
validate_score_range=self._advanced_config.validate_reward_range,
|
|
302
350
|
)
|
|
303
351
|
|
|
304
352
|
# Record observation
|
|
@@ -148,6 +148,9 @@ class PyMCInterface:
|
|
|
148
148
|
enable_pruning: bool = True,
|
|
149
149
|
pruning_config: Optional[PruningConfig] = None,
|
|
150
150
|
reward_average_priors: Optional[float | Dict[str, float]] = None,
|
|
151
|
+
prior_mu_alpha_sigma: float = 0.2,
|
|
152
|
+
prior_sigma_alpha_sigma: float = 0.2,
|
|
153
|
+
prior_sigma_y_sigma: float = 0.3,
|
|
151
154
|
model_selection_strategy: str = "multiarm_bandit_thompson",
|
|
152
155
|
):
|
|
153
156
|
_import.check()
|
|
@@ -161,6 +164,22 @@ class PyMCInterface:
|
|
|
161
164
|
self.reward_average_priors = (
|
|
162
165
|
reward_average_priors if reward_average_priors is not None else dict()
|
|
163
166
|
)
|
|
167
|
+
if prior_mu_alpha_sigma <= 0:
|
|
168
|
+
raise ValueError(
|
|
169
|
+
f"prior_mu_alpha_sigma must be > 0, got {prior_mu_alpha_sigma}"
|
|
170
|
+
)
|
|
171
|
+
if prior_sigma_alpha_sigma <= 0:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"prior_sigma_alpha_sigma must be > 0, got {prior_sigma_alpha_sigma}"
|
|
174
|
+
)
|
|
175
|
+
if prior_sigma_y_sigma <= 0:
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"prior_sigma_y_sigma must be > 0, got {prior_sigma_y_sigma}"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
self.prior_mu_alpha_sigma = prior_mu_alpha_sigma
|
|
181
|
+
self.prior_sigma_alpha_sigma = prior_sigma_alpha_sigma
|
|
182
|
+
self.prior_sigma_y_sigma = prior_sigma_y_sigma
|
|
164
183
|
|
|
165
184
|
# Strategy for model selection:
|
|
166
185
|
# "stack": Perform separate fits for each model (traditional approach)
|
|
@@ -491,13 +510,19 @@ class PyMCInterface:
|
|
|
491
510
|
with pm.Model(coords=coords if not is_prior_model else None) as model:
|
|
492
511
|
# Priors START
|
|
493
512
|
# Overall Goodness of the model itself; mu is set to be 0.5 (50% prob of solving the problem)
|
|
494
|
-
mu_alpha = pm.Normal(
|
|
513
|
+
mu_alpha = pm.Normal(
|
|
514
|
+
"mu_alpha",
|
|
515
|
+
mu=self.get_reward_average_prior(action=None),
|
|
516
|
+
sigma=self.prior_mu_alpha_sigma,
|
|
517
|
+
)
|
|
495
518
|
|
|
496
519
|
# expresses the strength of score fluctuation across answers
|
|
497
|
-
sigma_alpha = pm.HalfNormal(
|
|
520
|
+
sigma_alpha = pm.HalfNormal(
|
|
521
|
+
"sigma_alpha", sigma=self.prior_sigma_alpha_sigma
|
|
522
|
+
)
|
|
498
523
|
|
|
499
524
|
# expresses the strength of score fluctuation inside answers
|
|
500
|
-
sigma_y = pm.HalfNormal("sigma_y", sigma=
|
|
525
|
+
sigma_y = pm.HalfNormal("sigma_y", sigma=self.prior_sigma_y_sigma)
|
|
501
526
|
# Priors END
|
|
502
527
|
|
|
503
528
|
group_dims = "action" if not is_prior_model else None
|
|
@@ -543,14 +568,18 @@ class PyMCInterface:
|
|
|
543
568
|
# Priors START
|
|
544
569
|
# Overall Goodness of the model itself; mu is set to be 0.5 (50% prob of solving the problem)
|
|
545
570
|
mu_alpha = pm.Normal(
|
|
546
|
-
"mu_alpha",
|
|
571
|
+
"mu_alpha",
|
|
572
|
+
mu=self.get_reward_average_prior(action),
|
|
573
|
+
sigma=self.prior_mu_alpha_sigma,
|
|
547
574
|
)
|
|
548
575
|
|
|
549
576
|
# expresses the strength of score fluctuation across answers
|
|
550
|
-
sigma_alpha = pm.HalfNormal(
|
|
577
|
+
sigma_alpha = pm.HalfNormal(
|
|
578
|
+
"sigma_alpha", sigma=self.prior_sigma_alpha_sigma
|
|
579
|
+
)
|
|
551
580
|
|
|
552
581
|
# expresses the strength of score fluctuation inside answers
|
|
553
|
-
sigma_y = pm.HalfNormal("sigma_y", sigma=
|
|
582
|
+
sigma_y = pm.HalfNormal("sigma_y", sigma=self.prior_sigma_y_sigma)
|
|
554
583
|
# Priors END
|
|
555
584
|
|
|
556
585
|
group_dims = "child_idx" if not is_prior_model else None
|
|
@@ -14,6 +14,9 @@ class Node(Generic[StateT]):
|
|
|
14
14
|
# Scores should be in the range from 0 to 1. For root, we set it to be -1.0 as a placeholder.
|
|
15
15
|
score: float = -1.0
|
|
16
16
|
|
|
17
|
+
# If True, validate that score is in [0, 1] for non-root nodes.
|
|
18
|
+
validate_score_range: bool = dataclasses.field(default=True, kw_only=True)
|
|
19
|
+
|
|
17
20
|
# Reflects the order of expansion. The root has expand_idx = -1, followed by 0,1,2,...
|
|
18
21
|
expand_idx: int = -1
|
|
19
22
|
|
|
@@ -23,7 +26,9 @@ class Node(Generic[StateT]):
|
|
|
23
26
|
|
|
24
27
|
def __post_init__(self) -> None:
|
|
25
28
|
if not self.is_root():
|
|
26
|
-
if
|
|
29
|
+
if self.validate_score_range and not (
|
|
30
|
+
self.score <= 1.0 and self.score >= 0.0
|
|
31
|
+
):
|
|
27
32
|
raise RuntimeError(
|
|
28
33
|
f"The score value should be between 0 and 1, while {self.score} is set."
|
|
29
34
|
)
|
|
@@ -117,11 +122,14 @@ class Tree(Generic[StateT]):
|
|
|
117
122
|
state_score: StateScoreType[StateT],
|
|
118
123
|
parent: Node[StateT],
|
|
119
124
|
trial_id: Optional[TrialId] = None,
|
|
125
|
+
*,
|
|
126
|
+
validate_score_range: bool = True,
|
|
120
127
|
) -> Node[StateT]:
|
|
121
128
|
state, score = state_score
|
|
122
129
|
node = Node(
|
|
123
130
|
state=state,
|
|
124
131
|
score=score,
|
|
132
|
+
validate_score_range=validate_score_range,
|
|
125
133
|
parent=parent,
|
|
126
134
|
expand_idx=self.size - 1,
|
|
127
135
|
trial_id=trial_id,
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from .algos.ab_mcts_m._ab_mcts_m_imports import _import as _ab_mcts_m_import
|
|
2
|
-
|
|
3
|
-
if not _ab_mcts_m_import.is_successful():
|
|
4
|
-
# Create a placeholder that raises an informative error when accessed
|
|
5
|
-
class _ABMCTSMPlaceholder:
|
|
6
|
-
def __getattr__(self, name): # type: ignore
|
|
7
|
-
_ab_mcts_m_import.check()
|
|
8
|
-
raise ImportError("ABMCTSM import failed.")
|
|
9
|
-
|
|
10
|
-
def __call__(self, *args, **kwargs): # type: ignore
|
|
11
|
-
_ab_mcts_m_import.check()
|
|
12
|
-
raise ImportError("ABMCTSM import failed.")
|
|
13
|
-
|
|
14
|
-
ABMCTSM = _ABMCTSMPlaceholder() # type: ignore
|
|
15
|
-
else:
|
|
16
|
-
from .algos.ab_mcts_m.algo import ABMCTSM # type: ignore[assignment]
|
|
17
|
-
|
|
18
|
-
from .algos.ab_mcts_a.algo import ABMCTSA
|
|
19
|
-
from .algos.base import Algorithm
|
|
20
|
-
from .algos.standard_mcts import StandardMCTS
|
|
21
|
-
from .algos.tree_of_thought_bfs import TreeOfThoughtsBFSAlgo
|
|
22
|
-
from .ranker import top_k
|
|
23
|
-
from .vis import render
|
|
24
|
-
|
|
25
|
-
__all__ = [
|
|
26
|
-
"StandardMCTS",
|
|
27
|
-
"top_k",
|
|
28
|
-
"TreeOfThoughtsBFSAlgo",
|
|
29
|
-
"ABMCTSA",
|
|
30
|
-
"ABMCTSM",
|
|
31
|
-
"Algorithm",
|
|
32
|
-
"render",
|
|
33
|
-
]
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from treequest.imports import try_import
|
|
2
|
-
|
|
3
|
-
with try_import() as _import:
|
|
4
|
-
import jax
|
|
5
|
-
from packaging.version import Version
|
|
6
|
-
|
|
7
|
-
# TODO: Remove this hotfix after numpyro fixes incompatibility with jax>=0.7
|
|
8
|
-
# https://github.com/pyro-ppl/numpyro/issues/2051
|
|
9
|
-
if Version(jax.__version__) >= Version("0.7.0"):
|
|
10
|
-
import jax.experimental.pjit as _pjit
|
|
11
|
-
from jax.extend.core.primitives import jit_p
|
|
12
|
-
|
|
13
|
-
_pjit.pjit_p = jit_p # type: ignore
|
|
14
|
-
import numpy as np
|
|
15
|
-
import numpyro # type: ignore
|
|
16
|
-
import pandas as pd # type: ignore
|
|
17
|
-
import pymc as pm # type: ignore
|
|
18
|
-
from pymc.sampling.jax import sample_numpyro_nuts # type: ignore
|
|
19
|
-
from xarray import DataArray
|
|
20
|
-
|
|
21
|
-
__all__ = [
|
|
22
|
-
"jax",
|
|
23
|
-
"np",
|
|
24
|
-
"numpyro",
|
|
25
|
-
"pd",
|
|
26
|
-
"pm",
|
|
27
|
-
"sample_numpyro_nuts",
|
|
28
|
-
"DataArray",
|
|
29
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{treequest-0.3.1 → treequest-0.3.3}/src/treequest/vis/algo_adapters/multi_armed_bandit_ucb.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|