-
Notifications
You must be signed in to change notification settings - Fork 55
[FIX]: Enforce PromptTemplate construction invariants #132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,7 +21,7 @@ | |
|
|
||
| from collections.abc import Hashable | ||
| from dataclasses import dataclass, field | ||
| from typing import TYPE_CHECKING, Annotated, TypeAlias, TypeVar | ||
| from typing import TYPE_CHECKING, Annotated, TypeAlias, TypeVar, final | ||
|
|
||
| import yaml | ||
| from jinja2 import ( | ||
|
|
@@ -98,14 +98,30 @@ def __init__( | |
| super().__init__(msg) | ||
|
|
||
|
|
||
| @dataclass(frozen=True, kw_only=True, slots=True) | ||
| @final | ||
| @dataclass( | ||
| frozen=True, | ||
| kw_only=True, | ||
| slots=True, | ||
| init=False, | ||
| eq=False, | ||
| ) | ||
| class PromptTemplate: | ||
| """Compiled prompt template with metadata and an explicit render contract.""" | ||
|
|
||
| name: str | ||
| description: str | None | ||
| parameter_keys: tuple[str, ...] | ||
| _template: Template = field(repr=False, compare=False) | ||
| _template: Template = field(repr=False) | ||
|
|
||
| def __init__(self) -> None: | ||
| """Reject construction that bypasses template validation. | ||
|
|
||
| Raises: | ||
| TypeError: Always. Use :meth:`from_yaml` to construct an instance. | ||
| """ | ||
| msg = "Use PromptTemplate.from_yaml()" | ||
| raise TypeError(msg) | ||
|
|
||
| @classmethod | ||
| def from_yaml(cls, path: Path) -> Self: | ||
|
|
@@ -127,12 +143,31 @@ def from_yaml(cls, path: Path) -> Self: | |
| a valid prompt template. | ||
| """ | ||
| definition = _load_yaml_definition(path) | ||
| return cls( | ||
| name=definition.name, | ||
| description=definition.description, | ||
| parameter_keys=tuple(definition.parameters), | ||
| _template=_compile_template(definition, path=path), | ||
| return cls._from_validated_definition(definition=definition, path=path) | ||
|
|
||
| @classmethod | ||
| def _from_validated_definition( | ||
| cls, | ||
| *, | ||
| definition: _PromptTemplateYaml, | ||
| path: Path, | ||
| ) -> Self: | ||
| compiled = _compile_template(definition, path=path) | ||
|
|
||
| instance = object.__new__(cls) | ||
| object.__setattr__(instance, "name", definition.name) # ruff:ignore[unnecessary-dunder-call] | ||
| object.__setattr__( # ruff:ignore[unnecessary-dunder-call] | ||
| instance, | ||
| "description", | ||
| definition.description, | ||
| ) | ||
| object.__setattr__( # ruff:ignore[unnecessary-dunder-call] | ||
| instance, | ||
| "parameter_keys", | ||
| tuple(definition.parameters), | ||
| ) | ||
| object.__setattr__(instance, "_template", compiled) # ruff:ignore[unnecessary-dunder-call] | ||
| return instance | ||
|
Comment on lines
+157
to
+170
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was considering your comment on whether The _FROM_YAML: Final = object()
@final
@dataclass(frozen=True, kw_only=True, slots=True, eq=False)
class PromptTemplate:
name: str
description: str | None
parameter_keys: tuple[str, ...]
_template: Template = field(repr=False)
_token: InitVar[object]
def __post_init__(self, _token: object) -> None:
if _token is not _FROM_YAML:
raise TypeError("Use PromptTemplate.from_yaml()")
I checked this on 3.11: direct construction, bare
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nina Chikanov (@nina-msft) Yeah I am not a big fan of the token/sentinel thing. Can you PTAL at #144 - maybe a cleaner approach?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we go with #144 - can you close this out as stale?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Closing both in favor of your comment! Stay tuned! |
||
|
|
||
| def render(self, **kwargs: object) -> str: | ||
| """Render with exactly the declared keyword arguments. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.