trulens-providers-cortex 1.0.1a1__tar.gz
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.
- trulens_providers_cortex-1.0.1a1/PKG-INFO +26 -0
- trulens_providers_cortex-1.0.1a1/README.md +1 -0
- trulens_providers_cortex-1.0.1a1/pyproject.toml +38 -0
- trulens_providers_cortex-1.0.1a1/trulens/providers/cortex/__init__.py +21 -0
- trulens_providers_cortex-1.0.1a1/trulens/providers/cortex/endpoint.py +95 -0
- trulens_providers_cortex-1.0.1a1/trulens/providers/cortex/provider.py +105 -0
- trulens_providers_cortex-1.0.1a1/trulens/providers/cortex/py.typed +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: trulens-providers-cortex
|
|
3
|
+
Version: 1.0.1a1
|
|
4
|
+
Summary: Library to systematically track and evaluate LLM based applications.
|
|
5
|
+
Home-page: https://trulens.org/
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Snowflake Inc.
|
|
8
|
+
Author-email: ml-observability-wg-dl@snowflake.com
|
|
9
|
+
Requires-Python: >=3.9,<3.12
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Requires-Dist: snowflake-connector-python (>=3.11,<4.0)
|
|
18
|
+
Requires-Dist: snowflake-snowpark-python (>=1.18,<2.0)
|
|
19
|
+
Requires-Dist: trulens-core (>=1.0.0,<2.0.0)
|
|
20
|
+
Requires-Dist: trulens-feedback (>=1.0.0,<2.0.0)
|
|
21
|
+
Project-URL: Documentation, https://trulens.org/trulens/getting_started/
|
|
22
|
+
Project-URL: Repository, https://github.com/truera/trulens
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# trulens-providers-cortex
|
|
26
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# trulens-providers-cortex
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "poetry.core.masonry.api"
|
|
3
|
+
requires = [
|
|
4
|
+
"poetry-core",
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
[tool.poetry]
|
|
8
|
+
name = "trulens-providers-cortex"
|
|
9
|
+
version = "1.0.1a1"
|
|
10
|
+
description = "Library to systematically track and evaluate LLM based applications."
|
|
11
|
+
authors = [
|
|
12
|
+
"Snowflake Inc. <ml-observability-wg-dl@snowflake.com>",
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
packages = [
|
|
17
|
+
{ include = "trulens" },
|
|
18
|
+
]
|
|
19
|
+
homepage = "https://trulens.org/"
|
|
20
|
+
documentation = "https://trulens.org/trulens/getting_started/"
|
|
21
|
+
repository = "https://github.com/truera/trulens"
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Development Status :: 3 - Alpha",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[tool.poetry.dependencies]
|
|
30
|
+
python = ">=3.9,<3.12"
|
|
31
|
+
trulens-core = { version = "^1.0.0", allow-prereleases = true }
|
|
32
|
+
trulens-feedback = { version = "^1.0.0", allow-prereleases = true }
|
|
33
|
+
snowflake-connector-python = "^3.11"
|
|
34
|
+
snowflake-snowpark-python = "^1.18"
|
|
35
|
+
|
|
36
|
+
[tool.poetry.group.dev.dependencies]
|
|
37
|
+
trulens-core = { path = "../../core" }
|
|
38
|
+
trulens-feedback = { path = "../../feedback" }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
!!! note "Additional Dependency Required"
|
|
3
|
+
|
|
4
|
+
To use this module, you must have the `trulens-providers-cortex` package installed.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install trulens-providers-cortex
|
|
8
|
+
```
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from importlib.metadata import version
|
|
12
|
+
|
|
13
|
+
from trulens.core.utils.imports import safe_importlib_package_name
|
|
14
|
+
from trulens.providers.cortex.provider import Cortex
|
|
15
|
+
|
|
16
|
+
__version__ = version(safe_importlib_package_name(__package__ or __name__))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Cortex",
|
|
21
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import pprint
|
|
5
|
+
from typing import Any, Callable, ClassVar, Optional
|
|
6
|
+
|
|
7
|
+
from snowflake.snowpark import DataFrame
|
|
8
|
+
from snowflake.snowpark import Session
|
|
9
|
+
from trulens.core.feedback import Endpoint
|
|
10
|
+
from trulens.core.feedback import EndpointCallback
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
pp = pprint.PrettyPrinter()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CortexCallback(EndpointCallback):
|
|
18
|
+
model_config: ClassVar[dict] = dict(arbitrary_types_allowed=True)
|
|
19
|
+
|
|
20
|
+
def handle_generation(self, response: dict) -> None:
|
|
21
|
+
"""Get the usage information from Cortex LLM function response's usage field."""
|
|
22
|
+
usage = response["usage"]
|
|
23
|
+
|
|
24
|
+
# Increment number of requests.
|
|
25
|
+
super().handle_generation(response)
|
|
26
|
+
|
|
27
|
+
# Assume a response that had usage field was successful. Note at the time of writing 06/12/2024, the usage
|
|
28
|
+
# information from Cortex LLM functions is only available when called via snow SQL. It's not fully supported in
|
|
29
|
+
# Python API such as `from snowflake.cortex import Summarize, Complete, ExtractAnswer, Sentiment, Translate` yet.
|
|
30
|
+
|
|
31
|
+
self.cost.n_successful_requests += 1
|
|
32
|
+
|
|
33
|
+
for cost_field, cortex_field in [
|
|
34
|
+
("n_tokens", "total_tokens"),
|
|
35
|
+
("n_prompt_tokens", "prompt_tokens"),
|
|
36
|
+
("n_completion_tokens", "completion_tokens"),
|
|
37
|
+
]:
|
|
38
|
+
setattr(
|
|
39
|
+
self.cost,
|
|
40
|
+
cost_field,
|
|
41
|
+
getattr(self.cost, cost_field, 0) + usage.get(cortex_field, 0),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CortexEndpoint(Endpoint):
|
|
46
|
+
"""Snowflake Cortex endpoint."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, *args, **kwargs):
|
|
49
|
+
if hasattr(self, "name"):
|
|
50
|
+
# singleton already made
|
|
51
|
+
if len(kwargs) > 0:
|
|
52
|
+
logger.warning(
|
|
53
|
+
"Ignoring additional kwargs for singleton endpoint %s: %s",
|
|
54
|
+
self.name,
|
|
55
|
+
pp.pformat(kwargs),
|
|
56
|
+
)
|
|
57
|
+
self.warning()
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
kwargs["name"] = "cortex"
|
|
61
|
+
kwargs["callback_class"] = CortexCallback
|
|
62
|
+
|
|
63
|
+
super().__init__(*args, **kwargs)
|
|
64
|
+
self._instrument_class(Session, "sql")
|
|
65
|
+
|
|
66
|
+
def __new__(cls, *args, **kwargs):
|
|
67
|
+
return super(Endpoint, cls).__new__(cls, name="cortex")
|
|
68
|
+
|
|
69
|
+
def handle_wrapped_call(
|
|
70
|
+
self,
|
|
71
|
+
func: Callable,
|
|
72
|
+
bindings: inspect.BoundArguments,
|
|
73
|
+
response: Any,
|
|
74
|
+
callback: Optional[EndpointCallback],
|
|
75
|
+
) -> None:
|
|
76
|
+
counted_something = False
|
|
77
|
+
|
|
78
|
+
if isinstance(
|
|
79
|
+
response, DataFrame
|
|
80
|
+
): # response is a snowflake dataframe instance
|
|
81
|
+
response: dict = json.loads(response.collect()[0][0])
|
|
82
|
+
|
|
83
|
+
if "usage" in response:
|
|
84
|
+
counted_something = True
|
|
85
|
+
|
|
86
|
+
self.global_callback.handle_generation(response=response)
|
|
87
|
+
|
|
88
|
+
if callback is not None:
|
|
89
|
+
callback.handle_generation(response=response)
|
|
90
|
+
|
|
91
|
+
if not counted_something:
|
|
92
|
+
logger.warning(
|
|
93
|
+
"Unrecognized Cortex response format. It did not have usage information:\n%s",
|
|
94
|
+
pp.pformat(response),
|
|
95
|
+
)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import ClassVar, Dict, Optional, Sequence
|
|
4
|
+
|
|
5
|
+
import snowflake
|
|
6
|
+
import snowflake.connector
|
|
7
|
+
from snowflake.connector import SnowflakeConnection
|
|
8
|
+
from trulens.feedback import LLMProvider
|
|
9
|
+
from trulens.providers.cortex.endpoint import CortexEndpoint
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Cortex(LLMProvider):
|
|
13
|
+
# require `pip install snowflake-snowpark-python` and a active Snowflake account with proper privileges
|
|
14
|
+
# https://docs.snowflake.com/en/user-guide/snowflake-cortex/llm-functions#availability
|
|
15
|
+
|
|
16
|
+
DEFAULT_MODEL_ENGINE: ClassVar[str] = "snowflake-arctic"
|
|
17
|
+
|
|
18
|
+
model_engine: str
|
|
19
|
+
"""Snowflake's Cortex COMPLETE endpoint. Defaults to `snowflake-arctic`.
|
|
20
|
+
Reference: https://docs.snowflake.com/en/sql-reference/functions/complete-snowflake-cortex
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
endpoint: CortexEndpoint
|
|
24
|
+
snowflake_conn: SnowflakeConnection
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self, model_engine: Optional[str] = None, *args, **kwargs: Dict
|
|
28
|
+
):
|
|
29
|
+
self_kwargs = dict(kwargs)
|
|
30
|
+
|
|
31
|
+
self_kwargs["model_engine"] = (
|
|
32
|
+
self.DEFAULT_MODEL_ENGINE if model_engine is None else model_engine
|
|
33
|
+
)
|
|
34
|
+
self_kwargs["endpoint"] = CortexEndpoint(*args, **kwargs)
|
|
35
|
+
|
|
36
|
+
# Create a Snowflake connector
|
|
37
|
+
self_kwargs["snowflake_conn"] = snowflake.connector.connect(
|
|
38
|
+
account=os.environ["SNOWFLAKE_ACCOUNT"],
|
|
39
|
+
user=os.environ["SNOWFLAKE_USER"],
|
|
40
|
+
password=os.environ["SNOWFLAKE_USER_PASSWORD"],
|
|
41
|
+
database=os.environ["SNOWFLAKE_DATABASE"],
|
|
42
|
+
schema=os.environ["SNOWFLAKE_SCHEMA"],
|
|
43
|
+
warehouse=os.environ["SNOWFLAKE_WAREHOUSE"],
|
|
44
|
+
)
|
|
45
|
+
super().__init__(**self_kwargs)
|
|
46
|
+
|
|
47
|
+
def _exec_snowsql_complete_command(
|
|
48
|
+
self,
|
|
49
|
+
model: str,
|
|
50
|
+
temperature: float,
|
|
51
|
+
messages: Optional[Sequence[Dict]] = None,
|
|
52
|
+
):
|
|
53
|
+
# Ensure messages are formatted as a JSON array string
|
|
54
|
+
if messages is None:
|
|
55
|
+
messages = []
|
|
56
|
+
messages_json_str = json.dumps(messages)
|
|
57
|
+
|
|
58
|
+
options = {"temperature": temperature}
|
|
59
|
+
options_json_str = json.dumps(options)
|
|
60
|
+
|
|
61
|
+
completion_input_str = """
|
|
62
|
+
SELECT SNOWFLAKE.CORTEX.COMPLETE(
|
|
63
|
+
%s,
|
|
64
|
+
parse_json(%s),
|
|
65
|
+
parse_json(%s)
|
|
66
|
+
)
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
# Executing Snow SQL command requires an active snow session
|
|
70
|
+
cursor = self.snowflake_conn.cursor()
|
|
71
|
+
try:
|
|
72
|
+
cursor.execute(
|
|
73
|
+
completion_input_str,
|
|
74
|
+
(model, messages_json_str, options_json_str),
|
|
75
|
+
)
|
|
76
|
+
result = cursor.fetchall()
|
|
77
|
+
finally:
|
|
78
|
+
cursor.close()
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
def _create_chat_completion(
|
|
83
|
+
self,
|
|
84
|
+
prompt: Optional[str] = None,
|
|
85
|
+
messages: Optional[Sequence[Dict]] = None,
|
|
86
|
+
**kwargs,
|
|
87
|
+
) -> str:
|
|
88
|
+
if "model" not in kwargs:
|
|
89
|
+
kwargs["model"] = self.model_engine
|
|
90
|
+
if "temperature" not in kwargs:
|
|
91
|
+
kwargs["temperature"] = 0.0
|
|
92
|
+
|
|
93
|
+
if messages is not None:
|
|
94
|
+
kwargs["messages"] = messages
|
|
95
|
+
|
|
96
|
+
elif prompt is not None:
|
|
97
|
+
kwargs["messages"] = [{"role": "system", "content": prompt}]
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError("`prompt` or `messages` must be specified.")
|
|
100
|
+
|
|
101
|
+
res = self._exec_snowsql_complete_command(**kwargs)
|
|
102
|
+
|
|
103
|
+
completion = json.loads(res[0][0])["choices"][0]["messages"]
|
|
104
|
+
|
|
105
|
+
return completion
|
|
File without changes
|