-
Notifications
You must be signed in to change notification settings - Fork 248
Add experimental replicate.use()
function
#438
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
Open
aron
wants to merge
38
commits into
main
Choose a base branch
from
experimental-use-fn
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
e4398b9
Add initial test for `use()` functionality
aron 587ed82
Add unit tests for existing `use()` functionality
aron 70e1da7
Fix warning for missing pytest setting
aron 607150f
Remove unused ignores
aron 736604b
Refactor tests to be easier to work with
aron 40c97a7
Support conversion of file outputs into Path in use()
aron 196aef0
Add support for returning an iterator from use()
aron 9a293e6
Fix bug in output_iterator when prediction is terminal
aron 83d8fad
Update OutputIterator to use polling implementation
aron f017857
Ensure OutputIterator objects are converted into strings
aron 35eb88b
Implement PathProxy as a way to defer download of file data
aron 8d85629
Skip downloading files passed directly into other models in use()
aron 20a37d1
Add get_url_path() helper to get underlying URL for a PathProxy object
aron bae5dc8
Export the `use` function
aron ae1589f
Document the `use()` functionality
aron 82e40ce
Linting
aron bad0ce4
Rework the async test variant to give better test names
aron 35e66dc
Fix typing of Function create()
aron 639f234
Add support for typing use() function
aron 65a89d3
Clean up tests
aron b79a5cd
Clean up prediction fixtures
aron 80ce4e5
Remove redundant fixture data
aron bc5d7d8
Speed up test runs by using REPLICATE_POLL_INTERVAL
aron 4111e82
Actually use PathProxy
aron e8acdb2
Use new URLPath instead of PathProxy
aron 2df34ed
Silence warning when using cog.current_scope()
aron c982d53
Add asyncio support to `use()` function
aron 050798e
Document asyncio mode for `use()`
aron a00b5d2
Improve typing of OutputIterator
aron 83793a0
Correctly resolve OutputIterator when passed to `create()`
aron 2afd364
URLPath.__str__() uses __fspath__()
aron dd64e91
Implement use(ref, streaming=True) to return iterators
aron cd12cf4
Correctly handle concatenated output when not streaming
aron 1185b7b
Remove useless comments
aron f160fef
Implement `streaming` argument for `use()`
aron 57bab3e
Fix lint errors
aron adb4fa7
Remove top-level restrictions
aron 3b5200b
Clean up linting issues
aron File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -503,6 +503,222 @@ replicate = Client( | |
> Never hardcode authentication credentials like API tokens into your code. | ||
> Instead, pass them as environment variables when running your program. | ||
|
||
## Experimental `use()` interface | ||
|
||
The latest versions of `replicate >= 1.0.8` include a new experimental `use()` function that is intended to make running a model closer to calling a function rather than an API request. | ||
|
||
Some key differences to `replicate.run()`. | ||
|
||
1. You "import" the model using the `use()` syntax, after that you call the model like a function. | ||
2. The output type matches the model definition. | ||
3. Baked in support for streaming for all models. | ||
4. File outputs will be represented as `PathLike` objects and downloaded to disk when used*. | ||
|
||
> [!NOTE] | ||
> \* We've replaced the `FileOutput` implementation with `Path` objects. However to avoid unnecessary downloading of files until they are needed we've implemented a `PathProxy` class that will defer the download until the first time the object is used. If you need the underlying URL of the `Path` object you can use the `get_path_url(path: Path) -> str` helper. | ||
|
||
### Examples | ||
|
||
To use a model: | ||
|
||
> [!IMPORTANT] | ||
> For now `use()` MUST be called in the top level module scope. We may relax this in future. | ||
|
||
```py | ||
import replicate | ||
|
||
flux_dev = replicate.use("black-forest-labs/flux-dev") | ||
outputs = flux_dev(prompt="a cat wearing an amusing hat") | ||
|
||
for output in outputs: | ||
print(output) # Path(/tmp/output.webp) | ||
``` | ||
|
||
Models that implement iterators will return the output of the completed run as a list unless explicitly streaming (see Streaming section below). Language models that define `x-cog-iterator-display: concatenate` will return strings: | ||
|
||
```py | ||
claude = replicate.use("anthropic/claude-4-sonnet") | ||
|
||
output = claude(prompt="Give me a recipe for tasty smashed avocado on sourdough toast that could feed all of California.") | ||
|
||
print(output) # "Here's a recipe to feed all of California (about 39 million people)! ..." | ||
``` | ||
|
||
You can pass the results of one model directly into another: | ||
|
||
```py | ||
import replicate | ||
|
||
flux_dev = replicate.use("black-forest-labs/flux-dev") | ||
claude = replicate.use("anthropic/claude-4-sonnet") | ||
|
||
images = flux_dev(prompt="a cat wearing an amusing hat") | ||
|
||
result = claude(prompt="describe this image for me", image=images[0]) | ||
|
||
print(str(result)) # "This shows an image of a cat wearing a hat ..." | ||
``` | ||
|
||
To create an individual prediction that has not yet resolved, use the `create()` method: | ||
|
||
``` | ||
claude = replicate.use("anthropic/claude-4-sonnet") | ||
|
||
prediction = claude.create(prompt="Give me a recipe for tasty smashed avocado on sourdough toast that could feed all of California.") | ||
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.
☝🏼 I like it! |
||
|
||
prediction.logs() # get current logs (WIP) | ||
|
||
prediction.output() # get the output | ||
``` | ||
|
||
### Streaming | ||
|
||
Many models, particularly large language models (LLMs), will yield partial results as the model is running. To consume outputs from these models as they run you can pass the `streaming` argument to `use()`: | ||
|
||
```py | ||
claude = replicate.use("anthropic/claude-4-sonnet", streaming=True) | ||
|
||
output = claude(prompt="Give me a recipe for tasty smashed avocado on sourdough toast that could feed all of California.") | ||
|
||
for chunk in output: | ||
print(chunk) # "Here's a recipe ", "to feed all", " of California" | ||
``` | ||
|
||
### Downloading file outputs | ||
|
||
Output files are provided as Python [os.PathLike](https://docs.python.org/3.12/library/os.html#os.PathLike) objects. These are supported by most of the Python standard library like `open()` and `Path`, as well as third-party libraries like `pillow` and `ffmpeg-python`. | ||
|
||
The first time the file is accessed it will be downloaded to a temporary directory on disk ready for use. | ||
|
||
Here's an example of how to use the `pillow` package to convert file outputs: | ||
|
||
```py | ||
import replicate | ||
from PIL import Image | ||
|
||
flux_dev = replicate.use("black-forest-labs/flux-dev") | ||
|
||
images = flux_dev(prompt="a cat wearing an amusing hat") | ||
for i, path in enumerate(images): | ||
with Image.open(path) as img: | ||
img.save(f"./output_{i}.png", format="PNG") | ||
``` | ||
|
||
For libraries that do not support `Path` or `PathLike` instances you can use `open()` as you would with any other file. For example to use `requests` to upload the file to a different location: | ||
|
||
```py | ||
import replicate | ||
import requests | ||
|
||
flux_dev = replicate.use("black-forest-labs/flux-dev") | ||
|
||
images = flux_dev(prompt="a cat wearing an amusing hat") | ||
for path in images: | ||
with open(path, "rb") as f: | ||
r = requests.post("https://api.example.com/upload", files={"file": f}) | ||
``` | ||
|
||
### Accessing outputs as HTTPS URLs | ||
|
||
If you do not need to download the output to disk. You can access the underlying URL for a Path object returned from a model call by using the `get_path_url()` helper. | ||
|
||
```py | ||
import replicate | ||
from replicate import get_url_path | ||
|
||
flux_dev = replicate.use("black-forest-labs/flux-dev") | ||
outputs = flux_dev(prompt="a cat wearing an amusing hat") | ||
|
||
for output in outputs: | ||
print(get_url_path(output)) # "https://replicate.delivery/xyz" | ||
``` | ||
|
||
### Async Mode | ||
|
||
By default `use()` will return a function instance with a sync interface. You can pass `use_async=True` to have it return an `AsyncFunction` that provides an async interface. | ||
|
||
```py | ||
import asyncio | ||
import replicate | ||
|
||
async def main(): | ||
flux_dev = replicate.use("black-forest-labs/flux-dev", use_async=True) | ||
outputs = await flux_dev(prompt="a cat wearing an amusing hat") | ||
|
||
for output in outputs: | ||
print(Path(output)) | ||
|
||
asyncio.run(main()) | ||
``` | ||
|
||
When used in streaming mode then an `AsyncIterator` will be returned. | ||
|
||
```py | ||
import asyncio | ||
import replicate | ||
|
||
async def main(): | ||
claude = replicate.use("anthropic/claude-3.5-haiku", streaming=True, use_async=True) | ||
output = await claude(prompt="say hello") | ||
|
||
# Stream the response as it comes in. | ||
async for token in output: | ||
print(token) | ||
|
||
# Wait until model has completed. This will return either a `list` or a `str` depending | ||
# on whether the model uses AsyncIterator or ConcatenateAsyncIterator. You can check this | ||
# on the model schema by looking for `x-cog-display: concatenate`. | ||
print(await output) | ||
|
||
asyncio.run(main()) | ||
``` | ||
|
||
### Typing | ||
|
||
By default `use()` knows nothing about the interface of the model. To provide a better developer experience we provide two methods to add type annotations to the function returned by the `use()` helper. | ||
|
||
**1. Provide a function signature** | ||
|
||
The use method accepts a function signature as an additional `hint` keyword argument. When provided it will use this signature for the `model()` and `model.create()` functions. | ||
|
||
```py | ||
# Flux takes a required prompt string and optional image and seed. | ||
def hint(*, prompt: str, image: Path | None = None, seed: int | None = None) -> str: ... | ||
|
||
flux_dev = use("black-forest-labs/flux-dev", hint=hint) | ||
output1 = flux_dev() # will warn that `prompt` is missing | ||
output2 = flux_dev(prompt="str") # output2 will be typed as `str` | ||
``` | ||
|
||
**2. Provide a class** | ||
|
||
The second method requires creating a callable class with a `name` field. The name will be used as the function reference when passed to `use()`. | ||
|
||
```py | ||
class FluxDev: | ||
name = "black-forest-labs/flux-dev" | ||
|
||
def __call__( self, *, prompt: str, image: Path | None = None, seed: int | None = None ) -> str: ... | ||
|
||
flux_dev = use(FluxDev) | ||
output1 = flux_dev() # will warn that `prompt` is missing | ||
output2 = flux_dev(prompt="str") # output2 will be typed as `str` | ||
``` | ||
|
||
> [!WARNING] | ||
> Currently the typing system doesn't correctly support the `streaming` flag for models that return lists or use iterators. We're working on improvements here. | ||
|
||
In future we hope to provide tooling to generate and provide these models as packages to make working with them easier. For now you may wish to create your own. | ||
|
||
### TODO | ||
|
||
There are several key things still outstanding: | ||
|
||
1. Support for streaming text when available (rather than polling) | ||
2. Support for streaming files when available (rather than polling) | ||
3. Support for cleaning up downloaded files. | ||
4. Support for streaming logs using `OutputIterator`. | ||
|
||
## Development | ||
|
||
See [CONTRIBUTING.md](CONTRIBUTING.md) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I pass
images[0]
here, what's happening under the hood? Is it using the existing replicate.delivery HTTPS URL of the output file?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, file outputs are now a thin wrapper around
Path
(seePathProxy
) that will defer the download of the remote file to disk until the first time they are used.To get the underlying URL, there is a
get_path_url()
helper that will return the remote URL of the file.This is then used internally so that the file is never downloaded in cases like this where an output is just passed as an input to another model.
Feedback on this would be greatly appreciated. This and the
OutputIterator
are the two most complex pieces of this PR.