salesforce-data-customcode 6.0.3.dev1__tar.gz → 6.0.4__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.
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/PKG-INFO +5 -2
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/README.md +4 -1
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/pyproject.toml +2 -1
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/cli.py +6 -3
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/client.py +20 -8
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/deploy.py +109 -19
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/writer/base.py +11 -1
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/spark_base.py +10 -1
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/spark_default.py +70 -11
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/run.py +15 -3
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/.devcontainer/devcontainer.json +1 -1
- salesforce_data_customcode-6.0.4/src/datacustomcode/templates/function/payload/entrypoint.py +67 -0
- salesforce_data_customcode-6.0.4/src/datacustomcode/templates/function/payload/utility.py +104 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/.devcontainer/devcontainer.json +1 -1
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/payload/entrypoint.py +4 -2
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/token_provider.py +70 -36
- salesforce_data_customcode-6.0.3.dev1/src/datacustomcode/templates/function/payload/entrypoint.py +0 -173
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/LICENSE.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/auth.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/cmd.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/common_config.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/config.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/config.yaml +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/constants.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/credentials.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/einstein_platform_client.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/einstein_platform_config.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/einstein_predictions/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/einstein_predictions/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/einstein_predictions/impl/default.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/einstein_predictions/types.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/einstein_predictions_config.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/file/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/file/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/file/path/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/file/path/default.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/function/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/function/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/function/feature_types/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/function/feature_types/chunking.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/function/runtime.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/function_utils.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/reader/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/reader/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/reader/query_api.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/reader/sf_cli.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/reader/utils.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/writer/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/writer/csv.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/io/writer/print.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/default.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/errors.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/types/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/types/generate_text_request.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/types/generate_text_request_builder.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/types/generate_text_response.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway/types/generate_text_response_builder.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/llm_gateway_config.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/mixin.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/py.typed +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/scan.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/spark/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/spark/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/spark/default.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/template.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/Dockerfile.dependencies +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/README.md +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/build_native_dependencies.sh +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/chunking/payload/config.json +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/chunking/payload/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/chunking/requirements.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/example/chunking_with_llm/config.json +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/example/chunking_with_llm/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/example/chunking_with_llm/files/chunking_prompt.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/example/chunking_with_llm/tests/test.json +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/example/chunking_with_prediction/config.json +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/example/chunking_with_prediction/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/example/chunking_with_prediction/tests/test.json +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/payload/config.json +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/requirements-dev.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/function/requirements.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/Dockerfile +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/Dockerfile.dependencies +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/README.md +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/account.ipynb +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/build_native_dependencies.sh +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/jupyterlab.sh +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/payload/config.json +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/requirements-dev.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/templates/script/requirements.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: salesforce-data-customcode
|
|
3
|
-
Version: 6.0.
|
|
3
|
+
Version: 6.0.4
|
|
4
4
|
Summary: Data Cloud Custom Code SDK
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE.txt
|
|
@@ -341,13 +341,16 @@ from datacustomcode.client import Client, llm_gateway_generate_text_col
|
|
|
341
341
|
def main():
|
|
342
342
|
client = Client()
|
|
343
343
|
df = client.read_dlo("Input__dll")
|
|
344
|
+
# llm_gateway_generate_text_col returns a struct
|
|
345
|
+
# {status, response, error_code, error_message} per row, so per-row
|
|
346
|
+
# failures don't abort the Spark job. Pick the field you want with [].
|
|
344
347
|
df_generated = df.withColumn(
|
|
345
348
|
"greeting__c",
|
|
346
349
|
llm_gateway_generate_text_col(
|
|
347
350
|
"In one sentence, greet {name} from {city}.",
|
|
348
351
|
{"name": col("name__c"), "city": col("homecity__c")},
|
|
349
352
|
model_id="sfdc_ai__DefaultGPT4Omni", # An AI model in your org
|
|
350
|
-
),
|
|
353
|
+
)["response"],
|
|
351
354
|
)
|
|
352
355
|
|
|
353
356
|
dlo_name = "Output_dll"
|
|
@@ -316,13 +316,16 @@ from datacustomcode.client import Client, llm_gateway_generate_text_col
|
|
|
316
316
|
def main():
|
|
317
317
|
client = Client()
|
|
318
318
|
df = client.read_dlo("Input__dll")
|
|
319
|
+
# llm_gateway_generate_text_col returns a struct
|
|
320
|
+
# {status, response, error_code, error_message} per row, so per-row
|
|
321
|
+
# failures don't abort the Spark job. Pick the field you want with [].
|
|
319
322
|
df_generated = df.withColumn(
|
|
320
323
|
"greeting__c",
|
|
321
324
|
llm_gateway_generate_text_col(
|
|
322
325
|
"In one sentence, greet {name} from {city}.",
|
|
323
326
|
{"name": col("name__c"), "city": col("homecity__c")},
|
|
324
327
|
model_id="sfdc_ai__DefaultGPT4Omni", # An AI model in your org
|
|
325
|
-
),
|
|
328
|
+
)["response"],
|
|
326
329
|
)
|
|
327
330
|
|
|
328
331
|
dlo_name = "Output_dll"
|
|
@@ -18,7 +18,7 @@ license = "Apache-2.0"
|
|
|
18
18
|
name = "salesforce-data-customcode"
|
|
19
19
|
readme = "README.md"
|
|
20
20
|
requires-python = ">=3.10,<3.12"
|
|
21
|
-
version = "6.0.
|
|
21
|
+
version = "6.0.4"
|
|
22
22
|
|
|
23
23
|
[tool.black]
|
|
24
24
|
exclude = '''
|
|
@@ -114,6 +114,7 @@ build = "*"
|
|
|
114
114
|
coverage = ">=7.0.0,<8.0.0"
|
|
115
115
|
ipykernel = "^6.29.5"
|
|
116
116
|
mypy = "*"
|
|
117
|
+
pipreqs = "*"
|
|
117
118
|
poetry-dynamic-versioning = {extras = ["plugin"], version = "^1.8.2"}
|
|
118
119
|
pre-commit = "*"
|
|
119
120
|
pytest = "*"
|
{salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/cli.py
RENAMED
|
@@ -198,6 +198,7 @@ def zip(path: str, network: str):
|
|
|
198
198
|
default=None,
|
|
199
199
|
help="SF CLI org alias or username. Fetches credentials via `sf org display`.",
|
|
200
200
|
)
|
|
201
|
+
@click.option("--use-in-feature", default=None, hidden=True, deprecated=True)
|
|
201
202
|
def deploy(
|
|
202
203
|
path: str,
|
|
203
204
|
name: str,
|
|
@@ -207,6 +208,7 @@ def deploy(
|
|
|
207
208
|
profile: str,
|
|
208
209
|
network: str,
|
|
209
210
|
sf_cli_org: Optional[str],
|
|
211
|
+
use_in_feature: Optional[str],
|
|
210
212
|
):
|
|
211
213
|
from datacustomcode.constants import USE_IN_FEATURE_MAPPING_FOR_CONNECT_API
|
|
212
214
|
from datacustomcode.deploy import (
|
|
@@ -249,13 +251,14 @@ def deploy(
|
|
|
249
251
|
logger.info(f"Inferred use_in_feature: {use_in_feature}")
|
|
250
252
|
else:
|
|
251
253
|
click.secho(
|
|
252
|
-
"Error:
|
|
253
|
-
"
|
|
254
|
+
"Error: Function signature does not match a supported type. "
|
|
255
|
+
"Use SearchIndexChunkingV1Request and "
|
|
256
|
+
"SearchIndexChunkingV1Response in function signature.",
|
|
254
257
|
fg="red",
|
|
255
258
|
)
|
|
256
259
|
raise click.Abort()
|
|
257
260
|
|
|
258
|
-
# Map
|
|
261
|
+
# Map feature names to Connect API names
|
|
259
262
|
mapped_feature = USE_IN_FEATURE_MAPPING_FOR_CONNECT_API.get(
|
|
260
263
|
use_in_feature, use_in_feature
|
|
261
264
|
)
|
|
@@ -62,15 +62,23 @@ def llm_gateway_generate_text_col(
|
|
|
62
62
|
) -> "Column":
|
|
63
63
|
"""Build a Spark Column that runs the LLM Gateway per row.
|
|
64
64
|
|
|
65
|
+
The returned Column yields a struct ``{status, response, error_code,
|
|
66
|
+
error_message}`` for each row. Use ``[...]`` (or ``getField``) to pick the
|
|
67
|
+
field you want, e.g. ``llm_gateway_generate_text_col(...)["response"]``.
|
|
68
|
+
Per-row failures populate ``status`` / ``error_code`` / ``error_message``
|
|
69
|
+
so a single bad row does not abort the whole Spark job.
|
|
70
|
+
|
|
65
71
|
Example:
|
|
66
72
|
|
|
67
|
-
>>>
|
|
68
|
-
... "
|
|
69
|
-
...
|
|
70
|
-
...
|
|
71
|
-
...
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
>>> result = llm_gateway_generate_text_col(
|
|
74
|
+
... "In one sentence, greet {name} from {city}.",
|
|
75
|
+
... {"name": col("name__c"), "city": col("homecity__c")},
|
|
76
|
+
... model_id="sfdc_ai__DefaultGPT4Omni",
|
|
77
|
+
... )
|
|
78
|
+
>>> df.withColumn("greeting__c", result["response"])
|
|
79
|
+
>>> # …or keep the struct around and inspect failures:
|
|
80
|
+
>>> df.withColumn("llm", result).select(
|
|
81
|
+
... "llm.status", "llm.response", "llm.error_message"
|
|
74
82
|
... )
|
|
75
83
|
|
|
76
84
|
Args:
|
|
@@ -81,7 +89,11 @@ def llm_gateway_generate_text_col(
|
|
|
81
89
|
model_id: LLM model id. Defaults to ``sfdc_ai__DefaultGPT4Omni``.
|
|
82
90
|
|
|
83
91
|
Returns:
|
|
84
|
-
A Spark ``Column``
|
|
92
|
+
A Spark ``Column`` of ``StructType`` with fields ``status``,
|
|
93
|
+
``response``, ``error_code``, and ``error_message`` (all nullable
|
|
94
|
+
strings). On success, ``status == "SUCCESS"`` and ``response`` holds
|
|
95
|
+
the generated text; on failure, ``status == "ERROR"`` and the
|
|
96
|
+
``error_*`` fields carry diagnostic detail.
|
|
85
97
|
"""
|
|
86
98
|
gateway = Client()._get_spark_llm_gateway()
|
|
87
99
|
return gateway.llm_gateway_generate_text_col(template, values, model_id=model_id)
|
|
@@ -26,12 +26,13 @@ from typing import (
|
|
|
26
26
|
Callable,
|
|
27
27
|
Dict,
|
|
28
28
|
List,
|
|
29
|
+
Optional,
|
|
29
30
|
Union,
|
|
30
31
|
)
|
|
31
32
|
|
|
32
33
|
from loguru import logger
|
|
33
34
|
import pydantic
|
|
34
|
-
from pydantic import BaseModel
|
|
35
|
+
from pydantic import BaseModel, model_validator
|
|
35
36
|
import requests
|
|
36
37
|
|
|
37
38
|
from datacustomcode.cmd import cmd_output
|
|
@@ -366,23 +367,86 @@ class BaseConfig(BaseModel):
|
|
|
366
367
|
entryPoint: str
|
|
367
368
|
|
|
368
369
|
|
|
370
|
+
class DataObjectField(BaseModel):
|
|
371
|
+
name: str
|
|
372
|
+
label: str
|
|
373
|
+
dataType: str
|
|
374
|
+
isPrimaryKey: bool = False
|
|
375
|
+
keyQualifierFieldName: Optional[str] = None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class DataObject(BaseModel):
|
|
379
|
+
name: str
|
|
380
|
+
label: str
|
|
381
|
+
type: str
|
|
382
|
+
category: str
|
|
383
|
+
fields: list[DataObjectField]
|
|
384
|
+
|
|
385
|
+
|
|
369
386
|
class DataTransformConfig(BaseConfig):
|
|
370
387
|
sdkVersion: str
|
|
371
388
|
dataspace: str
|
|
372
389
|
permissions: Permissions
|
|
390
|
+
dataObjects: Optional[list[DataObject]] = None
|
|
373
391
|
|
|
374
392
|
|
|
375
393
|
class FunctionConfig(BaseConfig):
|
|
376
394
|
pass
|
|
377
395
|
|
|
378
396
|
|
|
397
|
+
class DloPermission(BaseModel):
|
|
398
|
+
dlo: list[str]
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class DmoPermission(BaseModel):
|
|
402
|
+
dmo: list[str]
|
|
403
|
+
|
|
404
|
+
|
|
379
405
|
class Permissions(BaseModel):
|
|
380
|
-
read: Union[DloPermission]
|
|
381
|
-
write: Union[DloPermission]
|
|
406
|
+
read: Union[DloPermission, DmoPermission]
|
|
407
|
+
write: Union[DloPermission, DmoPermission]
|
|
408
|
+
|
|
409
|
+
@model_validator(mode="after")
|
|
410
|
+
def _no_mixed_layers(self) -> "Permissions":
|
|
411
|
+
read_is_dlo = isinstance(self.read, DloPermission)
|
|
412
|
+
write_is_dlo = isinstance(self.write, DloPermission)
|
|
413
|
+
if read_is_dlo != write_is_dlo:
|
|
414
|
+
raise ValueError(
|
|
415
|
+
"permissions.read and permissions.write must both reference "
|
|
416
|
+
"DLOs or both reference DMOs (got "
|
|
417
|
+
f"read={type(self.read).__name__}, "
|
|
418
|
+
f"write={type(self.write).__name__})"
|
|
419
|
+
)
|
|
420
|
+
return self
|
|
382
421
|
|
|
383
422
|
|
|
384
|
-
|
|
385
|
-
|
|
423
|
+
def _permission_entries(perm: Union[DloPermission, DmoPermission]) -> list[str]:
|
|
424
|
+
"""Return the list of object names regardless of layer (DLO or DMO)."""
|
|
425
|
+
if isinstance(perm, DloPermission):
|
|
426
|
+
return perm.dlo
|
|
427
|
+
return perm.dmo
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _data_object_to_output(obj: DataObject) -> dict[str, Any]:
|
|
431
|
+
"""Convert a config.json DataObject into an outputDataObjects entry."""
|
|
432
|
+
fields: list[dict[str, Any]] = []
|
|
433
|
+
for field in obj.fields:
|
|
434
|
+
entry: dict[str, Any] = {
|
|
435
|
+
"isPrimaryKey": field.isPrimaryKey,
|
|
436
|
+
"label": field.label,
|
|
437
|
+
"name": field.name,
|
|
438
|
+
"type": field.dataType,
|
|
439
|
+
}
|
|
440
|
+
if field.keyQualifierFieldName is not None:
|
|
441
|
+
entry["keyQualifierField"] = field.keyQualifierFieldName
|
|
442
|
+
fields.append(entry)
|
|
443
|
+
return {
|
|
444
|
+
"category": obj.category,
|
|
445
|
+
"fields": fields,
|
|
446
|
+
"label": obj.label,
|
|
447
|
+
"name": obj.name,
|
|
448
|
+
"type": obj.type,
|
|
449
|
+
}
|
|
386
450
|
|
|
387
451
|
|
|
388
452
|
def get_config(directory: str) -> BaseConfig:
|
|
@@ -404,10 +468,17 @@ def get_config(directory: str) -> BaseConfig:
|
|
|
404
468
|
except json.JSONDecodeError as err:
|
|
405
469
|
raise ValueError(f"config.json at {config_path} is not valid JSON") from err
|
|
406
470
|
except pydantic.ValidationError as err:
|
|
407
|
-
|
|
471
|
+
errors = err.errors()
|
|
472
|
+
missing = [e for e in errors if e.get("type") == "missing"]
|
|
473
|
+
if missing and len(missing) == len(errors):
|
|
474
|
+
missing_fields = [str(e["loc"][0]) for e in missing]
|
|
475
|
+
raise ValueError(
|
|
476
|
+
f"config.json at {config_path} is missing required "
|
|
477
|
+
f"fields: {', '.join(missing_fields)}"
|
|
478
|
+
) from err
|
|
479
|
+
messages = [str(e.get("msg", "")) for e in errors]
|
|
408
480
|
raise ValueError(
|
|
409
|
-
f"config.json at {config_path} is
|
|
410
|
-
f"fields: {', '.join(missing_fields)}"
|
|
481
|
+
f"config.json at {config_path} is invalid: {'; '.join(messages)}"
|
|
411
482
|
) from err
|
|
412
483
|
|
|
413
484
|
|
|
@@ -421,26 +492,45 @@ def create_data_transform(
|
|
|
421
492
|
script_name = metadata.name
|
|
422
493
|
request_hydrated = DATA_TRANSFORM_REQUEST_TEMPLATE.copy()
|
|
423
494
|
|
|
424
|
-
# Add nodes for each write DLO
|
|
425
|
-
for i,
|
|
495
|
+
# Add nodes for each write entry (DLO or DMO)
|
|
496
|
+
for i, name in enumerate(
|
|
497
|
+
_permission_entries(data_transform_config.permissions.write), 1
|
|
498
|
+
):
|
|
426
499
|
request_hydrated["nodes"][f"node{i}"] = {
|
|
427
|
-
"relation_name":
|
|
500
|
+
"relation_name": name,
|
|
428
501
|
"config": {"materialized": "table"},
|
|
429
502
|
"compiled_code": "",
|
|
430
503
|
}
|
|
431
504
|
|
|
432
|
-
# Add sources for each read DLO
|
|
433
|
-
for i,
|
|
434
|
-
|
|
505
|
+
# Add sources for each read entry (DLO or DMO)
|
|
506
|
+
for i, name in enumerate(
|
|
507
|
+
_permission_entries(data_transform_config.permissions.read), 1
|
|
508
|
+
):
|
|
509
|
+
request_hydrated["sources"][f"source{i}"] = {"relation_name": name}
|
|
435
510
|
|
|
436
511
|
request_hydrated["macros"]["macro.byoc"]["arguments"][0]["name"] = script_name
|
|
437
512
|
|
|
513
|
+
definition: dict[str, Any] = {
|
|
514
|
+
"type": "DCSQL",
|
|
515
|
+
"manifest": request_hydrated,
|
|
516
|
+
"version": "56.0",
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
# outputDataObjects is only set for DMO-backed transforms. The server requires
|
|
520
|
+
# the schema of any DMO created/updated by the transform; DLO transforms use
|
|
521
|
+
# an existing materialized table and must not include this field.
|
|
522
|
+
if isinstance(data_transform_config.permissions.write, DmoPermission):
|
|
523
|
+
if not data_transform_config.dataObjects:
|
|
524
|
+
raise ValueError(
|
|
525
|
+
"DMO transforms require 'dataObjects' in config.json describing "
|
|
526
|
+
"the schema of each output DMO."
|
|
527
|
+
)
|
|
528
|
+
definition["outputDataObjects"] = [
|
|
529
|
+
_data_object_to_output(obj) for obj in data_transform_config.dataObjects
|
|
530
|
+
]
|
|
531
|
+
|
|
438
532
|
body = {
|
|
439
|
-
"definition":
|
|
440
|
-
"type": "DCSQL",
|
|
441
|
-
"manifest": request_hydrated,
|
|
442
|
-
"version": "56.0",
|
|
443
|
-
},
|
|
533
|
+
"definition": definition,
|
|
444
534
|
"label": f"{metadata.name}",
|
|
445
535
|
"name": f"{metadata.name}",
|
|
446
536
|
"type": "BATCH",
|
|
@@ -27,11 +27,21 @@ if TYPE_CHECKING:
|
|
|
27
27
|
class WriteMode(str, Enum):
|
|
28
28
|
APPEND = "append"
|
|
29
29
|
OVERWRITE = "overwrite"
|
|
30
|
-
OVERWRITE_PARTITIONS = "overwrite_partitions"
|
|
30
|
+
OVERWRITE_PARTITIONS = "overwrite_partitions" # Deprecated: raises error if used
|
|
31
31
|
MERGE = "merge"
|
|
32
32
|
MERGE_UPSERT_DELETE = "merge_upsert_delete"
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
class MergeRecordType(str, Enum):
|
|
36
|
+
"""Values for the _merge_record_type column used by MERGE_UPSERT_DELETE."""
|
|
37
|
+
|
|
38
|
+
UPSERT = "UPSERT"
|
|
39
|
+
DELETE = "DELETE"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
MERGE_RECORD_TYPE_COLUMN = "_merge_record_type"
|
|
43
|
+
|
|
44
|
+
|
|
35
45
|
class BaseDataCloudWriter(BaseDataAccessLayer):
|
|
36
46
|
"""Base class for Data Cloud writers."""
|
|
37
47
|
|
|
@@ -50,4 +50,13 @@ class SparkLLMGateway(ABC, UserExtendableNamedConfigMixin):
|
|
|
50
50
|
values: Union[Dict[str, "Column"], "Column"],
|
|
51
51
|
model_id: Optional[str] = None,
|
|
52
52
|
) -> "Column":
|
|
53
|
-
"""Build a Spark ``Column`` that invokes the LLM Gateway per row
|
|
53
|
+
"""Build a Spark ``Column`` that invokes the LLM Gateway per row and
|
|
54
|
+
yields a struct ``{status, response, error_code, error_message}``.
|
|
55
|
+
|
|
56
|
+
Select an individual field, e.g.
|
|
57
|
+
``llm_gateway_generate_text_col(...)["response"]``. Returning a struct
|
|
58
|
+
means a single failing row doesn't abort the Spark job.
|
|
59
|
+
Failing row leaves the rest of the DataFrame intact — callers can
|
|
60
|
+
inspect ``status`` / ``error_code`` per row instead of having the
|
|
61
|
+
Spark job abort.
|
|
62
|
+
"""
|
|
@@ -28,10 +28,16 @@ if TYPE_CHECKING:
|
|
|
28
28
|
from pyspark.sql import Column
|
|
29
29
|
|
|
30
30
|
from datacustomcode.llm_gateway.base import LLMGateway
|
|
31
|
+
from datacustomcode.llm_gateway.types.generate_text_response import (
|
|
32
|
+
GenerateTextResponse,
|
|
33
|
+
)
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
_DEFAULT_LLM_MODEL_ID = "sfdc_ai__DefaultGPT4Omni"
|
|
34
37
|
|
|
38
|
+
_STATUS_SUCCESS = "SUCCESS"
|
|
39
|
+
_STATUS_ERROR = "ERROR"
|
|
40
|
+
|
|
35
41
|
|
|
36
42
|
class DefaultSparkLLMGateway(SparkLLMGateway):
|
|
37
43
|
|
|
@@ -60,9 +66,17 @@ class DefaultSparkLLMGateway(SparkLLMGateway):
|
|
|
60
66
|
values: Union[Dict[str, "Column"], "Column"],
|
|
61
67
|
model_id: Optional[str] = None,
|
|
62
68
|
) -> "Column":
|
|
63
|
-
|
|
69
|
+
"""Build a per-row UDF that returns a struct ``{status, response,
|
|
70
|
+
error_code, error_message}`` so per-row failures do not abort the
|
|
71
|
+
Spark job. Callers select the field they want, e.g.
|
|
72
|
+
``llm_gateway_generate_text_col(...)["response"]``.
|
|
73
|
+
"""
|
|
64
74
|
from pyspark.sql.functions import struct, udf
|
|
65
|
-
from pyspark.sql.types import
|
|
75
|
+
from pyspark.sql.types import (
|
|
76
|
+
StringType,
|
|
77
|
+
StructField,
|
|
78
|
+
StructType,
|
|
79
|
+
)
|
|
66
80
|
|
|
67
81
|
if isinstance(values, dict):
|
|
68
82
|
values_col = struct(*[v.alias(k) for k, v in values.items()])
|
|
@@ -70,19 +84,32 @@ class DefaultSparkLLMGateway(SparkLLMGateway):
|
|
|
70
84
|
values_col = values
|
|
71
85
|
|
|
72
86
|
gateway = self._llm_gateway
|
|
87
|
+
result_schema = StructType(
|
|
88
|
+
[
|
|
89
|
+
StructField("status", StringType(), True),
|
|
90
|
+
StructField("response", StringType(), True),
|
|
91
|
+
StructField("error_code", StringType(), True),
|
|
92
|
+
StructField("error_message", StringType(), True),
|
|
93
|
+
]
|
|
94
|
+
)
|
|
73
95
|
|
|
74
|
-
def _generate(values_row: Any) -> str:
|
|
96
|
+
def _generate(values_row: Any) -> Dict[str, Optional[str]]:
|
|
75
97
|
if values_row is None:
|
|
76
|
-
return
|
|
98
|
+
return {
|
|
99
|
+
"status": _STATUS_ERROR,
|
|
100
|
+
"response": None,
|
|
101
|
+
"error_code": None,
|
|
102
|
+
"error_message": "values column was null for this row",
|
|
103
|
+
}
|
|
77
104
|
subs = (
|
|
78
105
|
values_row.asDict()
|
|
79
106
|
if hasattr(values_row, "asDict")
|
|
80
107
|
else dict(values_row)
|
|
81
108
|
)
|
|
82
109
|
prompt = template.format(**subs)
|
|
83
|
-
return
|
|
110
|
+
return _invoke_llm_gateway_as_struct(gateway, prompt, model_id)
|
|
84
111
|
|
|
85
|
-
return udf(_generate,
|
|
112
|
+
return udf(_generate, result_schema)(values_col)
|
|
86
113
|
|
|
87
114
|
|
|
88
115
|
def _build_underlying_gateway() -> "LLMGateway":
|
|
@@ -97,22 +124,33 @@ def _build_underlying_gateway() -> "LLMGateway":
|
|
|
97
124
|
return cfg.to_object()
|
|
98
125
|
|
|
99
126
|
|
|
100
|
-
def
|
|
127
|
+
def _call_llm_gateway(
|
|
101
128
|
gateway: "LLMGateway",
|
|
102
129
|
prompt: str,
|
|
103
130
|
model_id: Optional[str],
|
|
104
|
-
) ->
|
|
105
|
-
|
|
131
|
+
) -> "GenerateTextResponse":
|
|
132
|
+
"""Build the request and dispatch it to the underlying gateway."""
|
|
106
133
|
from datacustomcode.llm_gateway.types.generate_text_request_builder import (
|
|
107
134
|
GenerateTextRequestBuilder,
|
|
108
135
|
)
|
|
109
136
|
|
|
110
|
-
|
|
137
|
+
request = (
|
|
111
138
|
GenerateTextRequestBuilder()
|
|
112
139
|
.set_prompt(prompt)
|
|
113
140
|
.set_model(model_id or _DEFAULT_LLM_MODEL_ID)
|
|
141
|
+
.build()
|
|
114
142
|
)
|
|
115
|
-
|
|
143
|
+
return gateway.generate_text(request)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _invoke_llm_gateway(
|
|
147
|
+
gateway: "LLMGateway",
|
|
148
|
+
prompt: str,
|
|
149
|
+
model_id: Optional[str],
|
|
150
|
+
) -> str:
|
|
151
|
+
from datacustomcode.llm_gateway.errors import LLMGatewayCallError
|
|
152
|
+
|
|
153
|
+
response = _call_llm_gateway(gateway, prompt, model_id)
|
|
116
154
|
if response.is_error:
|
|
117
155
|
raise LLMGatewayCallError(
|
|
118
156
|
f"LLM Gateway call failed: status_code={response.status_code}, "
|
|
@@ -123,3 +161,24 @@ def _invoke_llm_gateway(
|
|
|
123
161
|
error_message=str(response.data) if response.data else None,
|
|
124
162
|
)
|
|
125
163
|
return response.text
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _invoke_llm_gateway_as_struct(
|
|
167
|
+
gateway: "LLMGateway",
|
|
168
|
+
prompt: str,
|
|
169
|
+
model_id: Optional[str],
|
|
170
|
+
) -> Dict[str, Optional[str]]:
|
|
171
|
+
response = _call_llm_gateway(gateway, prompt, model_id)
|
|
172
|
+
if response.is_error:
|
|
173
|
+
return {
|
|
174
|
+
"status": _STATUS_ERROR,
|
|
175
|
+
"response": None,
|
|
176
|
+
"error_code": response.error_code or None,
|
|
177
|
+
"error_message": str(response.data) if response.data else None,
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
"status": _STATUS_SUCCESS,
|
|
181
|
+
"response": response.text,
|
|
182
|
+
"error_code": None,
|
|
183
|
+
"error_message": None,
|
|
184
|
+
}
|
{salesforce_data_customcode-6.0.3.dev1 → salesforce_data_customcode-6.0.4}/src/datacustomcode/run.py
RENAMED
|
@@ -201,8 +201,20 @@ def run_function_with_test(entrypoint: str, test_file: str) -> None:
|
|
|
201
201
|
|
|
202
202
|
|
|
203
203
|
def add_py_folder(entrypoint: str):
|
|
204
|
-
|
|
204
|
+
"""Add py-files subfolder and entrypoint directory to sys.path.
|
|
205
|
+
|
|
206
|
+
This ensures:
|
|
207
|
+
1. py-files/ is available for additional dependencies
|
|
208
|
+
2. The entrypoint directory is available for local module imports
|
|
209
|
+
"""
|
|
210
|
+
default_py_folder = "py-files"
|
|
205
211
|
cwd = Path.cwd().joinpath(entrypoint)
|
|
206
|
-
|
|
212
|
+
entrypoint_dir = cwd.parent
|
|
213
|
+
py_folder = entrypoint_dir.joinpath(default_py_folder)
|
|
214
|
+
|
|
215
|
+
# Add py-files folder if it exists
|
|
216
|
+
if py_folder.exists():
|
|
217
|
+
sys.path.insert(0, str(py_folder))
|
|
207
218
|
|
|
208
|
-
|
|
219
|
+
# Add entrypoint directory to allow local module imports
|
|
220
|
+
sys.path.insert(0, str(entrypoint_dir))
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from utility import extract_citations, split_text_into_chunks
|
|
4
|
+
|
|
5
|
+
from datacustomcode.function import Runtime
|
|
6
|
+
from datacustomcode.function.feature_types.chunking import (
|
|
7
|
+
ChunkType,
|
|
8
|
+
SearchIndexChunkingV1Output,
|
|
9
|
+
SearchIndexChunkingV1Request,
|
|
10
|
+
SearchIndexChunkingV1Response,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
logging.basicConfig(level=logging.INFO)
|
|
15
|
+
|
|
16
|
+
# Default max chunk size (can be overridden if contract adds max_characters field)
|
|
17
|
+
DEFAULT_MAX_CHUNK_SIZE = 50
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def function(
|
|
21
|
+
request: SearchIndexChunkingV1Request, runtime: Runtime
|
|
22
|
+
) -> SearchIndexChunkingV1Response:
|
|
23
|
+
"""Chunk documents into smaller pieces for search indexing.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
request: SearchIndexChunkingV1Request with input documents
|
|
27
|
+
runtime: Runtime context (unused but required by contract)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
SearchIndexChunkingV1Response with chunked output
|
|
31
|
+
"""
|
|
32
|
+
print(f"Received {len(request.input)} documents to chunk")
|
|
33
|
+
|
|
34
|
+
chunks = []
|
|
35
|
+
seq_no = 1
|
|
36
|
+
|
|
37
|
+
# Use default max chunk size
|
|
38
|
+
max_chunk_size = DEFAULT_MAX_CHUNK_SIZE
|
|
39
|
+
|
|
40
|
+
# Process each document
|
|
41
|
+
for doc_idx, doc in enumerate(request.input):
|
|
42
|
+
text = doc.text
|
|
43
|
+
metadata = doc.metadata
|
|
44
|
+
|
|
45
|
+
print(f"Processing document {doc_idx + 1}: {len(text)} characters")
|
|
46
|
+
|
|
47
|
+
# Split the text using our simple chunking algorithm
|
|
48
|
+
text_chunks = split_text_into_chunks(text, max_chunk_size, overlap=20)
|
|
49
|
+
|
|
50
|
+
# Create chunk outputs
|
|
51
|
+
for chunk_text in text_chunks:
|
|
52
|
+
citations = extract_citations(metadata)
|
|
53
|
+
|
|
54
|
+
chunk_output = SearchIndexChunkingV1Output(
|
|
55
|
+
chunk_type=ChunkType.TEXT,
|
|
56
|
+
text=chunk_text.strip(),
|
|
57
|
+
seq_no=seq_no,
|
|
58
|
+
citations=citations,
|
|
59
|
+
)
|
|
60
|
+
chunks.append(chunk_output)
|
|
61
|
+
|
|
62
|
+
print(f"Chunk {seq_no}: {len(chunk_text)} chars")
|
|
63
|
+
seq_no += 1
|
|
64
|
+
|
|
65
|
+
print(f"Generated {len(chunks)} chunks total")
|
|
66
|
+
|
|
67
|
+
return SearchIndexChunkingV1Response(output=chunks)
|