-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sweep: Signature
prompt skeleton
#68
Comments
🚀 Here's the PR! #77See Sweep's progress at the progress dashboard! 💎 Sweep Pro: I'm using GPT-4. You have unlimited GPT-4 tickets. (tracking ID:
5bb0f61e79 )Actions (click)
Sandbox Execution ✓Here are the sandbox execution logs prior to making any changes: Sandbox logs for
|
**DSPy** is the framework for solving advanced tasks with language models (LMs) and retrieval models (RMs). **DSPy** unifies techniques for **prompting** and **fine-tuning** LMs — and approaches for **reasoning**, **self-improvement**, and **augmentation with retrieval and tools**. All of these are expressed through modules that compose and learn. | |
To make this possible: | |
- **DSPy** provides **composable and declarative modules** for instructing LMs in a familiar Pythonic syntax. It upgrades "prompting techniques" like chain-of-thought and self-reflection from hand-adapted _string manipulation tricks_ into truly modular _generalized operations that learn to adapt to your task_. | |
- **DSPy** introduces an **automatic compiler that teaches LMs** how to conduct the declarative steps in your program. Specifically, the **DSPy compiler** will internally _trace_ your program and then **craft high-quality prompts for large LMs (or train automatic finetunes for small LMs)** to teach them the steps of your task. | |
dspy/dspy/teleprompt/signature_opt.py
Lines 26 to 208 in bd7a12d
""" | |
class BasicGenerateInstruction(Signature): | |
"""You are an instruction optimizer for large language models. I will give you a ``signature`` of fields (inputs and outputs) in English. Your task is to propose an instruction that will lead a good language model to perform the task well. Don't be afraid to be creative.""" | |
basic_instruction = dspy.InputField(desc="The initial instructions before optimization") | |
proposed_instruction = dspy.OutputField(desc="The improved instructions for the language model") | |
proposed_prefix_for_output_field = dspy.OutputField(desc="The string at the end of the prompt, which will help the model start solving the task") | |
class GenerateInstructionGivenAttempts(Signature): | |
"""You are an experienced instruction optimizer for large language models. I will give some task instructions I've tried, along with their corresponding validation scores. The instructions are arranged in increasing order based on their scores, where higher scores indicate better quality. Your task is to propose a new instruction that will lead a good language model to perform the task even better. Don't be afraid to be creative.""" | |
attempted_instructions = dspy.InputField(format=dsp.passages2text) | |
proposed_instruction = dspy.OutputField(desc="The improved instructions for the language model") | |
proposed_prefix_for_output_field = dspy.OutputField(desc="The string at the end of the prompt, which will help the model start solving the task") | |
class SignatureOptimizer(Teleprompter): | |
def __init__(self, metric=None, breadth=10, depth=3, init_temperature=1.4, prompt_model="gpt-3.5-turbo-1106", verbose=False): | |
self.metric = metric | |
self.breadth = breadth | |
self.depth = depth | |
self.init_temperature = init_temperature | |
self.prompt_model = prompt_model | |
self.verbose = verbose | |
def _check_candidates_equal(self, candidate1, candidate2): | |
for p1, p2 in zip(candidate1["program"].predictors(), candidate2["program"].predictors()): | |
if not p1.extended_signature.instructions == p2.extended_signature.instructions: | |
return False | |
if not p1.extended_signature.fields[-1] == p2.extended_signature.fields[-1]: | |
return False | |
return True | |
def _drop_duplicates(self, candidates): | |
final_candidates = [] | |
last_batch = [] | |
last_batch_score = -1 | |
for c in candidates: | |
repeat = False | |
if c['score'] == last_batch_score: | |
for c2 in last_batch: | |
if (self._check_candidates_equal(c, c2)): | |
repeat = True | |
break | |
if not repeat: | |
last_batch.append(c) | |
else: | |
last_batch = [c] | |
last_batch_score = c['score'] | |
if not repeat: | |
final_candidates.append(c) | |
return final_candidates | |
def compile(self, student, *, devset, eval_kwargs): | |
"""student is a program that needs to be optimized, note that it may be zero-shot or already pre-optimized for demos != []""" | |
module = student.deepcopy() | |
evaluate = Evaluate(devset=devset, metric=self.metric, **eval_kwargs) | |
total_calls = 0 | |
candidates = {} | |
evaluated_candidates = {} | |
# Seed the prompt optimizer zero shot with just the instruction, generate BREADTH new prompts | |
for predictor in module.predictors(): | |
basic_instruction = predictor.extended_signature.instructions | |
basic_prefix = predictor.extended_signature.fields[-1].name | |
with dspy.settings.context(lm=self.prompt_model): | |
instruct = dspy.Predict(BasicGenerateInstruction, n=self.breadth-1, temperature=self.init_temperature)(basic_instruction=basic_instruction) | |
# Add in our initial prompt as a candidate as well | |
instruct.completions.proposed_instruction.append(basic_instruction) | |
instruct.completions.proposed_prefix_for_output_field.append(basic_prefix) | |
candidates[id(predictor)] = instruct.completions | |
evaluated_candidates[id(predictor)] = [] | |
latest_candidates = candidates | |
all_candidates = candidates | |
module_clone = module.deepcopy() | |
# For each iteration in depth... | |
for d in range(self.depth): | |
if (self.verbose): | |
print(f"Starting iteration {d}/{self.depth}.") | |
# Go through our module's predictors | |
for p_old, p_new in zip(module.predictors(), module_clone.predictors()): | |
candidates_ = latest_candidates[id(p_old)] # Use the most recently generated candidates for evaluation | |
if len(module.predictors()) > 1: | |
candidates_ = all_candidates[id(p_old)] # Unless our program has multiple predictors, in which case we need to reevaluate all prompts with the new prompt(s) for the other predictor(s) | |
# For each candidate | |
for c in candidates_: | |
# Get the candidate instruction and prefix | |
instruction, prefix = c.proposed_instruction.strip('"').strip(), c.proposed_prefix_for_output_field.strip('"').strip() | |
# Set this new module with our instruction / prefix | |
p_new.extended_signature.instructions = instruction | |
p_new.extended_signature.fields[-1] = p_new.extended_signature.fields[-1]._replace(name=prefix) | |
# Score the instruction / prefix | |
if (self.verbose): | |
print(f"----------------") | |
for i,predictor in enumerate(module_clone.predictors()): | |
print(f"Predictor {i}") | |
print(f"i: {predictor.extended_signature.instructions}") | |
print(f"p: {predictor.extended_signature.fields[-1].name}") | |
print() | |
score = evaluate(module_clone, devset=devset, **eval_kwargs) | |
total_calls += 1 | |
if (self.verbose): | |
print(f"----------------") | |
# Add it to our evaluated candidates list | |
evaluated_candidates[id(p_old)].append({ | |
"score": score, | |
"program": module_clone.deepcopy(), | |
"instruction": instruction, | |
"prefix": prefix, | |
"depth": d | |
}) | |
# Now that we've evaluated the candidates, set this predictor to the best performing version | |
# to ensure the next round of scores reflect the best possible version | |
best_candidate = max(evaluated_candidates[id(p_old)], key=lambda candidate: candidate['score']) | |
p_new.extended_signature.instructions = best_candidate["instruction"] | |
p_new.extended_signature.fields[-1] = p_new.extended_signature.fields[-1]._replace(name=best_candidate["prefix"]) | |
if (self.verbose): | |
print(f"Updating Predictor {id(p_old)} to:\ni: {best_candidate['instruction']}\np: {best_candidate['prefix']}") | |
print(f"Full predictor with update: ") | |
for i,predictor in enumerate(module_clone.predictors()): | |
print(f"Predictor {i}") | |
print(f"i: {predictor.extended_signature.instructions}") | |
print(f"p: {predictor.extended_signature.fields[-1].name}") | |
print() | |
for predictor_candidate_list in evaluated_candidates.values(): | |
predictor_candidate_list.sort(key=lambda x: x['score'], reverse=True) | |
if d == self.depth-1: | |
break | |
# Build Few-Shot Example of Optimized Prompts | |
attempts = {} | |
shortest_len = self.breadth | |
for p in module.predictors(): | |
attempts[id(p)] = [] # Initialize as empty list | |
shortest_len = min(len(evaluated_candidates[id(p)]),shortest_len) | |
for i in range(shortest_len-1,-1,-1): | |
for p_base in module.predictors(): | |
attempts[id(p_base)].append(f'Instruction #{shortest_len-i}: {evaluated_candidates[id(p_base)][i]["instruction"]}') | |
attempts[id(p_base)].append(f'Prefix #{shortest_len-i}: {evaluated_candidates[id(p_base)][i]["prefix"]}') | |
attempts[id(p_base)].append(f'Resulting Score #{shortest_len-i}: {evaluated_candidates[id(p_base)][i]["score"]}') | |
# Generate next batch of potential prompts to optimize, with previous attempts as input | |
new_candidates = {} | |
for predictor in module.predictors(): | |
with dspy.settings.context(lm=self.prompt_model): | |
instr = dspy.Predict(GenerateInstructionGivenAttempts, n=self.breadth, temperature=self.init_temperature)(attempted_instructions=attempts[id(predictor)]) | |
if (self.verbose): | |
print(f"{self.prompt_model.inspect_history(n=1)}") | |
# Get candidates for each predictor | |
new_candidates[id(predictor)] = instr.completions | |
all_candidates[id(predictor)].proposed_instruction.extend(instr.completions.proposed_instruction) | |
all_candidates[id(predictor)].proposed_prefix_for_output_field.extend(instr.completions.proposed_prefix_for_output_field) | |
latest_candidates = new_candidates | |
for candidate_list in evaluated_candidates.values(): | |
candidate_list.sort(key=lambda x: x['score'], reverse=True) | |
best_program = evaluated_candidates[id(p_old)][0]["program"] # Uses python loose scoping to get the best candidate from the last round | |
candidates = [] | |
for predictor in module.predictors(): | |
candidates.extend(evaluated_candidates[id(predictor)]) | |
candidates.sort(key=lambda x: x['score'], reverse=True) | |
candidates = self._drop_duplicates(candidates) | |
best_program.candidate_programs = candidates | |
best_program.total_calls = total_calls |
dspy/dsp/primitives/compiler.py
Lines 1 to 170 in bd7a12d
import os | |
import time | |
import tqdm | |
import ujson | |
import random | |
import subprocess | |
import dsp | |
from datasets.fingerprint import Hasher | |
if os.environ.get('DSP_NOTEBOOK_CACHEDIR'): | |
training_data_directory = os.path.join(os.environ.get('DSP_NOTEBOOK_CACHEDIR'), 'compiler') | |
else: | |
training_data_directory = 'cache/compiler' | |
compilations_assumed_to_exist={'ft-zvEdzQVQ5xwlxvNPrxl6kpnw': 'ada:ft-stanfordpraglab-2023-02-09-19-50-49'} | |
def openai_check_finetune(jobname): | |
if dsp.settings.force_reuse_cached_compilation and jobname in compilations_assumed_to_exist: | |
return compilations_assumed_to_exist[jobname] | |
command = f"""openai api fine_tunes.get -i {jobname}""" | |
print(command) | |
result = subprocess.run(command.split(), stdout=subprocess.PIPE) | |
output = result.stdout.decode("utf-8").strip() | |
try: | |
output = ujson.loads(output) | |
if output['status'] == 'succeeded': | |
return output['fine_tuned_model'] | |
if output['status'] in ['pending', 'running']: | |
print(f'Compiling, run ```openai api fine_tunes.follow -i {jobname}``` for details...') | |
time.sleep(60) | |
return openai_check_finetune(jobname) | |
except: | |
pass | |
return False | |
def convert_to_training_point2(y, inputs, outputs, template): | |
assert len(inputs) + len(outputs) == len(template.fields) | |
y_ = dsp.Example(**{f: y[f] for f in inputs}, demos=[]) | |
prompt = template(y_, show_guidelines=False) | |
completion = y[outputs[0]] | |
output_fields = template.fields[len(inputs):] | |
for field in output_fields[1:]: | |
completion += f"\n\n{field.name} " + y[field.output_variable] | |
completion = " " + completion + " </s>" | |
return {'prompt': prompt, 'completion': completion} | |
def simulate(program, input_examples): | |
training_data = [] | |
for input_example in tqdm.tqdm(input_examples): | |
prediction = program(input_example) | |
if prediction is not None: | |
# assert len(prediction.compiling_stages) == 2, "TMP" | |
for stage in prediction.compiling_stages: | |
name, template, inputs, outputs = stage['name'], stage['template'], stage['inputs'], stage['outputs'] | |
training_data.append(convert_to_training_point2(prediction.get(name), inputs, outputs, template)) | |
r = random.Random(0) | |
r.shuffle(training_data) | |
return training_data | |
def openai_finetune_(name, target): | |
training_data_path = name_to_path(name) | |
# Launch the fine-tune on the path | |
command = f"""openai api fine_tunes.create -t {training_data_path} -m {target} --n_epochs 4 --learning_rate_multiplier 0.05 --no_check_if_files_exist""" | |
print(command) | |
# command = """python script.py""" | |
process = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
while line := process.stdout.readline().decode().strip(): | |
if 'created fine-tune:' in line.lower(): | |
jobname = line.split()[-1] | |
break | |
# if 'costs $' in line.lower(): | |
# cost = line.split()[-1] | |
# break | |
# assert cost[0] == '$' | |
# if float(cost[1:]) > 300: | |
# print(f'Got cost {cost} -- you may wanna cancel the job: openai api fine_tunes.cancel -i {jobname}') | |
# print(cost) | |
print(jobname) | |
# Block until it's done | |
ft = openai_check_finetune(jobname) | |
assert ft, ft | |
# Return its name | |
return (jobname, ft) | |
def openai_finetune(name, target): | |
print(name) | |
training_data_path = name_to_path(name) | |
training_data_path += '.model' | |
# if path + stuff exists, load the tuple from it | |
try: | |
with open(training_data_path) as f: | |
jobname, ft = ujson.loads(f.readline()) | |
if openai_check_finetune(jobname): | |
return jobname, ft | |
except: | |
pass | |
jobname, ft = openai_finetune_(name, target) | |
with open(training_data_path, 'w') as f: | |
f.write(ujson.dumps((jobname, ft)) + '\n') | |
return jobname, ft | |
def name_to_path(name): | |
if not os.path.exists(training_data_directory): | |
os.makedirs(training_data_directory) | |
training_data_path = os.path.join(training_data_directory, f'{name}.jsonl') | |
return training_data_path | |
# 3. Check that the output file name has status "success" (not deleted or non-existent). Otherwise, re-call with n = n+1. | |
def finetune(training_data, target): | |
name = Hasher.hash(training_data) | |
training_data_path = name_to_path(name) | |
with open(training_data_path, 'w') as f: | |
for line in training_data: | |
f.write(ujson.dumps(line) + '\n') | |
jobname, ft = openai_finetune(name, target) | |
print(ft) | |
ft = dsp.GPT3(model=ft, stop=" </s>") | |
return ft | |
# 4. Return updated program. | |
def compile(program, examples, target='ada'): | |
training_data = simulate(program, examples) | |
compiled_lm = finetune(training_data, target=target) | |
def compiled_program(*args, **kwargs): | |
with dsp.settings.context(compiled_lm=compiled_lm, compiling=False): | |
return program(*args, **kwargs) | |
compiled_program.lm = compiled_lm |
dspy/dsp/templates/template_v2.py
Lines 1 to 287 in bd7a12d
from collections import namedtuple | |
import re | |
from typing import Union, Any | |
import dsp | |
from dsp.primitives.demonstrate import Example | |
from .utils import passages2text, format_answers | |
Field = namedtuple("Field", "name separator input_variable output_variable description") | |
# TODO: de-duplicate with dsp/templates/template.py | |
class TemplateV2: | |
def __init__( | |
self, | |
template, | |
format_handlers={ | |
"passages": passages2text, | |
"context": passages2text, | |
"answer": format_answers, | |
"answers": format_answers, | |
}, | |
): | |
self.format_handlers = format_handlers | |
template = template.strip() | |
self.instructions = re.search("(.*)\n", template).group(1) | |
template = template[len(self.instructions) :].strip() | |
self.fields = [] | |
while len(template) > 0: | |
match = re.search("(.*)(\s){(.*)}\s(.*\${.*})", template) | |
if match is not None: | |
name = match.group(1) | |
separator = match.group(2) | |
variable = match.group(3) | |
description = match.group(4) | |
else: | |
match = re.search("(.*)(\s){(.*)}", template) | |
if match is not None: | |
name = match.group(1) | |
separator = match.group(2) | |
variable = match.group(3) | |
description = None | |
else: | |
raise ValueError(f"Could not parse template") | |
var_match = re.match("(.*) -> (.*)", variable) | |
if var_match is not None: | |
input_variable = var_match.group(1) | |
output_variable = var_match.group(2) | |
else: | |
input_variable = variable | |
output_variable = variable | |
self.fields.append( | |
Field( | |
name=name, | |
separator=separator, | |
input_variable=input_variable, | |
output_variable=output_variable, | |
description=description, | |
) | |
) | |
template = template[len(match.group(0)) :].strip() | |
def query(self, example: Example, is_demo: bool = False) -> str: | |
"""Retrieves the input variables from the example and formats them into a query string.""" | |
result: list[str] = [] | |
if not is_demo: | |
has_value = [ | |
field.input_variable in example | |
and example[field.input_variable] is not None | |
and example[field.input_variable] != "" | |
for field in self.fields | |
] | |
for i in range(1, len(has_value)): | |
if has_value[i - 1] and not any(has_value[i:]): | |
example[self.fields[i].input_variable] = "" | |
break | |
for field in self.fields: | |
if ( | |
field.input_variable in example | |
and example[field.input_variable] is not None | |
): | |
if field.input_variable in self.format_handlers: | |
format_handler = self.format_handlers[field.input_variable] | |
else: | |
def format_handler(x): | |
return " ".join(x.split()) | |
formatted_value = format_handler(example[field.input_variable]) | |
separator = '\n' if field.separator == ' ' and '\n' in formatted_value else field.separator | |
result.append( | |
f"{field.name}{separator}{formatted_value}" | |
) | |
if self._has_augmented_guidelines() and ("augmented" in example and example.augmented): | |
return "\n\n".join([r for r in result if r]) | |
return "\n".join([r for r in result if r]) | |
def guidelines(self, show_guidelines=True) -> str: | |
"""Returns the task guidelines as described in the lm prompt""" | |
if (not show_guidelines) or ( | |
hasattr(dsp.settings, "show_guidelines") | |
and not dsp.settings.show_guidelines | |
): | |
return "" | |
result = "Follow the following format.\n\n" | |
example = dsp.Example() | |
for field in self.fields: | |
example[field.input_variable] = field.description | |
example.augmented = self._has_augmented_guidelines() | |
result += self.query(example) | |
return result | |
def _has_augmented_guidelines(self): | |
return len(self.fields) > 3 or any( | |
("\n" in field.separator) or ('\n' in field.description) for field in self.fields | |
) | |
def extract( | |
self, example: Union[Example, dict[str, Any]], raw_pred: str | |
) -> Example: | |
"""Extracts the answer from the LM raw prediction using the template structure | |
Args: | |
example (Union[Example, dict[str, Any]]): Contains the input variables that raw_pred was completed on. | |
raw_pred (str): LM generated string | |
Returns: | |
Example: The example with the output variables filled in | |
""" | |
example = dsp.Example(example) | |
raw_pred = raw_pred.strip() | |
idx = 0 | |
while idx < len(self.fields): | |
if ( | |
self.fields[idx].input_variable not in example | |
or example[self.fields[idx].input_variable] is None | |
): | |
break | |
idx += 1 | |
import dspy | |
idx = min(idx, len(self.fields) - 1) | |
while raw_pred != "" and idx < len(self.fields): | |
if idx < len(self.fields) - 1: | |
next_field_name = "\n" + self.fields[idx + 1].name | |
offset = raw_pred.find(next_field_name) | |
if offset >= 0: | |
if dspy.settings.release >= 20231003: | |
example[self.fields[idx].output_variable] = raw_pred[:offset].strip().rstrip('---').strip() | |
raw_pred = raw_pred[offset + len(next_field_name) :].strip().rstrip('---').strip() | |
else: | |
example[self.fields[idx].output_variable] = raw_pred[:offset].strip() | |
raw_pred = raw_pred[offset + len(next_field_name) :].strip() | |
idx += 1 | |
else: | |
if dspy.settings.release >= 20231003: | |
example[self.fields[idx].output_variable] = raw_pred.strip().rstrip('---').strip() | |
else: | |
example[self.fields[idx].output_variable] = raw_pred.strip() | |
raw_pred = "" | |
idx += 1 | |
break | |
else: | |
assert idx == len(self.fields) - 1, (idx, len(self.fields)) | |
if dspy.settings.release >= 20231003: | |
example[self.fields[idx].output_variable] = raw_pred.strip().rstrip('---').strip() | |
else: | |
example[self.fields[idx].output_variable] = raw_pred.strip() | |
break | |
return example | |
def __call__(self, example, show_guidelines=True) -> str: | |
example = dsp.Example(example) | |
if hasattr(dsp.settings, 'query_only') and dsp.settings.query_only: | |
return self.query(example) | |
# The training data should not contain the output variable | |
if self.fields[-1].input_variable in example: | |
del example[self.fields[-1].input_variable] | |
rdemos = [ | |
self.query(demo, is_demo=True) | |
for demo in example.demos | |
if ( | |
("augmented" not in demo or not demo.augmented) | |
and ( # validate that the training example has the same primitive input var as the template | |
self.fields[-1].input_variable in demo | |
and demo[self.fields[-1].input_variable] is not None | |
) | |
) | |
] | |
ademos = [ | |
self.query(demo, is_demo=True) | |
for demo in example.demos | |
if "augmented" in demo and demo.augmented | |
] | |
# Move the rdemos to ademos if rdemo has all the fields filled in | |
rdemos_ = [] | |
new_ademos = [] | |
for rdemo in rdemos: | |
if all( | |
(field.name in rdemo) | |
for field in self.fields | |
if field.input_variable in example | |
): | |
import dspy | |
if dspy.settings.release >= 20230928: | |
new_ademos.append(rdemo) | |
else: | |
ademos.append(rdemo) | |
else: | |
rdemos_.append(rdemo) | |
ademos = new_ademos + ademos | |
rdemos = rdemos_ | |
long_query = self._has_augmented_guidelines() | |
if long_query: | |
example["augmented"] = True | |
query = self.query(example) | |
# if it has more lines than fields | |
if len(query.split('\n')) > len(self.fields): | |
long_query = True | |
if "augmented" not in example or not example.augmented: | |
example["augmented"] = True | |
query = self.query(example) | |
rdemos = "\n\n".join(rdemos) | |
if len(rdemos) >= 1 and len(ademos) == 0 and not long_query: | |
rdemos_and_query = "\n\n".join([rdemos, query]) | |
parts = [ | |
self.instructions, | |
self.guidelines(show_guidelines), | |
rdemos_and_query, | |
] | |
elif len(rdemos) == 0: | |
parts = [ | |
self.instructions, | |
self.guidelines(show_guidelines), | |
*ademos, | |
query, | |
] | |
else: | |
parts = [ | |
self.instructions, | |
rdemos, | |
self.guidelines(show_guidelines), | |
*ademos, | |
query, | |
] | |
prompt = "\n\n---\n\n".join([p.strip() for p in parts if p]) | |
dspy/dsp/templates/template_v3.py
Lines 1 to 71 in bd7a12d
from typing import Callable | |
from dsp.templates import TemplateV2, passages2text, format_answers, Field | |
class Type: | |
"""A primitive datatype that defines and represents a prompt label.""" | |
def __init__(self, prefix: str, desc: str, format=None) -> None: | |
self.prefix = prefix | |
self.desc = desc | |
self.format = format | |
def __call__(self, **kwargs): | |
kwargs = {**self.__dict__, **kwargs} | |
return Type(**kwargs) | |
def __eq__(self, __value: object) -> bool: | |
return isinstance(__value, Type) and self.__dict__ == __value.__dict__ | |
class Template(TemplateV2): | |
"""A template datatype that represents the structure of communicate with the LM.""" | |
def __init__(self, instructions: str, **kwargs): | |
self.instructions = instructions | |
self.kwargs = kwargs | |
self.fields: list[Field] = [] | |
self.format_handlers: dict[str, Callable] = { | |
"context": passages2text, | |
"passages": passages2text, | |
"answers": format_answers, | |
} | |
for key, value in kwargs.items(): | |
prefix: str = value.prefix | |
separator: str = ( | |
" " if prefix.rstrip() == prefix and len(prefix) > 0 else prefix[len(prefix.rstrip()) :] | |
) | |
field = Field( | |
name=prefix.strip(), | |
description=value.desc, | |
input_variable=key, | |
output_variable=key, | |
separator=separator, | |
) | |
self.fields.append(field) | |
if value.format: | |
self.format_handlers[key] = value.format | |
# equality | |
def __eq__(self, other): | |
if set(self.kwargs.keys()) != set(other.kwargs.keys()): | |
print('here2') | |
return False | |
for k in self.kwargs.keys(): | |
v1, v2 = self.kwargs[k], other.kwargs[k] | |
if not v1 == v2: | |
print(k, v1, v2) | |
# print("here?", self.instructions == other.instructions, self.kwargs == other.kwargs) | |
return self.instructions == other.instructions and self.kwargs == other.kwargs | |
def __str__(self) -> str: | |
# field names | |
field_names = [field.name for field in self.fields] | |
Step 2: ⌨️ Coding
Modify dspy/teleprompt/signature_opt.py with contents:
• Modify the `Signature` class to define dynamic placeholders within the signature. This can be done by adding a new attribute to the class that will hold the placeholders. The placeholders can be defined as a list of strings, where each string represents a placeholder in the signature.
• Update the `__init__` method of the `Signature` class to accept an additional argument that will be used to set the placeholders attribute.
• Update the methods of the `Signature` class that generate and manipulate the signature to incorporate the placeholders. This will involve modifying the methods to insert the placeholders into the appropriate positions in the signature.--- +++ @@ -25,6 +25,12 @@ """ class BasicGenerateInstruction(Signature): + placeholders = [] + + def __init__(self, placeholders=None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.placeholders = placeholders if placeholders is not None else [] + """You are an instruction optimizer for large language models. I will give you a ``signature`` of fields (inputs and outputs) in English. Your task is to propose an instruction that will lead a good language model to perform the task well. Don't be afraid to be creative.""" basic_instruction = dspy.InputField(desc="The initial instructions before optimization") @@ -120,7 +126,7 @@ instruction, prefix = c.proposed_instruction.strip('"').strip(), c.proposed_prefix_for_output_field.strip('"').strip() # Set this new module with our instruction / prefix - p_new.extended_signature.instructions = instruction + p_new.extended_signature.instructions = instruction.format(*p_new.extended_signature.placeholders) p_new.extended_signature.fields[-1] = p_new.extended_signature.fields[-1]._replace(name=prefix) # Score the instruction / prefix
- Running GitHub Actions for
dspy/teleprompt/signature_opt.py
✓ Edit
Check dspy/teleprompt/signature_opt.py with contents:Ran GitHub Actions for bfa03c255618d6a409d41584ab1f2605b0040dc7:
Create dsp/modules/signature_sampler.py with contents:
• Create a new Python file named `signature_sampler.py` in the `dsp/modules` directory.
• In this file, define a new class named `SignatureSampler` that will be responsible for sampling `k` signature variations.
• The `SignatureSampler` class should have a method named `sample` that accepts a list of signatures and a number `k` as arguments. This method should randomly select `k` signatures from the list and return them.
• Import the `Signature` class from `dspy/teleprompt/signature_opt.py` in `signature_sampler.py`.
- Running GitHub Actions for
dsp/modules/signature_sampler.py
✓ Edit
Check dsp/modules/signature_sampler.py with contents:Ran GitHub Actions for e9740c8a2e8a82b5a5153d1f44e39a83c021ea5e:
Create dsp/modules/module.py with contents:
• Modify the `Module` class to use the `SignatureSampler` class to sample signature variations and execute the resulting Python code.
• Import the `SignatureSampler` class from `dsp/modules/signature_sampler.py`.
• In the `execute` method of the `Module` class, use the `SignatureSampler` to sample `k` signature variations. Then, construct a prompt using the sampled signatures and the placeholders defined in the `Signature` class.
• Execute the Python code generated by the prompt and return the result.
- Running GitHub Actions for
dsp/modules/module.py
✓ Edit
Check dsp/modules/module.py with contents:Ran GitHub Actions for a47f75e332ce9ad2394321554d00c67912625fd3:
Modify dsp/primitives/compiler.py with contents:
• Modify the `Compiler` class to compile and persist prompts using the sampled Signature variations.
• Import the `SignatureSampler` class from `dsp/modules/signature_sampler.py`.
• In the `compile` method of the `Compiler` class, use the `SignatureSampler` to sample `k` signature variations. Then, construct a prompt using the sampled signatures and the placeholders defined in the `Signature` class.
• Compile the prompt and persist the result. This can be done by writing the compiled prompt to a file or storing it in a database.--- +++ @@ -7,6 +7,7 @@ import dsp from datasets.fingerprint import Hasher +from dsp.modules.signature_sampler import SignatureSampler if os.environ.get('DSP_NOTEBOOK_CACHEDIR'): training_data_directory = os.path.join(os.environ.get('DSP_NOTEBOOK_CACHEDIR'), 'compiler') @@ -159,9 +160,17 @@ return ft # 4. Return updated program. -def compile(program, examples, target='ada'): - training_data = simulate(program, examples) - compiled_lm = finetune(training_data, target=target) +def compile(self, program, examples, k, target='ada'): + signature_sampler = SignatureSampler() + sampled_signatures = signature_sampler.sample(program.signatures, k) + prompt = '' + for signature in sampled_signatures: + prompt += signature.instructions.format(*signature.placeholders) + compiled_prompt = program.compile(prompt) + # Here add code to persist the compiled prompt. + # This can be writing to a file or storing it in a database. + + compiled_lm = finetune(compiled_prompt, target=target) def compiled_program(*args, **kwargs): with dsp.settings.context(compiled_lm=compiled_lm, compiling=False):
- Running GitHub Actions for
dsp/primitives/compiler.py
✓ Edit
Check dsp/primitives/compiler.py with contents:Ran GitHub Actions for e450e00dbb0c0bd86a2eece039714086b8e55cce:
Create dsp/retriever/retriever_interface.py with contents:
• Create a new Python file named `retriever_interface.py` in the `dsp/retriever` directory.
• In this file, define a new class named `RetrieverInterface` that will be responsible for interacting with the database of Signatures.
• The `RetrieverInterface` class should have a method named `retrieve` that accepts a query as an argument. This method should query the database for signatures that match the query and return them.
- Running GitHub Actions for
dsp/retriever/retriever_interface.py
✓ Edit
Check dsp/retriever/retriever_interface.py with contents:Ran GitHub Actions for 136e2080a9902364a49d41c8be24895986bedf11:
Modify dsp/primitives/compiler.py with contents:
• Modify the `Compiler` class to use the `RetrieverInterface` to retrieve signatures from the database.
• Import the `RetrieverInterface` class from `dsp/retriever/retriever_interface.py`.
• In the `compile` method of the `Compiler` class, use the `RetrieverInterface` to retrieve signatures from the database. Then, use the `SignatureSampler` to sample `k` signature variations from the retrieved signatures.
• Construct a prompt using the sampled signatures and the placeholders defined in the `Signature` class, compile the prompt, and persist the result.--- +++ @@ -7,6 +7,8 @@ import dsp from datasets.fingerprint import Hasher +from dsp.retriever.retriever_interface import RetrieverInterface +from dsp.modules.signature_sampler import SignatureSampler if os.environ.get('DSP_NOTEBOOK_CACHEDIR'): training_data_directory = os.path.join(os.environ.get('DSP_NOTEBOOK_CACHEDIR'), 'compiler') @@ -159,9 +161,19 @@ return ft # 4. Return updated program. -def compile(program, examples, target='ada'): - training_data = simulate(program, examples) - compiled_lm = finetune(training_data, target=target) +def compile(self, program, examples, k, target='ada', database=None): + retriever = RetrieverInterface(database) + retrieved_signatures = retriever.retrieve("") # Add appropriate query if needed + signature_sampler = SignatureSampler() + sampled_signatures = signature_sampler.sample(retrieved_signatures, k) + prompt = '' + for signature in sampled_signatures: + prompt += signature.instructions.format(*signature.placeholders) + compiled_prompt = program.compile(prompt) + # Here add code to persist the compiled prompt. + # This can be writing to a file or storing it in a database. + + compiled_lm = finetune(compiled_prompt, target=target) def compiled_program(*args, **kwargs): with dsp.settings.context(compiled_lm=compiled_lm, compiling=False):
- Running GitHub Actions for
dsp/primitives/compiler.py
✓ Edit
Check dsp/primitives/compiler.py with contents:Ran GitHub Actions for 7a1b36b965c9527bd425b50a05dde635c1c612c3:
Step 3: 🔁 Code Review
I have finished reviewing the code for completeness. I did not find errors for sweep/signature_prompt_skeleton_1
.
🎉 Latest improvements to Sweep:
- We just released a dashboard to track Sweep's progress on your issue in real-time, showing every stage of the process – from search to planning and coding.
- Sweep uses OpenAI's latest Assistant API to plan code changes and modify code! This is 3x faster and significantly more reliable as it allows Sweep to edit code and validate the changes in tight iterations, the same way as a human would.
- Try using the GitHub issues extension to create Sweep issues directly from your editor! GitHub Issues and Pull Requests.
💡 To recreate the pull request edit the issue title or description. To tweak the pull request, leave a comment on the pull request.
Join Our Discord
Details
Summary
To enhance the generative capabilities of DSPy, we are proposing the integration of an "immutable" prompt "skeleton" mechanism that mimics DeepMind's FunSearch methodology as described in their paper and illustrated in Extended Data Figure 1. This feature will allow DSPy to merge ideas by sampling different signature variations and prompting the Language Model (LM) to fill in dynamic content within a consistent structural backdrop.
Background
According to DeepMind's research, their prompting structure involved selecting and sorting two programs from a database (referred to as priority v0 and priority v1), to encourage the LM to merge ideas. The prompt also included a new priority function with an empty body (priority v2) for the LM to complete. This process results in the generation of novel programs by leveraging existing knowledge and the generative power of the LM within a fixed format.
Goals
k
signature variations and integrate parts into the prompt skeleton for dynamic content population.Specifications
k
signature variations that should be external to the Signature class, potentially as part of the module or a dedicated retriever algorithm.Action Items
Additional Context
The implementation should be mindful of the DSPy's
Signature
class's existing architecture and mechanisms. The immutable prompt skeleton should function effectively with current input/output field constraints. The new feature should lead to the innovation of relevant and diverse solutions within the DSPy framework, leveraging the established work from DeepMind's research.Relevant Files
To guide the implementation and provide context around how and where the changes might integrate within the DSPy codebase, the following files have been identified as possibly relevant:
Ensure ALL action items are well thought out, thorough, tested, and finished.
Checklist
dspy/teleprompt/signature_opt.py
✓ bfa03c2 Editdspy/teleprompt/signature_opt.py
✓ Editdsp/modules/signature_sampler.py
✓ e9740c8 Editdsp/modules/signature_sampler.py
✓ Editdsp/modules/module.py
✓ a47f75e Editdsp/modules/module.py
✓ Editdsp/primitives/compiler.py
✓ e450e00 Editdsp/primitives/compiler.py
✓ Editdsp/retriever/retriever_interface.py
✓ 136e208 Editdsp/retriever/retriever_interface.py
✓ Editdsp/primitives/compiler.py
✓ 7a1b36b Editdsp/primitives/compiler.py
✓ EditThe text was updated successfully, but these errors were encountered: