From 43b2d7473f01b160a4ef4def50349b722caaf675 Mon Sep 17 00:00:00 2001 From: Robin Picard Date: Thu, 27 Feb 2025 14:15:59 +0100 Subject: [PATCH] Create the Macro class --- docs/api/macros.md | 1 + docs/reference/macros.md | 39 +++++++++++++++++++++++++++ outlines/__init__.py | 1 + outlines/function.py | 12 +++++++++ outlines/macros.py | 58 ++++++++++++++++++++++++++++++++++++++++ tests/test_macros.py | 57 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 168 insertions(+) create mode 100644 docs/api/macros.md create mode 100644 docs/reference/macros.md create mode 100644 outlines/macros.py create mode 100644 tests/test_macros.py diff --git a/docs/api/macros.md b/docs/api/macros.md new file mode 100644 index 000000000..36d7b198e --- /dev/null +++ b/docs/api/macros.md @@ -0,0 +1 @@ +::: outlines.macros diff --git a/docs/reference/macros.md b/docs/reference/macros.md new file mode 100644 index 000000000..e0534a14c --- /dev/null +++ b/docs/reference/macros.md @@ -0,0 +1,39 @@ +# Macros + +Outlines offers a convenient way of encapsulating a model, a prompt template, and an output type in a single object called a `Macro`. After instantiating a `Macro`, it can be called just like a function with arguments that will be passed to the template to create the prompt. The prompt is then used to call the model with the output type first specified to generate an answer. + +## Create a Macro + +To create a Macro, you need to provide 3 arguments: + +- A model: an instance of an Outlines model class from module `outlines.models` +- A template: either an instance of `outlines.templates.Template` or a callable that takes arguments and returns a prompt +- An output type: a type instance from `outlines.types` that is used to define the structure of the output + +```python +from pydantic import BaseModel +from outlines.models import transformers +from outlines.templates import Template +from outlines.types import JsonType + +class OutputModel(BaseModel): + result: int + +model = transformers.from_transformers( + "microsoft/Phi-3-mini-4k-instruct", + "microsoft/Phi-3-mini-4k-instruct" +) +template = Template.from_str("What is 2 times {{ num }}?") +output_type = JsonType(OutputModel) + +macro = Macro(model, template, output_type) +``` + +## Call a Macro + +Once the Macro is instantiated, it can be called just like a function with arguments that will be passed to the template to create the prompt. The prompt is then used to call the model with the output type first specified to generate an answer. + +```python +result = macro(num=3) +print(result) # Expected output: { "result" : 6 } +``` diff --git a/outlines/__init__.py b/outlines/__init__.py index 4aacb6c7e..42660a8a0 100644 --- a/outlines/__init__.py +++ b/outlines/__init__.py @@ -9,6 +9,7 @@ from outlines.base import vectorize from outlines.caching import clear_cache, disable_cache, get_cache from outlines.function import Function +from outlines.macros import Macro from outlines.generate import Generator from outlines.templates import Template, prompt diff --git a/outlines/function.py b/outlines/function.py index cabaf1fef..6003f507f 100644 --- a/outlines/function.py +++ b/outlines/function.py @@ -20,6 +20,11 @@ class Function: the function can be called with arguments that will be used to render the prompt template. + Note: + This class is part of the deprecated 'function' module and is deprecated + starting from version 1.0.0. It will be removed in version 1.1.0. Please + pin your version to <1.1.0 if you need to continue using it. + """ prompt_template: "Template" @@ -27,6 +32,13 @@ class Function: model_name: str generator: Optional["SequenceGeneratorAdapter"] = None + def __post_init__(self): + raise DeprecationWarning( + "The 'function' module is deprecated starting from version 1.0.0 " + + "and will be removed in version 1.1.0. Please use the `Macro` " + + "class instead. See https://github.com/dottxt-ai/outlines/tree/main/docs/reference/macros.md" + ) + @classmethod def from_github(cls, program_path: str, function_name: str = "fn"): """Load a function stored on GitHub""" diff --git a/outlines/macros.py b/outlines/macros.py new file mode 100644 index 000000000..a9ecd2667 --- /dev/null +++ b/outlines/macros.py @@ -0,0 +1,58 @@ +from dataclasses import dataclass +from typing import Any, Callable, Union + +from outlines.generate import Generator +from outlines.templates import Template +from outlines.models import Model + + +@dataclass +class Macro: + """ + Macro is a class that encapsulates a model, a prompt template, and an + output type. It can be called to generate a response. + + Parameters + ---------- + model : Model + The Outlines model to be used for generating responses. + template : Union[Template, Callable] + A callable that takes arguments and returns a prompt string. + output_type : Any + The expected output type of the generated response. + + Examples + -------- + from pydantic import BaseModel + from transformers import AutoModelForCausalLM, AutoTokenizer + from outlines import models, Macro + from outlines.types import JsonType + from outlines.templates import Template + + class OutputModel(BaseModel): + result: int + + model = models.from_transformers( + AutoModelForCausalLM.from_pretrained("microsoft/Phi-3-mini-4k-instruct"), + AutoTokenizer.from_pretrained("microsoft/Phi-3-mini-4k-instruct") + ) + + template_string = "What is 2 times {{ num }}?" + template = Template.from_str(template_string) + + my_macro = Macro(model, template, JsonType(OutputModel)) + + result = my_macro(num=3) + print(result) # Expected output: { "result" : 6 } + """ + model: Model + template: Union[Template, Callable] + output_type: Any + + def __post_init__(self): + self.template = self.template + self.generator = Generator(self.model, self.output_type) + + def __call__(self, *args, **kwargs): + prompt = self.template(*args, **kwargs) + return self.generator(prompt) diff --git a/tests/test_macros.py b/tests/test_macros.py new file mode 100644 index 000000000..ab6cbb7c1 --- /dev/null +++ b/tests/test_macros.py @@ -0,0 +1,57 @@ +import pytest + +import jinja2 + +from outlines.macros import Macro +from outlines.templates import Template +from outlines.models import Model +from outlines.generate import Generator +from typing import Any + + +@pytest.fixture(scope="session") +def model(): + class MockModel(Model): + type_adapter = None + + def generate(self, model_input: str, output_type=None, **kwargs) -> Any: + return model_input + + return MockModel() + + +def test_macro_initialization(model): + template = Template.from_str("Test {{ value }}") + output_type = None + macro = Macro(model, template, output_type) + + assert macro.generator == Generator(model, output_type) + assert macro.template == template + + +def test_macro_template_call(model): + template = Template.from_str("Test {{ value }}") + output_type = None + macro = Macro(model, template, output_type) + result = macro(value="example") + + assert result == "Test example" + + +def test_macro_callable_call(model): + def template(value): + return f"Test {value}" + + output_type = None + macro = Macro(model, template, output_type) + result = macro("example") + + assert result == "Test example" + +def test_macro_template_error(model): + template = Template.from_str("Test {{ value }}") + output_type = None + macro = Macro(model, template, output_type) + + with pytest.raises(jinja2.exceptions.UndefinedError): + macro(foo="bar")