pixeltable 0.2.28__py3-none-any.whl → 0.2.30__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 +1 -1
- pixeltable/__version__.py +2 -2
- pixeltable/catalog/__init__.py +1 -1
- pixeltable/catalog/dir.py +6 -0
- pixeltable/catalog/globals.py +25 -0
- pixeltable/catalog/named_function.py +4 -0
- pixeltable/catalog/path_dict.py +37 -11
- pixeltable/catalog/schema_object.py +6 -0
- pixeltable/catalog/table.py +96 -19
- pixeltable/catalog/table_version.py +22 -8
- pixeltable/dataframe.py +201 -3
- pixeltable/env.py +9 -3
- pixeltable/exec/expr_eval_node.py +1 -1
- pixeltable/exec/sql_node.py +2 -2
- pixeltable/exprs/function_call.py +134 -29
- pixeltable/exprs/inline_expr.py +22 -2
- pixeltable/exprs/row_builder.py +1 -1
- pixeltable/exprs/similarity_expr.py +9 -2
- pixeltable/func/__init__.py +1 -0
- pixeltable/func/aggregate_function.py +151 -68
- pixeltable/func/callable_function.py +50 -16
- pixeltable/func/expr_template_function.py +62 -24
- pixeltable/func/function.py +191 -23
- pixeltable/func/function_registry.py +2 -1
- pixeltable/func/query_template_function.py +11 -6
- pixeltable/func/signature.py +64 -7
- pixeltable/func/tools.py +116 -0
- pixeltable/func/udf.py +57 -35
- pixeltable/functions/__init__.py +2 -2
- pixeltable/functions/anthropic.py +36 -2
- pixeltable/functions/globals.py +54 -34
- pixeltable/functions/json.py +3 -8
- pixeltable/functions/math.py +67 -0
- pixeltable/functions/ollama.py +4 -4
- pixeltable/functions/openai.py +31 -2
- pixeltable/functions/timestamp.py +1 -1
- pixeltable/functions/video.py +2 -8
- pixeltable/functions/vision.py +1 -1
- pixeltable/globals.py +347 -79
- pixeltable/index/embedding_index.py +44 -24
- pixeltable/metadata/__init__.py +1 -1
- pixeltable/metadata/converters/convert_16.py +2 -1
- pixeltable/metadata/converters/convert_17.py +2 -1
- pixeltable/metadata/converters/convert_23.py +35 -0
- pixeltable/metadata/converters/convert_24.py +47 -0
- pixeltable/metadata/converters/util.py +4 -2
- pixeltable/metadata/notes.py +2 -0
- pixeltable/metadata/schema.py +1 -0
- pixeltable/type_system.py +192 -48
- {pixeltable-0.2.28.dist-info → pixeltable-0.2.30.dist-info}/METADATA +4 -2
- {pixeltable-0.2.28.dist-info → pixeltable-0.2.30.dist-info}/RECORD +54 -57
- pixeltable-0.2.30.dist-info/entry_points.txt +3 -0
- pixeltable/tool/create_test_db_dump.py +0 -311
- pixeltable/tool/create_test_video.py +0 -81
- pixeltable/tool/doc_plugins/griffe.py +0 -50
- pixeltable/tool/doc_plugins/mkdocstrings.py +0 -6
- pixeltable/tool/doc_plugins/templates/material/udf.html.jinja +0 -135
- pixeltable/tool/embed_udf.py +0 -9
- pixeltable/tool/mypy_plugin.py +0 -55
- pixeltable-0.2.28.dist-info/entry_points.txt +0 -3
- {pixeltable-0.2.28.dist-info → pixeltable-0.2.30.dist-info}/LICENSE +0 -0
- {pixeltable-0.2.28.dist-info → pixeltable-0.2.30.dist-info}/WHEEL +0 -0
pixeltable/func/udf.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Callable, Optional, overload
|
|
3
|
+
from typing import Any, Callable, Optional, Sequence, overload
|
|
4
4
|
|
|
5
5
|
import pixeltable.exceptions as excs
|
|
6
6
|
import pixeltable.type_system as ts
|
|
7
7
|
|
|
8
8
|
from .callable_function import CallableFunction
|
|
9
|
-
from .expr_template_function import ExprTemplateFunction
|
|
9
|
+
from .expr_template_function import ExprTemplateFunction, ExprTemplate
|
|
10
10
|
from .function import Function
|
|
11
11
|
from .function_registry import FunctionRegistry
|
|
12
12
|
from .globals import validate_symbol_path
|
|
@@ -21,13 +21,14 @@ def udf(decorated_fn: Callable) -> Function: ...
|
|
|
21
21
|
# Decorator schema invoked with parentheses: @pxt.udf(**kwargs)
|
|
22
22
|
@overload
|
|
23
23
|
def udf(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
*,
|
|
25
|
+
batch_size: Optional[int] = None,
|
|
26
|
+
substitute_fn: Optional[Callable] = None,
|
|
27
|
+
is_method: bool = False,
|
|
28
|
+
is_property: bool = False,
|
|
29
|
+
type_substitutions: Optional[Sequence[dict]] = None,
|
|
30
|
+
_force_stored: bool = False
|
|
31
|
+
) -> Callable[[Callable], CallableFunction]: ...
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def udf(*args, **kwargs):
|
|
@@ -52,6 +53,7 @@ def udf(*args, **kwargs):
|
|
|
52
53
|
substitute_fn = kwargs.pop('substitute_fn', None)
|
|
53
54
|
is_method = kwargs.pop('is_method', None)
|
|
54
55
|
is_property = kwargs.pop('is_property', None)
|
|
56
|
+
type_substitutions = kwargs.pop('type_substitutions', None)
|
|
55
57
|
force_stored = kwargs.pop('_force_stored', False)
|
|
56
58
|
if len(kwargs) > 0:
|
|
57
59
|
raise excs.Error(f'Invalid @udf decorator kwargs: {", ".join(kwargs.keys())}')
|
|
@@ -65,6 +67,7 @@ def udf(*args, **kwargs):
|
|
|
65
67
|
substitute_fn=substitute_fn,
|
|
66
68
|
is_method=is_method,
|
|
67
69
|
is_property=is_property,
|
|
70
|
+
type_substitutions=type_substitutions,
|
|
68
71
|
force_stored=force_stored
|
|
69
72
|
)
|
|
70
73
|
|
|
@@ -79,9 +82,10 @@ def make_function(
|
|
|
79
82
|
substitute_fn: Optional[Callable] = None,
|
|
80
83
|
is_method: bool = False,
|
|
81
84
|
is_property: bool = False,
|
|
85
|
+
type_substitutions: Optional[Sequence[dict]] = None,
|
|
82
86
|
function_name: Optional[str] = None,
|
|
83
87
|
force_stored: bool = False
|
|
84
|
-
) ->
|
|
88
|
+
) -> CallableFunction:
|
|
85
89
|
"""
|
|
86
90
|
Constructs a `CallableFunction` from the specified parameters.
|
|
87
91
|
If `substitute_fn` is specified, then `decorated_fn`
|
|
@@ -104,25 +108,43 @@ def make_function(
|
|
|
104
108
|
# Display name to use for error messages
|
|
105
109
|
errmsg_name = function_name if function_path is None else function_path
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
111
|
+
signatures: list[Signature]
|
|
112
|
+
if type_substitutions is None:
|
|
113
|
+
sig = Signature.create(decorated_fn, param_types, return_type)
|
|
114
|
+
|
|
115
|
+
# batched functions must have a batched return type
|
|
116
|
+
# TODO: remove 'Python' from the error messages when we have full inference with Annotated types
|
|
117
|
+
if batch_size is not None and not sig.is_batched:
|
|
118
|
+
raise excs.Error(f'{errmsg_name}(): batch_size is specified; Python return type must be a `Batch`')
|
|
119
|
+
if batch_size is not None and len(sig.batched_parameters) == 0:
|
|
120
|
+
raise excs.Error(f'{errmsg_name}(): batch_size is specified; at least one Python parameter must be `Batch`')
|
|
121
|
+
if batch_size is None and len(sig.batched_parameters) > 0:
|
|
122
|
+
raise excs.Error(f'{errmsg_name}(): batched parameters in udf, but no `batch_size` given')
|
|
123
|
+
|
|
124
|
+
if is_method and is_property:
|
|
125
|
+
raise excs.Error(f'Cannot specify both `is_method` and `is_property` (in function `{function_name}`)')
|
|
126
|
+
if is_property and len(sig.parameters) != 1:
|
|
127
|
+
raise excs.Error(
|
|
128
|
+
f"`is_property=True` expects a UDF with exactly 1 parameter, but `{function_name}` has {len(sig.parameters)}"
|
|
129
|
+
)
|
|
130
|
+
if (is_method or is_property) and function_path is None:
|
|
131
|
+
raise excs.Error('Stored functions cannot be declared using `is_method` or `is_property`')
|
|
132
|
+
|
|
133
|
+
signatures = [sig]
|
|
134
|
+
else:
|
|
135
|
+
if function_path is None:
|
|
136
|
+
raise excs.Error(
|
|
137
|
+
f'{errmsg_name}(): type substitutions can only be used with module UDFs (not locally defined UDFs)'
|
|
138
|
+
)
|
|
139
|
+
if batch_size is not None:
|
|
140
|
+
raise excs.Error(f'{errmsg_name}(): type substitutions cannot be used with batched functions')
|
|
141
|
+
if is_method is not None or is_property is not None:
|
|
142
|
+
# TODO: Support this for `is_method`?
|
|
143
|
+
raise excs.Error(f'{errmsg_name}(): type substitutions cannot be used with `is_method` or `is_property`')
|
|
144
|
+
signatures = [
|
|
145
|
+
Signature.create(decorated_fn, param_types, return_type, type_substitutions=subst)
|
|
146
|
+
for subst in type_substitutions
|
|
147
|
+
]
|
|
126
148
|
|
|
127
149
|
if substitute_fn is None:
|
|
128
150
|
py_fn = decorated_fn
|
|
@@ -132,8 +154,8 @@ def make_function(
|
|
|
132
154
|
py_fn = substitute_fn
|
|
133
155
|
|
|
134
156
|
result = CallableFunction(
|
|
135
|
-
|
|
136
|
-
|
|
157
|
+
signatures=signatures,
|
|
158
|
+
py_fns=[py_fn] * len(signatures), # All signatures share the same Python function
|
|
137
159
|
self_path=function_path,
|
|
138
160
|
self_name=function_name,
|
|
139
161
|
batch_size=batch_size,
|
|
@@ -171,12 +193,12 @@ def expr_udf(*args: Any, **kwargs: Any) -> Any:
|
|
|
171
193
|
import pixeltable.exprs as exprs
|
|
172
194
|
var_exprs = [exprs.Variable(param.name, param.col_type) for param in sig.parameters.values()]
|
|
173
195
|
# call the function with the parameter expressions to construct an Expr with parameters
|
|
174
|
-
|
|
175
|
-
assert isinstance(
|
|
176
|
-
sig.return_type =
|
|
196
|
+
expr = py_fn(*var_exprs)
|
|
197
|
+
assert isinstance(expr, exprs.Expr)
|
|
198
|
+
sig.return_type = expr.col_type
|
|
177
199
|
if function_path is not None:
|
|
178
200
|
validate_symbol_path(function_path)
|
|
179
|
-
return ExprTemplateFunction(
|
|
201
|
+
return ExprTemplateFunction([ExprTemplate(expr, sig)], self_path=function_path, name=py_fn.__name__)
|
|
180
202
|
|
|
181
203
|
if len(args) == 1:
|
|
182
204
|
assert len(kwargs) == 0 and callable(args[0])
|
pixeltable/functions/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from pixeltable.utils.code import local_public_names
|
|
2
2
|
|
|
3
|
-
from . import (anthropic, audio, fireworks, gemini, huggingface, image, json, llama_cpp, mistralai, ollama,
|
|
4
|
-
string, timestamp, together, video, vision, whisper)
|
|
3
|
+
from . import (anthropic, audio, fireworks, gemini, huggingface, image, json, llama_cpp, math, mistralai, ollama,
|
|
4
|
+
openai, string, timestamp, together, video, vision, whisper)
|
|
5
5
|
from .globals import *
|
|
6
6
|
|
|
7
7
|
__all__ = local_public_names(__name__, exclude=['globals']) + local_public_names(globals.__name__)
|
|
@@ -10,7 +10,8 @@ from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union
|
|
|
10
10
|
import tenacity
|
|
11
11
|
|
|
12
12
|
import pixeltable as pxt
|
|
13
|
-
from pixeltable import env
|
|
13
|
+
from pixeltable import env, exprs
|
|
14
|
+
from pixeltable.func import Tools
|
|
14
15
|
from pixeltable.utils.code import local_public_names
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
@@ -47,7 +48,7 @@ def messages(
|
|
|
47
48
|
system: Optional[str] = None,
|
|
48
49
|
temperature: Optional[float] = None,
|
|
49
50
|
tool_choice: Optional[list[dict]] = None,
|
|
50
|
-
tools: Optional[dict] = None,
|
|
51
|
+
tools: Optional[list[dict]] = None,
|
|
51
52
|
top_k: Optional[int] = None,
|
|
52
53
|
top_p: Optional[float] = None,
|
|
53
54
|
) -> dict:
|
|
@@ -77,6 +78,21 @@ def messages(
|
|
|
77
78
|
>>> msgs = [{'role': 'user', 'content': tbl.prompt}]
|
|
78
79
|
... tbl['response'] = messages(msgs, model='claude-3-haiku-20240307')
|
|
79
80
|
"""
|
|
81
|
+
if tools is not None:
|
|
82
|
+
# Reformat `tools` into Anthropic format
|
|
83
|
+
tools = [
|
|
84
|
+
{
|
|
85
|
+
'name': tool['name'],
|
|
86
|
+
'description': tool['description'],
|
|
87
|
+
'input_schema': {
|
|
88
|
+
'type': 'object',
|
|
89
|
+
'properties': tool['parameters']['properties'],
|
|
90
|
+
'required': tool['required'],
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
for tool in tools
|
|
94
|
+
]
|
|
95
|
+
|
|
80
96
|
return _retry(_anthropic_client().messages.create)(
|
|
81
97
|
messages=messages,
|
|
82
98
|
model=model,
|
|
@@ -92,6 +108,24 @@ def messages(
|
|
|
92
108
|
).dict()
|
|
93
109
|
|
|
94
110
|
|
|
111
|
+
def invoke_tools(tools: Tools, response: exprs.Expr) -> exprs.InlineDict:
|
|
112
|
+
"""Converts an Anthropic response dict to Pixeltable tool invocation format and calls `tools._invoke()`."""
|
|
113
|
+
return tools._invoke(_anthropic_response_to_pxt_tool_calls(response))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pxt.udf
|
|
117
|
+
def _anthropic_response_to_pxt_tool_calls(response: dict) -> Optional[dict]:
|
|
118
|
+
anthropic_tool_calls = [r for r in response['content'] if r['type'] == 'tool_use']
|
|
119
|
+
if len(anthropic_tool_calls) > 0:
|
|
120
|
+
return {
|
|
121
|
+
tool_call['name']: {
|
|
122
|
+
'args': tool_call['input']
|
|
123
|
+
}
|
|
124
|
+
for tool_call in anthropic_tool_calls
|
|
125
|
+
}
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
95
129
|
_T = TypeVar('_T')
|
|
96
130
|
|
|
97
131
|
|
pixeltable/functions/globals.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import builtins
|
|
2
2
|
from typing import _GenericAlias # type: ignore[attr-defined]
|
|
3
3
|
from typing import Optional, Union
|
|
4
|
+
import typing
|
|
4
5
|
|
|
5
6
|
import sqlalchemy as sql
|
|
6
7
|
|
|
@@ -16,23 +17,24 @@ def cast(expr: exprs.Expr, target_type: Union[ts.ColumnType, type, _GenericAlias
|
|
|
16
17
|
return expr
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
T = typing.TypeVar('T')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@func.uda(allows_window=True, type_substitutions=({T: Optional[int]}, {T: Optional[float]})) # type: ignore[misc]
|
|
24
|
+
class sum(func.Aggregator, typing.Generic[T]):
|
|
23
25
|
"""Sums the selected integers or floats."""
|
|
24
26
|
def __init__(self):
|
|
25
|
-
self.sum:
|
|
27
|
+
self.sum: T = None
|
|
26
28
|
|
|
27
|
-
def update(self, val:
|
|
29
|
+
def update(self, val: T) -> None:
|
|
28
30
|
if val is None:
|
|
29
31
|
return
|
|
30
32
|
if self.sum is None:
|
|
31
33
|
self.sum = val
|
|
32
34
|
else:
|
|
33
|
-
self.sum += val
|
|
35
|
+
self.sum += val # type: ignore[operator]
|
|
34
36
|
|
|
35
|
-
def value(self) ->
|
|
37
|
+
def value(self) -> T:
|
|
36
38
|
return self.sum
|
|
37
39
|
|
|
38
40
|
|
|
@@ -43,12 +45,22 @@ def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
|
|
|
43
45
|
return sql.sql.func.sum(val)
|
|
44
46
|
|
|
45
47
|
|
|
46
|
-
@func.uda(
|
|
47
|
-
|
|
48
|
+
@func.uda(
|
|
49
|
+
allows_window=True,
|
|
50
|
+
# Allow counting non-null values of any type
|
|
51
|
+
# TODO: I couldn't include "Array" because we don't have a way to represent a generic array (of arbitrary dimension).
|
|
52
|
+
# TODO: should we have an "Any" type that can be used here?
|
|
53
|
+
type_substitutions=tuple(
|
|
54
|
+
{T: Optional[t]} # type: ignore[misc]
|
|
55
|
+
for t in (ts.String, ts.Int, ts.Float, ts.Bool, ts.Timestamp,
|
|
56
|
+
ts.Json, ts.Image, ts.Video, ts.Audio, ts.Document)
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
class count(func.Aggregator, typing.Generic[T]):
|
|
48
60
|
def __init__(self):
|
|
49
61
|
self.count = 0
|
|
50
62
|
|
|
51
|
-
def update(self, val:
|
|
63
|
+
def update(self, val: T) -> None:
|
|
52
64
|
if val is not None:
|
|
53
65
|
self.count += 1
|
|
54
66
|
|
|
@@ -62,74 +74,82 @@ def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
|
|
|
62
74
|
|
|
63
75
|
|
|
64
76
|
@func.uda(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
allows_window=True,
|
|
78
|
+
type_substitutions=tuple({T: Optional[t]} for t in (str, int, float, bool, ts.Timestamp)) # type: ignore[misc]
|
|
79
|
+
)
|
|
80
|
+
class min(func.Aggregator, typing.Generic[T]):
|
|
68
81
|
def __init__(self):
|
|
69
|
-
self.val:
|
|
82
|
+
self.val: T = None
|
|
70
83
|
|
|
71
|
-
def update(self, val:
|
|
84
|
+
def update(self, val: T) -> None:
|
|
72
85
|
if val is None:
|
|
73
86
|
return
|
|
74
87
|
if self.val is None:
|
|
75
88
|
self.val = val
|
|
76
89
|
else:
|
|
77
|
-
self.val = builtins.min(self.val, val)
|
|
90
|
+
self.val = builtins.min(self.val, val) # type: ignore[call-overload]
|
|
78
91
|
|
|
79
|
-
def value(self) ->
|
|
92
|
+
def value(self) -> T:
|
|
80
93
|
return self.val
|
|
81
94
|
|
|
82
95
|
|
|
83
96
|
@min.to_sql
|
|
84
97
|
def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
|
|
98
|
+
if val.type.python_type == bool:
|
|
99
|
+
# TODO: min/max aggregation of booleans is not supported in Postgres (but it is in Python).
|
|
100
|
+
# Right now we simply force the computation to be done in Python; we might consider implementing an alternate
|
|
101
|
+
# way of doing it in SQL. (min/max of booleans is simply logical and/or, respectively.)
|
|
102
|
+
return None
|
|
85
103
|
return sql.sql.func.min(val)
|
|
86
104
|
|
|
87
105
|
|
|
88
106
|
@func.uda(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
allows_window=True,
|
|
108
|
+
type_substitutions=tuple({T: Optional[t]} for t in (str, int, float, bool, ts.Timestamp)) # type: ignore[misc]
|
|
109
|
+
)
|
|
110
|
+
class max(func.Aggregator, typing.Generic[T]):
|
|
92
111
|
def __init__(self):
|
|
93
|
-
self.val:
|
|
112
|
+
self.val: T = None
|
|
94
113
|
|
|
95
|
-
def update(self, val:
|
|
114
|
+
def update(self, val: T) -> None:
|
|
96
115
|
if val is None:
|
|
97
116
|
return
|
|
98
117
|
if self.val is None:
|
|
99
118
|
self.val = val
|
|
100
119
|
else:
|
|
101
|
-
self.val = builtins.max(self.val, val)
|
|
120
|
+
self.val = builtins.max(self.val, val) # type: ignore[call-overload]
|
|
102
121
|
|
|
103
|
-
def value(self) ->
|
|
122
|
+
def value(self) -> T:
|
|
104
123
|
return self.val
|
|
105
124
|
|
|
106
125
|
|
|
107
126
|
@max.to_sql
|
|
108
127
|
def _(val: sql.ColumnElement) -> Optional[sql.ColumnElement]:
|
|
128
|
+
if val.type.python_type == bool:
|
|
129
|
+
# TODO: see comment in @min.to_sql.
|
|
130
|
+
return None
|
|
109
131
|
return sql.sql.func.max(val)
|
|
110
132
|
|
|
111
133
|
|
|
112
|
-
@func.uda(
|
|
113
|
-
|
|
114
|
-
requires_order_by=False)
|
|
115
|
-
class mean(func.Aggregator):
|
|
134
|
+
@func.uda(type_substitutions=({T: Optional[int]}, {T: Optional[float]})) # type: ignore[misc]
|
|
135
|
+
class mean(func.Aggregator, typing.Generic[T]):
|
|
116
136
|
def __init__(self):
|
|
117
|
-
self.sum:
|
|
137
|
+
self.sum: T = None
|
|
118
138
|
self.count = 0
|
|
119
139
|
|
|
120
|
-
def update(self, val:
|
|
140
|
+
def update(self, val: T) -> None:
|
|
121
141
|
if val is None:
|
|
122
142
|
return
|
|
123
143
|
if self.sum is None:
|
|
124
144
|
self.sum = val
|
|
125
145
|
else:
|
|
126
|
-
self.sum += val
|
|
146
|
+
self.sum += val # type: ignore[operator]
|
|
127
147
|
self.count += 1
|
|
128
148
|
|
|
129
|
-
def value(self) -> Optional[float]:
|
|
149
|
+
def value(self) -> Optional[float]: # Always a float
|
|
130
150
|
if self.count == 0:
|
|
131
151
|
return None
|
|
132
|
-
return self.sum / self.count
|
|
152
|
+
return self.sum / self.count # type: ignore[operator]
|
|
133
153
|
|
|
134
154
|
|
|
135
155
|
@mean.to_sql
|
pixeltable/functions/json.py
CHANGED
|
@@ -16,20 +16,15 @@ import pixeltable as pxt
|
|
|
16
16
|
from pixeltable.utils.code import local_public_names
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
@pxt.uda
|
|
20
|
-
update_types=[pxt.JsonType(nullable=True)],
|
|
21
|
-
value_type=pxt.JsonType(),
|
|
22
|
-
requires_order_by=False,
|
|
23
|
-
allows_window=False,
|
|
24
|
-
)
|
|
19
|
+
@pxt.uda
|
|
25
20
|
class make_list(pxt.Aggregator):
|
|
26
21
|
"""
|
|
27
22
|
Collects arguments into a list.
|
|
28
23
|
"""
|
|
29
|
-
def __init__(self):
|
|
24
|
+
def __init__(self) -> None:
|
|
30
25
|
self.output: list[Any] = []
|
|
31
26
|
|
|
32
|
-
def update(self, obj:
|
|
27
|
+
def update(self, obj: pxt.Json) -> None:
|
|
33
28
|
if obj is None:
|
|
34
29
|
return
|
|
35
30
|
self.output.append(obj)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import math
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import sqlalchemy as sql
|
|
6
|
+
|
|
7
|
+
import pixeltable as pxt
|
|
8
|
+
from pixeltable.utils.code import local_public_names
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pxt.udf(is_method=True)
|
|
12
|
+
def abs(self: float) -> float:
|
|
13
|
+
return builtins.abs(self)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@abs.to_sql
|
|
17
|
+
def _(self: sql.ColumnElement) -> sql.ColumnElement:
|
|
18
|
+
return sql.func.abs(self)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pxt.udf(is_method=True)
|
|
22
|
+
def ceil(self: float) -> float:
|
|
23
|
+
# This ensures the same behavior as SQL
|
|
24
|
+
if math.isfinite(self):
|
|
25
|
+
return float(math.ceil(self))
|
|
26
|
+
else:
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@ceil.to_sql
|
|
31
|
+
def _(self: sql.ColumnElement) -> sql.ColumnElement:
|
|
32
|
+
return sql.func.ceiling(self)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pxt.udf(is_method=True)
|
|
36
|
+
def floor(self: float) -> float:
|
|
37
|
+
# This ensures the same behavior as SQL
|
|
38
|
+
if math.isfinite(self):
|
|
39
|
+
return float(math.floor(self))
|
|
40
|
+
else:
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@floor.to_sql
|
|
45
|
+
def _(self: sql.ColumnElement) -> sql.ColumnElement:
|
|
46
|
+
return sql.func.floor(self)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pxt.udf(is_method=True)
|
|
50
|
+
def round(self: float, digits: Optional[int] = None) -> float:
|
|
51
|
+
# Set digits explicitly to 0 to guarantee a return type of float; this ensures the same behavior as SQL
|
|
52
|
+
return builtins.round(self, digits or 0)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@round.to_sql
|
|
56
|
+
def _(self: sql.ColumnElement, digits: Optional[sql.ColumnElement] = None) -> sql.ColumnElement:
|
|
57
|
+
if digits is None:
|
|
58
|
+
return sql.func.round(self)
|
|
59
|
+
else:
|
|
60
|
+
return sql.func.round(sql.cast(self, sql.Numeric), sql.cast(digits, sql.Integer))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
__all__ = local_public_names(__name__)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def __dir__():
|
|
67
|
+
return __all__
|
pixeltable/functions/ollama.py
CHANGED
|
@@ -34,7 +34,7 @@ def generate(
|
|
|
34
34
|
template: str = '',
|
|
35
35
|
context: Optional[list[int]] = None,
|
|
36
36
|
raw: bool = False,
|
|
37
|
-
format: str =
|
|
37
|
+
format: Optional[str] = None,
|
|
38
38
|
options: Optional[dict] = None,
|
|
39
39
|
) -> dict:
|
|
40
40
|
"""
|
|
@@ -44,7 +44,7 @@ def generate(
|
|
|
44
44
|
prompt: The prompt to generate a response for.
|
|
45
45
|
model: The model name.
|
|
46
46
|
suffix: The text after the model response.
|
|
47
|
-
format: The format of the response; must be one of `'json'` or `
|
|
47
|
+
format: The format of the response; must be one of `'json'` or `None`.
|
|
48
48
|
system: System message.
|
|
49
49
|
template: Prompt template to use.
|
|
50
50
|
context: The context parameter returned from a previous call to `generate()`.
|
|
@@ -77,7 +77,7 @@ def chat(
|
|
|
77
77
|
*,
|
|
78
78
|
model: str,
|
|
79
79
|
tools: Optional[list[dict]] = None,
|
|
80
|
-
format: str =
|
|
80
|
+
format: Optional[str] = None,
|
|
81
81
|
options: Optional[dict] = None,
|
|
82
82
|
) -> dict:
|
|
83
83
|
"""
|
|
@@ -87,7 +87,7 @@ def chat(
|
|
|
87
87
|
messages: The messages of the chat.
|
|
88
88
|
model: The model name.
|
|
89
89
|
tools: Tools for the model to use.
|
|
90
|
-
format: The format of the response; must be one of `'json'` or `
|
|
90
|
+
format: The format of the response; must be one of `'json'` or `None`.
|
|
91
91
|
options: Additional options to pass to the `chat` call, such as `max_tokens`, `temperature`, `top_p`, and `top_k`.
|
|
92
92
|
For details, see the
|
|
93
93
|
[Valid Parameters and Values](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values)
|
pixeltable/functions/openai.py
CHANGED
|
@@ -7,6 +7,7 @@ the [Working with OpenAI](https://pixeltable.readme.io/docs/working-with-openai)
|
|
|
7
7
|
|
|
8
8
|
import base64
|
|
9
9
|
import io
|
|
10
|
+
import json
|
|
10
11
|
import pathlib
|
|
11
12
|
import uuid
|
|
12
13
|
from typing import TYPE_CHECKING, Callable, Optional, TypeVar, Union
|
|
@@ -16,8 +17,8 @@ import PIL.Image
|
|
|
16
17
|
import tenacity
|
|
17
18
|
|
|
18
19
|
import pixeltable as pxt
|
|
19
|
-
from pixeltable import env
|
|
20
|
-
from pixeltable.func import Batch
|
|
20
|
+
from pixeltable import env, exprs
|
|
21
|
+
from pixeltable.func import Batch, Tools
|
|
21
22
|
from pixeltable.utils.code import local_public_names
|
|
22
23
|
|
|
23
24
|
if TYPE_CHECKING:
|
|
@@ -225,6 +226,16 @@ def chat_completions(
|
|
|
225
226
|
]
|
|
226
227
|
tbl['response'] = chat_completions(messages, model='gpt-4o-mini')
|
|
227
228
|
"""
|
|
229
|
+
|
|
230
|
+
if tools is not None:
|
|
231
|
+
tools = [
|
|
232
|
+
{
|
|
233
|
+
'type': 'function',
|
|
234
|
+
'function': tool
|
|
235
|
+
}
|
|
236
|
+
for tool in tools
|
|
237
|
+
]
|
|
238
|
+
|
|
228
239
|
result = _retry(_openai_client().chat.completions.create)(
|
|
229
240
|
messages=messages,
|
|
230
241
|
model=model,
|
|
@@ -453,6 +464,24 @@ def moderations(input: str, *, model: Optional[str] = None) -> dict:
|
|
|
453
464
|
return result.dict()
|
|
454
465
|
|
|
455
466
|
|
|
467
|
+
def invoke_tools(tools: Tools, response: exprs.Expr) -> exprs.InlineDict:
|
|
468
|
+
"""Converts an OpenAI response dict to Pixeltable tool invocation format and calls `tools._invoke()`."""
|
|
469
|
+
return tools._invoke(_openai_response_to_pxt_tool_calls(response))
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@pxt.udf
|
|
473
|
+
def _openai_response_to_pxt_tool_calls(response: dict) -> Optional[dict]:
|
|
474
|
+
openai_tool_calls = response['choices'][0]['message']['tool_calls']
|
|
475
|
+
if openai_tool_calls is not None:
|
|
476
|
+
return {
|
|
477
|
+
tool_call['function']['name']: {
|
|
478
|
+
'args': json.loads(tool_call['function']['arguments'])
|
|
479
|
+
}
|
|
480
|
+
for tool_call in openai_tool_calls
|
|
481
|
+
}
|
|
482
|
+
return None
|
|
483
|
+
|
|
484
|
+
|
|
456
485
|
_T = TypeVar('_T')
|
|
457
486
|
|
|
458
487
|
|
|
@@ -232,7 +232,7 @@ def _(
|
|
|
232
232
|
sql.cast(day, sql.Integer),
|
|
233
233
|
sql.cast(hour, sql.Integer),
|
|
234
234
|
sql.cast(minute, sql.Integer),
|
|
235
|
-
sql.cast(second + microsecond / 1000000.0, sql.
|
|
235
|
+
sql.cast(second + microsecond / 1000000.0, sql.Float))
|
|
236
236
|
|
|
237
237
|
# @pxt.udf
|
|
238
238
|
# def date(self: datetime) -> datetime:
|
pixeltable/functions/video.py
CHANGED
|
@@ -47,13 +47,7 @@ _format_defaults = { # format -> (codec, ext)
|
|
|
47
47
|
# output_container.mux(packet)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
@pxt.uda(
|
|
51
|
-
init_types=[pxt.IntType()],
|
|
52
|
-
update_types=[pxt.ImageType()],
|
|
53
|
-
value_type=pxt.VideoType(),
|
|
54
|
-
requires_order_by=True,
|
|
55
|
-
allows_window=False,
|
|
56
|
-
)
|
|
50
|
+
@pxt.uda(requires_order_by=True)
|
|
57
51
|
class make_video(pxt.Aggregator):
|
|
58
52
|
"""
|
|
59
53
|
Aggregator that creates a video from a sequence of images.
|
|
@@ -80,7 +74,7 @@ class make_video(pxt.Aggregator):
|
|
|
80
74
|
for packet in self.stream.encode(av_frame):
|
|
81
75
|
self.container.mux(packet)
|
|
82
76
|
|
|
83
|
-
def value(self) ->
|
|
77
|
+
def value(self) -> pxt.Video:
|
|
84
78
|
for packet in self.stream.encode():
|
|
85
79
|
self.container.mux(packet)
|
|
86
80
|
self.container.close()
|
pixeltable/functions/vision.py
CHANGED
|
@@ -220,7 +220,7 @@ def eval_detections(
|
|
|
220
220
|
return result
|
|
221
221
|
|
|
222
222
|
|
|
223
|
-
@pxt.uda
|
|
223
|
+
@pxt.uda
|
|
224
224
|
class mean_ap(pxt.Aggregator):
|
|
225
225
|
"""
|
|
226
226
|
Calculates the mean average precision (mAP) over
|