pixeltable 0.3.14__py3-none-any.whl → 0.5.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pixeltable/__init__.py +42 -8
- pixeltable/{dataframe.py → _query.py} +470 -206
- pixeltable/_version.py +1 -0
- pixeltable/catalog/__init__.py +5 -4
- pixeltable/catalog/catalog.py +1785 -432
- pixeltable/catalog/column.py +190 -113
- pixeltable/catalog/dir.py +2 -4
- pixeltable/catalog/globals.py +19 -46
- pixeltable/catalog/insertable_table.py +191 -98
- pixeltable/catalog/path.py +63 -23
- pixeltable/catalog/schema_object.py +11 -15
- pixeltable/catalog/table.py +843 -436
- pixeltable/catalog/table_metadata.py +103 -0
- pixeltable/catalog/table_version.py +978 -657
- pixeltable/catalog/table_version_handle.py +72 -16
- pixeltable/catalog/table_version_path.py +112 -43
- pixeltable/catalog/tbl_ops.py +53 -0
- pixeltable/catalog/update_status.py +191 -0
- pixeltable/catalog/view.py +134 -90
- pixeltable/config.py +134 -22
- pixeltable/env.py +471 -157
- pixeltable/exceptions.py +6 -0
- pixeltable/exec/__init__.py +4 -1
- pixeltable/exec/aggregation_node.py +7 -8
- pixeltable/exec/cache_prefetch_node.py +83 -110
- pixeltable/exec/cell_materialization_node.py +268 -0
- pixeltable/exec/cell_reconstruction_node.py +168 -0
- pixeltable/exec/component_iteration_node.py +4 -3
- pixeltable/exec/data_row_batch.py +8 -65
- pixeltable/exec/exec_context.py +16 -4
- pixeltable/exec/exec_node.py +13 -36
- pixeltable/exec/expr_eval/evaluators.py +11 -7
- pixeltable/exec/expr_eval/expr_eval_node.py +27 -12
- pixeltable/exec/expr_eval/globals.py +8 -5
- pixeltable/exec/expr_eval/row_buffer.py +1 -2
- pixeltable/exec/expr_eval/schedulers.py +106 -56
- pixeltable/exec/globals.py +35 -0
- pixeltable/exec/in_memory_data_node.py +19 -19
- pixeltable/exec/object_store_save_node.py +293 -0
- pixeltable/exec/row_update_node.py +16 -9
- pixeltable/exec/sql_node.py +351 -84
- pixeltable/exprs/__init__.py +1 -1
- pixeltable/exprs/arithmetic_expr.py +27 -22
- pixeltable/exprs/array_slice.py +3 -3
- pixeltable/exprs/column_property_ref.py +36 -23
- pixeltable/exprs/column_ref.py +213 -89
- pixeltable/exprs/comparison.py +5 -5
- pixeltable/exprs/compound_predicate.py +5 -4
- pixeltable/exprs/data_row.py +164 -54
- pixeltable/exprs/expr.py +70 -44
- pixeltable/exprs/expr_dict.py +3 -3
- pixeltable/exprs/expr_set.py +17 -10
- pixeltable/exprs/function_call.py +100 -40
- pixeltable/exprs/globals.py +2 -2
- pixeltable/exprs/in_predicate.py +4 -4
- pixeltable/exprs/inline_expr.py +18 -32
- pixeltable/exprs/is_null.py +7 -3
- pixeltable/exprs/json_mapper.py +8 -8
- pixeltable/exprs/json_path.py +56 -22
- pixeltable/exprs/literal.py +27 -5
- pixeltable/exprs/method_ref.py +2 -2
- pixeltable/exprs/object_ref.py +2 -2
- pixeltable/exprs/row_builder.py +167 -67
- pixeltable/exprs/rowid_ref.py +25 -10
- pixeltable/exprs/similarity_expr.py +58 -40
- pixeltable/exprs/sql_element_cache.py +4 -4
- pixeltable/exprs/string_op.py +5 -5
- pixeltable/exprs/type_cast.py +3 -5
- pixeltable/func/__init__.py +1 -0
- pixeltable/func/aggregate_function.py +8 -8
- pixeltable/func/callable_function.py +9 -9
- pixeltable/func/expr_template_function.py +17 -11
- pixeltable/func/function.py +18 -20
- pixeltable/func/function_registry.py +6 -7
- pixeltable/func/globals.py +2 -3
- pixeltable/func/mcp.py +74 -0
- pixeltable/func/query_template_function.py +29 -27
- pixeltable/func/signature.py +46 -19
- pixeltable/func/tools.py +31 -13
- pixeltable/func/udf.py +18 -20
- pixeltable/functions/__init__.py +16 -0
- pixeltable/functions/anthropic.py +123 -77
- pixeltable/functions/audio.py +147 -10
- pixeltable/functions/bedrock.py +13 -6
- pixeltable/functions/date.py +7 -4
- pixeltable/functions/deepseek.py +35 -43
- pixeltable/functions/document.py +81 -0
- pixeltable/functions/fal.py +76 -0
- pixeltable/functions/fireworks.py +11 -20
- pixeltable/functions/gemini.py +195 -39
- pixeltable/functions/globals.py +142 -14
- pixeltable/functions/groq.py +108 -0
- pixeltable/functions/huggingface.py +1056 -24
- pixeltable/functions/image.py +115 -57
- pixeltable/functions/json.py +1 -1
- pixeltable/functions/llama_cpp.py +28 -13
- pixeltable/functions/math.py +67 -5
- pixeltable/functions/mistralai.py +18 -55
- pixeltable/functions/net.py +70 -0
- pixeltable/functions/ollama.py +20 -13
- pixeltable/functions/openai.py +240 -226
- pixeltable/functions/openrouter.py +143 -0
- pixeltable/functions/replicate.py +4 -4
- pixeltable/functions/reve.py +250 -0
- pixeltable/functions/string.py +239 -69
- pixeltable/functions/timestamp.py +16 -16
- pixeltable/functions/together.py +24 -84
- pixeltable/functions/twelvelabs.py +188 -0
- pixeltable/functions/util.py +6 -1
- pixeltable/functions/uuid.py +30 -0
- pixeltable/functions/video.py +1515 -107
- pixeltable/functions/vision.py +8 -8
- pixeltable/functions/voyageai.py +289 -0
- pixeltable/functions/whisper.py +16 -8
- pixeltable/functions/whisperx.py +179 -0
- pixeltable/{ext/functions → functions}/yolox.py +2 -4
- pixeltable/globals.py +362 -115
- pixeltable/index/base.py +17 -21
- pixeltable/index/btree.py +28 -22
- pixeltable/index/embedding_index.py +100 -118
- pixeltable/io/__init__.py +4 -2
- pixeltable/io/datarows.py +8 -7
- pixeltable/io/external_store.py +56 -105
- pixeltable/io/fiftyone.py +13 -13
- pixeltable/io/globals.py +31 -30
- pixeltable/io/hf_datasets.py +61 -16
- pixeltable/io/label_studio.py +74 -70
- pixeltable/io/lancedb.py +3 -0
- pixeltable/io/pandas.py +21 -12
- pixeltable/io/parquet.py +25 -105
- pixeltable/io/table_data_conduit.py +250 -123
- pixeltable/io/utils.py +4 -4
- pixeltable/iterators/__init__.py +2 -1
- pixeltable/iterators/audio.py +26 -25
- pixeltable/iterators/base.py +9 -3
- pixeltable/iterators/document.py +112 -78
- pixeltable/iterators/image.py +12 -15
- pixeltable/iterators/string.py +11 -4
- pixeltable/iterators/video.py +523 -120
- pixeltable/metadata/__init__.py +14 -3
- pixeltable/metadata/converters/convert_13.py +2 -2
- pixeltable/metadata/converters/convert_18.py +2 -2
- pixeltable/metadata/converters/convert_19.py +2 -2
- pixeltable/metadata/converters/convert_20.py +2 -2
- pixeltable/metadata/converters/convert_21.py +2 -2
- pixeltable/metadata/converters/convert_22.py +2 -2
- pixeltable/metadata/converters/convert_24.py +2 -2
- pixeltable/metadata/converters/convert_25.py +2 -2
- pixeltable/metadata/converters/convert_26.py +2 -2
- pixeltable/metadata/converters/convert_29.py +4 -4
- pixeltable/metadata/converters/convert_30.py +34 -21
- pixeltable/metadata/converters/convert_34.py +2 -2
- pixeltable/metadata/converters/convert_35.py +9 -0
- pixeltable/metadata/converters/convert_36.py +38 -0
- pixeltable/metadata/converters/convert_37.py +15 -0
- pixeltable/metadata/converters/convert_38.py +39 -0
- pixeltable/metadata/converters/convert_39.py +124 -0
- pixeltable/metadata/converters/convert_40.py +73 -0
- pixeltable/metadata/converters/convert_41.py +12 -0
- pixeltable/metadata/converters/convert_42.py +9 -0
- pixeltable/metadata/converters/convert_43.py +44 -0
- pixeltable/metadata/converters/util.py +20 -31
- pixeltable/metadata/notes.py +9 -0
- pixeltable/metadata/schema.py +140 -53
- pixeltable/metadata/utils.py +74 -0
- pixeltable/mypy/__init__.py +3 -0
- pixeltable/mypy/mypy_plugin.py +123 -0
- pixeltable/plan.py +382 -115
- pixeltable/share/__init__.py +1 -1
- pixeltable/share/packager.py +547 -83
- pixeltable/share/protocol/__init__.py +33 -0
- pixeltable/share/protocol/common.py +165 -0
- pixeltable/share/protocol/operation_types.py +33 -0
- pixeltable/share/protocol/replica.py +119 -0
- pixeltable/share/publish.py +257 -59
- pixeltable/store.py +311 -194
- pixeltable/type_system.py +373 -211
- pixeltable/utils/__init__.py +2 -3
- pixeltable/utils/arrow.py +131 -17
- pixeltable/utils/av.py +298 -0
- pixeltable/utils/azure_store.py +346 -0
- pixeltable/utils/coco.py +6 -6
- pixeltable/utils/code.py +3 -3
- pixeltable/utils/console_output.py +4 -1
- pixeltable/utils/coroutine.py +6 -23
- pixeltable/utils/dbms.py +32 -6
- pixeltable/utils/description_helper.py +4 -5
- pixeltable/utils/documents.py +7 -18
- pixeltable/utils/exception_handler.py +7 -30
- pixeltable/utils/filecache.py +6 -6
- pixeltable/utils/formatter.py +86 -48
- pixeltable/utils/gcs_store.py +295 -0
- pixeltable/utils/http.py +133 -0
- pixeltable/utils/http_server.py +2 -3
- pixeltable/utils/iceberg.py +1 -2
- pixeltable/utils/image.py +17 -0
- pixeltable/utils/lancedb.py +90 -0
- pixeltable/utils/local_store.py +322 -0
- pixeltable/utils/misc.py +5 -0
- pixeltable/utils/object_stores.py +573 -0
- pixeltable/utils/pydantic.py +60 -0
- pixeltable/utils/pytorch.py +5 -6
- pixeltable/utils/s3_store.py +527 -0
- pixeltable/utils/sql.py +26 -0
- pixeltable/utils/system.py +30 -0
- pixeltable-0.5.7.dist-info/METADATA +579 -0
- pixeltable-0.5.7.dist-info/RECORD +227 -0
- {pixeltable-0.3.14.dist-info → pixeltable-0.5.7.dist-info}/WHEEL +1 -1
- pixeltable-0.5.7.dist-info/entry_points.txt +2 -0
- pixeltable/__version__.py +0 -3
- pixeltable/catalog/named_function.py +0 -40
- pixeltable/ext/__init__.py +0 -17
- pixeltable/ext/functions/__init__.py +0 -11
- pixeltable/ext/functions/whisperx.py +0 -77
- pixeltable/utils/media_store.py +0 -77
- pixeltable/utils/s3.py +0 -17
- pixeltable-0.3.14.dist-info/METADATA +0 -434
- pixeltable-0.3.14.dist-info/RECORD +0 -186
- pixeltable-0.3.14.dist-info/entry_points.txt +0 -3
- {pixeltable-0.3.14.dist-info → pixeltable-0.5.7.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pixeltable UDFs that wrap the OpenRouter API.
|
|
3
|
+
|
|
4
|
+
OpenRouter provides a unified interface to multiple LLM providers. In order to use it,
|
|
5
|
+
you must first sign up at https://openrouter.ai, create an API key, and configure it
|
|
6
|
+
as described in the Working with OpenRouter tutorial.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
import pixeltable as pxt
|
|
12
|
+
from pixeltable.env import Env, register_client
|
|
13
|
+
from pixeltable.utils.code import local_public_names
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
import openai
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@register_client('openrouter')
|
|
20
|
+
def _(api_key: str, site_url: str | None = None, app_name: str | None = None) -> 'openai.AsyncOpenAI':
|
|
21
|
+
import openai
|
|
22
|
+
|
|
23
|
+
# Create default headers for OpenRouter
|
|
24
|
+
default_headers: dict[str, Any] = {}
|
|
25
|
+
if site_url:
|
|
26
|
+
default_headers['HTTP-Referer'] = site_url
|
|
27
|
+
if app_name:
|
|
28
|
+
default_headers['X-Title'] = app_name
|
|
29
|
+
|
|
30
|
+
return openai.AsyncOpenAI(base_url='https://openrouter.ai/api/v1', api_key=api_key, default_headers=default_headers)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _openrouter_client() -> 'openai.AsyncOpenAI':
|
|
34
|
+
return Env.get().get_client('openrouter')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pxt.udf(resource_pool='request-rate:openrouter')
|
|
38
|
+
async def chat_completions(
|
|
39
|
+
messages: list,
|
|
40
|
+
*,
|
|
41
|
+
model: str,
|
|
42
|
+
model_kwargs: dict[str, Any] | None = None,
|
|
43
|
+
tools: list[dict[str, Any]] | None = None,
|
|
44
|
+
tool_choice: dict[str, Any] | None = None,
|
|
45
|
+
provider: dict[str, Any] | None = None,
|
|
46
|
+
transforms: list[str] | None = None,
|
|
47
|
+
) -> dict:
|
|
48
|
+
"""
|
|
49
|
+
Chat Completion API via OpenRouter.
|
|
50
|
+
|
|
51
|
+
OpenRouter provides access to multiple LLM providers through a unified API.
|
|
52
|
+
For additional details, see: <https://openrouter.ai/docs>
|
|
53
|
+
|
|
54
|
+
Supported models can be found at: <https://openrouter.ai/models>
|
|
55
|
+
|
|
56
|
+
Request throttling:
|
|
57
|
+
Applies the rate limit set in the config (section `openrouter`, key `rate_limit`). If no rate
|
|
58
|
+
limit is configured, uses a default of 600 RPM.
|
|
59
|
+
|
|
60
|
+
__Requirements:__
|
|
61
|
+
|
|
62
|
+
- `pip install openai`
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
messages: A list of messages comprising the conversation so far.
|
|
66
|
+
model: ID of the model to use (e.g., `'anthropic/claude-3.5-sonnet'`, `'openai/gpt-4'`).
|
|
67
|
+
model_kwargs: Additional OpenAI-compatible parameters.
|
|
68
|
+
tools: List of tools available to the model.
|
|
69
|
+
tool_choice: Controls which (if any) tool is called by the model.
|
|
70
|
+
provider: OpenRouter-specific provider preferences (e.g., `{'order': ['Anthropic', 'OpenAI']}`).
|
|
71
|
+
transforms: List of message transforms to apply (e.g., `['middle-out']`).
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
A dictionary containing the response in OpenAI format.
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
Basic chat completion:
|
|
78
|
+
|
|
79
|
+
>>> messages = [{'role': 'user', 'content': tbl.prompt}]
|
|
80
|
+
... tbl.add_computed_column(
|
|
81
|
+
... response=chat_completions(
|
|
82
|
+
... messages,
|
|
83
|
+
... model='anthropic/claude-3.5-sonnet'
|
|
84
|
+
... )
|
|
85
|
+
... )
|
|
86
|
+
|
|
87
|
+
With provider routing:
|
|
88
|
+
|
|
89
|
+
>>> tbl.add_computed_column(
|
|
90
|
+
... response=chat_completions(
|
|
91
|
+
... messages,
|
|
92
|
+
... model='anthropic/claude-3.5-sonnet',
|
|
93
|
+
... provider={'require_parameters': True, 'order': ['Anthropic']}
|
|
94
|
+
... )
|
|
95
|
+
... )
|
|
96
|
+
|
|
97
|
+
With transforms:
|
|
98
|
+
|
|
99
|
+
>>> tbl.add_computed_column(
|
|
100
|
+
... response=chat_completions(
|
|
101
|
+
... messages,
|
|
102
|
+
... model='openai/gpt-4',
|
|
103
|
+
... transforms=['middle-out'] # Optimize for long contexts
|
|
104
|
+
... )
|
|
105
|
+
... )
|
|
106
|
+
"""
|
|
107
|
+
if model_kwargs is None:
|
|
108
|
+
model_kwargs = {}
|
|
109
|
+
|
|
110
|
+
Env.get().require_package('openai')
|
|
111
|
+
|
|
112
|
+
# Handle tools if provided
|
|
113
|
+
if tools is not None:
|
|
114
|
+
model_kwargs['tools'] = [{'type': 'function', 'function': tool} for tool in tools]
|
|
115
|
+
|
|
116
|
+
if tool_choice is not None:
|
|
117
|
+
if tool_choice['auto']:
|
|
118
|
+
model_kwargs['tool_choice'] = 'auto'
|
|
119
|
+
elif tool_choice['required']:
|
|
120
|
+
model_kwargs['tool_choice'] = 'required'
|
|
121
|
+
else:
|
|
122
|
+
assert tool_choice['tool'] is not None
|
|
123
|
+
model_kwargs['tool_choice'] = {'type': 'function', 'function': {'name': tool_choice['tool']}}
|
|
124
|
+
|
|
125
|
+
# Prepare OpenRouter-specific parameters for extra_body
|
|
126
|
+
extra_body: dict[str, Any] = {}
|
|
127
|
+
if provider is not None:
|
|
128
|
+
extra_body['provider'] = provider
|
|
129
|
+
if transforms is not None:
|
|
130
|
+
extra_body['transforms'] = transforms
|
|
131
|
+
|
|
132
|
+
# Make the API call
|
|
133
|
+
result = await _openrouter_client().chat.completions.create(
|
|
134
|
+
messages=messages, model=model, extra_body=extra_body if extra_body else None, **model_kwargs
|
|
135
|
+
)
|
|
136
|
+
return result.model_dump()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
__all__ = local_public_names(__name__)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def __dir__() -> list[str]:
|
|
143
|
+
return __all__
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Pixeltable
|
|
2
|
+
Pixeltable UDFs
|
|
3
3
|
that wrap various endpoints from the Replicate API. In order to use them, you must
|
|
4
4
|
first `pip install replicate` and configure your Replicate credentials, as described in
|
|
5
|
-
the [Working with Replicate](https://pixeltable.
|
|
5
|
+
the [Working with Replicate](https://docs.pixeltable.com/notebooks/integrations/working-with-replicate) tutorial.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from typing import TYPE_CHECKING, Any
|
|
@@ -12,7 +12,7 @@ from pixeltable.env import Env, register_client
|
|
|
12
12
|
from pixeltable.utils.code import local_public_names
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
import replicate
|
|
15
|
+
import replicate
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@register_client('replicate')
|
|
@@ -27,7 +27,7 @@ def _replicate_client() -> 'replicate.Client':
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@pxt.udf(resource_pool='request-rate:replicate')
|
|
30
|
-
async def run(input: dict[str, Any], *, ref: str) ->
|
|
30
|
+
async def run(input: dict[str, Any], *, ref: str) -> pxt.Json:
|
|
31
31
|
"""
|
|
32
32
|
Run a model on Replicate.
|
|
33
33
|
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pixeltable [UDFs](https://docs.pixeltable.com/platform/udfs-in-pixeltable) that wrap [Reve](https://app.reve.com/) image
|
|
3
|
+
generation API. In order to use them, the API key must be specified either with `REVE_API_KEY` environment variable,
|
|
4
|
+
or as `api_key` in the `reve` section of the Pixeltable config file.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import atexit
|
|
9
|
+
import logging
|
|
10
|
+
import re
|
|
11
|
+
from io import BytesIO
|
|
12
|
+
|
|
13
|
+
import aiohttp
|
|
14
|
+
import PIL.Image
|
|
15
|
+
|
|
16
|
+
import pixeltable as pxt
|
|
17
|
+
from pixeltable.env import Env, register_client
|
|
18
|
+
from pixeltable.utils.code import local_public_names
|
|
19
|
+
from pixeltable.utils.image import to_base64
|
|
20
|
+
|
|
21
|
+
_logger = logging.getLogger('pixeltable')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ReveRateLimitedError(Exception):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ReveContentViolationError(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ReveUnexpectedError(Exception):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _ReveClient:
|
|
37
|
+
"""
|
|
38
|
+
Client for interacting with the Reve API. Maintains a long-lived HTTP session to the service.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
api_key: str
|
|
42
|
+
session: aiohttp.ClientSession
|
|
43
|
+
|
|
44
|
+
def __init__(self, api_key: str):
|
|
45
|
+
self.api_key = api_key
|
|
46
|
+
self.session = Env.get().event_loop.run_until_complete(self._start_session())
|
|
47
|
+
atexit.register(lambda: asyncio.run(self.session.close()))
|
|
48
|
+
|
|
49
|
+
async def _start_session(self) -> aiohttp.ClientSession:
|
|
50
|
+
# Maintains a long-lived TPC connection. The default keepalive timeout is 15 seconds.
|
|
51
|
+
return aiohttp.ClientSession(base_url='https://api.reve.com')
|
|
52
|
+
|
|
53
|
+
async def _post(self, endpoint: str, *, payload: dict) -> PIL.Image.Image:
|
|
54
|
+
# Reve supports other formats as well, but we only use PNG for now
|
|
55
|
+
requested_content_type = 'image/png'
|
|
56
|
+
request_headers = {
|
|
57
|
+
'Authorization': f'Bearer {self.api_key}',
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
'Accept': requested_content_type,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async with self.session.post(endpoint, json=payload, headers=request_headers) as resp:
|
|
63
|
+
request_id = resp.headers.get('X-Reve-Request-Id')
|
|
64
|
+
error_code = resp.headers.get('X-Reve-Error-Code')
|
|
65
|
+
match resp.status:
|
|
66
|
+
case 200:
|
|
67
|
+
if error_code is not None:
|
|
68
|
+
raise ReveUnexpectedError(
|
|
69
|
+
f'Reve request {request_id} returned an unexpected error {error_code}'
|
|
70
|
+
)
|
|
71
|
+
content_violation = resp.headers.get('X-Reve-Content-Violation', 'false')
|
|
72
|
+
if content_violation.lower() != 'false':
|
|
73
|
+
raise ReveContentViolationError(
|
|
74
|
+
f'Reve request {request_id} resulted in a content violation error'
|
|
75
|
+
)
|
|
76
|
+
if resp.content_type != requested_content_type:
|
|
77
|
+
raise ReveUnexpectedError(
|
|
78
|
+
f'Reve request {request_id} expected content type {requested_content_type}, '
|
|
79
|
+
f'got {resp.content_type}'
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
img_data = await resp.read()
|
|
83
|
+
if len(img_data) == 0:
|
|
84
|
+
raise ReveUnexpectedError(f'Reve request {request_id} resulted in an empty image')
|
|
85
|
+
img = PIL.Image.open(BytesIO(img_data))
|
|
86
|
+
img.load()
|
|
87
|
+
_logger.debug(
|
|
88
|
+
f'Reve request {request_id} successful. Image bytes: {len(img_data)}, size: {img.size}'
|
|
89
|
+
f', format: {img.format}, mode: {img.mode}'
|
|
90
|
+
)
|
|
91
|
+
return img
|
|
92
|
+
case 429:
|
|
93
|
+
# Try to honor the server-provided Retry-After value if present
|
|
94
|
+
# Note: Retry-After value can also be given in the form of HTTP Date, which we don't currently
|
|
95
|
+
# handle
|
|
96
|
+
retry_after_seconds = None
|
|
97
|
+
retry_after_header = resp.headers.get('Retry-After')
|
|
98
|
+
if retry_after_header is not None and re.fullmatch(r'\d{1,2}', retry_after_header):
|
|
99
|
+
retry_after_seconds = int(retry_after_header)
|
|
100
|
+
_logger.info(
|
|
101
|
+
f'Reve request {request_id} failed due to rate limiting, retry after header value: '
|
|
102
|
+
f'{retry_after_header}'
|
|
103
|
+
)
|
|
104
|
+
# This error message is formatted specifically so that RequestRateScheduler can extract the retry
|
|
105
|
+
# delay from it
|
|
106
|
+
raise ReveRateLimitedError(
|
|
107
|
+
f'Reve request {request_id} failed due to rate limiting (429). retry-after:'
|
|
108
|
+
f'{retry_after_seconds}'
|
|
109
|
+
)
|
|
110
|
+
case _:
|
|
111
|
+
_logger.info(
|
|
112
|
+
f'Reve request {request_id} failed with status code {resp.status} and error code {error_code}'
|
|
113
|
+
)
|
|
114
|
+
raise ReveUnexpectedError(
|
|
115
|
+
f'Reve request failed with status code {resp.status} and error code {error_code}'
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@register_client('reve')
|
|
120
|
+
def _(api_key: str) -> _ReveClient:
|
|
121
|
+
return _ReveClient(api_key=api_key)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _client() -> _ReveClient:
|
|
125
|
+
return Env.get().get_client('reve')
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# TODO Regarding rate limiting: Reve appears to be going for a credits per minute rate limiting model, but does not
|
|
129
|
+
# currently communicate rate limit information in responses. Therefore neither of the currently implemented limiting
|
|
130
|
+
# strategies is a perfect match, but "request-rate" is the closest. Reve does not currently enforce the rate limits,
|
|
131
|
+
# but when it does, we can revisit this choice.
|
|
132
|
+
@pxt.udf(resource_pool='request-rate:reve')
|
|
133
|
+
async def create(prompt: str, *, aspect_ratio: str | None = None, version: str | None = None) -> PIL.Image.Image:
|
|
134
|
+
"""
|
|
135
|
+
Creates an image from a text prompt.
|
|
136
|
+
|
|
137
|
+
This UDF wraps the `https://api.reve.com/v1/image/create` endpoint. For more information, refer to the official
|
|
138
|
+
[API documentation](https://api.reve.com/console/docs/create).
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
prompt: prompt describing the desired image
|
|
142
|
+
aspect_ratio: desired image aspect ratio, e.g. '3:2', '16:9', '1:1', etc.
|
|
143
|
+
version: specific model version to use. Latest if not specified.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
A generated image
|
|
147
|
+
|
|
148
|
+
Examples:
|
|
149
|
+
Add a computed column with generated square images to a table with text prompts:
|
|
150
|
+
|
|
151
|
+
>>> t.add_computed_column(
|
|
152
|
+
... img=reve.create(t.prompt, aspect_ratio='1:1')
|
|
153
|
+
... )
|
|
154
|
+
"""
|
|
155
|
+
payload = {'prompt': prompt}
|
|
156
|
+
if aspect_ratio is not None:
|
|
157
|
+
payload['aspect_ratio'] = aspect_ratio
|
|
158
|
+
if version is not None:
|
|
159
|
+
payload['version'] = version
|
|
160
|
+
|
|
161
|
+
result = await _client()._post('/v1/image/create', payload=payload)
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@pxt.udf(resource_pool='request-rate:reve')
|
|
166
|
+
async def edit(image: PIL.Image.Image, edit_instruction: str, *, version: str | None = None) -> PIL.Image.Image:
|
|
167
|
+
"""
|
|
168
|
+
Edits images based on a text prompt.
|
|
169
|
+
|
|
170
|
+
This UDF wraps the `https://api.reve.com/v1/image/edit` endpoint. For more information, refer to the official
|
|
171
|
+
[API documentation](https://api.reve.com/console/docs/edit)
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
image: image to edit
|
|
175
|
+
edit_instruction: text prompt describing the desired edit
|
|
176
|
+
version: specific model version to use. Latest if not specified.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
A generated image
|
|
180
|
+
|
|
181
|
+
Examples:
|
|
182
|
+
Add a computed column with catalog-ready images to the table with product pictures:
|
|
183
|
+
|
|
184
|
+
>>> t.add_computed_column(
|
|
185
|
+
... catalog_img=reve.edit(
|
|
186
|
+
... t.product_img,
|
|
187
|
+
... 'Remove background and distractions from the product picture, improve lighting.'
|
|
188
|
+
... )
|
|
189
|
+
... )
|
|
190
|
+
"""
|
|
191
|
+
payload = {'edit_instruction': edit_instruction, 'reference_image': to_base64(image)}
|
|
192
|
+
if version is not None:
|
|
193
|
+
payload['version'] = version
|
|
194
|
+
|
|
195
|
+
result = await _client()._post('/v1/image/edit', payload=payload)
|
|
196
|
+
return result
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@pxt.udf(resource_pool='request-rate:reve')
|
|
200
|
+
async def remix(
|
|
201
|
+
prompt: str, images: list[PIL.Image.Image], *, aspect_ratio: str | None = None, version: str | None = None
|
|
202
|
+
) -> PIL.Image.Image:
|
|
203
|
+
"""
|
|
204
|
+
Creates images based on a text prompt and reference images.
|
|
205
|
+
|
|
206
|
+
The prompt may include `<img>0</img>`, `<img>1</img>`, etc. tags to refer to the images in the `images` argument.
|
|
207
|
+
|
|
208
|
+
This UDF wraps the `https://api.reve.com/v1/image/remix` endpoint. For more information, refer to the official
|
|
209
|
+
[API documentation](https://api.reve.com/console/docs/remix)
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
prompt: prompt describing the desired image
|
|
213
|
+
images: list of reference images
|
|
214
|
+
aspect_ratio: desired image aspect ratio, e.g. '3:2', '16:9', '1:1', etc.
|
|
215
|
+
version: specific model version to use. Latest by default.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
A generated image
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
Add a computed column with promotional collages to a table with original images:
|
|
222
|
+
|
|
223
|
+
>>> t.add_computed_column(
|
|
224
|
+
... promo_img=(
|
|
225
|
+
... reve.remix(
|
|
226
|
+
... 'Generate a product promotional image by combining the image of the product'
|
|
227
|
+
... ' from <img>0</img> with the landmark scene from <img>1</img>',
|
|
228
|
+
... images=[t.product_img, t.local_landmark_img],
|
|
229
|
+
... aspect_ratio='16:9',
|
|
230
|
+
... )
|
|
231
|
+
... )
|
|
232
|
+
... )
|
|
233
|
+
"""
|
|
234
|
+
if len(images) == 0:
|
|
235
|
+
raise pxt.Error('Must include at least 1 reference image')
|
|
236
|
+
|
|
237
|
+
payload = {'prompt': prompt, 'reference_images': [to_base64(img) for img in images]}
|
|
238
|
+
if version is not None:
|
|
239
|
+
payload['version'] = version
|
|
240
|
+
if aspect_ratio is not None:
|
|
241
|
+
payload['aspect_ratio'] = aspect_ratio
|
|
242
|
+
result = await _client()._post('/v1/image/remix', payload=payload)
|
|
243
|
+
return result
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
__all__ = local_public_names(__name__)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def __dir__() -> list[str]:
|
|
250
|
+
return __all__
|