Skip to content

Commit

Permalink
Use Jinja2 instead of Mako
Browse files Browse the repository at this point in the history
  • Loading branch information
rlouf committed Apr 19, 2023
1 parent 294e3e1 commit 2989c36
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 60 deletions.
20 changes: 10 additions & 10 deletions examples/meta_prompting.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
def split_into_steps(question, model_name: str):
@text.completion(model_name)
def solve(question):
"""${question}
"""{{question}}
Let's solve this problem by splitting it into steps.
"""

Expand All @@ -29,14 +29,14 @@ def solve(question):
def fill_in_the_blanks(question, model_name: str):
@text.completion(model_name, stops_at=["."])
def determine_goal(question):
"""${question}
"""{{question}}
In order to solve this problem, we will analyze each of the options and determine
"""

@text.completion(model_name, stops_at=["."])
def solve(memory):
"""${memory}. Let's begin."""
"""{{memory}}. Let's begin."""

_, completed = determine_goal(question)
_, completed = solve(completed)
Expand All @@ -48,7 +48,7 @@ def ask_an_expert(question, model_name: str):
@text.completion(model_name, stops_at=['"'])
def find_expert(question):
"""
${question}
{{question}}
I entered my question into the Expert Generator
and waited. The Expert Generator will render a
simulation of an expert to answer my question.
Expand All @@ -66,10 +66,10 @@ def find_expert(question):
@text.completion(model_name)
def get_answer(question, expert, memory):
"""
${memory}
{{memory}}
I am ready to ask my question.
"${expert}" I say,
${question}
"{{expert}}" I say,
{{question}}
"""

expert, completed = find_expert(question)
Expand All @@ -82,16 +82,16 @@ def ask_an_expert_simple(question, model_name: str):
@text.completion(model_name, stops_at=["\n", "."])
def find_expert(question):
"""
Q: ${question}
Q: {{question}}
A: A good person to answer this question would be
"""

@text.completion(model_name)
def get_answer(expert, memory):
"""
${memory}.
{{memory}}.
For instance,${expert} would answer
For instance,{{expert}} would answer
"""

expert, completed = find_expert(question)
Expand Down
31 changes: 8 additions & 23 deletions outlines/text.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import inspect
import re
from typing import Any, Callable, Dict, List, Optional, Tuple, cast

from mako.template import Template
from jinja2 import Template


def render(template: str, **values: Optional[Dict[str, Any]]) -> str:
r"""Parse a Mako template and translate it into an Outlines graph.
r"""Parse a Jinaj2 template and translate it into an Outlines graph.
This function removes extra whitespaces and linebreaks from templates to
allow users to enter prompt more naturally than if they used Python's
Expand All @@ -15,10 +14,10 @@ def render(template: str, **values: Optional[Dict[str, Any]]) -> str:
Examples
--------
Outlines follow Mako's syntax
Outlines follow Jinja2's syntax
>>> import outlines
>>> outline = outlines.render("I like ${food} and ${sport}", food="tomatoes", sport="tennis")
>>> outline = outlines.render("I like {{food}} and {{sport}}", food="tomatoes", sport="tennis")
I like tomatoes and tennis
If the first line of the template is empty, `render` removes it
Expand Down Expand Up @@ -72,21 +71,10 @@ def render(template: str, **values: Optional[Dict[str, Any]]) -> str:
>>> render(tpl)
... 'First Line\n Second Line'
Finally, `render` removes the indentation introduced when using `\` to
escape linebreaks:
>>> tpl = '''
... Long test \
... That we break'''
>>> tpl
'\n Long test That we break'
>>> render(tpl)
'Long test That we break'
Parameters
----------
template
A string that contains a template written in the Mako syntax.
A string that contains a template written with the Jinja2 syntax.
**values
Map from the variables in the template to their value.
Expand All @@ -99,12 +87,9 @@ def render(template: str, **values: Optional[Dict[str, Any]]) -> str:
# Dedent, and remove extra linebreak
template = inspect.cleandoc(template)

# Remove extra whitespace due to linebreaks with "\"
# TODO: this will remove indentation, we need to only remove
# whitespaces when the sequence does not start with `\n`
template = re.sub(" +", " ", template)

mako_template = Template(template)
mako_template = Template(
template, trim_blocks=True, lstrip_blocks=True, keep_trailing_newline=False
)
return mako_template.render(**values)


Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ classifiers = [
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
dependencies = [
"mako",
"jinja2",
"pillow",
"rich"
]
Expand Down
61 changes: 35 additions & 26 deletions tests/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ def test_render():
"""
assert text.render(tpl) == "A test\nAnother test"

tpl = """
A test line
An indented line
"""
assert text.render(tpl) == "A test line\n An indented line"


@pytest.mark.xfail(
reason="We need a regexp that can match whitespace sequences except those that follow a linebreak"
)
def test_render_escaped_linebreak():
tpl = """
A long test \
that we break \
Expand All @@ -32,53 +43,51 @@ def test_render():
assert text.render(tpl) == "A long test that we break in several lines"


@pytest.mark.xfail(reason="The regex used to strip whitespaces is too aggressive")
def test_render_indented():
tpl = """
A test line
An indented line
def test_render_jinja():
"""Make sure that we can use basic Jinja2 syntax, and give examples
of how we can use it for basic use cases.
"""
assert text.render(tpl) == "A test line\n An indented line"


@pytest.mark.xfail(reason="Mako adds newlines after for and if blocks")
def test_render_mako():
"""Make sure that we can use basic Mako syntax."""
# Notice the newline after the end of the loop
examples = ["one", "two"]
prompt = text.render(
"""
% for e in examples:
Example: ${e}
% endfor
""",
{% for e in examples %}
Example: {{e}}
{% endfor -%}""",
examples=examples,
)
assert prompt == "Example: one\nExample: two"
assert prompt == "Example: one\nExample: two\n"

# We can remove the newline by cloing with -%}
examples = ["one", "two"]
prompt = text.render(
"""
% for i, e in enumerate(examples):
Example ${i}: ${e}
% endfor
""",
{% for e in examples %}
Example: {{e}}
{% endfor -%}
Final""",
examples=examples,
)
assert prompt == "Example 0: one\nExample 1: two"
assert prompt == "Example: one\nExample: two\nFinal"

# Same for conditionals
tpl = """
% if is_true:
{% if is_true %}
true
% endif
{% endif -%}
final
"""
assert text.render(tpl, is_true=True) == "true"
assert text.render(tpl, is_true=False) == ""
assert text.render(tpl, is_true=True) == "true\nfinal"
assert text.render(tpl, is_true=False) == "final"


def test_prompt_basic():
@text.prompt
def test_tpl(variable):
"""${variable} test"""
"""{{variable}} test"""

with pytest.raises(TypeError):
test_tpl(v="test")
Expand All @@ -100,7 +109,7 @@ def test_single_quote_tpl(variable):
def test_prompt_kwargs():
@text.prompt
def test_kwarg_tpl(var, other_var="other"):
"""${var} and ${other_var}"""
"""{{var}} and {{other_var}}"""

p = test_kwarg_tpl("test")
assert p == "test and other"
Expand Down

0 comments on commit 2989c36

Please sign in to comment.