salesforce-data-customcode 6.0.3.dev1__py3-none-any.whl → 6.0.3.dev2__py3-none-any.whl

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.
datacustomcode/client.py CHANGED
@@ -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
- >>> df.withColumn(
68
- ... "greeting__c",
69
- ... llm_gateway_generate_text_col(
70
- ... "In one sentence, greet {name} from {city}.",
71
- ... {"name": col("name__c"), "city": col("homecity__c")},
72
- ... model_id="sfdc_ai__DefaultGPT4Omni",
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`` that, when evaluated, produces the generated text.
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)
@@ -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 StringType
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 _invoke_llm_gateway(gateway, prompt, model_id)
110
+ return _invoke_llm_gateway_as_struct(gateway, prompt, model_id)
84
111
 
85
- return udf(_generate, StringType())(values_col)
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 _invoke_llm_gateway(
127
+ def _call_llm_gateway(
101
128
  gateway: "LLMGateway",
102
129
  prompt: str,
103
130
  model_id: Optional[str],
104
- ) -> str:
105
- from datacustomcode.llm_gateway.errors import LLMGatewayCallError
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
- builder = (
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
- response = gateway.generate_text(builder.build())
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
+ }
@@ -16,7 +16,9 @@ def main():
16
16
  You can use your AI models configured in Salesforce to generate column
17
17
  values. See README.md for how to test locally before deploying to Data Cloud.
18
18
 
19
- Example:
19
+ Example (the per-row helper returns a struct
20
+ ``{status, response, error_code, error_message}`` — pick the field you
21
+ want with ``[...]``):
20
22
 
21
23
  >>> from datacustomcode.client import llm_gateway_generate_text_col
22
24
  df_generated = df.withColumn(
@@ -25,7 +27,7 @@ def main():
25
27
  ... "In one sentence, greet {name} from {city}.",
26
28
  ... {"name": col("name__c"), "city": col("homecity__c")},
27
29
  ... model_id="sfdc_ai__DefaultGPT4Omni",
28
- ... ),
30
+ ... )["response"],
29
31
  ... )
30
32
 
31
33
  You can also invoke the LLM with a literal plain text prompt — no
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: salesforce-data-customcode
3
- Version: 6.0.3.dev1
3
+ Version: 6.0.3.dev2
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"
@@ -1,7 +1,7 @@
1
1
  datacustomcode/__init__.py,sha256=qx-X3U9lU_791ZbAxhGqQeyDw6eqTi9-E9AbYnwJfxU,2071
2
2
  datacustomcode/auth.py,sha256=fpSjhIBdv9trC8yq2vuljAix_Euu-4Ah7HDCGhYjOxI,8309
3
3
  datacustomcode/cli.py,sha256=ObqhOasZSZ8ZNFP6pywV3FpsUOz45_KaZdMNT4BHOKs,13069
4
- datacustomcode/client.py,sha256=NzPtJkHszRh0GqKUkobPyDUCDauUIaeI-zrac9VR7l8,12813
4
+ datacustomcode/client.py,sha256=eV_Bv-YfxXcayjgtAbu9tXb_UcHgcE2f3-L_3vvc0cI,13616
5
5
  datacustomcode/cmd.py,sha256=ZMs46aydJw2EaU26JgCtZmnqESQFHvvaJz10hnjZTBk,3537
6
6
  datacustomcode/common_config.py,sha256=SAUnxj3kqmOeWwPmFoYq4tuxokMgURVm4QwcGi-avL4,1928
7
7
  datacustomcode/config.py,sha256=2Pk61ieQsEWSxKDxlS66_rliKE8iX0j-9LweEm0aaGo,4072
@@ -41,8 +41,8 @@ datacustomcode/llm_gateway/__init__.py,sha256=rcoGpSkv36tZuAOcTxKkhNpM0qV03qgmer
41
41
  datacustomcode/llm_gateway/base.py,sha256=CTUhZiZ0JFlmWj6kQpTR4_-mUvqkBKl_Fkdvv9VwxY8,1262
42
42
  datacustomcode/llm_gateway/default.py,sha256=QiwrDaUvrh00I-fYYhmogTtqC5r0fmm-JrXxNQLvTc0,1854
43
43
  datacustomcode/llm_gateway/errors.py,sha256=ABjztpJwAYMBlmFX808CHHtu4nD6zA4ECelzNJQkJg0,1197
44
- datacustomcode/llm_gateway/spark_base.py,sha256=apM3FYDPNz3JTgEdlf-7jeryzTYmSzjJsCIt95ZEpvg,1553
45
- datacustomcode/llm_gateway/spark_default.py,sha256=ctBzkP1vDxgrmoIC6PvfmwxmlPVotkcADK3-M5qyFKI,3794
44
+ datacustomcode/llm_gateway/spark_base.py,sha256=caf-fHTOo9SMTkK5Ozhrt70b51Rz_6hDY0rwa6jKwuE,2000
45
+ datacustomcode/llm_gateway/spark_default.py,sha256=7yC8lSJ7DKXTqtm_i0JjFqxML8hkT65h6n3i1CID8Ds,5745
46
46
  datacustomcode/llm_gateway/types/__init__.py,sha256=gamfOD1VnAtEslRBpqh-yKiVjkG_wYWYdSatGIfsN-w,621
47
47
  datacustomcode/llm_gateway/types/generate_text_request.py,sha256=ChxOCzp7St1EYgwl4tWiwfrPyVci4zPyBhnEjWc1Vfs,1448
48
48
  datacustomcode/llm_gateway/types/generate_text_request_builder.py,sha256=R4hCgatMPSfWYqaOmeC1IMQj753iDgu7wU992c4tkmk,2565
@@ -86,13 +86,13 @@ datacustomcode/templates/script/examples/employee_hierarchy/employee_data.csv,sh
86
86
  datacustomcode/templates/script/examples/employee_hierarchy/entrypoint.py,sha256=Mfm3iQtEHTQRW6cmZTwRKdTh3IeGWR-teXrd6x-YLSY,2223
87
87
  datacustomcode/templates/script/jupyterlab.sh,sha256=IHR3YQ8d_busuyvesByhJgzCKULIFPI-Hqogs0NPhCs,2432
88
88
  datacustomcode/templates/script/payload/config.json,sha256=0d2mEMt4NHIeOUTsgiPMuRKdOLIQxmh-Wq-IfEqU-gc,28
89
- datacustomcode/templates/script/payload/entrypoint.py,sha256=Ox2guNka3r0GW2jzKsmH7AP70j1-1Qqr_jhtH5dkPOM,1590
89
+ datacustomcode/templates/script/payload/entrypoint.py,sha256=bLUhC4l03H-OQJKdMf11vGXmBnga6QitRXo-g9HibZg,1741
90
90
  datacustomcode/templates/script/requirements-dev.txt,sha256=OWwuy1awesqOZOS3Zizmdd6BDnSePpGFN5bq5IrALvc,176
91
91
  datacustomcode/templates/script/requirements.txt,sha256=qJbqzs5z0Hrx590U3T7dHq_m8UQEx2TdhgrJSz-QHIQ,40
92
92
  datacustomcode/token_provider.py,sha256=KPj_ca6mcBmhXHaPmZjAI4it8QvOWdwKJL8yfqxfYwU,5446
93
93
  datacustomcode/version.py,sha256=9LlbVrzwBvut1L308QbwNBgxdQk5CrghH39JARVSxms,989
94
- salesforce_data_customcode-6.0.3.dev1.dist-info/METADATA,sha256=wmHyC6LrpV7uYwKuMBQEwfrztUX9gLb7v7YRwsYYGAg,21724
95
- salesforce_data_customcode-6.0.3.dev1.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
96
- salesforce_data_customcode-6.0.3.dev1.dist-info/entry_points.txt,sha256=WpQ94UB7UuRCYGOLtJV3vAgm1tl7iVf43VYRWIuNu5Y,74
97
- salesforce_data_customcode-6.0.3.dev1.dist-info/licenses/LICENSE.txt,sha256=iOi8EmQpfkFhMENi7VYtkl2EqS14pEOccoXHiW2dyPU,11443
98
- salesforce_data_customcode-6.0.3.dev1.dist-info/RECORD,,
94
+ salesforce_data_customcode-6.0.3.dev2.dist-info/METADATA,sha256=jplSGhHPtA0lYdotZM5jQLNH8WFhDRsE5yv5KRDFCYM,21930
95
+ salesforce_data_customcode-6.0.3.dev2.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
96
+ salesforce_data_customcode-6.0.3.dev2.dist-info/entry_points.txt,sha256=WpQ94UB7UuRCYGOLtJV3vAgm1tl7iVf43VYRWIuNu5Y,74
97
+ salesforce_data_customcode-6.0.3.dev2.dist-info/licenses/LICENSE.txt,sha256=iOi8EmQpfkFhMENi7VYtkl2EqS14pEOccoXHiW2dyPU,11443
98
+ salesforce_data_customcode-6.0.3.dev2.dist-info/RECORD,,