Skip to content

Commit 14bd655

Browse files
authored
Merge pull request #167 from bespokelabsai/trung/code-object
Use dill pickle to capture the execution context
2 parents 9ec38e8 + 94111a4 commit 14bd655

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

src/bespokelabs/curator/prompter/prompter.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import logging
55
import os
66
from datetime import datetime
7+
from io import BytesIO
78
from typing import Any, Callable, Dict, Iterable, Optional, Type, TypeVar, Union
89

10+
import dill
911
from datasets import Dataset
1012
from pydantic import BaseModel
1113
from xxhash import xxh64
@@ -219,7 +221,9 @@ def _get_function_hash(func) -> str:
219221
if func is None:
220222
return xxh64("").hexdigest()
221223

222-
return xxh64(_get_function_source(func)).hexdigest()
224+
file = BytesIO()
225+
dill.Pickler(file, recurse=True).dump(func)
226+
return xxh64(file.getvalue()).hexdigest()
223227

224228

225229
def _get_function_source(func) -> str:

tests/test_caching.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from datasets import Dataset
2+
3+
from bespokelabs.curator import Prompter
4+
5+
6+
def test_same_value_caching(tmp_path):
7+
"""Test that using the same value multiple times uses cache."""
8+
values = []
9+
10+
# Test with same value multiple times
11+
for _ in range(3):
12+
13+
def prompt_func():
14+
return f"Say '1'. Do not explain."
15+
16+
prompter = Prompter(
17+
prompt_func=prompt_func,
18+
model_name="gpt-4o-mini",
19+
)
20+
result = prompter(working_dir=str(tmp_path))
21+
values.append(result.to_pandas().iloc[0]["response"])
22+
23+
# Count cache directories, excluding metadata.db
24+
cache_dirs = [d for d in tmp_path.glob("*") if d.name != "metadata.db"]
25+
assert len(cache_dirs) == 1, f"Expected 1 cache directory but found {len(cache_dirs)}"
26+
assert values == ["1", "1", "1"], "Same value should produce same results"
27+
28+
29+
def test_different_values_caching(tmp_path):
30+
"""Test that using different values creates different cache entries."""
31+
values = []
32+
33+
# Test with different values
34+
for x in [1, 2, 3]:
35+
36+
def prompt_func():
37+
return f"Say '{x}'. Do not explain."
38+
39+
prompter = Prompter(
40+
prompt_func=prompt_func,
41+
model_name="gpt-4o-mini",
42+
)
43+
result = prompter(working_dir=str(tmp_path))
44+
values.append(result.to_pandas().iloc[0]["response"])
45+
46+
# Count cache directories, excluding metadata.db
47+
cache_dirs = [d for d in tmp_path.glob("*") if d.name != "metadata.db"]
48+
assert len(cache_dirs) == 3, f"Expected 3 cache directories but found {len(cache_dirs)}"
49+
assert values == ["1", "2", "3"], "Different values should produce different results"
50+
51+
52+
def test_same_dataset_caching(tmp_path):
53+
"""Test that using the same dataset multiple times uses cache."""
54+
dataset = Dataset.from_list([{"instruction": "Say '1'. Do not explain."}])
55+
prompter = Prompter(
56+
prompt_func=lambda x: x["instruction"],
57+
model_name="gpt-4o-mini",
58+
)
59+
60+
result = prompter(dataset=dataset, working_dir=str(tmp_path))
61+
assert result.to_pandas().iloc[0]["response"] == "1"
62+
63+
result = prompter(dataset=dataset, working_dir=str(tmp_path))
64+
assert result.to_pandas().iloc[0]["response"] == "1"
65+
66+
# Count cache directories, excluding metadata.db
67+
cache_dirs = [d for d in tmp_path.glob("*") if d.name != "metadata.db"]
68+
assert len(cache_dirs) == 1, f"Expected 1 cache directory but found {len(cache_dirs)}"
69+
70+
71+
def test_different_dataset_caching(tmp_path):
72+
"""Test that using different datasets creates different cache entries."""
73+
dataset1 = Dataset.from_list([{"instruction": "Say '1'. Do not explain."}])
74+
dataset2 = Dataset.from_list([{"instruction": "Say '2'. Do not explain."}])
75+
prompter = Prompter(
76+
prompt_func=lambda x: x["instruction"],
77+
model_name="gpt-4o-mini",
78+
)
79+
80+
result = prompter(dataset=dataset1, working_dir=str(tmp_path))
81+
assert result.to_pandas().iloc[0]["response"] == "1"
82+
83+
result = prompter(dataset=dataset2, working_dir=str(tmp_path))
84+
assert result.to_pandas().iloc[0]["response"] == "2"
85+
86+
# Count cache directories, excluding metadata.db
87+
cache_dirs = [d for d in tmp_path.glob("*") if d.name != "metadata.db"]
88+
assert len(cache_dirs) == 2, f"Expected 2 cache directory but found {len(cache_dirs)}"
89+
90+
91+
def test_nested_call_caching(tmp_path):
92+
"""Test that changing a nested upstream function invalidates the cache."""
93+
94+
def value_generator():
95+
return 1
96+
97+
def prompt_func():
98+
return f"Say '{value_generator()}'. Do not explain."
99+
100+
prompter = Prompter(
101+
prompt_func=prompt_func,
102+
model_name="gpt-4o-mini",
103+
)
104+
result = prompter(working_dir=str(tmp_path))
105+
assert result.to_pandas().iloc[0]["response"] == "1"
106+
107+
def value_generator():
108+
return 2
109+
110+
result = prompter(working_dir=str(tmp_path))
111+
assert result.to_pandas().iloc[0]["response"] == "2"
112+
113+
# Count cache directories, excluding metadata.db
114+
cache_dirs = [d for d in tmp_path.glob("*") if d.name != "metadata.db"]
115+
assert len(cache_dirs) == 2, f"Expected 2 cache directory but found {len(cache_dirs)}"

0 commit comments

Comments
 (0)