pixeltable 0.4.0rc3__py3-none-any.whl → 0.4.20__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.
Potentially problematic release.
This version of pixeltable might be problematic. Click here for more details.
- pixeltable/__init__.py +23 -5
- pixeltable/_version.py +1 -0
- pixeltable/catalog/__init__.py +5 -3
- pixeltable/catalog/catalog.py +1318 -404
- pixeltable/catalog/column.py +186 -115
- pixeltable/catalog/dir.py +1 -2
- pixeltable/catalog/globals.py +11 -43
- pixeltable/catalog/insertable_table.py +167 -79
- pixeltable/catalog/path.py +61 -23
- pixeltable/catalog/schema_object.py +9 -10
- pixeltable/catalog/table.py +626 -308
- pixeltable/catalog/table_metadata.py +101 -0
- pixeltable/catalog/table_version.py +713 -569
- pixeltable/catalog/table_version_handle.py +37 -6
- pixeltable/catalog/table_version_path.py +42 -29
- pixeltable/catalog/tbl_ops.py +50 -0
- pixeltable/catalog/update_status.py +191 -0
- pixeltable/catalog/view.py +108 -94
- pixeltable/config.py +128 -22
- pixeltable/dataframe.py +188 -100
- pixeltable/env.py +407 -136
- pixeltable/exceptions.py +6 -0
- pixeltable/exec/__init__.py +3 -0
- pixeltable/exec/aggregation_node.py +7 -8
- pixeltable/exec/cache_prefetch_node.py +83 -110
- pixeltable/exec/cell_materialization_node.py +231 -0
- pixeltable/exec/cell_reconstruction_node.py +135 -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 +7 -6
- 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 +190 -30
- pixeltable/exec/globals.py +32 -0
- pixeltable/exec/in_memory_data_node.py +18 -18
- pixeltable/exec/object_store_save_node.py +293 -0
- pixeltable/exec/row_update_node.py +16 -9
- pixeltable/exec/sql_node.py +206 -101
- 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 +34 -30
- pixeltable/exprs/column_ref.py +92 -96
- pixeltable/exprs/comparison.py +5 -5
- pixeltable/exprs/compound_predicate.py +5 -4
- pixeltable/exprs/data_row.py +152 -55
- pixeltable/exprs/expr.py +62 -43
- pixeltable/exprs/expr_dict.py +3 -3
- pixeltable/exprs/expr_set.py +17 -10
- pixeltable/exprs/function_call.py +75 -37
- pixeltable/exprs/globals.py +1 -2
- pixeltable/exprs/in_predicate.py +4 -4
- pixeltable/exprs/inline_expr.py +10 -27
- pixeltable/exprs/is_null.py +1 -3
- pixeltable/exprs/json_mapper.py +8 -8
- pixeltable/exprs/json_path.py +56 -22
- pixeltable/exprs/literal.py +5 -5
- pixeltable/exprs/method_ref.py +2 -2
- pixeltable/exprs/object_ref.py +2 -2
- pixeltable/exprs/row_builder.py +127 -53
- pixeltable/exprs/rowid_ref.py +8 -12
- pixeltable/exprs/similarity_expr.py +50 -25
- 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 +10 -10
- 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 +20 -18
- pixeltable/func/signature.py +43 -16
- pixeltable/func/tools.py +23 -13
- pixeltable/func/udf.py +18 -20
- pixeltable/functions/__init__.py +6 -0
- pixeltable/functions/anthropic.py +93 -33
- pixeltable/functions/audio.py +114 -10
- pixeltable/functions/bedrock.py +13 -6
- pixeltable/functions/date.py +1 -1
- pixeltable/functions/deepseek.py +20 -9
- pixeltable/functions/fireworks.py +2 -2
- pixeltable/functions/gemini.py +28 -11
- pixeltable/functions/globals.py +13 -13
- pixeltable/functions/groq.py +108 -0
- pixeltable/functions/huggingface.py +1046 -23
- pixeltable/functions/image.py +9 -18
- pixeltable/functions/llama_cpp.py +23 -8
- pixeltable/functions/math.py +3 -4
- pixeltable/functions/mistralai.py +4 -15
- pixeltable/functions/ollama.py +16 -9
- pixeltable/functions/openai.py +104 -82
- pixeltable/functions/openrouter.py +143 -0
- pixeltable/functions/replicate.py +2 -2
- pixeltable/functions/reve.py +250 -0
- pixeltable/functions/string.py +21 -28
- pixeltable/functions/timestamp.py +13 -14
- pixeltable/functions/together.py +4 -6
- pixeltable/functions/twelvelabs.py +92 -0
- pixeltable/functions/util.py +6 -1
- pixeltable/functions/video.py +1388 -106
- pixeltable/functions/vision.py +7 -7
- pixeltable/functions/whisper.py +15 -7
- pixeltable/functions/whisperx.py +179 -0
- pixeltable/{ext/functions → functions}/yolox.py +2 -4
- pixeltable/globals.py +332 -105
- pixeltable/index/base.py +13 -22
- pixeltable/index/btree.py +23 -22
- pixeltable/index/embedding_index.py +32 -44
- pixeltable/io/__init__.py +4 -2
- pixeltable/io/datarows.py +7 -6
- pixeltable/io/external_store.py +49 -77
- pixeltable/io/fiftyone.py +11 -11
- pixeltable/io/globals.py +29 -28
- pixeltable/io/hf_datasets.py +17 -9
- pixeltable/io/label_studio.py +70 -66
- pixeltable/io/lancedb.py +3 -0
- pixeltable/io/pandas.py +12 -11
- pixeltable/io/parquet.py +13 -93
- pixeltable/io/table_data_conduit.py +71 -47
- pixeltable/io/utils.py +3 -3
- pixeltable/iterators/__init__.py +2 -1
- pixeltable/iterators/audio.py +21 -11
- pixeltable/iterators/document.py +116 -55
- pixeltable/iterators/image.py +5 -2
- pixeltable/iterators/video.py +293 -13
- pixeltable/metadata/__init__.py +4 -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_34.py +2 -2
- pixeltable/metadata/converters/convert_36.py +2 -2
- 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/util.py +13 -12
- pixeltable/metadata/notes.py +4 -0
- pixeltable/metadata/schema.py +79 -42
- pixeltable/metadata/utils.py +74 -0
- pixeltable/mypy/__init__.py +3 -0
- pixeltable/mypy/mypy_plugin.py +123 -0
- pixeltable/plan.py +274 -223
- pixeltable/share/__init__.py +1 -1
- pixeltable/share/packager.py +259 -129
- pixeltable/share/protocol/__init__.py +34 -0
- pixeltable/share/protocol/common.py +170 -0
- pixeltable/share/protocol/operation_types.py +33 -0
- pixeltable/share/protocol/replica.py +109 -0
- pixeltable/share/publish.py +213 -57
- pixeltable/store.py +238 -175
- pixeltable/type_system.py +104 -63
- pixeltable/utils/__init__.py +2 -3
- pixeltable/utils/arrow.py +108 -13
- pixeltable/utils/av.py +298 -0
- pixeltable/utils/azure_store.py +305 -0
- pixeltable/utils/code.py +3 -3
- pixeltable/utils/console_output.py +4 -1
- pixeltable/utils/coroutine.py +6 -23
- pixeltable/utils/dbms.py +31 -5
- pixeltable/utils/description_helper.py +4 -5
- pixeltable/utils/documents.py +5 -6
- pixeltable/utils/exception_handler.py +7 -30
- pixeltable/utils/filecache.py +6 -6
- pixeltable/utils/formatter.py +4 -6
- pixeltable/utils/gcs_store.py +283 -0
- pixeltable/utils/http_server.py +2 -3
- pixeltable/utils/iceberg.py +1 -2
- pixeltable/utils/image.py +17 -0
- pixeltable/utils/lancedb.py +88 -0
- pixeltable/utils/local_store.py +316 -0
- pixeltable/utils/misc.py +5 -0
- pixeltable/utils/object_stores.py +528 -0
- pixeltable/utils/pydantic.py +60 -0
- pixeltable/utils/pytorch.py +5 -6
- pixeltable/utils/s3_store.py +392 -0
- pixeltable-0.4.20.dist-info/METADATA +587 -0
- pixeltable-0.4.20.dist-info/RECORD +218 -0
- {pixeltable-0.4.0rc3.dist-info → pixeltable-0.4.20.dist-info}/WHEEL +1 -1
- pixeltable-0.4.20.dist-info/entry_points.txt +2 -0
- pixeltable/__version__.py +0 -3
- 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/utils/sample.py +0 -25
- pixeltable-0.4.0rc3.dist-info/METADATA +0 -435
- pixeltable-0.4.0rc3.dist-info/RECORD +0 -189
- pixeltable-0.4.0rc3.dist-info/entry_points.txt +0 -3
- {pixeltable-0.4.0rc3.dist-info → pixeltable-0.4.20.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__
|
|
@@ -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/datastore/custom-functions) 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__
|
pixeltable/functions/string.py
CHANGED
|
@@ -15,7 +15,7 @@ import builtins
|
|
|
15
15
|
import re
|
|
16
16
|
import textwrap
|
|
17
17
|
from string import whitespace
|
|
18
|
-
from typing import Any
|
|
18
|
+
from typing import Any
|
|
19
19
|
|
|
20
20
|
import sqlalchemy as sql
|
|
21
21
|
|
|
@@ -78,9 +78,7 @@ def contains(self: str, substr: str, case: bool = True) -> bool:
|
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
@contains.to_sql
|
|
81
|
-
def _(
|
|
82
|
-
self: sql.ColumnElement, substr: sql.ColumnElement, case: Optional[sql.ColumnElement] = None
|
|
83
|
-
) -> sql.ColumnElement:
|
|
81
|
+
def _(self: sql.ColumnElement, substr: sql.ColumnElement, case: sql.ColumnElement | None = None) -> sql.ColumnElement:
|
|
84
82
|
# Replace all occurrences of `%`, `_`, and `\` with escaped versions
|
|
85
83
|
escaped_substr = sql.func.regexp_replace(substr, r'(%|_|\\)', r'\\\1', 'g')
|
|
86
84
|
if case is None:
|
|
@@ -153,7 +151,7 @@ def fill(self: str, width: int, **kwargs: Any) -> str:
|
|
|
153
151
|
|
|
154
152
|
|
|
155
153
|
@pxt.udf(is_method=True)
|
|
156
|
-
def find(self: str, substr: str, start: int = 0, end:
|
|
154
|
+
def find(self: str, substr: str, start: int = 0, end: int | None = None) -> int:
|
|
157
155
|
"""
|
|
158
156
|
Return the lowest index in string where `substr` is found within the slice `s[start:end]`.
|
|
159
157
|
|
|
@@ -169,10 +167,7 @@ def find(self: str, substr: str, start: int = 0, end: Optional[int] = None) -> i
|
|
|
169
167
|
|
|
170
168
|
@find.to_sql
|
|
171
169
|
def _(
|
|
172
|
-
self: sql.ColumnElement,
|
|
173
|
-
substr: sql.ColumnElement,
|
|
174
|
-
start: sql.ColumnElement,
|
|
175
|
-
end: Optional[sql.ColumnElement] = None,
|
|
170
|
+
self: sql.ColumnElement, substr: sql.ColumnElement, start: sql.ColumnElement, end: sql.ColumnElement | None = None
|
|
176
171
|
) -> sql.ColumnElement:
|
|
177
172
|
sl = pxt.functions.string.slice._to_sql(self, start, end)
|
|
178
173
|
if sl is None:
|
|
@@ -227,7 +222,7 @@ def fullmatch(self: str, pattern: str, case: bool = True, flags: int = 0) -> boo
|
|
|
227
222
|
|
|
228
223
|
|
|
229
224
|
@pxt.udf(is_method=True)
|
|
230
|
-
def index(self: str, substr: str, start: int = 0, end:
|
|
225
|
+
def index(self: str, substr: str, start: int = 0, end: int | None = None) -> int:
|
|
231
226
|
"""
|
|
232
227
|
Return the lowest index in string where `substr` is found within the slice `[start:end]`.
|
|
233
228
|
Raises ValueError if `substr` is not found.
|
|
@@ -413,7 +408,7 @@ def _(self: sql.ColumnElement) -> sql.ColumnElement:
|
|
|
413
408
|
|
|
414
409
|
|
|
415
410
|
@pxt.udf(is_method=True)
|
|
416
|
-
def lstrip(self: str, chars:
|
|
411
|
+
def lstrip(self: str, chars: str | None = None) -> str:
|
|
417
412
|
"""
|
|
418
413
|
Return a copy of the string with leading characters removed. The `chars` argument is a string specifying the set of
|
|
419
414
|
characters to be removed. If omitted or `None`, whitespace characters are removed.
|
|
@@ -427,7 +422,7 @@ def lstrip(self: str, chars: Optional[str] = None) -> str:
|
|
|
427
422
|
|
|
428
423
|
|
|
429
424
|
@lstrip.to_sql
|
|
430
|
-
def _(self: sql.ColumnElement, chars:
|
|
425
|
+
def _(self: sql.ColumnElement, chars: sql.ColumnElement | None = None) -> sql.ColumnElement:
|
|
431
426
|
return sql.func.ltrim(self, chars if chars is not None else whitespace)
|
|
432
427
|
|
|
433
428
|
|
|
@@ -535,7 +530,7 @@ def _(self: sql.ColumnElement, n: sql.ColumnElement) -> sql.ColumnElement:
|
|
|
535
530
|
|
|
536
531
|
|
|
537
532
|
@pxt.udf(is_method=True)
|
|
538
|
-
def replace(self: str, substr: str, repl: str, n:
|
|
533
|
+
def replace(self: str, substr: str, repl: str, n: int | None = None) -> str:
|
|
539
534
|
"""
|
|
540
535
|
Replace occurrences of `substr` with `repl`.
|
|
541
536
|
|
|
@@ -551,7 +546,7 @@ def replace(self: str, substr: str, repl: str, n: Optional[int] = None) -> str:
|
|
|
551
546
|
|
|
552
547
|
@replace.to_sql
|
|
553
548
|
def _(
|
|
554
|
-
self: sql.ColumnElement, substr: sql.ColumnElement, repl: sql.ColumnElement, n:
|
|
549
|
+
self: sql.ColumnElement, substr: sql.ColumnElement, repl: sql.ColumnElement, n: sql.ColumnElement | None = None
|
|
555
550
|
) -> sql.ColumnElement:
|
|
556
551
|
if n is not None:
|
|
557
552
|
return None # SQL does not support bounding the number of replacements
|
|
@@ -560,7 +555,7 @@ def _(
|
|
|
560
555
|
|
|
561
556
|
|
|
562
557
|
@pxt.udf(is_method=True)
|
|
563
|
-
def replace_re(self: str, pattern: str, repl: str, n:
|
|
558
|
+
def replace_re(self: str, pattern: str, repl: str, n: int | None = None, flags: int = 0) -> str:
|
|
564
559
|
"""
|
|
565
560
|
Replace occurrences of a regular expression pattern with `repl`.
|
|
566
561
|
|
|
@@ -591,7 +586,7 @@ def _(self: sql.ColumnElement) -> sql.ColumnElement:
|
|
|
591
586
|
|
|
592
587
|
|
|
593
588
|
@pxt.udf(is_method=True)
|
|
594
|
-
def rfind(self: str, substr: str, start:
|
|
589
|
+
def rfind(self: str, substr: str, start: int | None = 0, end: int | None = None) -> int:
|
|
595
590
|
"""
|
|
596
591
|
Return the highest index where `substr` is found, such that `substr` is contained within `[start:end]`.
|
|
597
592
|
|
|
@@ -606,7 +601,7 @@ def rfind(self: str, substr: str, start: Optional[int] = 0, end: Optional[int] =
|
|
|
606
601
|
|
|
607
602
|
|
|
608
603
|
@pxt.udf(is_method=True)
|
|
609
|
-
def rindex(self: str, substr: str, start:
|
|
604
|
+
def rindex(self: str, substr: str, start: int | None = 0, end: int | None = None) -> int:
|
|
610
605
|
"""
|
|
611
606
|
Return the highest index where `substr` is found, such that `substr` is contained within `[start:end]`.
|
|
612
607
|
Raises ValueError if `substr` is not found.
|
|
@@ -643,7 +638,7 @@ def rpartition(self: str, sep: str = ' ') -> list:
|
|
|
643
638
|
|
|
644
639
|
|
|
645
640
|
@pxt.udf(is_method=True)
|
|
646
|
-
def rstrip(self: str, chars:
|
|
641
|
+
def rstrip(self: str, chars: str | None = None) -> str:
|
|
647
642
|
"""
|
|
648
643
|
Return a copy of string with trailing characters removed.
|
|
649
644
|
|
|
@@ -656,12 +651,12 @@ def rstrip(self: str, chars: Optional[str] = None) -> str:
|
|
|
656
651
|
|
|
657
652
|
|
|
658
653
|
@rstrip.to_sql
|
|
659
|
-
def _(self: sql.ColumnElement, chars:
|
|
654
|
+
def _(self: sql.ColumnElement, chars: sql.ColumnElement | None = None) -> sql.ColumnElement:
|
|
660
655
|
return sql.func.rtrim(self, chars if chars is not None else whitespace)
|
|
661
656
|
|
|
662
657
|
|
|
663
658
|
@pxt.udf(is_method=True)
|
|
664
|
-
def slice(self: str, start:
|
|
659
|
+
def slice(self: str, start: int | None = None, stop: int | None = None, step: int | None = None) -> str:
|
|
665
660
|
"""
|
|
666
661
|
Return a slice.
|
|
667
662
|
|
|
@@ -676,9 +671,9 @@ def slice(self: str, start: Optional[int] = None, stop: Optional[int] = None, st
|
|
|
676
671
|
@slice.to_sql
|
|
677
672
|
def _(
|
|
678
673
|
self: sql.ColumnElement,
|
|
679
|
-
start:
|
|
680
|
-
stop:
|
|
681
|
-
step:
|
|
674
|
+
start: sql.ColumnElement | None = None,
|
|
675
|
+
stop: sql.ColumnElement | None = None,
|
|
676
|
+
step: sql.ColumnElement | None = None,
|
|
682
677
|
) -> sql.ColumnElement:
|
|
683
678
|
if step is not None:
|
|
684
679
|
return None
|
|
@@ -709,9 +704,7 @@ def _(
|
|
|
709
704
|
|
|
710
705
|
|
|
711
706
|
@pxt.udf(is_method=True)
|
|
712
|
-
def slice_replace(
|
|
713
|
-
self: str, start: Optional[int] = None, stop: Optional[int] = None, repl: Optional[str] = None
|
|
714
|
-
) -> str:
|
|
707
|
+
def slice_replace(self: str, start: int | None = None, stop: int | None = None, repl: str | None = None) -> str:
|
|
715
708
|
"""
|
|
716
709
|
Replace a positional slice with another value.
|
|
717
710
|
|
|
@@ -744,7 +737,7 @@ def _(self: sql.ColumnElement, substr: sql.ColumnElement) -> sql.ColumnElement:
|
|
|
744
737
|
|
|
745
738
|
|
|
746
739
|
@pxt.udf(is_method=True)
|
|
747
|
-
def strip(self: str, chars:
|
|
740
|
+
def strip(self: str, chars: str | None = None) -> str:
|
|
748
741
|
"""
|
|
749
742
|
Return a copy of string with leading and trailing characters removed.
|
|
750
743
|
|
|
@@ -757,7 +750,7 @@ def strip(self: str, chars: Optional[str] = None) -> str:
|
|
|
757
750
|
|
|
758
751
|
|
|
759
752
|
@strip.to_sql
|
|
760
|
-
def _(self: sql.ColumnElement, chars:
|
|
753
|
+
def _(self: sql.ColumnElement, chars: sql.ColumnElement | None = None) -> sql.ColumnElement:
|
|
761
754
|
return sql.func.trim(self, chars if chars is not None else whitespace)
|
|
762
755
|
|
|
763
756
|
|
|
@@ -11,7 +11,6 @@ t.select(t.timestamp_col.year, t.timestamp_col.weekday()).collect()
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
from datetime import datetime
|
|
14
|
-
from typing import Optional
|
|
15
14
|
|
|
16
15
|
import sqlalchemy as sql
|
|
17
16
|
|
|
@@ -237,12 +236,12 @@ def _(
|
|
|
237
236
|
microsecond: sql.ColumnElement = _SQL_ZERO,
|
|
238
237
|
) -> sql.ColumnElement:
|
|
239
238
|
return sql.func.make_timestamptz(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
239
|
+
year.cast(sql.Integer),
|
|
240
|
+
month.cast(sql.Integer),
|
|
241
|
+
day.cast(sql.Integer),
|
|
242
|
+
hour.cast(sql.Integer),
|
|
243
|
+
minute.cast(sql.Integer),
|
|
244
|
+
(second + microsecond / 1000000.0).cast(sql.Float),
|
|
246
245
|
)
|
|
247
246
|
|
|
248
247
|
|
|
@@ -271,13 +270,13 @@ def _(
|
|
|
271
270
|
@pxt.udf(is_method=True)
|
|
272
271
|
def replace(
|
|
273
272
|
self: datetime,
|
|
274
|
-
year:
|
|
275
|
-
month:
|
|
276
|
-
day:
|
|
277
|
-
hour:
|
|
278
|
-
minute:
|
|
279
|
-
second:
|
|
280
|
-
microsecond:
|
|
273
|
+
year: int | None = None,
|
|
274
|
+
month: int | None = None,
|
|
275
|
+
day: int | None = None,
|
|
276
|
+
hour: int | None = None,
|
|
277
|
+
minute: int | None = None,
|
|
278
|
+
second: int | None = None,
|
|
279
|
+
microsecond: int | None = None,
|
|
281
280
|
) -> datetime:
|
|
282
281
|
"""
|
|
283
282
|
Return a datetime with the same attributes, except for those attributes given new values by whichever keyword
|
pixeltable/functions/together.py
CHANGED
|
@@ -7,7 +7,7 @@ the [Working with Together AI](https://pixeltable.readme.io/docs/together-ai) tu
|
|
|
7
7
|
|
|
8
8
|
import base64
|
|
9
9
|
import io
|
|
10
|
-
from typing import TYPE_CHECKING, Any, Callable,
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Callable, TypeVar
|
|
11
11
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
import PIL.Image
|
|
@@ -50,7 +50,7 @@ def _retry(fn: Callable[..., T]) -> Callable[..., T]:
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
@pxt.udf(resource_pool='request-rate:together:chat')
|
|
53
|
-
async def completions(prompt: str, *, model: str, model_kwargs:
|
|
53
|
+
async def completions(prompt: str, *, model: str, model_kwargs: dict[str, Any] | None = None) -> dict:
|
|
54
54
|
"""
|
|
55
55
|
Generate completions based on a given prompt using a specified model.
|
|
56
56
|
|
|
@@ -89,7 +89,7 @@ async def completions(prompt: str, *, model: str, model_kwargs: Optional[dict[st
|
|
|
89
89
|
|
|
90
90
|
@pxt.udf(resource_pool='request-rate:together:chat')
|
|
91
91
|
async def chat_completions(
|
|
92
|
-
messages: list[dict[str, str]], *, model: str, model_kwargs:
|
|
92
|
+
messages: list[dict[str, str]], *, model: str, model_kwargs: dict[str, Any] | None = None
|
|
93
93
|
) -> dict:
|
|
94
94
|
"""
|
|
95
95
|
Generate chat completions based on a given prompt using a specified model.
|
|
@@ -183,9 +183,7 @@ def _(model: str) -> ts.ArrayType:
|
|
|
183
183
|
|
|
184
184
|
|
|
185
185
|
@pxt.udf(resource_pool='request-rate:together:images')
|
|
186
|
-
async def image_generations(
|
|
187
|
-
prompt: str, *, model: str, model_kwargs: Optional[dict[str, Any]] = None
|
|
188
|
-
) -> PIL.Image.Image:
|
|
186
|
+
async def image_generations(prompt: str, *, model: str, model_kwargs: dict[str, Any] | None = None) -> PIL.Image.Image:
|
|
189
187
|
"""
|
|
190
188
|
Generate images based on a given prompt using a specified model.
|
|
191
189
|
|