Skip to content
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

Refactor the sequence generation #366

Merged
merged 25 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f70a05f
Move `prompts.py` to root folder
rlouf Nov 16, 2023
f942fb1
Rename `sample.py` to `samplers.py`
rlouf Nov 16, 2023
84b70c2
Separate indexing from generation. /text -> /generate and flatten
rlouf Nov 16, 2023
009f02a
Add `Index` type
rlouf Nov 16, 2023
121e983
Add generator that samples the next tokens
rlouf Nov 15, 2023
f0ef464
Create FSM that stops generation when token found
rlouf Nov 22, 2023
190cac0
Create Regex FSM
rlouf Nov 22, 2023
0ed3609
Add user interface for text generation
rlouf Nov 23, 2023
0e599ef
Remove old text generation logic
rlouf Nov 27, 2023
dc3c1ef
Use `torch.multinomial` instead of custom sampler
rlouf Nov 30, 2023
557fb73
Fix `datetime.time` regex
rlouf Nov 30, 2023
f6a711d
Add aliases and deprecation warnings for old API
rlouf Nov 30, 2023
45c7571
Convert JSON output to pydantic model or dictionary
rlouf Nov 30, 2023
97a62d6
Move `index` to `fsm`
rlouf Dec 1, 2023
3f1c955
Update the documentation
rlouf Dec 1, 2023
71b2003
Make `max_token` change FSM state
rlouf Dec 1, 2023
3c0edde
Add `torch.inference_mode` decorator
rlouf Dec 4, 2023
0357640
Rename `next_instruction` to `forbidden_logits`
rlouf Dec 4, 2023
40019eb
Move init of generator state outside of `SequenceGeneration`
rlouf Dec 4, 2023
7c82d66
Move `SequenceGenerator` to `api.py`
rlouf Dec 4, 2023
17096a4
Return FSM states with the sequence generator
rlouf Dec 4, 2023
dd8e21a
Make FSM return allowed tokens
rlouf Dec 6, 2023
3e15a11
Make `stream` output tokens with whitespaces
rlouf Dec 7, 2023
36feb3c
Bump Pytorch version
rlouf Dec 7, 2023
8190126
Update the documentation
rlouf Dec 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 29 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,34 +83,21 @@ is to ensure that there is a well-defined interface between their output and
user-defined code. **Outlines** provides ways to control the generation of
language models to make their output more predictable.

### Early stopping

You can stop the generation after a given sequence has been found:

``` python
import outlines.text.generate as generate
import outlines.models as models

model = models.transformers("gpt2")
answer = generate.continuation(model, stop=["."])("Tell me a one-sentence joke.")
```

### Multiple choices

You can reduce the completion to a choice between multiple possibilities:

``` python
import outlines.text.generate as generate
import outlines.models as models
import outlines

model = models.transformers("gpt2")
model = outlines.models.transformers("gpt2")

prompt = """You are a sentiment-labelling assistant.
Is the following review positive or negative?
Review: This restaurant is just awesome!
"""
answer = generate.choice(model, ["Positive", "Negative"])(prompt)
answer = outlines.generate.choice(model, ["Positive", "Negative"])(prompt)
```

### Type constraint
Expand All @@ -119,16 +106,15 @@ You can instruct the model to only return integers or floats:


``` python
import outlines.text.generate as generate
import outlines.models as models
import outlines

model = models.transformers("gpt2")
model = outlines.models.transformers("gpt2")

prompt = "1+1="
answer = generate.integer(model)(prompt)
answer = outlines.generate.format(model, int)(prompt)

prompt = "sqrt(2)="
answer = generate.float(model)(prompt)
answer = outlines.generate.format(model, float)(prompt)
```

### Efficient regex-guided generation
Expand All @@ -138,15 +124,13 @@ Outlines also comes with fast regex-guided generation. In fact, the `choice`,
hood:

``` python
import outlines.models as models
import outlines.text.generate as generate
import outlines


model = models.transformers("gpt2-medium")
model = outlines.models.transformers("gpt2-medium")

prompt = "Is 1+1=2? "
unguided = generate.continuation(model, max_tokens=30)(prompt)
guided = generate.regex(model, r"\s*([Yy]es|[Nn]o|[Nn]ever|[Aa]lways)", max_tokens=30)(
unguided = outlines.generate.continuation(model, max_tokens=30)(prompt)
guided = outlines.generate.regex(model, r"\s*([Yy]es|[Nn]o|[Nn]ever|[Aa]lways)", max_tokens=30)(
prompt
)

Expand All @@ -162,15 +146,13 @@ print(guided)
```

``` python
import outlines.models as models
import outlines.text.generate as generate

import outlines

model = models.transformers("gpt2-medium")
model = outlines.models.transformers("gpt2-medium")

prompt = "What is the IP address of the Google DNS servers? "
unguided = generate.continuation(model, max_tokens=30)(prompt)
guided = generate.regex(
guided = outlines.generate.regex(
model,
r"((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)",
max_tokens=30,
Expand Down Expand Up @@ -199,9 +181,7 @@ Outlines 〰 allows to guide the generation process so the output is *guaranteed
from enum import Enum
from pydantic import BaseModel, constr

import outlines.models as models
import outlines.text.generate as generate

import outlines
import torch


Expand All @@ -228,10 +208,10 @@ class Character(BaseModel):
strength: int


model = models.transformers("gpt2", device="cuda")
model = outlines.models.transformers("gpt2", device="cuda")

# Construct guided sequence generator
generator = generate.json(model, Character, max_tokens=100)
generator = outlines.generate.json(model, Character, max_tokens=100)

# Draw a sample
rng = torch.Generator(device="cuda")
Expand Down Expand Up @@ -269,14 +249,14 @@ The method works with union types, optional types, arrays, nested schemas, etc.
Outlines can infer the structure of the output from the signature of a function. The result is a dictionary, and can be passed directly to the function using the usual dictionary expansion syntax `**`:

```python
from outlines import models
from outlines import text
import outlines


def add(a: int, b: int):
return a + b

model = models.transformers("mistralai/Mistral-7B")
generator = text.generate.json(model, add)
model = outlines.models.transformers("mistralai/Mistral-7B")
generator = outlines.generate.json(model, add)
result = generator("Return two integers named a and b respectively. a is odd and b even.")

print(add(**result))
Expand All @@ -300,9 +280,7 @@ Template functions require no superfluous abstraction, they use the Jinja2
templating engine to help build complex prompts in a concise manner:

``` python
import outlines.text as text
import outlines.models as models

import outlines

examples = [
("The food was digusting", "Negative"),
Expand All @@ -311,7 +289,7 @@ examples = [
("The waiter was rude", "Negative")
]

@text.prompt
@outlines.prompt
def labelling(to_label, examples):
"""You are a sentiment-labelling assistant.
Expand All @@ -321,9 +299,9 @@ def labelling(to_label, examples):
{{ to_label }} //
"""

model = models.transformers("gpt2")
model = outlines.models.transformers("gpt2")
prompt = labelling("Just awesome", examples)
answer = text.generate.continuation(model, max_tokens=100)(prompt)
answer = outlines.generate.continuation(model, max_tokens=100)(prompt)
```

### Tools
Expand All @@ -337,7 +315,7 @@ extract the function's name, description, signature and source:

``` python
from typing import Callable, List
import outlines.text as text
import outlines


def google_search(query: str):
Expand All @@ -350,7 +328,7 @@ def wikipedia_search(query: str):
pass


@text.prompt
@outlines.prompt
def my_commands(tools: List[Callable]):
"""AVAILABLE COMMANDS:
Expand All @@ -374,7 +352,7 @@ extract the expected response's schema:

``` python
from pydantic import BaseModel, Field
import outlines.text as text
import outlines


class Joke(BaseModel):
Expand All @@ -384,7 +362,7 @@ class Joke(BaseModel):
)


@text.prompt
@outlines.prompt
def joke_ppt(response_model):
"""Tell a joke and explain why the joke is funny.
Expand Down
1 change: 0 additions & 1 deletion docs/api/continuation.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/api/fsm.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
::: outlines.text.fsm
::: outlines.fsm.fsm
2 changes: 1 addition & 1 deletion docs/api/json_schema.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
::: outlines.text.json_schema
::: outlines.fsm.json_schema
2 changes: 1 addition & 1 deletion docs/api/parsing.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
::: outlines.text.parsing
::: outlines.fsm.parsing
2 changes: 1 addition & 1 deletion docs/api/prompts.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
::: outlines.text.prompts
::: outlines.prompts
2 changes: 1 addition & 1 deletion docs/api/regex.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
::: outlines.text.generate.regex
::: outlines.generate.regex
1 change: 0 additions & 1 deletion docs/api/sample.md

This file was deleted.

1 change: 1 addition & 0 deletions docs/api/samplers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
::: outlines.generate.samplers
10 changes: 4 additions & 6 deletions docs/examples/chain_of_density.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ The prompt also asks the model to return a list of JSON objects that contain the
We can now implement the prompt provided in the paper:

```python
from outlines import text
import outlines

@text.prompt
@outlines.prompt
def chain_of_density(article):
"""Article: {{ article }}
Expand Down Expand Up @@ -86,12 +86,10 @@ class Summaries(BaseModel):
We now generate the prompt by passing the article we want to summarize to the template. We load a quantized version of Mistral-7B using the AutoAWQ library, and then use JSON-guided generation to generate the summaries:

```python
from outlines import models

model = models.awq("TheBloke/Mistral-7B-OpenOrca-AWQ")
model = outlines.models.awq("TheBloke/Mistral-7B-OpenOrca-AWQ")

prompt = chain_of_density(article)
result = text.generate.json(model, Summaries)(prompt)
result = outlines.generate.json(model, Summaries)(prompt)
```

We can now check the results:
Expand Down
9 changes: 4 additions & 5 deletions docs/examples/dating_profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import torch
import transformers
from pydantic import BaseModel, conlist, constr

import outlines.models as models
import outlines.text as text
import outlines
```

## Defining the profile with Pydantic
Expand Down Expand Up @@ -59,7 +58,7 @@ We will use Outlines' prompt templating abilities to generate the prompt for us.

```python

@text.prompt
@outlines.prompt
def dating_profile_prompt(description: str, examples: list[Example]):
"""
You are a world-renowned matchmaker who understands the modern dating
Expand Down Expand Up @@ -136,7 +135,7 @@ config = transformers.AutoConfig.from_pretrained(
"mosaicml/mpt-7b-8k-instruct", trust_remote_code=True
)
config.init_device = "meta"
model = models.transformers(
model = outlines.models.transformers(
model_name="mosaicml/mpt-7b-8k-instruct",
device="cuda",
model_kwargs={
Expand All @@ -163,7 +162,7 @@ it's a good excuse for a date. I watch the latest series because I'm paying,
with my hard-earned money, for every streaming service."""

prompt = dating_profile_prompt(new_description, samples)
profile = text.generate.json(model, DatingProfile)(prompt)
profile = outlines.generate.json(model, DatingProfile)(prompt)
parsed_profile = DatingProfile.model_validate_json(profile)
```

Expand Down
8 changes: 4 additions & 4 deletions examples/babyagi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from collections import deque
from typing import Deque, List

import outlines
import outlines.models as models
import outlines.text as text

model = models.openai("gpt-3.5-turbo")

Expand All @@ -18,7 +18,7 @@
#################


@text.prompt
@outlines.prompt
def perform_task_ppt(objective: str, task: str):
"""You are an AI who performs one task based on the following objective: {{objective}}.

Expand All @@ -33,7 +33,7 @@ def perform_task_ppt(objective: str, task: str):
#####################


@text.prompt
@outlines.prompt
def create_tasks_ppt(
objective: str, previous_task: str, result: str, task_list: List[str]
):
Expand Down Expand Up @@ -69,7 +69,7 @@ def create_tasks_fmt(result: str) -> List[str]:
########################


@text.prompt
@outlines.prompt
def prioritize_tasks_ppt(objective: str, task_names: List[str], next_task_id: int):
"""You are a task prioritization AI tasked with cleaning the formatting of \
and reprioritizing the following tasks: {{task_names}}.
Expand Down
8 changes: 4 additions & 4 deletions examples/dating_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import transformers
from pydantic import BaseModel, conlist

import outlines.models as models
import outlines.text as text
import outlines
from outlines import models


class QuestionChoice(str, Enum):
Expand Down Expand Up @@ -41,7 +41,7 @@ class Example:
profile: DatingProfile


@text.prompt
@outlines.prompt
def dating_profile_prompt(description: str, examples: list[Example]):
"""
You are a world-renowned matchmaker who understands the modern dating market. Your job is to generate dating app profiles for male clients interested in women based on a provided description. The profiles should be authentic, show off their strengths, and maximize their likelihood of getting matches on dating apps.
Expand Down Expand Up @@ -121,7 +121,7 @@ def dating_profile_prompt(description: str, examples: list[Example]):
new_description = "I'm a laid-back lawyer who spends a lot of his free-time gaming. I work in a corporate office, but ended up here after the start-up I cofounded got acquired, so still play ping pong with my cool coworkers every day. I have a bar at home where I make cocktails, which is great for entertaining friends. I secretly like to wear suits and get a new one tailored every few months. I also like weddings because I get to wear those suits, and it's a good excuse for a date. I watch the latest series because I'm paying, with my hard-earned money, for every streaming service."

prompt = dating_profile_prompt(description=new_description, examples=samples)
profile = text.generate.json(model, DatingProfile)(prompt) # type: ignore
profile = outlines.generate.json(model, DatingProfile)(prompt) # type: ignore
print(profile)

# Sample generated profiles
Expand Down
4 changes: 2 additions & 2 deletions examples/math_generate_code.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Example from https://dust.tt/spolu/a/d12ac33169"""
import outlines
import outlines.models as models
import outlines.text as text

examples = [
{"question": "What is 37593 * 67?", "code": "37593 * 67"},
Expand All @@ -17,7 +17,7 @@
question = "Carla is downloading a 200 GB file. She can download 2 GB/minute, but 40% of the way through the download, the download fails. Then Carla has to restart the download from the beginning. How load did it take her to download the file in minutes?"


@text.prompt
@outlines.prompt
def answer_with_code_prompt(question, examples):
"""
{% for example in examples %}
Expand Down
Loading