diff --git a/docs/tutorial.md b/docs/tutorial.md index bd7313edc..a17aebb24 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -305,7 +305,7 @@ Bob lives at the following address: ## Calling code -The following script shows how to execute python code ([file](https://github.com/IBM/prompt-declaration-language//blob/main/examples/tutorial/calling_code.pdl)). The python code is executed locally (or in a containerized way if using `pdl --sandbox`). In principle, PDL is agnostic of any specific programming language, but we currently only support Python. Variables defined in PDL are copied into the global scope of the Python code, so those variables can be used directly in the code. However, mutating variables in Python has no effect on the variables in the PDL program. The result of the code must be assigned to the variable `result` internally to be propagated to the result of the block. A variable `def` on the code block will then be set to this result. +The following script shows how to execute python code ([file](https://github.com/IBM/prompt-declaration-language//blob/main/examples/tutorial/calling_code.pdl)). The python code is executed locally (or in a containerized way if using `pdl --sandbox`). In principle, PDL is agnostic of any specific programming language, but we currently only support Python, Jinja, and shell commands. Variables defined in PDL are copied into the global scope of the Python code, so those variables can be used directly in the code. However, mutating variables in Python has no effect on the variables in the PDL program. The result of the code must be assigned to the variable `result` internally to be propagated to the result of the block. A variable `def` on the code block will then be set to this result. In order to define variables that are carried over to the next Python code block, a special variable `PDL_SESSION` can be used, and variables assigned to it as fields. diff --git a/examples/hello/hello-code-command.pdl b/examples/hello/hello-code-command.pdl new file mode 100644 index 000000000..f7d0f267d --- /dev/null +++ b/examples/hello/hello-code-command.pdl @@ -0,0 +1,4 @@ +description: Hello world showing call out to shell command +lang: command +code: | + echo "Hello World!" diff --git a/examples/hello/hello-code-jinja.pdl b/examples/hello/hello-code-jinja.pdl new file mode 100644 index 000000000..ffdfef81c --- /dev/null +++ b/examples/hello/hello-code-jinja.pdl @@ -0,0 +1,6 @@ +description: Hello world showing call out to shell command +defs: + world: "World" +lang: jinja +code: | + Hello {{ world }}! diff --git a/pdl-live/src/pdl_ast.d.ts b/pdl-live/src/pdl_ast.d.ts index a7080591b..122e5cd59 100644 --- a/pdl-live/src/pdl_ast.d.ts +++ b/pdl-live/src/pdl_ast.d.ts @@ -2322,7 +2322,7 @@ export type Kind15 = "code"; * Programming language of the code. * */ -export type Lang = "python" | "command"; +export type Lang = "python" | "command" | "jinja"; /** * Code to execute. * diff --git a/src/pdl/pdl-schema.json b/src/pdl/pdl-schema.json index 35447a176..288f432d6 100644 --- a/src/pdl/pdl-schema.json +++ b/src/pdl/pdl-schema.json @@ -2691,7 +2691,8 @@ "description": "Programming language of the code.\n ", "enum": [ "python", - "command" + "command", + "jinja" ], "title": "Lang", "type": "string" diff --git a/src/pdl/pdl_ast.py b/src/pdl/pdl_ast.py index 9609a5faf..9d5aafe67 100644 --- a/src/pdl/pdl_ast.py +++ b/src/pdl/pdl_ast.py @@ -281,7 +281,7 @@ class CodeBlock(Block): """Execute a piece of code.""" kind: Literal[BlockKind.CODE] = BlockKind.CODE - lang: Literal["python", "command"] + lang: Literal["python", "command", "jinja"] """Programming language of the code. """ code: "BlocksType" diff --git a/src/pdl/pdl_interpreter.py b/src/pdl/pdl_interpreter.py index 22344bb70..4e60952d6 100644 --- a/src/pdl/pdl_interpreter.py +++ b/src/pdl/pdl_interpreter.py @@ -1261,8 +1261,18 @@ def step_call_code( loc=loc, trace=block.model_copy(update={"code": code_s}), ) from exc + case "jinja": + try: + result = call_jinja(code_s, scope) + background = [{"role": state.role, "content": result}] + except Exception as exc: + raise PDLRuntimeError( + f"Code error: {repr(exc)}", + loc=loc, + trace=block.model_copy(update={"code": code_s}), + ) from exc case _: - message = f"Unsupported language: {block.lan}" + message = f"Unsupported language: {block.lang}" raise PDLRuntimeError( message, loc=loc, @@ -1300,6 +1310,14 @@ def call_command(code: str) -> str: return output +def call_jinja(code: str, scope: dict) -> Any: + template = Template( + code, + ) + result = template.render(scope) + return result + + def step_call( state: InterpreterState, scope: ScopeType, block: CallBlock, loc: LocationType ) -> Generator[YieldMessage, Any, tuple[Any, Messages, ScopeType, CallBlock]]: diff --git a/tests/results/examples/hello/hello-code-command.result b/tests/results/examples/hello/hello-code-command.result new file mode 100644 index 000000000..ea2fd5c3f --- /dev/null +++ b/tests/results/examples/hello/hello-code-command.result @@ -0,0 +1,2 @@ +Hello World! + diff --git a/tests/results/examples/hello/hello-code-jinja.result b/tests/results/examples/hello/hello-code-jinja.result new file mode 100644 index 000000000..980a0d5f1 --- /dev/null +++ b/tests/results/examples/hello/hello-code-jinja.result @@ -0,0 +1 @@ +Hello World! diff --git a/tests/test_code.py b/tests/test_code.py index 3ebcee683..e90c361d4 100644 --- a/tests/test_code.py +++ b/tests/test_code.py @@ -1,3 +1,4 @@ +from pdl.pdl import exec_str from pdl.pdl_ast import Program from pdl.pdl_interpreter import InterpreterState, empty_scope, process_prog @@ -71,3 +72,54 @@ def test_command(): document, _, scope, _ = process_prog(state, empty_scope, data) assert document == "Hello World!" assert scope["world"] == "World" + + +def test_jinja1(): + prog_str = """ +defs: + world: "World" +lang: jinja +code: | + Hello {{ world }}! +""" + result = exec_str(prog_str) + assert result == "Hello World!" + + +def test_jinja2(): + prog_str = """ +defs: + world: "World" +lang: jinja +code: | + Hello ${ world }! +""" + result = exec_str(prog_str) + assert result == "Hello World!" + + +def test_jinja3(): + prog_str = """ +defs: + scores: + array: + - 10 + - 90 + - 50 + - 60 + - 100 +lang: jinja +code: | + {% for score in scores %} + {% if score > 80 %}good{% else %}bad{% endif %}{% endfor %} +""" + result = exec_str(prog_str) + assert ( + result + == """ + bad + good + bad + bad + good""" + )