Skip to content

Policy Environments

Main Class

PolicyEnv

The global environment accessible to policies.

It can be used for:

  • Fetching prompts, data, and examples.
  • Caching LLM requests.
  • Tracing nodes, query, answers, and logging information.

Attributes:

Name Type Description
requests_cache LLMCache | None

The requests cache, or None if caching is disabled. The exact type of the request cache is not statically determined and depends on the value of constructor argument make_cache.

templates

The prompt templates manager.

tracer

The tracer, which can also be used for logging.

examples

The example database.

Source code in src/delphyne/stdlib/environments.py
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
class PolicyEnv:
    """
    The global environment accessible to policies.

    It can be used for:

    - Fetching prompts, data, and examples.
    - Caching LLM requests.
    - Tracing nodes, query, answers, and logging information.

    Attributes:
        requests_cache: The requests cache, or `None` if caching is
            disabled. The exact type of the request cache is not
            statically determined and depends on the value of
            constructor argument `make_cache`.
        templates: The prompt templates manager.
        tracer: The tracer, which can also be used for logging.
        examples: The example database.
    """

    def __init__(
        self,
        *,
        prompt_dirs: Sequence[Path] = (),
        demonstration_files: Sequence[Path] = (),
        data_dirs: Sequence[Path] = (),
        cache: CacheSpec | None = None,
        do_not_match_identical_queries: bool = False,
    ):
        """
        Args:
            prompt_dirs: A sequence of directories where Jinja prompt
                templates can be found.
            demonstration_files: A sequence of paths to demonstration
                files (with or without extension `.demo.yaml`), to
                create an example database from.
            data_dirs: A sequence of directories where data files can be
                found.
            cache: A cache specification, or `None` to disable caching.
                When caching is enabled, the `requests_cache` attribute
                can be accessed by policies to properly wrap LLM models.
            do_not_match_identical_queries: See `ExampleDatabase`.
        """
        self.templates = TemplatesManager(prompt_dirs, data_dirs)
        self.examples = ExampleDatabase(do_not_match_identical_queries)
        self.tracer = Tracer()
        self.requests_cache: md.LLMCache | None = None
        if cache is not None:
            self.requests_cache = md.LLMCache(cache)
        for path in demonstration_files:
            if not path.suffix.endswith(DEMO_FILE_EXT):
                path = path.with_suffix(DEMO_FILE_EXT)
            try:
                with path.open() as f:
                    content = yaml.safe_load(f)
                    demos = pydantic_load(list[Demo], content)
                    for demo in demos:
                        self.examples.add_demonstration(demo)
            except Exception as e:
                raise InvalidDemoFile(path, e)

__init__

__init__(
    *,
    prompt_dirs: Sequence[Path] = (),
    demonstration_files: Sequence[Path] = (),
    data_dirs: Sequence[Path] = (),
    cache: CacheSpec | None = None,
    do_not_match_identical_queries: bool = False,
)

Parameters:

Name Type Description Default
prompt_dirs Sequence[Path]

A sequence of directories where Jinja prompt templates can be found.

()
demonstration_files Sequence[Path]

A sequence of paths to demonstration files (with or without extension .demo.yaml), to create an example database from.

()
data_dirs Sequence[Path]

A sequence of directories where data files can be found.

()
cache CacheSpec | None

A cache specification, or None to disable caching. When caching is enabled, the requests_cache attribute can be accessed by policies to properly wrap LLM models.

None
do_not_match_identical_queries bool False
Source code in src/delphyne/stdlib/environments.py
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
def __init__(
    self,
    *,
    prompt_dirs: Sequence[Path] = (),
    demonstration_files: Sequence[Path] = (),
    data_dirs: Sequence[Path] = (),
    cache: CacheSpec | None = None,
    do_not_match_identical_queries: bool = False,
):
    """
    Args:
        prompt_dirs: A sequence of directories where Jinja prompt
            templates can be found.
        demonstration_files: A sequence of paths to demonstration
            files (with or without extension `.demo.yaml`), to
            create an example database from.
        data_dirs: A sequence of directories where data files can be
            found.
        cache: A cache specification, or `None` to disable caching.
            When caching is enabled, the `requests_cache` attribute
            can be accessed by policies to properly wrap LLM models.
        do_not_match_identical_queries: See `ExampleDatabase`.
    """
    self.templates = TemplatesManager(prompt_dirs, data_dirs)
    self.examples = ExampleDatabase(do_not_match_identical_queries)
    self.tracer = Tracer()
    self.requests_cache: md.LLMCache | None = None
    if cache is not None:
        self.requests_cache = md.LLMCache(cache)
    for path in demonstration_files:
        if not path.suffix.endswith(DEMO_FILE_EXT):
            path = path.with_suffix(DEMO_FILE_EXT)
        try:
            with path.open() as f:
                content = yaml.safe_load(f)
                demos = pydantic_load(list[Demo], content)
                for demo in demos:
                    self.examples.add_demonstration(demo)
        except Exception as e:
            raise InvalidDemoFile(path, e)

InvalidDemoFile dataclass

Bases: Exception

Exception raised when a demonstration file could not be parsed.

Source code in src/delphyne/stdlib/environments.py
301
302
303
304
305
306
307
308
@dataclass
class InvalidDemoFile(Exception):
    """
    Exception raised when a demonstration file could not be parsed.
    """

    file: Path
    exn: Exception

Example Database

Example dataclass

An example, usable for few-shot prompting.

Attributes:

Name Type Description
args QuerySerializedArgs

The serialized query arguments.

answer Answer

The answer to the query.

tags Sequence[str]

A sequence of tags associated with the example, which policies can use to select appropriate examples.

Source code in src/delphyne/stdlib/environments.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@dataclass
class Example:
    """
    An example, usable for few-shot prompting.

    Attributes:
        args: The serialized query arguments.
        answer: The answer to the query.
        tags: A sequence of tags associated with the example, which
            policies can use to select appropriate examples.
    """

    args: QuerySerializedArgs
    answer: Answer
    tags: Sequence[str]

QuerySerializedArgs

QuerySerializedArgs: dict[str, Any]

Serialized query arguments, as a dictionary mapping attributed to JSON values (assemblies of integers, strings, dictionnaries, list, tuples...).

ExampleDatabase dataclass

A simple example database.

Attributes:

Name Type Description
do_not_match_identical_queries bool

If set to True, the examples method won't return examples that match identical queries (i.e., with the exact same arguments). This is useful in the context of writing demonstrations, where one may want to see how an LLM would answer a query, even when a ground-truth answer is provided already.

TODO: add provenance info for better error messages.

Source code in src/delphyne/stdlib/environments.py
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@dataclass
class ExampleDatabase:
    """
    A simple example database.

    Attributes:
        do_not_match_identical_queries: If set to `True`, the `examples`
            method won't return examples that match identical queries
            (i.e., with the exact same arguments). This is useful in the
            context of writing demonstrations, where one may want to see
            how an LLM would answer a query, even when a ground-truth
            answer is provided already.

    TODO: add provenance info for better error messages.
    """

    do_not_match_identical_queries: bool = False

    # Maps each query name to a list of
    _examples: dict[_QueryName, list[Example]] = field(
        default_factory=lambda: defaultdict(list)
    )

    def add_query_demonstration(self, demo: QueryDemo):
        """
        Add all examples from a standalone query demonstration to the
        database.
        """
        if not demo.answers:
            return
        if (ex := demo.answers[0].example) is not None and not ex:
            # If the user explicitly asked not to include the example.
            # TODO: What if the user asked to include several answers?
            # Right now, we only allow the first one to be added.
            return
        demo_answer = demo.answers[0]
        answer = translate_answer(demo_answer)
        example = Example(demo.args, answer, demo_answer.tags)
        self._examples[demo.query].append(example)

    def add_demonstration(self, demo: Demo):
        """
        Add all exmples from a demonstration to the database.
        """
        if isinstance(demo, QueryDemo):
            self.add_query_demonstration(demo)
        else:
            assert isinstance(demo, StrategyDemo)
            for q in demo.queries:
                self.add_query_demonstration(q)

    def examples(
        self, query_name: str, query_args: QuerySerializedArgs
    ) -> Iterable[Example]:
        """
        Obtain all potential examples that can be used for few-shot
        prompting with a given query.
        """
        for ex in self._examples[query_name]:
            if self.do_not_match_identical_queries:
                if _equal_query_args(ex.args, query_args):
                    continue
            yield ex

add_query_demonstration

add_query_demonstration(demo: QueryDemo)

Add all examples from a standalone query demonstration to the database.

Source code in src/delphyne/stdlib/environments.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def add_query_demonstration(self, demo: QueryDemo):
    """
    Add all examples from a standalone query demonstration to the
    database.
    """
    if not demo.answers:
        return
    if (ex := demo.answers[0].example) is not None and not ex:
        # If the user explicitly asked not to include the example.
        # TODO: What if the user asked to include several answers?
        # Right now, we only allow the first one to be added.
        return
    demo_answer = demo.answers[0]
    answer = translate_answer(demo_answer)
    example = Example(demo.args, answer, demo_answer.tags)
    self._examples[demo.query].append(example)

add_demonstration

add_demonstration(demo: Demo)

Add all exmples from a demonstration to the database.

Source code in src/delphyne/stdlib/environments.py
111
112
113
114
115
116
117
118
119
120
def add_demonstration(self, demo: Demo):
    """
    Add all exmples from a demonstration to the database.
    """
    if isinstance(demo, QueryDemo):
        self.add_query_demonstration(demo)
    else:
        assert isinstance(demo, StrategyDemo)
        for q in demo.queries:
            self.add_query_demonstration(q)

examples

examples(query_name: str, query_args: QuerySerializedArgs) -> Iterable[Example]

Obtain all potential examples that can be used for few-shot prompting with a given query.

Source code in src/delphyne/stdlib/environments.py
122
123
124
125
126
127
128
129
130
131
132
133
def examples(
    self, query_name: str, query_args: QuerySerializedArgs
) -> Iterable[Example]:
    """
    Obtain all potential examples that can be used for few-shot
    prompting with a given query.
    """
    for ex in self._examples[query_name]:
        if self.do_not_match_identical_queries:
            if _equal_query_args(ex.args, query_args):
                continue
        yield ex

Tracer

TemplatesManager

Bases: AbstractTemplatesManager

A class for managing Jinja prompt templates.

Templates are configured with the trim_blocks and lstrip_blocks options set to True (no newlines are inserted after blocks and indentation can be used within blocks without affecting the output). The keep_trailing_newline option is set to False so trailing new lines at the end of template files are ignored.

Templates are first searched in the provided prompt folders and then in the standard library (delphyne.stdlib.templates). For example, to show standard formatting instructions, you can include the following in your instance prompts:

{% include 'stdlib/format.jinja' %}

All templates automatically have access to the following global objects:

  • A yaml filter for converting an object into a YAML string.
  • A json filter for converting an object into a JSON string.
  • A fail function that takes an error message as an argument and raises an exception on Python side.
Source code in src/delphyne/stdlib/environments.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
class TemplatesManager(qu.AbstractTemplatesManager):
    """
    A class for managing Jinja prompt templates.

    Templates are configured with the `trim_blocks` and `lstrip_blocks`
    options set to `True` (no newlines are inserted after blocks and
    indentation can be used within blocks without affecting the output).
    The `keep_trailing_newline` option is set to `False` so trailing new
    lines at the end of template files are ignored.

    Templates are first searched in the provided prompt folders and then
    in the standard library (`delphyne.stdlib.templates`). For example,
    to show standard formatting instructions, you can include the
    following in your instance prompts:

    ```jinja
    {% include 'stdlib/format.jinja' %}
    ```

    All templates automatically have access to the following global
    objects:

    - A `yaml` filter for converting an object into a YAML string.
    - A `json` filter for converting an object into a JSON string.
    - A `fail` function that takes an error message as an argument and
      raises an exception on Python side.
    """

    def __init__(self, prompt_dirs: Sequence[Path], data_dirs: Sequence[Path]):
        """
        Args:
            prompt_dirs: A sequence of directories where Jinja prompt
                templates can be found.
            data_dirs: A sequence of directories where data files can be
                found.
        """
        self.prompt_folders = prompt_dirs
        self.data = _load_data(data_dirs)
        loader = jinja2.ChoiceLoader(
            [
                jinja2.FileSystemLoader(self.prompt_folders),
                jinja2.PackageLoader("delphyne.stdlib"),
            ]
        )
        self.env = jinja2.Environment(
            loader=loader,
            trim_blocks=True,
            lstrip_blocks=True,
            keep_trailing_newline=False,
        )
        self.env.filters["yaml"] = dump_yaml_object
        self.env.filters["json"] = _dump_json_object
        self.env.globals["fail"] = _fail_from_template  # type: ignore

    @override
    def prompt(
        self,
        *,
        query_name: str,
        prompt_kind: Literal["system", "instance"] | str,
        template_args: dict[str, Any],
        default_template: str | None = None,
    ) -> str:
        suffix = "." + prompt_kind
        template_name = f"{query_name}{suffix}{JINJA_EXTENSION}"
        try:
            template = self.env.get_template(template_name)
        except jinja2.TemplateNotFound:
            if default_template is not None:
                template = self.env.from_string(default_template)
            else:
                raise qu.TemplateFileMissing(template_name)
        try:
            assert "data" not in template_args
            template_args |= {"data": self.data}
            return template.render(template_args)
        except jinja2.TemplateNotFound as e:
            raise qu.TemplateError(template_name, e)
        except jinja2.UndefinedError as e:
            raise qu.TemplateError(template_name, e)
        except jinja2.TemplateSyntaxError as e:
            raise qu.TemplateError(template_name, e)

__init__

__init__(prompt_dirs: Sequence[Path], data_dirs: Sequence[Path])

Parameters:

Name Type Description Default
prompt_dirs Sequence[Path]

A sequence of directories where Jinja prompt templates can be found.

required
data_dirs Sequence[Path]

A sequence of directories where data files can be found.

required
Source code in src/delphyne/stdlib/environments.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
def __init__(self, prompt_dirs: Sequence[Path], data_dirs: Sequence[Path]):
    """
    Args:
        prompt_dirs: A sequence of directories where Jinja prompt
            templates can be found.
        data_dirs: A sequence of directories where data files can be
            found.
    """
    self.prompt_folders = prompt_dirs
    self.data = _load_data(data_dirs)
    loader = jinja2.ChoiceLoader(
        [
            jinja2.FileSystemLoader(self.prompt_folders),
            jinja2.PackageLoader("delphyne.stdlib"),
        ]
    )
    self.env = jinja2.Environment(
        loader=loader,
        trim_blocks=True,
        lstrip_blocks=True,
        keep_trailing_newline=False,
    )
    self.env.filters["yaml"] = dump_yaml_object
    self.env.filters["json"] = _dump_json_object
    self.env.globals["fail"] = _fail_from_template  # type: ignore

TemplateFileMissing dataclass

Bases: Exception

Exception raised when a template file is missing.

This exception should only be raised when a top-level template file is missing. If an include statement fails within a template, a TemplateError exception should be raised instead.

Source code in src/delphyne/core/queries.py
144
145
146
147
148
149
150
151
152
153
154
@dataclass
class TemplateFileMissing(Exception):
    """
    Exception raised when a template file is missing.

    This exception should only be raised when a top-level template file
    is missing. If an `include` statement fails within a template, a
    `TemplateError` exception should be raised instead.
    """

    file: str