salesforce-data-customcode 6.0.3.dev2__tar.gz → 6.0.4.dev1__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.dev2 → salesforce_data_customcode-6.0.4.dev1}/PKG-INFO +1 -1
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/pyproject.toml +2 -1
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/cli.py +6 -3
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/deploy.py +109 -19
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/writer/base.py +11 -1
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/run.py +15 -3
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/.devcontainer/devcontainer.json +1 -1
- salesforce_data_customcode-6.0.4.dev1/src/datacustomcode/templates/function/payload/entrypoint.py +67 -0
- salesforce_data_customcode-6.0.4.dev1/src/datacustomcode/templates/function/payload/utility.py +104 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/.devcontainer/devcontainer.json +1 -1
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/token_provider.py +70 -36
- salesforce_data_customcode-6.0.3.dev2/src/datacustomcode/templates/function/payload/entrypoint.py +0 -173
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/LICENSE.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/README.md +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/auth.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/client.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/cmd.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/common_config.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/config.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/config.yaml +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/constants.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/credentials.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/einstein_platform_client.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/einstein_platform_config.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/einstein_predictions/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/einstein_predictions/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/einstein_predictions/impl/default.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/einstein_predictions/types.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/einstein_predictions_config.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/file/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/file/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/file/path/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/file/path/default.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/function/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/function/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/function/feature_types/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/function/feature_types/chunking.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/function/runtime.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/function_utils.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/reader/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/reader/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/reader/query_api.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/reader/sf_cli.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/reader/utils.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/writer/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/writer/csv.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/io/writer/print.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/default.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/errors.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/spark_base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/spark_default.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/types/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/types/generate_text_request.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/types/generate_text_request_builder.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/types/generate_text_response.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway/types/generate_text_response_builder.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/llm_gateway_config.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/mixin.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/py.typed +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/scan.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/spark/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/spark/base.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/spark/default.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/template.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/__init__.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/Dockerfile.dependencies +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/README.md +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/build_native_dependencies.sh +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/chunking/payload/config.json +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/chunking/payload/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/chunking/requirements.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/config.json +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/files/chunking_prompt.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/tests/test.json +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/config.json +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/tests/test.json +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/payload/config.json +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/requirements-dev.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/function/requirements.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/Dockerfile +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/Dockerfile.dependencies +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/README.md +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/account.ipynb +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/build_native_dependencies.sh +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/jupyterlab.sh +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/payload/config.json +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/payload/entrypoint.py +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/requirements-dev.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/templates/script/requirements.txt +0 -0
- {salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/src/datacustomcode/version.py +0 -0
{salesforce_data_customcode-6.0.3.dev2 → salesforce_data_customcode-6.0.4.dev1}/pyproject.toml
RENAMED
|
@@ -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.dev1"
|
|
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 = "*"
|
|
@@ -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
|
)
|
|
@@ -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
|
|
|
@@ -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))
|
salesforce_data_customcode-6.0.4.dev1/src/datacustomcode/templates/function/payload/entrypoint.py
ADDED
|
@@ -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)
|
salesforce_data_customcode-6.0.4.dev1/src/datacustomcode/templates/function/payload/utility.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Utility functions for text chunking operations."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import (
|
|
5
|
+
Dict,
|
|
6
|
+
List,
|
|
7
|
+
Optional,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from datacustomcode.function.feature_types.chunking import SearchIndexChunkingV1Metadata
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def split_text_into_chunks(text: str, max_size: int, overlap: int = 20) -> List[str]:
|
|
16
|
+
"""Split text into chunks with overlap, trying to break at natural boundaries.
|
|
17
|
+
|
|
18
|
+
Tries to break at natural boundaries in order of preference:
|
|
19
|
+
1. Paragraph boundaries (\\n\\n)
|
|
20
|
+
2. Line boundaries (\\n)
|
|
21
|
+
3. Sentence boundaries (. ! ?)
|
|
22
|
+
4. Word boundaries (space)
|
|
23
|
+
5. Hard cut if no good boundary found
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
text: Text to split
|
|
27
|
+
max_size: Maximum characters per chunk
|
|
28
|
+
overlap: Number of characters to overlap between chunks
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of text chunks
|
|
32
|
+
"""
|
|
33
|
+
if len(text) <= max_size:
|
|
34
|
+
return [text]
|
|
35
|
+
|
|
36
|
+
chunks = []
|
|
37
|
+
start = 0
|
|
38
|
+
|
|
39
|
+
while start < len(text):
|
|
40
|
+
# Determine end position for this chunk
|
|
41
|
+
end = start + max_size
|
|
42
|
+
|
|
43
|
+
if end >= len(text):
|
|
44
|
+
# Last chunk
|
|
45
|
+
chunks.append(text[start:])
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
# Try to find a good breaking point (in order of preference)
|
|
49
|
+
chunk_text = text[start:end]
|
|
50
|
+
break_point = None
|
|
51
|
+
|
|
52
|
+
# Try to break at paragraph boundary (\n\n)
|
|
53
|
+
last_paragraph = chunk_text.rfind("\n\n")
|
|
54
|
+
if last_paragraph > max_size * 0.5: # Only if it's past halfway
|
|
55
|
+
break_point = start + last_paragraph + 2 # +2 to skip the \n\n
|
|
56
|
+
|
|
57
|
+
# Try to break at line boundary (\n)
|
|
58
|
+
if break_point is None:
|
|
59
|
+
last_newline = chunk_text.rfind("\n")
|
|
60
|
+
if last_newline > max_size * 0.5:
|
|
61
|
+
break_point = start + last_newline + 1
|
|
62
|
+
|
|
63
|
+
# Try to break at sentence boundary (. ! ?)
|
|
64
|
+
if break_point is None:
|
|
65
|
+
for punct in [". ", "! ", "? "]:
|
|
66
|
+
last_sentence = chunk_text.rfind(punct)
|
|
67
|
+
if last_sentence > max_size * 0.5:
|
|
68
|
+
break_point = start + last_sentence + len(punct)
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
# Try to break at word boundary (space)
|
|
72
|
+
if break_point is None:
|
|
73
|
+
last_space = chunk_text.rfind(" ")
|
|
74
|
+
if last_space > max_size * 0.5:
|
|
75
|
+
break_point = start + last_space + 1
|
|
76
|
+
|
|
77
|
+
# If no good breaking point, just hard cut
|
|
78
|
+
if break_point is None:
|
|
79
|
+
break_point = end
|
|
80
|
+
|
|
81
|
+
chunks.append(text[start:break_point].strip())
|
|
82
|
+
|
|
83
|
+
# Move start position with overlap
|
|
84
|
+
start = max(break_point - overlap, start + 1)
|
|
85
|
+
|
|
86
|
+
return chunks
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def extract_citations(
|
|
90
|
+
metadata: Optional[SearchIndexChunkingV1Metadata],
|
|
91
|
+
) -> Dict[str, str]:
|
|
92
|
+
"""Extract citations from document metadata.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
metadata: Document metadata containing source DMO fields
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Dictionary of citation key-value pairs
|
|
99
|
+
"""
|
|
100
|
+
citations = {}
|
|
101
|
+
if metadata and metadata.source_dmo_fields:
|
|
102
|
+
for key, value in metadata.source_dmo_fields.items():
|
|
103
|
+
citations[key] = str(value)
|
|
104
|
+
return citations
|
|
@@ -101,49 +101,83 @@ class SFCLITokenProvider(TokenProvider):
|
|
|
101
101
|
|
|
102
102
|
from datacustomcode.deploy import AccessTokenResponse
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
104
|
+
def _run_sf_command(args: list[str], description: str) -> dict:
|
|
105
|
+
try:
|
|
106
|
+
result = subprocess.run(
|
|
107
|
+
args,
|
|
108
|
+
capture_output=True,
|
|
109
|
+
text=True,
|
|
110
|
+
check=True,
|
|
111
|
+
timeout=30,
|
|
112
|
+
)
|
|
113
|
+
except FileNotFoundError as exc:
|
|
114
|
+
raise RuntimeError(
|
|
115
|
+
"The 'sf' command was not found. "
|
|
116
|
+
"Install Salesforce CLI: "
|
|
117
|
+
"https://developer.salesforce.com/tools/salesforcecli"
|
|
118
|
+
) from exc
|
|
119
|
+
except subprocess.TimeoutExpired as exc:
|
|
120
|
+
raise RuntimeError(
|
|
121
|
+
f"'{description}' timed out for org '{self.sf_cli_org}'"
|
|
122
|
+
) from exc
|
|
123
|
+
except subprocess.CalledProcessError as exc:
|
|
124
|
+
raise RuntimeError(
|
|
125
|
+
f"'{description}' failed for org '{self.sf_cli_org}': "
|
|
126
|
+
f"{exc.stderr}"
|
|
127
|
+
) from exc
|
|
125
128
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
try:
|
|
130
|
+
data = json.loads(result.stdout)
|
|
131
|
+
except json.JSONDecodeError as exc:
|
|
132
|
+
raise RuntimeError(
|
|
133
|
+
f"Failed to parse JSON from '{description}': {result.stdout}"
|
|
134
|
+
) from exc
|
|
132
135
|
|
|
133
|
-
|
|
136
|
+
if data.get("status") != 0:
|
|
137
|
+
raise RuntimeError(
|
|
138
|
+
f"SF CLI error for org '{self.sf_cli_org}': "
|
|
139
|
+
f"{data.get('message', 'unknown error')}"
|
|
140
|
+
)
|
|
141
|
+
return dict(data)
|
|
142
|
+
|
|
143
|
+
# Get org info from sf org display
|
|
144
|
+
display_data = _run_sf_command(
|
|
145
|
+
["sf", "org", "display", "--target-org", self.sf_cli_org, "--json"],
|
|
146
|
+
"sf org display",
|
|
147
|
+
)
|
|
148
|
+
result_data = display_data.get("result", {})
|
|
149
|
+
instance_url = result_data.get("instanceUrl")
|
|
150
|
+
if not instance_url:
|
|
134
151
|
raise RuntimeError(
|
|
135
|
-
f"
|
|
136
|
-
f"
|
|
152
|
+
f"'sf org display' did not return an instance URL "
|
|
153
|
+
f"for org '{self.sf_cli_org}'"
|
|
137
154
|
)
|
|
138
155
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
156
|
+
# Try show-access-token first (SF CLI >= 2.136.6); fall back to the
|
|
157
|
+
# token from sf org display (older CLIs don't redact it).
|
|
158
|
+
access_token = None
|
|
159
|
+
try:
|
|
160
|
+
token_data = _run_sf_command(
|
|
161
|
+
[
|
|
162
|
+
"sf",
|
|
163
|
+
"org",
|
|
164
|
+
"auth",
|
|
165
|
+
"show-access-token",
|
|
166
|
+
"--target-org",
|
|
167
|
+
self.sf_cli_org,
|
|
168
|
+
"--json",
|
|
169
|
+
],
|
|
170
|
+
"sf org auth show-access-token",
|
|
171
|
+
)
|
|
172
|
+
access_token = token_data.get("result", {}).get("accessToken")
|
|
173
|
+
except RuntimeError:
|
|
174
|
+
# Command not available on older SF CLI versions
|
|
175
|
+
access_token = result_data.get("accessToken")
|
|
142
176
|
|
|
143
|
-
if not access_token
|
|
177
|
+
if not access_token:
|
|
144
178
|
raise RuntimeError(
|
|
145
|
-
f"
|
|
146
|
-
f"
|
|
179
|
+
f"Could not obtain an access token for org '{self.sf_cli_org}'. "
|
|
180
|
+
f"Upgrade SF CLI to 2.136.6+ or ensure the org is authenticated."
|
|
147
181
|
)
|
|
148
182
|
|
|
149
183
|
return AccessTokenResponse(access_token=access_token, instance_url=instance_url)
|
salesforce_data_customcode-6.0.3.dev2/src/datacustomcode/templates/function/payload/entrypoint.py
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import List
|
|
3
|
-
from uuid import uuid4
|
|
4
|
-
|
|
5
|
-
from datacustomcode.einstein_predictions.types import (
|
|
6
|
-
PredictionColumBuilder,
|
|
7
|
-
PredictionRequestBuilder,
|
|
8
|
-
PredictionType,
|
|
9
|
-
)
|
|
10
|
-
from datacustomcode.function import Runtime
|
|
11
|
-
from datacustomcode.llm_gateway.types.generate_text_request_builder import (
|
|
12
|
-
GenerateTextRequestBuilder,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def chunk_text(text: str, chunk_size: int = 1000) -> List[str]:
|
|
19
|
-
"""
|
|
20
|
-
Split text into chunks of approximately chunk_size characters.
|
|
21
|
-
Tries to split at sentence boundaries when possible.
|
|
22
|
-
"""
|
|
23
|
-
if not text:
|
|
24
|
-
return []
|
|
25
|
-
|
|
26
|
-
chunks = []
|
|
27
|
-
current_chunk = ""
|
|
28
|
-
|
|
29
|
-
# Split text into sentences (simple split by period)
|
|
30
|
-
sentences = text.split(". ")
|
|
31
|
-
|
|
32
|
-
for sentence in sentences:
|
|
33
|
-
if len(current_chunk) + len(sentence) <= chunk_size:
|
|
34
|
-
current_chunk += sentence + ". "
|
|
35
|
-
else:
|
|
36
|
-
if current_chunk:
|
|
37
|
-
chunks.append(current_chunk.strip())
|
|
38
|
-
current_chunk = sentence + ". "
|
|
39
|
-
|
|
40
|
-
if current_chunk:
|
|
41
|
-
chunks.append(current_chunk.strip())
|
|
42
|
-
|
|
43
|
-
return chunks
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def make_einstein_prediction(runtime: Runtime) -> None:
|
|
47
|
-
column = (
|
|
48
|
-
PredictionColumBuilder()
|
|
49
|
-
.set_column_name("col1")
|
|
50
|
-
.set_string_values(["str1", "str2"])
|
|
51
|
-
.build()
|
|
52
|
-
)
|
|
53
|
-
prediction_request = (
|
|
54
|
-
PredictionRequestBuilder()
|
|
55
|
-
.set_prediction_type(PredictionType.REGRESSION)
|
|
56
|
-
.set_model_api_name("regressionModel")
|
|
57
|
-
.set_prediction_columns([column])
|
|
58
|
-
.build()
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
prediction_response = runtime.einstein_predictions.predict(prediction_request)
|
|
62
|
-
logger.info(
|
|
63
|
-
f"Einstein prediction results - success: [{prediction_response.is_success}] "
|
|
64
|
-
f"response data: {prediction_response.data}"
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def generate_text(runtime: Runtime, prompt: str, model: str = "sfdc_ai__DefaultGPT52"):
|
|
69
|
-
builder = GenerateTextRequestBuilder()
|
|
70
|
-
llm_request = builder.set_prompt(prompt).set_model(model).build()
|
|
71
|
-
llm_response = runtime.llm_gateway.generate_text(llm_request)
|
|
72
|
-
logger.info(
|
|
73
|
-
f"LLM Gateway generate text results - success: [{llm_response.is_success}] "
|
|
74
|
-
f"response data: {llm_response.data}"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def function(request: dict, runtime: Runtime) -> dict:
|
|
79
|
-
logger.info("Inside Function")
|
|
80
|
-
logger.info(request)
|
|
81
|
-
|
|
82
|
-
items = request["input"]
|
|
83
|
-
output_chunks = []
|
|
84
|
-
current_seq_no = 1 # Start sequence number from 1
|
|
85
|
-
|
|
86
|
-
"""
|
|
87
|
-
You can use your AI models configured in Salesforce to generate texts
|
|
88
|
-
or predict an outcome. See README.md for how to test locally before
|
|
89
|
-
deploying to Data Cloud.
|
|
90
|
-
|
|
91
|
-
Example:
|
|
92
|
-
|
|
93
|
-
>>> generated_text = generate_text(runtime, "Generate a greeting message")
|
|
94
|
-
... prediction = make_einstein_prediction(runtime)
|
|
95
|
-
|
|
96
|
-
"""
|
|
97
|
-
|
|
98
|
-
for item in items:
|
|
99
|
-
# Item is DocElement as dict
|
|
100
|
-
logger.info(f"Processing item: {item}")
|
|
101
|
-
|
|
102
|
-
text = item.get("text", "")
|
|
103
|
-
metadata = item.get("metadata", {})
|
|
104
|
-
|
|
105
|
-
# Create chunks from the text
|
|
106
|
-
text_chunks = chunk_text(text, chunk_size=100) # Using a larger chunk size
|
|
107
|
-
|
|
108
|
-
# Create chunk dictionaries for each text chunk
|
|
109
|
-
for chunk_content in text_chunks:
|
|
110
|
-
chunk_dict = {
|
|
111
|
-
"text": chunk_content,
|
|
112
|
-
"metadata": metadata,
|
|
113
|
-
"seq_no": current_seq_no,
|
|
114
|
-
"chunk_type": "text",
|
|
115
|
-
"chunk_id": str(uuid4()),
|
|
116
|
-
"tag_metadata": {},
|
|
117
|
-
"citations": {},
|
|
118
|
-
"source_record": item,
|
|
119
|
-
}
|
|
120
|
-
output_chunks.append(chunk_dict)
|
|
121
|
-
current_seq_no += 1 # Increment sequence number for next chunk
|
|
122
|
-
|
|
123
|
-
logger.info("Completed chunking")
|
|
124
|
-
response = {
|
|
125
|
-
"output": output_chunks,
|
|
126
|
-
"status": {"status_type": "success", "status_message": "Chunking completed"},
|
|
127
|
-
}
|
|
128
|
-
logger.info(response)
|
|
129
|
-
return response
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
# Test the function
|
|
133
|
-
if __name__ == "__main__":
|
|
134
|
-
# Configure logging
|
|
135
|
-
logging.basicConfig(level=logging.INFO)
|
|
136
|
-
|
|
137
|
-
# Create test data with two DocElements
|
|
138
|
-
test_request = {
|
|
139
|
-
"input": [
|
|
140
|
-
{
|
|
141
|
-
"text": (
|
|
142
|
-
"""This is the first sentence of the first document, which is
|
|
143
|
-
intentionally made longer to test chunking. """
|
|
144
|
-
"""Here is the second sentence of the first document, which is also
|
|
145
|
-
quite long and should ensure that the chunking function splits
|
|
146
|
-
this text into two chunks when the chunk size is set to 100."""
|
|
147
|
-
),
|
|
148
|
-
"metadata": {"source": "test1", "type": "document"},
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
"text": (
|
|
152
|
-
"""This is the first sentence of the second document, and it is
|
|
153
|
-
also extended to be longer than usual for testing purposes. """
|
|
154
|
-
"""The second sentence of the second document is similarly lengthy,
|
|
155
|
-
so that the chunking function will again create two chunks for
|
|
156
|
-
this document."""
|
|
157
|
-
),
|
|
158
|
-
"metadata": {"source": "test2", "type": "document"},
|
|
159
|
-
},
|
|
160
|
-
]
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
# Run the function
|
|
164
|
-
result = function(test_request, Runtime())
|
|
165
|
-
|
|
166
|
-
# Print the results in a more readable format
|
|
167
|
-
print("\nChunking Results:")
|
|
168
|
-
print("----------------")
|
|
169
|
-
for chunk in result["output"]:
|
|
170
|
-
print(f"\nChunk #{chunk['seq_no']}:")
|
|
171
|
-
print(f"Text: {chunk['text'][:100]}...") # Print first 100 chars of each chunk
|
|
172
|
-
print(f"Source: {chunk['metadata']['source']}")
|
|
173
|
-
print(f"Chunk ID: {chunk['chunk_id']}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|