salesforce-data-customcode 6.0.1__tar.gz → 6.0.2.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.
Files changed (101) hide show
  1. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/PKG-INFO +48 -1
  2. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/README.md +47 -0
  3. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/pyproject.toml +1 -1
  4. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/__init__.py +15 -0
  5. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/cli.py +10 -14
  6. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/client.py +102 -1
  7. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/config.yaml +3 -0
  8. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/einstein_platform_config.py +8 -4
  9. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/einstein_predictions_config.py +1 -1
  10. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/function/feature_types/chunking.py +1 -0
  11. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/writer/base.py +1 -11
  12. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/llm_gateway/__init__.py +4 -0
  13. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/llm_gateway/default.py +2 -0
  14. salesforce_data_customcode-6.0.2.dev1/src/datacustomcode/llm_gateway/spark_base.py +55 -0
  15. salesforce_data_customcode-6.0.2.dev1/src/datacustomcode/llm_gateway/spark_default.py +119 -0
  16. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/llm_gateway/types/generate_text_request.py +7 -0
  17. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/llm_gateway/types/generate_text_request_builder.py +6 -0
  18. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/llm_gateway_config.py +43 -2
  19. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/.devcontainer/devcontainer.json +1 -1
  20. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/entrypoint.py +3 -0
  21. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/entrypoint.py +3 -0
  22. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/payload/entrypoint.py +11 -12
  23. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/.devcontainer/devcontainer.json +1 -1
  24. salesforce_data_customcode-6.0.2.dev1/src/datacustomcode/templates/script/payload/entrypoint.py +52 -0
  25. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/token_provider.py +36 -63
  26. salesforce_data_customcode-6.0.1/src/datacustomcode/proxy/__init__.py +0 -14
  27. salesforce_data_customcode-6.0.1/src/datacustomcode/proxy/base.py +0 -24
  28. salesforce_data_customcode-6.0.1/src/datacustomcode/proxy/client/__init__.py +0 -14
  29. salesforce_data_customcode-6.0.1/src/datacustomcode/proxy/client/base.py +0 -32
  30. salesforce_data_customcode-6.0.1/src/datacustomcode/templates/script/payload/entrypoint.py +0 -25
  31. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/LICENSE.txt +0 -0
  32. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/auth.py +0 -0
  33. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/cmd.py +0 -0
  34. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/common_config.py +0 -0
  35. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/config.py +0 -0
  36. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/constants.py +0 -0
  37. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/credentials.py +0 -0
  38. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/deploy.py +0 -0
  39. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/einstein_platform_client.py +0 -0
  40. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/einstein_predictions/__init__.py +0 -0
  41. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/einstein_predictions/base.py +0 -0
  42. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/einstein_predictions/impl/default.py +0 -0
  43. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/einstein_predictions/types.py +0 -0
  44. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/file/__init__.py +0 -0
  45. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/file/base.py +0 -0
  46. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/file/path/__init__.py +0 -0
  47. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/file/path/default.py +0 -0
  48. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/function/__init__.py +0 -0
  49. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/function/base.py +0 -0
  50. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/function/feature_types/__init__.py +0 -0
  51. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/function/runtime.py +0 -0
  52. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/function_utils.py +0 -0
  53. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/__init__.py +0 -0
  54. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/base.py +0 -0
  55. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/reader/__init__.py +0 -0
  56. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/reader/base.py +0 -0
  57. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/reader/query_api.py +0 -0
  58. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/reader/sf_cli.py +0 -0
  59. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/reader/utils.py +0 -0
  60. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/writer/__init__.py +0 -0
  61. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/writer/csv.py +0 -0
  62. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/io/writer/print.py +0 -0
  63. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/llm_gateway/base.py +0 -0
  64. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/llm_gateway/types/__init__.py +0 -0
  65. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/llm_gateway/types/generate_text_response.py +0 -0
  66. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/llm_gateway/types/generate_text_response_builder.py +0 -0
  67. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/mixin.py +0 -0
  68. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/py.typed +0 -0
  69. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/run.py +0 -0
  70. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/scan.py +0 -0
  71. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/spark/__init__.py +0 -0
  72. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/spark/base.py +0 -0
  73. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/spark/default.py +0 -0
  74. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/template.py +0 -0
  75. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/__init__.py +0 -0
  76. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/Dockerfile.dependencies +0 -0
  77. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/README.md +0 -0
  78. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/build_native_dependencies.sh +0 -0
  79. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/chunking/payload/config.json +0 -0
  80. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/chunking/payload/entrypoint.py +0 -0
  81. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/chunking/requirements.txt +0 -0
  82. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/config.json +0 -0
  83. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/files/chunking_prompt.txt +0 -0
  84. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/example/chunking_with_llm/tests/test.json +0 -0
  85. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/config.json +0 -0
  86. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/example/chunking_with_prediction/tests/test.json +0 -0
  87. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/payload/config.json +0 -0
  88. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/requirements-dev.txt +0 -0
  89. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/function/requirements.txt +0 -0
  90. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/Dockerfile +0 -0
  91. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/Dockerfile.dependencies +0 -0
  92. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/README.md +0 -0
  93. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/account.ipynb +0 -0
  94. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/build_native_dependencies.sh +0 -0
  95. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv +0 -0
  96. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py +0 -0
  97. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/jupyterlab.sh +0 -0
  98. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/payload/config.json +0 -0
  99. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/requirements-dev.txt +0 -0
  100. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/src/datacustomcode/templates/script/requirements.txt +0 -0
  101. {salesforce_data_customcode-6.0.1 → salesforce_data_customcode-6.0.2.dev1}/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.1
3
+ Version: 6.0.2.dev1
4
4
  Summary: Data Cloud Custom Code SDK
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE.txt
@@ -330,6 +330,53 @@ Options:
330
330
  - `--function-invoke-opt TEXT`: Currently we support only `UnstructuredChunking` for functions.
331
331
 
332
332
 
333
+ ## Testing LLM Gateway
334
+
335
+ You can use AI models configured in Salesforce to generate responses while transforming your data. Below is a sample code example:
336
+
337
+ ```
338
+ from datacustomcode.client import Client, llm_gateway_generate_text_col
339
+
340
+
341
+ def main():
342
+ client = Client()
343
+ df = client.read_dlo("Input__dll")
344
+ df_generated = df.withColumn(
345
+ "greeting__c",
346
+ llm_gateway_generate_text_col(
347
+ "In one sentence, greet {name} from {city}.",
348
+ {"name": col("name__c"), "city": col("homecity__c")},
349
+ model_id="sfdc_ai__DefaultGPT4Omni", # An AI model in your org
350
+ max_tokens=100,
351
+ ),
352
+ )
353
+
354
+ dlo_name = "Output_dll"
355
+ client.write_to_dlo(dlo_name, df_upper1, write_mode=WriteMode.APPEND)
356
+
357
+ greeting = client.llm_gateway_generate_text("In one sentence, generate a greeting message", "sfdc_ai__DefaultGPT52")
358
+
359
+ if __name__ == "__main__":
360
+ main()
361
+ ```
362
+
363
+ In order to test this code on your local machine before deploying it to Data Cloud, you must first set up an External Client App that allows access to the Agent API. Follow this guide to create the ECA https://developer.salesforce.com/docs/ai/agentforce/guide/agent-api-get-started.html#create-a-salesforce-app. You must use `http://localhost:1717/OauthRedirect` as the callback URL.
364
+
365
+ Once the ECA is set up, log in to your org using this ECA
366
+ ```
367
+ sf org login web \
368
+ --alias myorg \
369
+ --instance-url https://{MY_DOMAIN_URL} \
370
+ --client-id {CONSUMER_KEY} \
371
+ --scopes "sfap_api api"
372
+ ```
373
+
374
+ then you can test your code using `myorg` alias
375
+ ```
376
+ datacustomcode run ./payload/entrypoint.py --sf-cli-org myorg
377
+ ```
378
+
379
+
333
380
  ## Docker usage
334
381
 
335
382
  The SDK provides Docker-based development options that allow you to test your code in an environment that closely resembles Data Cloud's execution environment.
@@ -305,6 +305,53 @@ Options:
305
305
  - `--function-invoke-opt TEXT`: Currently we support only `UnstructuredChunking` for functions.
306
306
 
307
307
 
308
+ ## Testing LLM Gateway
309
+
310
+ You can use AI models configured in Salesforce to generate responses while transforming your data. Below is a sample code example:
311
+
312
+ ```
313
+ from datacustomcode.client import Client, llm_gateway_generate_text_col
314
+
315
+
316
+ def main():
317
+ client = Client()
318
+ df = client.read_dlo("Input__dll")
319
+ df_generated = df.withColumn(
320
+ "greeting__c",
321
+ llm_gateway_generate_text_col(
322
+ "In one sentence, greet {name} from {city}.",
323
+ {"name": col("name__c"), "city": col("homecity__c")},
324
+ model_id="sfdc_ai__DefaultGPT4Omni", # An AI model in your org
325
+ max_tokens=100,
326
+ ),
327
+ )
328
+
329
+ dlo_name = "Output_dll"
330
+ client.write_to_dlo(dlo_name, df_upper1, write_mode=WriteMode.APPEND)
331
+
332
+ greeting = client.llm_gateway_generate_text("In one sentence, generate a greeting message", "sfdc_ai__DefaultGPT52")
333
+
334
+ if __name__ == "__main__":
335
+ main()
336
+ ```
337
+
338
+ In order to test this code on your local machine before deploying it to Data Cloud, you must first set up an External Client App that allows access to the Agent API. Follow this guide to create the ECA https://developer.salesforce.com/docs/ai/agentforce/guide/agent-api-get-started.html#create-a-salesforce-app. You must use `http://localhost:1717/OauthRedirect` as the callback URL.
339
+
340
+ Once the ECA is set up, log in to your org using this ECA
341
+ ```
342
+ sf org login web \
343
+ --alias myorg \
344
+ --instance-url https://{MY_DOMAIN_URL} \
345
+ --client-id {CONSUMER_KEY} \
346
+ --scopes "sfap_api api"
347
+ ```
348
+
349
+ then you can test your code using `myorg` alias
350
+ ```
351
+ datacustomcode run ./payload/entrypoint.py --sf-cli-org myorg
352
+ ```
353
+
354
+
308
355
  ## Docker usage
309
356
 
310
357
  The SDK provides Docker-based development options that allow you to test your code in an environment that closely resembles Data Cloud's execution environment.
@@ -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.1"
21
+ version = "6.0.2.dev1"
22
22
 
23
23
  [tool.black]
24
24
  exclude = '''
@@ -17,8 +17,11 @@ __all__ = [
17
17
  "AuthType",
18
18
  "Client",
19
19
  "Credentials",
20
+ "DefaultSparkLLMGateway",
20
21
  "PrintDataCloudWriter",
21
22
  "QueryAPIDataCloudReader",
23
+ "SparkLLMGateway",
24
+ "llm_gateway_generate_text_col",
22
25
  ]
23
26
 
24
27
 
@@ -44,4 +47,16 @@ def __getattr__(name: str):
44
47
  from datacustomcode.io.reader.query_api import QueryAPIDataCloudReader
45
48
 
46
49
  return QueryAPIDataCloudReader
50
+ elif name == "SparkLLMGateway":
51
+ from datacustomcode.llm_gateway import SparkLLMGateway
52
+
53
+ return SparkLLMGateway
54
+ elif name == "DefaultSparkLLMGateway":
55
+ from datacustomcode.llm_gateway import DefaultSparkLLMGateway
56
+
57
+ return DefaultSparkLLMGateway
58
+ elif name == "llm_gateway_generate_text_col":
59
+ from datacustomcode.client import llm_gateway_generate_text_col
60
+
61
+ return llm_gateway_generate_text_col
47
62
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -198,11 +198,6 @@ 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(
202
- "--use-in-feature",
203
- default="SearchIndexChunking",
204
- help="Feature where this function will be used.",
205
- )
206
201
  def deploy(
207
202
  path: str,
208
203
  name: str,
@@ -212,7 +207,6 @@ def deploy(
212
207
  profile: str,
213
208
  network: str,
214
209
  sf_cli_org: Optional[str],
215
- use_in_feature: Optional[str],
216
210
  ):
217
211
  from datacustomcode.constants import USE_IN_FEATURE_MAPPING_FOR_CONNECT_API
218
212
  from datacustomcode.deploy import (
@@ -248,18 +242,20 @@ def deploy(
248
242
  )
249
243
 
250
244
  if package_type == "function":
251
- # Try to infer use_in_feature from function signature; fall back to
252
- # the explicit flag value (defaults to SearchIndexChunking)
245
+ # Infer use_in_feature from function signature
253
246
  entrypoint_path = os.path.join(path, ENTRYPOINT_FILE)
254
- inferred = infer_use_in_feature(entrypoint_path)
255
- if inferred:
256
- use_in_feature = inferred
247
+ use_in_feature = infer_use_in_feature(entrypoint_path)
248
+ if use_in_feature:
257
249
  logger.info(f"Inferred use_in_feature: {use_in_feature}")
258
250
  else:
259
- logger.info(f"Using use_in_feature: {use_in_feature}")
251
+ click.secho(
252
+ "Error: Could not infer function invoke options. "
253
+ "Please provide --use-in-feature",
254
+ fg="red",
255
+ )
256
+ raise click.Abort()
260
257
 
261
- assert use_in_feature is not None
262
- # Map feature names to Connect API names
258
+ # Map user-provided feature names to API names
263
259
  mapped_feature = USE_IN_FEATURE_MAPPING_FOR_CONNECT_API.get(
264
260
  use_in_feature, use_in_feature
265
261
  )
@@ -18,24 +18,80 @@ from enum import Enum
18
18
  from typing import (
19
19
  TYPE_CHECKING,
20
20
  ClassVar,
21
+ Dict,
21
22
  Optional,
23
+ Union,
22
24
  )
23
25
 
24
26
  from datacustomcode.config import config
25
27
  from datacustomcode.file.path.default import DefaultFindFilePath
26
28
  from datacustomcode.io.reader.base import BaseDataCloudReader
29
+ from datacustomcode.llm_gateway_config import spark_llm_gateway_config
27
30
  from datacustomcode.spark.default import DefaultSparkSessionProvider
28
31
 
29
32
  if TYPE_CHECKING:
30
33
  from pathlib import Path
31
34
 
32
- from pyspark.sql import DataFrame as PySparkDataFrame
35
+ from pyspark.sql import Column, DataFrame as PySparkDataFrame
33
36
 
34
37
  from datacustomcode.io.reader.base import BaseDataCloudReader
35
38
  from datacustomcode.io.writer.base import BaseDataCloudWriter, WriteMode
39
+ from datacustomcode.llm_gateway.spark_base import SparkLLMGateway
36
40
  from datacustomcode.spark.base import BaseSparkSessionProvider
37
41
 
38
42
 
43
+ def _build_spark_llm_gateway() -> "SparkLLMGateway":
44
+ """Instantiate the SDK-configured :class:`SparkLLMGateway`.
45
+
46
+ Raises:
47
+ RuntimeError: If no ``spark_llm_gateway_config`` has been loaded.
48
+ """
49
+ cfg = spark_llm_gateway_config.spark_llm_gateway_config
50
+ if cfg is None:
51
+ raise RuntimeError(
52
+ "spark_llm_gateway_config is not configured. Add a "
53
+ "'spark_llm_gateway_config' section to config.yaml."
54
+ )
55
+ return cfg.to_object()
56
+
57
+
58
+ def llm_gateway_generate_text_col(
59
+ template: str,
60
+ values: Union[Dict[str, "Column"], "Column"],
61
+ model_id: Optional[str] = None,
62
+ max_tokens: Optional[int] = None,
63
+ ) -> "Column":
64
+ """Build a Spark Column that runs the LLM Gateway per row.
65
+
66
+ Example:
67
+
68
+ >>> df.withColumn(
69
+ ... "greeting__c",
70
+ ... llm_gateway_generate_text_col(
71
+ ... "In one sentence, greet {name} from {city}.",
72
+ ... {"name": col("name__c"), "city": col("homecity__c")},
73
+ ... model_id="sfdc_ai__DefaultGPT4Omni",
74
+ ... max_tokens=100,
75
+ ... ),
76
+ ... )
77
+
78
+ Args:
79
+ template: The prompt template, with ``{field}`` placeholders matching
80
+ keys in ``values``. Substitution uses ``str.format``.
81
+ values: Either a mapping from placeholder name to Spark ``Column``, or
82
+ a single ``Column`` whose value is already a struct.
83
+ model_id: LLM model id. Defaults to ``sfdc_ai__DefaultGPT4Omni``.
84
+ max_tokens: Maximum tokens to generate. Defaults to 200.
85
+
86
+ Returns:
87
+ A Spark ``Column`` that, when evaluated, produces the generated text.
88
+ """
89
+ gateway = Client()._get_spark_llm_gateway()
90
+ return gateway.llm_gateway_generate_text_col(
91
+ template, values, model_id=model_id, max_tokens=max_tokens
92
+ )
93
+
94
+
39
95
  class DataCloudObjectType(Enum):
40
96
  DLO = "dlo"
41
97
  DMO = "dmo"
@@ -94,18 +150,23 @@ class Client:
94
150
  finder: Find a file path
95
151
  reader: A custom reader to use for reading Data Cloud objects.
96
152
  writer: A custom writer to use for writing Data Cloud objects.
153
+ spark_llm_gateway: Optional custom :class:`SparkLLMGateway`. When
154
+ omitted, the gateway is lazily resolved from
155
+ ``spark_llm_gateway_config``.
97
156
 
98
157
  Example:
99
158
  >>> client = Client()
100
159
  >>> file_path = client.find_file_path("data.csv")
101
160
  >>> dlo = client.read_dlo("my_dlo")
102
161
  >>> client.write_to_dmo("my_dmo", dlo)
162
+ >>> answer = client.llm_gateway_generate_text("Generate a greeting message")
103
163
  """
104
164
 
105
165
  _instance: ClassVar[Optional[Client]] = None
106
166
  _reader: BaseDataCloudReader
107
167
  _writer: BaseDataCloudWriter
108
168
  _file: DefaultFindFilePath
169
+ _spark_llm_gateway: Optional[SparkLLMGateway]
109
170
  _data_layer_history: dict[DataCloudObjectType, set[str]]
110
171
  _code_type: str
111
172
 
@@ -114,11 +175,13 @@ class Client:
114
175
  reader: Optional[BaseDataCloudReader] = None,
115
176
  writer: Optional[BaseDataCloudWriter] = None,
116
177
  spark_provider: Optional[BaseSparkSessionProvider] = None,
178
+ spark_llm_gateway: Optional[SparkLLMGateway] = None,
117
179
  code_type: str = "script",
118
180
  ) -> Client:
119
181
 
120
182
  if cls._instance is None:
121
183
  cls._instance = super().__new__(cls)
184
+ cls._instance._spark_llm_gateway = spark_llm_gateway
122
185
  # Initialize Readers and Writers from config
123
186
  # and/or provided reader and writer
124
187
  if reader is None or writer is None:
@@ -225,6 +288,44 @@ class Client:
225
288
 
226
289
  return self._file.find_file_path(file_name) # type: ignore[no-any-return]
227
290
 
291
+ def llm_gateway_generate_text(
292
+ self,
293
+ prompt: str,
294
+ model_id: Optional[str] = None,
295
+ max_tokens: Optional[int] = None,
296
+ ) -> str:
297
+ """Issue a one-shot LLM Gateway call. This is the scalar counterpart to
298
+ :func:`llm_gateway_generate_text_col`: it runs **once** — not per row.
299
+ Use the column helper method instead when you want to fan a prompt out across
300
+ every row of a DataFrame.
301
+
302
+ Example:
303
+
304
+ >>> response = Client().llm_gateway_generate_text(
305
+ ... "Generate a greeting message"
306
+ ... )
307
+
308
+ Args:
309
+ prompt: The literal prompt to send. Plain text — no
310
+ ``{field}`` substitution is performed on this string.
311
+ model_id: LLM model id to target. Defaults to
312
+ ``sfdc_ai__DefaultGPT4Omni`` when ``None``.
313
+ max_tokens: Hard upper bound on the number of tokens the model
314
+ may generate. Defaults to 200 when ``None``.
315
+
316
+ Returns:
317
+ The generated text as a plain Python ``str``; empty when the
318
+ gateway response carries no generated text.
319
+ """
320
+ return self._get_spark_llm_gateway().llm_gateway_generate_text(
321
+ prompt, model_id=model_id, max_tokens=max_tokens
322
+ )
323
+
324
+ def _get_spark_llm_gateway(self) -> SparkLLMGateway:
325
+ if self._spark_llm_gateway is None:
326
+ self._spark_llm_gateway = _build_spark_llm_gateway()
327
+ return self._spark_llm_gateway
328
+
228
329
  def _validate_data_layer_history_does_not_contain(
229
330
  self, data_cloud_object_type: DataCloudObjectType
230
331
  ) -> None:
@@ -28,3 +28,6 @@ llm_gateway_config:
28
28
  type_config_name: DefaultLLMGateway
29
29
  options:
30
30
  credentials_profile: default
31
+
32
+ spark_llm_gateway_config:
33
+ type_config_name: DefaultSparkLLMGateway
@@ -15,20 +15,23 @@
15
15
 
16
16
  from typing import (
17
17
  ClassVar,
18
+ Generic,
18
19
  Optional,
19
20
  Type,
20
- cast,
21
+ TypeVar,
21
22
  )
22
23
 
23
24
  from datacustomcode.common_config import BaseObjectConfig
24
25
 
26
+ _T = TypeVar("_T")
25
27
 
26
- class CredentialsObjectConfig(BaseObjectConfig):
28
+
29
+ class CredentialsObjectConfig(BaseObjectConfig, Generic[_T]):
27
30
  type_to_create: ClassVar[Type]
28
31
  credentials_profile: Optional[str] = None
29
32
  sf_cli_org: Optional[str] = None
30
33
 
31
- def to_object(self):
34
+ def to_object(self) -> _T:
32
35
  """Create an object instance, automatically including credentials in options"""
33
36
 
34
37
  options = self.options.copy()
@@ -38,4 +41,5 @@ class CredentialsObjectConfig(BaseObjectConfig):
38
41
  options["sf_cli_org"] = self.sf_cli_org
39
42
 
40
43
  type_ = self.type_to_create.subclass_from_config_name(self.type_config_name)
41
- return cast(type_, type_(**options))
44
+ instance: _T = type_(**options)
45
+ return instance
@@ -28,7 +28,7 @@ from datacustomcode.einstein_predictions.base import EinsteinPredictions
28
28
  _E = TypeVar("_E", bound=EinsteinPredictions)
29
29
 
30
30
 
31
- class EinsteinPredictionsObjectConfig(CredentialsObjectConfig, Generic[_E]):
31
+ class EinsteinPredictionsObjectConfig(CredentialsObjectConfig[_E], Generic[_E]):
32
32
  type_to_create: ClassVar[Type[EinsteinPredictions]] = EinsteinPredictions # type: ignore[type-abstract]
33
33
 
34
34
 
@@ -16,6 +16,7 @@
16
16
  """
17
17
  Pydantic models for Search Index Chunking V1
18
18
  """
19
+
19
20
  from enum import Enum
20
21
  from typing import (
21
22
  Dict,
@@ -27,21 +27,11 @@ if TYPE_CHECKING:
27
27
  class WriteMode(str, Enum):
28
28
  APPEND = "append"
29
29
  OVERWRITE = "overwrite"
30
- OVERWRITE_PARTITIONS = "overwrite_partitions" # Deprecated: raises error if used
30
+ OVERWRITE_PARTITIONS = "overwrite_partitions"
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
-
45
35
  class BaseDataCloudWriter(BaseDataAccessLayer):
46
36
  """Base class for Data Cloud writers."""
47
37
 
@@ -15,8 +15,12 @@
15
15
 
16
16
  from datacustomcode.llm_gateway.base import LLMGateway
17
17
  from datacustomcode.llm_gateway.default import DefaultLLMGateway
18
+ from datacustomcode.llm_gateway.spark_base import SparkLLMGateway
19
+ from datacustomcode.llm_gateway.spark_default import DefaultSparkLLMGateway
18
20
 
19
21
  __all__ = [
20
22
  "DefaultLLMGateway",
23
+ "DefaultSparkLLMGateway",
21
24
  "LLMGateway",
25
+ "SparkLLMGateway",
22
26
  ]
@@ -34,6 +34,8 @@ class DefaultLLMGateway(EinsteinPlatformClient, LLMGateway):
34
34
 
35
35
  payload: Dict[str, Any] = {"prompt": request.prompt}
36
36
 
37
+ if request.max_tokens is not None:
38
+ payload["max_tokens"] = request.max_tokens
37
39
  if request.localization:
38
40
  payload["localization"] = request.localization
39
41
  if request.tags:
@@ -0,0 +1,55 @@
1
+ # Copyright (c) 2025, Salesforce, Inc.
2
+ # SPDX-License-Identifier: Apache-2
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ from __future__ import annotations
16
+
17
+ from abc import ABC, abstractmethod
18
+ from typing import (
19
+ TYPE_CHECKING,
20
+ Any,
21
+ Dict,
22
+ Optional,
23
+ Union,
24
+ )
25
+
26
+ from datacustomcode.mixin import UserExtendableNamedConfigMixin
27
+
28
+ if TYPE_CHECKING:
29
+ from pyspark.sql import Column
30
+
31
+
32
+ class SparkLLMGateway(ABC, UserExtendableNamedConfigMixin):
33
+ CONFIG_NAME: str
34
+
35
+ def __init__(self, **kwargs: Any) -> None:
36
+ pass
37
+
38
+ @abstractmethod
39
+ def llm_gateway_generate_text(
40
+ self,
41
+ prompt: str,
42
+ model_id: Optional[str] = None,
43
+ max_tokens: Optional[int] = None,
44
+ ) -> str:
45
+ """Issue a one-shot LLM Gateway call and return the generated text."""
46
+
47
+ @abstractmethod
48
+ def llm_gateway_generate_text_col(
49
+ self,
50
+ template: str,
51
+ values: Union[Dict[str, "Column"], "Column"],
52
+ model_id: Optional[str] = None,
53
+ max_tokens: Optional[int] = None,
54
+ ) -> "Column":
55
+ """Build a Spark ``Column`` that invokes the LLM Gateway per row."""
@@ -0,0 +1,119 @@
1
+ # Copyright (c) 2025, Salesforce, Inc.
2
+ # SPDX-License-Identifier: Apache-2
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ from __future__ import annotations
16
+
17
+ from typing import (
18
+ TYPE_CHECKING,
19
+ Any,
20
+ Dict,
21
+ Optional,
22
+ Union,
23
+ )
24
+
25
+ from datacustomcode.llm_gateway.spark_base import SparkLLMGateway
26
+
27
+ if TYPE_CHECKING:
28
+ from pyspark.sql import Column
29
+
30
+ from datacustomcode.llm_gateway.base import LLMGateway
31
+
32
+
33
+ _DEFAULT_LLM_MODEL_ID = "sfdc_ai__DefaultGPT4Omni"
34
+ _DEFAULT_LLM_MAX_TOKENS = 200
35
+
36
+
37
+ class DefaultSparkLLMGateway(SparkLLMGateway):
38
+
39
+ CONFIG_NAME = "DefaultSparkLLMGateway"
40
+
41
+ def __init__(
42
+ self,
43
+ llm_gateway: Optional["LLMGateway"] = None,
44
+ **kwargs: Any,
45
+ ) -> None:
46
+ super().__init__(**kwargs)
47
+ if llm_gateway is None:
48
+ llm_gateway = _build_underlying_gateway()
49
+ self._llm_gateway: "LLMGateway" = llm_gateway
50
+
51
+ def llm_gateway_generate_text(
52
+ self,
53
+ prompt: str,
54
+ model_id: Optional[str] = None,
55
+ max_tokens: Optional[int] = None,
56
+ ) -> str:
57
+ return _invoke_llm_gateway(self._llm_gateway, prompt, model_id, max_tokens)
58
+
59
+ def llm_gateway_generate_text_col(
60
+ self,
61
+ template: str,
62
+ values: Union[Dict[str, "Column"], "Column"],
63
+ model_id: Optional[str] = None,
64
+ max_tokens: Optional[int] = None,
65
+ ) -> "Column":
66
+
67
+ from pyspark.sql.functions import struct, udf
68
+ from pyspark.sql.types import StringType
69
+
70
+ if isinstance(values, dict):
71
+ values_col = struct(*[v.alias(k) for k, v in values.items()])
72
+ else:
73
+ values_col = values
74
+
75
+ gateway = self._llm_gateway
76
+
77
+ def _generate(values_row: Any) -> str:
78
+ if values_row is None:
79
+ return ""
80
+ subs = (
81
+ values_row.asDict()
82
+ if hasattr(values_row, "asDict")
83
+ else dict(values_row)
84
+ )
85
+ prompt = template.format(**subs)
86
+ return _invoke_llm_gateway(gateway, prompt, model_id, max_tokens)
87
+
88
+ return udf(_generate, StringType())(values_col)
89
+
90
+
91
+ def _build_underlying_gateway() -> "LLMGateway":
92
+ from datacustomcode.llm_gateway_config import llm_gateway_config
93
+
94
+ cfg = llm_gateway_config.llm_gateway_config
95
+ if cfg is None:
96
+ raise RuntimeError(
97
+ "llm_gateway_config is not configured. Add an 'llm_gateway_config' "
98
+ "section to config.yaml."
99
+ )
100
+ return cfg.to_object()
101
+
102
+
103
+ def _invoke_llm_gateway(
104
+ gateway: "LLMGateway",
105
+ prompt: str,
106
+ model_id: Optional[str],
107
+ max_tokens: Optional[int],
108
+ ) -> str:
109
+ from datacustomcode.llm_gateway.types.generate_text_request_builder import (
110
+ GenerateTextRequestBuilder,
111
+ )
112
+
113
+ builder = (
114
+ GenerateTextRequestBuilder()
115
+ .set_prompt(prompt)
116
+ .set_model(model_id or _DEFAULT_LLM_MODEL_ID)
117
+ .set_max_tokens(max_tokens or _DEFAULT_LLM_MAX_TOKENS)
118
+ )
119
+ return gateway.generate_text(builder.build()).text
@@ -40,6 +40,13 @@ class GenerateTextRequest(BaseModel):
40
40
  )
41
41
  model_name: str = Field(..., min_length=1, description="Name of the model to use")
42
42
  prompt: str = Field(..., description="Input prompt")
43
+ max_tokens: Optional[int] = Field(
44
+ default=None,
45
+ ge=1,
46
+ description=(
47
+ "Maximum number of tokens to generate. If None, server default applies."
48
+ ),
49
+ )
43
50
  localization: Optional[Dict[str, Any]] = Field(
44
51
  default=None, description="Localization settings"
45
52
  )