Example: generating and running code
Warning: This notebook runs LLM-generated code without any checks. Run at your own risk.
Loading a code model:
[ ]:
from guidance import models, gen
import guidance
import re
model = models.Transformers("Qwen/Qwen2.5-Coder-1.5B")
Loading the HumanEval dataset:
[2]:
from datasets import load_dataset
dataset = load_dataset("openai_humaneval")
Let’s write a very simple baseline
[3]:
import re
import guidance
@guidance
def baseline(lm, prompt):
r = re.findall(r'def (.*?)\(', prompt)
name = r[-1]
lm += f'Here is an implementation of {name}:\n'
lm += '```python\n' + prompt + gen(max_tokens=200, stop_regex=r'```|if __name__|def test|\n[^\s]', name='program')
lm = lm.set('program', prompt + lm['program'])
return lm
[4]:
idx = 121
prompt = dataset['test']['prompt'][idx]
lm = model + baseline(prompt)
baseline_program = lm['program']
Here is simple function to evaluate a generated program with the HumanEval evaluation tests:
[5]:
# Returns True if it passes the evaluation tests, False otherwise
def eval_program(program, i):
namespace = {}
# Loads the `check` function
exec(dataset['test']['test'][i], namespace)
try:
# Executes the function definition
exec(program, namespace)
except Exception as e:
print("Program not valid")
print(e)
return False
name = dataset['test']['entry_point'][i]
try:
# Run the unit tests
namespace['check'](namespace[name])
# If we get here, we passed the tests
return True
except Exception as e:
print("Program ran, but hit exception")
print(e)
return False
[6]:
lm['program']
[6]:
'\ndef solution(lst):\n """Given a non-empty list of integers, return the sum of all of the odd elements that are in even positions.\n \n\n Examples\n solution([5, 8, 7, 1]) ==> 12\n solution([3, 3, 3, 3, 3]) ==> 9\n solution([30, 13, 24, 321]) ==>0\n """\n # Initialize the sum to 0\n sum = 0\n \n # Iterate over the list\n for i in range(len(lst)):\n # Check if the element is odd and in an even position\n if lst[i] % 2 != 0 and i % 2 == 0:\n # Add the element to the sum\n sum += lst[i]\n \n # Return the sum\n return sum\n'
[7]:
eval_program(lm['program'], idx)
[7]:
True
Let’s try another one:
[8]:
idx = 71
prompt = dataset['test']['prompt'][idx]
lm = model + baseline(prompt)
baseline_program = lm['program']
[9]:
eval_program(lm['program'], idx)
[9]:
True
Let’s manually check the basic example from the docstring:
[10]:
exec(lm['program'])
triangle_area(3, 4, 5) # should be 6.00
[10]:
6.0
[11]:
@guidance
def write_tests(lm, prompt):
r = re.findall(r'def (.*?)\(', prompt)
name = r[-1]
lm += '```python\n' + prompt + ' pass\n'
lm += f'\ndef test_{name}():\n'
lm += ' """Turns the example(s) in the docstring above into asserts"""\n'
examples = re.findall(rf'{re.escape(name)}\((.*?)\)\s*(?:==|=>)\s*([^\n]+)', prompt)
if not examples:
raise ValueError(f'Could not find docstring examples for {name}')
args = []
expected = []
for arg, result in examples:
arg = arg.strip()
result = result.strip()
lm += f' assert {name}({arg}) == {result}\n'
args.append(arg)
expected.append(result)
lm = lm.set('args', args)
lm = lm.set('expected', expected)
return lm
[12]:
lm = model + write_tests(prompt)
args = lm['args']
expected = lm['expected']
[13]:
# (input, expected output)
list(zip(lm['args'], lm['expected']))
[13]:
[('3, 4, 5', '6.00'), ('1, 2, 10', '-1')]
Let’s combine the baseline and the test generation prompts into a single guidance function:
[14]:
@guidance
def reconstruct_tests(lm, name, args, expected):
"""Helper to format tests nicely"""
lm += f'def test_{name}():\n'
for arg, e in zip(args, expected):
lm += f' assert {name}({arg}) == {e}\n'
return lm
@guidance
def add_program_and_tests(lm, name, program, args, expected):
"""Helper to format program and tests nicely"""
lm += f'Here is an implementation of {name}:\n'
lm += '```python\n'
lm += program + '\n'
lm += reconstruct_tests(name, args, expected) + '```\n'
return lm
def format_program_and_tests(name, program, args, expected):
tests = ''.join(f' assert {name}({arg}) == {e}\n' for arg, e in zip(args, expected))
return f'Here is an implementation of {name}:\n```python\n{program}\ndef test_{name}():\n{tests}```\n'
def baseline_and_tests(lm, prompt, program, args, expected):
r = re.findall(r'def (.*?)\(', prompt)
name = r[-1]
lm2 = lm.set('args', args)
lm2 = lm2.set('expected', expected)
lm2 = lm2.set('program', program)
lm2 = lm2.set('name', name)
lm2 += format_program_and_tests(name, program, args, expected)
return lm2
[15]:
lm = baseline_and_tests(model, prompt, baseline_program, args, expected)
[16]:
lm['args'], lm['expected']
[16]:
(['3, 4, 5', '1, 2, 10'], ['6.00', '-1'])
Now, if we have a generated program and a set of tests, we can write a guidance function that runs the tests and outputs the results:
[17]:
# Helper function to load the program
def load_program(name, program):
error = None
try:
exec(program, globals())
fn = eval(name)
except Exception as e:
fn = None
error = e
return fn, error
# Tolerance when x and y are floats
def equals(x, y):
if isinstance(x, float) and isinstance(y, float):
return abs(x - y) < 0.00001
else:
return x == y
@guidance
def run_tests(lm, name, program, args, expected):
fn, error = load_program(name, program)
all_pass = True
lm += 'Running the test(s) above gives:\n'
for arg, e in zip(args, expected):
# Reconstruct the test
lm += f'assert {name}({arg}) == {e}\n'
try:
arg = eval(arg)
expected_result = eval(e)
except:
continue
try:
if isinstance(arg, tuple):
r = fn(*arg)
else:
r = fn(arg)
except Exception as ex:
r = ex
if equals(r, expected_result):
lm += 'Assertion passed.\n'
else:
all_pass = False
lm += f'Assertion failed.\n'
lm += f'Expected: {e}\n'
lm += f'Actual: {r}\n'
lm += '---\n'
lm = lm.set('all_pass', all_pass)
return lm
[18]:
model + run_tests(lm['name'], lm['program'], lm['args'], lm['expected'])
[18]:
<guidance.models._transformers.Transformers at 0x12af1d760>
Now, we can put this all together into a function that gets the LM to rewrite the program when the tests don’t work:
[19]:
def run_tests_and_fix(lm, prompt, program, args, expected):
lm2 = baseline_and_tests(lm, prompt, program, args, expected)
name, program, args, expected = lm2['name'], lm2['program'], lm2['args'], lm2['expected']
i = 0
# Try this at most 3 times
while i != 3:
i += 1
lm2 += run_tests(name, program, args, expected)
# Passing the tests, I can stop.
if lm2['all_pass']:
break
lm2 += f'\n'
# Get the model to think about what's wrong
lm2 += f'My implementation of {name} is wrong, because''' + gen(stop='\n') + '\n'
lm2 += f'In order to fix it, I need to''' + gen(stop='\n') + '\n'
lm2 += f'Here is a fixed implementation:\n'
# Write a new program
lm2 += '```python\n' + prompt + gen(max_tokens=200, stop_regex=r'```|if __name__|def test|\n[^\s]', name='program')
lm2 += '```\n'
# Reset the slate, start over with new program
program = prompt + lm2['program']
lm2 = lm + format_program_and_tests(name, program, args, expected)
lm2 = lm2.set('program', program)
lm + 'ae' + gen(max_tokens=10)
return lm2
[20]:
lm = run_tests_and_fix(model, prompt, baseline_program, args, expected)
[21]:
program = lm['program']
exec(program)
print(triangle_area(3, 4, 5))
print(triangle_area(1, 2, 10))
6.0
-1
In this particular case, having more rounds allows the model to fix its program on the unit tests. Does it also result in a program that passes the evaluation tests?
[22]:
eval_program(program, idx)
[22]:
True
Yes indeed.
In conclusion, this notebook illustrates how easy it is to guide generation depending on what previous generations are (e.g. the test results depend on the current version of the code.)