跳到内容

模板

Template 数据类

表示一个 prompt 模板。

我们返回一个 Template 类而非一个简单的函数,以便调用者可以访问模板。

源代码位于 outlines/templates.py
@dataclass
class Template:
    """Represents a prompt template.

    We return a `Template` class instead of a simple function so the
    template can be accessed by callers.

    """

    template: jinja2.Template
    signature: Optional[inspect.Signature]

    def __call__(self, *args, **kwargs) -> str:
        """Render and return the template.

        Returns
        -------
        The rendered template as a Python ``str``.

        """
        if self.signature is not None:
            bound_arguments = self.signature.bind(*args, **kwargs)
            bound_arguments.apply_defaults()
            return self.template.render(**bound_arguments.arguments)
        else:
            return self.template.render(**kwargs)

    @classmethod
    def from_string(cls, content: str, filters: Dict[str, Callable] = {}):
        """Create a `Template` instance from a string containing a Jinja template.

        Parameters
        ----------
        content : str
            The string content to be converted into a template.

        Returns
        -------
        An instance of the class with the provided content as a template.
        """
        return cls(build_template_from_string(content, filters), None)

    @classmethod
    def from_file(cls, path: Path, filters: Dict[str, Callable] = {}):
        """Create a `Template` instance from a file containing a Jinja template.

        Note: This method does not allow to include and inheritance to reference files
        that are outside the folder or subfolders of the file given to `from_file`.

        Parameters
        ----------
        path : Path
            The path to the file containing the Jinja template.

        Returns
        -------
        Template
            An instance of the Template class with the template loaded from the file.
        """
        # We don't use a `Signature` here because it seems not feasible to infer one from a Jinja2 environment that is
        # split across multiple files (since e.g. we support features like Jinja2 includes and template inheritance)
        return cls(build_template_from_file(path, filters), None)

__call__(*args, **kwargs)

渲染并返回模板。

返回值

类型 描述
渲染后的模板,为一个 Python ``str`` 字符串。
源代码位于 outlines/templates.py
def __call__(self, *args, **kwargs) -> str:
    """Render and return the template.

    Returns
    -------
    The rendered template as a Python ``str``.

    """
    if self.signature is not None:
        bound_arguments = self.signature.bind(*args, **kwargs)
        bound_arguments.apply_defaults()
        return self.template.render(**bound_arguments.arguments)
    else:
        return self.template.render(**kwargs)

from_file(path, filters={}) 类方法

从包含 Jinja 模板的文件创建 Template 实例。

注意:此方法不允许通过 include 和 inheritance 引用位于提供给 from_file 的文件所在文件夹或其子文件夹之外的文件。

参数

名称 类型 描述 默认值
path Path

包含 Jinja 模板的文件的路径。

必需

返回值

类型 描述
Template

一个 Template 类实例,其中模板已从文件中加载。

源代码位于 outlines/templates.py
@classmethod
def from_file(cls, path: Path, filters: Dict[str, Callable] = {}):
    """Create a `Template` instance from a file containing a Jinja template.

    Note: This method does not allow to include and inheritance to reference files
    that are outside the folder or subfolders of the file given to `from_file`.

    Parameters
    ----------
    path : Path
        The path to the file containing the Jinja template.

    Returns
    -------
    Template
        An instance of the Template class with the template loaded from the file.
    """
    # We don't use a `Signature` here because it seems not feasible to infer one from a Jinja2 environment that is
    # split across multiple files (since e.g. we support features like Jinja2 includes and template inheritance)
    return cls(build_template_from_file(path, filters), None)

from_string(content, filters={}) 类方法

从包含 Jinja 模板的字符串创建 Template 实例。

参数

名称 类型 描述 默认值
content str

要转换为模板的字符串内容。

必需

返回值

类型 描述
一个类实例,其中提供的内容作为模板。
源代码位于 outlines/templates.py
@classmethod
def from_string(cls, content: str, filters: Dict[str, Callable] = {}):
    """Create a `Template` instance from a string containing a Jinja template.

    Parameters
    ----------
    content : str
        The string content to be converted into a template.

    Returns
    -------
    An instance of the class with the provided content as a template.
    """
    return cls(build_template_from_string(content, filters), None)

create_jinja_env(loader, filters)

创建一个新的 Jinja 环境。

Jinja 环境加载了一系列预定义过滤器:- name:获取函数名称 - description:获取函数的 docstring - source:获取函数的源代码 - signature:获取函数的签名 - args:获取函数的参数 - schema:显示 JSON Schema

用户可以传递额外的过滤器,和/或覆盖现有过滤器。

参数

loader 一个可选的 BaseLoader 实例 filters 一个过滤器字典,映射过滤器名称和对应的函数。

源代码位于 outlines/templates.py
def create_jinja_env(
    loader: Optional[jinja2.BaseLoader], filters: Dict[str, Callable]
) -> jinja2.Environment:
    """Create a new Jinja environment.

    The Jinja environment is loaded with a set of pre-defined filters:
    - `name`: get the name of a function
    - `description`: get a function's docstring
    - `source`: get a function's source code
    - `signature`: get a function's signature
    - `args`: get a function's arguments
    - `schema`: isplay a JSON Schema

    Users may pass additional filters, and/or override existing ones.

    Arguments
    ---------
    loader
       An optional `BaseLoader` instance
    filters
       A dictionary of filters, map between the filter's name and the
       corresponding function.

    """
    env = jinja2.Environment(
        loader=loader,
        trim_blocks=True,
        lstrip_blocks=True,
        keep_trailing_newline=True,
        undefined=jinja2.StrictUndefined,
    )

    env.filters["name"] = get_fn_name
    env.filters["description"] = get_fn_description
    env.filters["source"] = get_fn_source
    env.filters["signature"] = get_fn_signature
    env.filters["schema"] = get_schema
    env.filters["args"] = get_fn_args

    # The filters passed by the user may override the
    # pre-defined filters.
    for name, filter_fn in filters.items():
        env.filters[name] = filter_fn

    return env

get_fn_args(fn)

返回一个函数的参数,包括注解和提供的默认值(如果存在)。

源代码位于 outlines/templates.py
def get_fn_args(fn: Callable):
    """Returns the arguments of a function with annotations and default values if provided."""
    if not callable(fn):
        raise TypeError("The `args` filter only applies to callables.")

    arg_str_list = []
    signature = inspect.signature(fn)
    arg_str_list = [str(param) for param in signature.parameters.values()]
    arg_str = ", ".join(arg_str_list)
    return arg_str

get_fn_description(fn)

返回一个可调用对象的 docstring 的第一行。

源代码位于 outlines/templates.py
def get_fn_description(fn: Callable):
    """Returns the first line of a callable's docstring."""
    if not callable(fn):
        raise TypeError("The `description` filter only applies to callables.")

    docstring = inspect.getdoc(fn)
    if docstring is None:
        description = ""
    else:
        description = docstring.split("\n")[0].strip()

    return description

get_fn_name(fn)

返回一个可调用对象的名称。

源代码位于 outlines/templates.py
def get_fn_name(fn: Callable):
    """Returns the name of a callable."""
    if not callable(fn):
        raise TypeError("The `name` filter only applies to callables.")

    if not hasattr(fn, "__name__"):
        name = type(fn).__name__
    else:
        name = fn.__name__

    return name

get_fn_signature(fn)

返回一个可调用对象的签名。

源代码位于 outlines/templates.py
def get_fn_signature(fn: Callable):
    """Return the signature of a callable."""
    if not callable(fn):
        raise TypeError("The `source` filter only applies to callables.")

    source = textwrap.dedent(inspect.getsource(fn))
    re_search = re.search(re.compile(r"\(([^)]+)\)"), source)
    if re_search is None:
        signature = ""
    else:
        signature = re_search.group(1)

    return signature

get_fn_source(fn)

返回一个可调用对象的源代码。

源代码位于 outlines/templates.py
def get_fn_source(fn: Callable):
    """Return the source code of a callable."""
    if not callable(fn):
        raise TypeError("The `source` filter only applies to callables.")

    source = textwrap.dedent(inspect.getsource(fn))
    re_search = re.search(re.compile(r"(\bdef\b.*)", re.DOTALL), source)
    if re_search is not None:
        source = re_search.group(0)
    else:
        raise TypeError("Could not read the function's source code")

    return source

get_schema_dict(model)

返回一个美化打印的字典

源代码位于 outlines/templates.py
@get_schema.register(dict)
def get_schema_dict(model: Dict):
    """Return a pretty-printed dictionary"""
    return json.dumps(model, indent=2)

get_schema_pydantic(model)

返回一个 Pydantic 模型的 schema。

源代码位于 outlines/templates.py
@get_schema.register(type(pydantic.BaseModel))
def get_schema_pydantic(model: Type[pydantic.BaseModel]):
    """Return the schema of a Pydantic model."""
    if not isinstance(model, type(pydantic.BaseModel)):
        raise TypeError("The `schema` filter only applies to Pydantic models.")

    if hasattr(model, "model_json_schema"):
        def_key = "$defs"
        raw_schema = model.model_json_schema()
    else:  # pragma: no cover
        def_key = "definitions"
        raw_schema = model.schema()

    definitions = raw_schema.get(def_key, None)
    schema = parse_pydantic_schema(raw_schema, definitions)

    return json.dumps(schema, indent=2)

parse_pydantic_schema(raw_schema, definitions)

解析 Basemodel.[schema|model_json_schema]() 的输出。

对于嵌套模型,这会递归地跟踪对其他 schema 的引用。其他 schema 存储在顶级模型 schema 的“definitions”键下。

源代码位于 outlines/templates.py
def parse_pydantic_schema(raw_schema, definitions):
    """Parse the output of `Basemodel.[schema|model_json_schema]()`.

    This recursively follows the references to other schemas in case
    of nested models. Other schemas are stored under the "definitions"
    key in the schema of the top-level model.

    """
    simple_schema = {}
    for name, value in raw_schema["properties"].items():
        if "description" in value:
            simple_schema[name] = value["description"]
        elif "$ref" in value:
            refs = value["$ref"].split("/")
            simple_schema[name] = parse_pydantic_schema(
                definitions[refs[2]], definitions
            )
        else:
            simple_schema[name] = f"<{name}>"

    return simple_schema

prompt(fn=None, filters={})

装饰一个包含 prompt 模板的函数。

这允许在函数的 docstring 中定义 prompt,并通过提供一定程度的封装来简化其操作。它内部使用 render 函数来渲染模板。

>>> import outlines
>>>
>>> @outlines.prompt
>>> def build_prompt(question):
...    "I have a ${question}"
...
>>> prompt = build_prompt("How are you?")

此 API 在“agent”场景下也很有用,其中 prompt 的部分内容在 agent 初始化时设置,之后不再修改。在这种情况下,我们可以在初始化时部分应用 prompt 函数。

>>> import outlines
>>> import functools as ft
...
>>> @outlines.prompt
... def solve_task(name: str, objective: str, task: str):
...     """Your name is {{name}}.
...     Your overall objective is to {{objective}}.
...     Please solve the following task: {{task}}
...     """
...
>>> hal = ft.partial(solve_task, "HAL", "Travel to Jupiter")

额外的 Jinja2 过滤器可以作为关键字参数提供给装饰器。

>>> def reverse(s: str) -> str:
...     return s[::-1]
...
>>> @outlines.prompt(filters={ 'reverse': reverse })
... def reverse_prompt(text):
...     """{{ text | reverse }}"""
...
>>> prompt = reverse_prompt("Hello")
>>> print(prompt)
... "olleH"

返回值

类型 描述
一个 `Template` 可调用类,在被调用时将渲染模板。
源代码位于 outlines/templates.py
def prompt(
    fn: Optional[Callable] = None,
    filters: Dict[str, Callable] = {},
) -> Callable:
    """Decorate a function that contains a prompt template.

    This allows to define prompts in the docstring of a function and simplify their
    manipulation by providing some degree of encapsulation. It uses the `render`
    function internally to render templates.

    ```pycon
    >>> import outlines
    >>>
    >>> @outlines.prompt
    >>> def build_prompt(question):
    ...    "I have a ${question}"
    ...
    >>> prompt = build_prompt("How are you?")
    ```

    This API can also be helpful in an "agent" context where parts of the prompt
    are set when the agent is initialized and never modified later. In this situation
    we can partially apply the prompt function at initialization.

    ```pycon
    >>> import outlines
    >>> import functools as ft
    ...
    >>> @outlines.prompt
    ... def solve_task(name: str, objective: str, task: str):
    ...     \"""Your name is {{name}}.
    ...     Your overall objective is to {{objective}}.
    ...     Please solve the following task: {{task}}
    ...     \"""
    ...
    >>> hal = ft.partial(solve_task, "HAL", "Travel to Jupiter")
    ```

    Additional Jinja2 filters can be provided as keyword arguments to the decorator.

    ```pycon
    >>> def reverse(s: str) -> str:
    ...     return s[::-1]
    ...
    >>> @outlines.prompt(filters={ 'reverse': reverse })
    ... def reverse_prompt(text):
    ...     \"""{{ text | reverse }}\"""
    ...
    >>> prompt = reverse_prompt("Hello")
    >>> print(prompt)
    ... "olleH"
    ```

    Returns
    -------
    A `Template` callable class which will render the template when called.

    """
    warnings.warn(
        "The @prompt decorator is deprecated and will be removed in outlines 1.1.0. "
        "Instead of using docstring templates, please use Template.from_file() to "
        "load your prompts from separate template files, or a simple Python function "
        "that returns text. This helps keep prompt content separate from code and is "
        "more maintainable.",
        DeprecationWarning,
        stacklevel=2,
    )

    if fn is None:
        return lambda fn: prompt(fn, cast(Dict[str, Callable], filters))

    signature = inspect.signature(fn)

    # The docstring contains the template that will be rendered to be used
    # as a prompt to the language model.
    docstring = fn.__doc__
    if docstring is None:
        raise TypeError("Could not find a template in the function's docstring.")

    template = build_template_from_string(cast(str, docstring), filters)

    return Template(template, signature)