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.
@@ -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