feat: expose Function failure tracebacks (#4861)

Function refresh failures and skipped-row diagnostics lose the traceback
when decoded by the SDK. Expose the server-provided optional `traceback`
on job failure details and Function error records in Rust, Python, and
Node.js, while keeping existing compact error messages.

Responses from older servers remain supported when the field is absent.
Tracebacks may be truncated by the server. The Rust `Error::JobFailed`
variant now stores `Box<JobFailure>` so the added diagnostics do not
enlarge every `Result` error payload.
This commit is contained in:
Xuanwo
2026-11-05 15:16:28 +08:00
committed by GitHub
parent b32afe9794
commit edc49e60e2
14 changed files with 82 additions and 10 deletions
+6
View File
@@ -465,6 +465,10 @@ class JobFailureInfo:
@property
def message(self) -> Optional[str]: ...
@property
def traceback(self) -> Optional[str]:
"""User-code traceback, possibly truncated by the server."""
...
@property
def retryable(self) -> Optional[bool]: ...
@property
def sql_job_id(self) -> Optional[str]: ...
@@ -1014,6 +1018,8 @@ class FunctionErrorRecord:
table_version: int
error_type: str
error_message: str
traceback: Optional[str]
"""User-code traceback, possibly truncated by the server."""
created_at_millis: int
class FunctionErrorFragment:
+13
View File
@@ -2055,6 +2055,9 @@ def test_remote_function_errors_lists_the_rows_a_refresh_skipped():
"table_version": 11,
"error_type": "ValueError",
"error_message": "bad input 'x'",
"traceback": (
"Traceback (most recent call last):\nValueError: bad input 'x'"
),
"created_at_millis": 1700000000000,
}
],
@@ -2102,6 +2105,9 @@ def test_remote_function_errors_lists_the_rows_a_refresh_skipped():
assert errors.records[0].row_id == 42
assert errors.records[0].row_address == 12884901897
assert errors.records[0].function_version == "2"
assert errors.records[0].traceback == (
"Traceback (most recent call last):\nValueError: bad input 'x'"
)
assert repr(errors.records[0]) == (
'FunctionErrorRecord(job_id="j-7", fragment_id=3, row_offset=9, '
'row_address=12884901897, column="embedding", '
@@ -5128,6 +5134,10 @@ def test_remote_connection_jobs_surface():
failure=dict(
phase="execute",
message="worker died",
traceback=(
"Traceback (most recent call last):\n"
"RuntimeError: worker died"
),
retryable=True,
sql_job_id="sql-job-1",
),
@@ -5217,6 +5227,9 @@ def test_remote_connection_jobs_surface():
assert progress["waiting_for_job_id"] == "job-maintenance"
assert progress["paused_at_millis"] == 1200
assert job.failure.message == "worker died"
assert job.failure.traceback == (
"Traceback (most recent call last):\nRuntimeError: worker died"
)
assert job.failure.sql_job_id == "sql-job-1"
assert repr(job.failure) == (
'JobFailureInfo(phase="execute", message="worker died", retryable=True, '
+3
View File
@@ -232,6 +232,8 @@ impl From<lancedb::database::JobInfo> for JobInfo {
pub struct JobFailureInfo {
phase: Option<String>,
message: Option<String>,
/// User-code traceback, possibly truncated by the server.
traceback: Option<String>,
retryable: Option<bool>,
sql_job_id: Option<String>,
}
@@ -342,6 +344,7 @@ impl From<lancedb::database::JobDescription> for JobDescription {
failure: description.failure.map(|failure| JobFailureInfo {
phase: failure.phase,
message: failure.message,
traceback: failure.traceback,
retryable: failure.retryable,
sql_job_id: failure.sql_job_id,
}),
+3
View File
@@ -622,6 +622,8 @@ pub struct FunctionErrorRecord {
pub table_version: u64,
pub error_type: String,
pub error_message: String,
/// User-code traceback, possibly truncated by the server.
pub traceback: Option<String>,
pub created_at_millis: i64,
}
@@ -662,6 +664,7 @@ impl From<lancedb::function::FunctionErrorRecord> for FunctionErrorRecord {
table_version: record.table_version,
error_type: record.error_type,
error_message: record.error_message,
traceback: record.traceback,
created_at_millis: record.created_at_millis,
}
}