Skip to content

Releases: IBM/prompt-declaration-language

Version 0.9.3

Choose a tag to compare

@mandel mandel released this 26 Feb 20:20
v0.9.3
a160a73

New Features

The main new features are:

  • Add the variables pdl_usage and pdl_scope to the PDL scope
  • Add a scope field to the code blocks
  • Add a parameters field to Jinja code blocks

New special variable

The pdl_usage variable contains the statistics accumulated by the calls the the models. Here is a simple examples showing the fields of the pdl_usage variable.

text:
- Hello
- model: ollama/granite4:micro
- |
  Usage Statistics:
  - Model calls: ${ pdl_usage.model_calls }
  - Prompt tokens: ${ pdl_usage.prompt_tokens }
  - Completion tokens: ${ pdl_usage.completion_tokens }
  - Total tokens: ${ pdl_usage.prompt_tokens + pdl_usage.completion_tokens }

The pdl_scope variable allows to do reflexion on the variables in scope at the current execution point. The following examples list the available variables:

defs:
  greeting: Hello
  name: World
text:
- |
  ${ pdl_scope["greeting"]}, ${ pdl_scope["name"] }!
  Available variables in scope:
- for:
    key: ${ pdl_scope.keys() }
  repeat:
    text: |
      - ${ key }

The scope field in code blocks

The user can now specify which variables are available the the scope of the code executed in a code Python and Jinja code block (by default all of the variables in pdl_scope are available). Here is an example where the variable x and y are in scope for the code block, but not the variable z:

defs:
  z: 0
lang: python
scope:
  x: 10
  y: 20
code: |
  result = x + y

Jinja code block parameters

The user can now configure Jinja template behavior by passing parameters to the Jinja Template constructor. Here is an example that configures Jinja's whitespace handling:

defs:
  items:
    data:
    - apple
    - banana
    - cherry
lang: jinja
parameters:
  trim_blocks: true
  lstrip_blocks: true
code: |
  Shopping List:
  {% for item in items %}
    - {{ item }}
  {% endfor %}
  
  Total items: {{ items | length }}

Non-backward compatible changes

This release introduce some changes that are not compatible with previous versions of PDL:

  • the syntax of the signatures generated by .signature on functions has changed
  • the re-evaluation of rule of aggregator declarations, functions have changed

What's Changed

  • Fix in MessageBlock by @vazirim in #1410
  • fix: close event loop cleanly by @mandel in #1415
  • tests: update tests results by @mandel in #1438
  • docs: Add missing comments and docstrings to pdl_ast.py by @mandel in #1440
  • refactor: replace Union and Optional with | None syntax by @mandel in #1441
  • change to assistant message post-processing by @vazirim in #1442
  • Prevent AggregatorBlock from being cached in replay by @mandel in #1449
  • Add usage to scope by @gbdrt in #1448
  • Change MessageBlock.tool_calls type to OptionalExpressionList by @mandel in #1447
  • fix: replay of blocks raising exceptions by @mandel in #1451
  • Fix: Add FunctionBlock to replay check in process_block_body_with_replay by @mandel in #1464
  • feat: implement scope field handling for CodeBlock by @mandel in #1479
  • feat: add pdl_scope to the scope by @mandel in #1480
  • feat: Use LiteLLM syntax for tool call signature by @mandel in #1477
  • tests: switch examples to granite 4 by @mandel in #1475
  • feat: add a parameters field to Jinja code blocks by @mandel in #1485
  • feat: add a get_tool_calls function in stdlib by @mandel in #1484
  • docs: fix tutorial by @mandel in #1496
  • fix: FileAggregator resource leak by @mandel in #1497

Full Changelog: v0.9.2...v0.9.3

Version 0.9.2

Choose a tag to compare

@mandel mandel released this 11 Dec 21:28
v0.9.2
7b63b89

New Features

The main new feature is:

  • sequence block that corresponds the a general version of text, lastOfm and array.

The sequence block expect a list a PDL blocks that and executed in sequence and whose results are combine following the provided join specification. For example, an array can be created as follow:

sequence:
- Hello
- model: ollama/granite4
join:
  as: array

What's Changed

New Contributors

Full Changelog: v0.9.1...v0.9.2

Version 0.9.1

Choose a tag to compare

@mandel mandel released this 17 Nov 18:42
v0.9.1
03c4859

New Features

The main new feature is:

  • support CSV format in the parser field.

What's Changed

Full Changelog: v0.9.0...v0.9.1

Version 0.9.0

Choose a tag to compare

@mandel mandel released this 10 Nov 04:37
v0.9.0
a68bc88

New Features

The main new features are:

  • contribute to a file or standard input and error
  • @pdl Python decorator

contribute to files

The user can declare a file aggregator that can be used in the contribute to accumulate the results of the blocks.
For example, in the following program, an aggregator named log associated to the file /tmp/log.txt is declared.
This aggregator is used to store the output of the model in the file /tmp/log.txt.

defs:
  log:
    aggregator: 
      file: /tmp/log.txt
text:
- Hello
- model: ollama_chat/granite3.3:2b
  contribute: [ result, context, log ]

By default, the aggregator stdin and stderr are available to contribute to the standard input and standard error.
It is useful, for example to outpout debug information. In the following program, the raw output of the model is emitted
on the standard error:

text:
- Hello
- model: ollama_chat/granite3.3:2b
  modelResponse: raw_response
  contribute:
    - result
    - context
    - stderr:
        value: ${raw_response}

@pdl Python decorator

In order to make it easy to define functions as PDL programs, we provide a @pdl decorator.

from pdl.pdl import pdl

@pdl
def chain_of_thought(scope):
    """
    lastOf:
    - "Question: ${ question }\n"
    - "Answer: Let's think step by step. "
    - model: ollama_chat/granite3.3:2b
      parameters:
          stop:
          - "<|endoftext|>"
          - "Question:"
          include_stop_sequence: false
    """
    return

def main():
    result = chain_of_thought(
        scope={"model": "ollama_chat/granite3.2:2b", 
               "question": "How to write Hello World in OCaml?\n"}
    )
    print(result)

if __name__ == "__main__":
    main()

In this example, the function chain_pf_thought is a string representing a PDL program. The free variables in the string are values passed in the dictionary scope given on argument.

What's Changed

Full Changelog: v0.8.0...v0.9.0

Version 0.8.0

Choose a tag to compare

@mandel mandel released this 06 Sep 01:59
v0.8.0
82ba569

New Features

The main changes in this release are:

  • AutoPDL: automatic optimization of PDL programs
  • Calling PDL functions from Jinja and Python
  • Map/reduce block

AutoPDL: automatic optimization of PDL programs

Given a PDL program with some free variables, a search space for these free variable, a loss function, and a training set, you can optimize the PDL program using the pdl-optimize command. This allows to optimize any part of a PDL program such as the textual prompts, the few-shots examples, or the prompting patterns. This work is based on the paper AutoPDL: Automatic Prompt Optimization for LLM Agents. A new section of the manual is describing how to use the optimizer.

Calling PDL functions from Jinja and Python

It is now possible to call a function defined in PDL inside a Jinja expression or a Python code block. Here is an example where the function translate is called in the Jinja expression ${ translate("Hello", language="French") }:

description: Calling a PDL function from Jinja
defs:
  translate:
    function:
      sentence: string
      language: string
    return:
      lastOf:
      - |
        Translate the sentence '${ sentence }' to ${ language }.
        Only give the result of the translation.
      - model: ollama_chat/granite3.2:2b
text: |
  The way to say hello in French is ${ translate("Hello", language="French") }.

Map/reduce block

Similar to the repeat loops, PDL now offers a map block. The difference with the repeat block is that the context is not accumulated between iterations, each iteration is executed with the same context. Here is an example of map block:

lastOf:
- "Hello, "
- for:
    name: [Alice, Bob, Charlie]
  map:
    lastOf:
    - my name is ${ name }
    - model: ollama/granite3.2:2b
  join:
    as: array

What's Changed

Full Changelog: v0.7.1...v0.8.0

Version 0.7.1

Choose a tag to compare

@mandel mandel released this 30 Jun 15:19
v0.7.1
0e12c98

What's Changed

  • refactor: make the event loop thread part of the interpreter state by @mandel in #990
  • fix: contribute directly a value to the context by @mandel in #991

Full Changelog: v0.7.0...v0.7.1

Version 0.7.0

Choose a tag to compare

@mandel mandel released this 25 Jun 13:24
v0.7.0
db6394c

New Features

The main new features are the following and are detailed bellow:

  • new retry attribute to any block,
  • new index field to introduce a loop index in repeat blocks
  • new syntax for types with suport for JSON Schema,
  • extract the signature of a function f with f.signature,
  • loop and sequences with independent contexts,
  • support for granite-io processors created in Python.

The retry field

Any block can now have a retry field indicating how many times a block should be executed if it encounters a runtime error. For example, a model call can be retried 5 times as follows:

model: replicate/ibm-granite/granite-3.3-8b-instruct
input: How to program "Hello World!"?
retry: 5

The loop index field

A common pattern in PDL programs is to introduce a variable to index the loop iterations. For example, a loop that turns a list of strings into a list of object with a field name and id would be written as follows:

defs:
  id: -1
for:
  name: [ "Alice", "Nicolas", "Rosa", "Remi" ]
repeat:
  defs:
    id: ${ id + 1}
  data:
    name: ${name}
    id: ${id}
join:
  as: array

With the new index field that introduced a variable name as loop index, this code can now be simplified as follows:

for:
  name: [ "Alice", "Nicolas", "Rosa", "Remi" ]
index: id
repeat:
  data:
    name: ${name}
    id: ${id}
join:
  as: array

New type syntax

We extended the type syntax to be able to write directly some JSON Schema as block specification. To do so, the type must contain the type or enum field. For example, we can write a model block that checks that the output starts with the letter "A" as follows:

model: ollama/granite3.3:2b
input: Generate a word that starts with the letter "A". Just output the single word.
spec:
  type: string
  pattern: "^[Aa]"

To make the syntax more uniform, we are using the JSON Schema syntax for the base type. So for example, we are using number instead of float for floating point numbers. We discuss the breaking changes below.

Extract function signature

In order to make the use of PDL functions as tools by LLMs easier, we provide the ability to extract the signature of a function f by executing f.signature. Here is an example:

defs:
  calc:
    description: Calculator function
    function:
      expr:
        type: string
        description: Arithmetic expression to calculate
    return:
      lang: python
      code: result = ${ expr }
text: ${ calc.signature }

The output is:

{"type": "function", "name": "calc", "description": "Calculator function", "parameters": {"type": "object", "properties": {"expr": {"type": "string", "description": "Arithmetic expression to calculate"}}, "required": ["expr"], "additionalProperties": false}}

An example of tool use is given in search.pdl.

Independent contexts

Blocks containing lists of blocks (text, array, object, and lastOf) as well as loops can now be annotated with context: independent. It means that each sub-block is executed in an independent copy of the context. Therefore, if we execute the following program, both model calls are executed with the same input containing the message Hello:

lastOf:
- Hello
- context: independent
  array:
  - model: ollama/granite3.2:2b
  - model: ollama/granite3.3:2b

Support for granite-io processors objects

In addition to the ability to call granite-io processors using the lookup mechanism using backend and processors names, it is possible now to use granite-io processors or backends created in Python. Here is an example:

defs:
  io_proc:
    lang: python
    code: |
      from granite_io import make_backend, make_io_processor
      model_name = "granite3.2:2b"
      backend = make_backend("openai", { "model_name": model_name })
      result = make_io_processor(model_name, backend=backend)
processor: ${ io_proc }
input: Write an Hello World program in Python.

Breaking Changes

This version of PDL is coming with a large number of breaking changes:

  • types syntax,
  • granite-io syntax,
  • rename max_iterations into maxIterations,
  • trace format.

Types syntax

As mentioned above, to be consistent with JSON Schema, we renamed the basic types as follows:

  • str -> string
  • bool -> boolean
  • int -> integer
  • float -> number
  • obj -> object
  • list -> array

Moreover, since we can use JSON schema, we removed the old way to put constraints on types. So for example, the following type:

spec: { int: { minimum: 0 } }

must be rewritten:

spec: { type: integer, minimum: 0 }

Finally, the type null now corresponds to a value of any type. For example, the identity function can be written as follows:

function:
  x:
return: ${ x }

Granite-io processors

The structure of the granite-io block changed. Now, the processor field is required and the definition of io-processor is given has sub-fields of this field. So a block that was defined as follows:

processor: 
model: "granite3.2:2b"
backend: openai
input: Hello

is now defined like this:

processor: 
    type: Granite 3.2
    model: "granite3.2:2b"
    backend: openai
input: Hello

Loop iteration bound

The syntax to bound the number of iterations of a loop changed. It is now maxIteration. Here is an example

index: i
repeat: ${ i }
maxIterations: 3

Trace format

The format of the traces generated with the --trace (-t) option has changed. Some internal fields like defsite have changed to pdl__defsite. It means that traces generated with old version of the interpreter are not compatible with the new version of the UI.

What's Changed

Read more

Version 0.6.2

Choose a tag to compare

@mandel mandel released this 03 Jun 14:39
v0.6.2

What's Changed

  • Fix rag example by fully qualifying import by @esnible in #930
  • Change to sys.path for python code block by @vazirim in #931
  • granite-io hallucination demo example and notebook by @vazirim in #932
  • Add contrib. prompt library by @claudiosv in #927
  • Fixed the bug where pdl.version was not set by @vite-falcon in #882
  • feat: add a parse_dict function to pdl_parser by @mandel in #943
  • feat: specify PDL types in the AST by @mandel in #942
  • chore(deps): Update granite-io requirement from <0.4,>=0.2 to >=0.2,<0.5 by @dependabot in #946
  • chore(deps): Update faiss-cpu requirement from ~=1.10.0 to >=1.10,<1.12 by @dependabot in #940
  • Fix for extra fields in messages sent to LLMs by @vazirim in #952

Full Changelog: v0.6.1...v0.6.2

Version 0.6.1

Choose a tag to compare

@vazirim vazirim released this 18 Apr 19:13
7f2246b

What's Changed

  • feat: update rust python support to pull in python stdlib by @starpit in #877
  • feat: port rust model pull logic to use rust AST by @starpit in #875
  • fix: avoid openssl in rust app for now by @starpit in #880
  • gsm8k multi-plan, tree-of-thought, tree-of-thought with few shots by @vazirim in #881
  • feat: rust interpreter support for modelResponse, ollama-rs tooling calling, and no-stream by @starpit in #883
  • chore: minor code cleanups to rust interpreter by @starpit in #884
  • test: update github action rust interpreter test to remove ollama pull by @starpit in #885
  • fix: add kind tags to rust ast blocks by @starpit in #887
  • fix: update rust interpreter to avoid global emit/scope state by @starpit in #888
  • fix: rust pull logic may not always pull by @starpit in #889
  • feat: remove tauri cli support for running python interpreter by @starpit in #890
  • chore: bump rust dependencies to resolve alert by @starpit in #893
  • test: tauri github actions test should apt install the deb by @starpit in #892
  • feat: support for --data and --data-file in rust interpreter by @starpit in #894
  • fix: begin phasing in Metadata (common defs, etc. attrs) into rust AST by @starpit in #895
  • chore: update granite-io dependency by @mandel in #896
  • refactor: move def attr into Metadata (rust interpreter) by @starpit in #897
  • feat: introduce Expr typing and apply it to IfBlock.condition by @starpit in #899
  • feat: update rust Call AST to use Expr for condition attr by @starpit in #901
  • chore: bump to rust 2024 edition by @starpit in #902
  • feat: continue to flesh out block metadata structure in rust by @starpit in #898
  • refactor: add metadata attr to remaining rust block asts by @starpit in #903
  • feat: update rust Repeat AST to use Expr for for attr by @mandel in #904
  • refactor: introduce Advanced enum to rust AST by @starpit in #906
  • refactor: refactor rust ast to place metadata in common struct by @starpit in #909
  • fix: improve deserialization of python-generated model block traces by @starpit in #910
  • fix: in rust ast, allow ModelBlock model to be an expr by @starpit in #911
  • feat: initial pdl__id and --trace support for rust interpreter by @starpit in #912
  • fix: update rust interpreter to create Data blocks for expr eval, and model_input trace field by @starpit in #914
  • fix: populate trace context field in rust interpreter by @starpit in #915
  • Update stop sequences in parameters by @jgchn in #861
  • refactor: extract platform-generic logic from run_ollama_model() handler by @starpit in #916
  • fix: rust interpreter was not handling pdl__context for re-runs of traces by @starpit in #917
  • feat: improve support for importing stdlib in python code blocks by @starpit in #918
  • skeleton-of-thought example by @vazirim in #919
  • Bump litellm and openai versions by @vazirim in #920
  • fix: improve support for rust interpreter python imports from venv by @starpit in #922
  • chore: bump tauri and npm dependencies by @starpit in #923
  • chore: bump ui to 0.6.1 by @starpit in #921

Full Changelog: v0.6.0...v0.6.1

Version 0.6.0

Choose a tag to compare

@mandel mandel released this 08 Apr 14:23
v0.6.0
79b9bf7

This new release has two major changes:

  • pdl-lint, a linter for PDL programs (thanks to @vite-falcon!)
  • mesages in the context with the same role are not automatically merged anymore.

For example, the following program generates 3 messages:

lastOf:
  - role: user
    text:
      - hello
      - "\n"
      - world
  - ${pdl_context}

result:

[{"role": "user", "content": "hello", "defsite": "lastOf.text.0"}, {"role": "user", "content": "\n", "defsite": "lastOf.text.1"}, {"role": "user", "content": "world", "defsite": "lastOf.text.2"}]

To generate only one message, you have to use a message block:

lastOf:
  - role: user
    content:
      text:
        - hello
        - "\n"
        - world
  - ${pdl_context}

result

[{"role": "user", "content": "hello\nworld", "defsite": "lastOf.message"}]

What's Changed

  • Switch to granite-io version 0.2 by @mandel in #818
  • feat: update beeai compiler to support compiling directly from python source by @starpit in #834
  • Examples restructuring, tutorial changes by @vazirim in #836
  • Bug fixes for setting default parameters by @vazirim in #838
  • fix: pdl view trace.json fixes by @starpit in #843
  • fix: Bug in pdl_schema_error_analyzer that raises exception during analysis by @vite-falcon in #851
  • Clean up run examples and automate result updating via GH Actions by @jgchn in #853
  • feat: do not merge messages with same role by @mandel in #846
  • fixes to react examples by @vazirim in #859
  • feat: message blocks contribute the message instead of the content to the context by @mandel in #862
  • feat: do not stringify messages content by @mandel in #858
  • feat: rust interpreter by @starpit in #857
  • gsm8k plan with few-shots by @vazirim in #870
  • feat: add pdl-lint tool that can be configured via pyproject.toml by @vite-falcon in #864

New Contributors

Full Changelog: v0.5.1...v0.6.0