fix(data-integrity): Replace unconditional commit with flush in LLMTraceRepository.save()
CI / push-validation (pull_request) Successful in 17s
CI / helm (pull_request) Successful in 24s
CI / quality (pull_request) Successful in 35s
CI / lint (pull_request) Successful in 39s
CI / typecheck (pull_request) Successful in 51s
CI / security (pull_request) Successful in 1m1s
CI / build (pull_request) Successful in 3m30s
CI / e2e_tests (pull_request) Successful in 3m59s
CI / integration_tests (pull_request) Successful in 4m0s
CI / unit_tests (pull_request) Failing after 5m26s
CI / docker (pull_request) Has been skipped
CI / coverage (pull_request) Successful in 10m26s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 56m55s
CI / push-validation (pull_request) Successful in 17s
CI / helm (pull_request) Successful in 24s
CI / quality (pull_request) Successful in 35s
CI / lint (pull_request) Successful in 39s
CI / typecheck (pull_request) Successful in 51s
CI / security (pull_request) Successful in 1m1s
CI / build (pull_request) Successful in 3m30s
CI / e2e_tests (pull_request) Successful in 3m59s
CI / integration_tests (pull_request) Successful in 4m0s
CI / unit_tests (pull_request) Failing after 5m26s
CI / docker (pull_request) Has been skipped
CI / coverage (pull_request) Successful in 10m26s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 56m55s
The save() method was calling session.commit() unconditionally, which violated UnitOfWork transaction boundaries when called within a UoW transaction. This caused premature commits of outer transactions and prevented proper rollback on subsequent failures. Changed session.commit() to session.flush() to make changes visible within the transaction without committing, allowing the UoW to control the transaction boundary. This aligns with the pattern used by other repositories in the codebase. Added comprehensive BDD tests with @tdd_issue_7505 tag to verify: - Trace is visible within the transaction after save() - Transaction is not committed yet - Transaction can be rolled back - Trace does not exist after rollback Fixes #7505
This commit is contained in:
@@ -269,8 +269,20 @@ Feature: LLM trace observability
|
||||
Given LANGCHAIN_TRACING_V2 is set to "TRUE"
|
||||
Then the langsmith_enabled check should return True
|
||||
|
||||
Scenario: Repository save trace with no tool calls stores null
|
||||
Given a SQLAlchemy in-memory repository
|
||||
And a valid LLM trace
|
||||
When I save the trace via the repository
|
||||
Then the raw tool_calls_json in the database should be null
|
||||
Scenario: Repository save trace with no tool calls stores null
|
||||
Given a SQLAlchemy in-memory repository
|
||||
And a valid LLM trace
|
||||
When I save the trace via the repository
|
||||
Then the raw tool_calls_json in the database should be null
|
||||
|
||||
# --- UnitOfWork transaction boundary (Issue #7505) ----------------------
|
||||
|
||||
@tdd_issue_7505
|
||||
Scenario: Repository save uses flush not commit within UnitOfWork
|
||||
Given a SQLAlchemy in-memory repository
|
||||
And a valid LLM trace
|
||||
When I save the trace via the repository within a UnitOfWork transaction
|
||||
Then the trace should be visible within the transaction
|
||||
And the transaction should not be committed yet
|
||||
And I should be able to rollback the transaction
|
||||
And the trace should not exist after rollback
|
||||
|
||||
@@ -946,3 +946,63 @@ def step_raw_tool_calls_null(context: Context) -> None:
|
||||
)
|
||||
assert row is not None
|
||||
assert row.tool_calls_json is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# UnitOfWork transaction boundary (Issue #7505)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@when("I save the trace via the repository within a UnitOfWork transaction")
|
||||
def step_save_within_uow(context: Context) -> None:
|
||||
"""Save a trace within a UnitOfWork transaction to verify flush behavior."""
|
||||
# Create a new session for the UnitOfWork transaction
|
||||
session = context.sqla_factory()
|
||||
context.uow_session = session
|
||||
|
||||
# Save the trace using the SQLAlchemy repository
|
||||
# The repository should use flush(), not commit()
|
||||
context.sqla_repo.save(context.trace)
|
||||
|
||||
# At this point, the trace should be visible within the transaction
|
||||
# but not yet committed to the database
|
||||
|
||||
|
||||
@then("the trace should be visible within the transaction")
|
||||
def step_trace_visible_in_transaction(context: Context) -> None:
|
||||
"""Verify the trace is visible within the UnitOfWork transaction."""
|
||||
session = context.uow_session
|
||||
row = (
|
||||
session.query(LLMTraceModel).filter_by(trace_id=context.trace.trace_id).first()
|
||||
)
|
||||
assert row is not None, "Trace should be visible within the transaction"
|
||||
|
||||
|
||||
@then("the transaction should not be committed yet")
|
||||
def step_transaction_not_committed(context: Context) -> None:
|
||||
"""Verify the transaction is still open and not committed."""
|
||||
session = context.uow_session
|
||||
# Check that the session is still in a transaction
|
||||
assert session.in_transaction(), "Session should still be in a transaction"
|
||||
|
||||
|
||||
@then("I should be able to rollback the transaction")
|
||||
def step_rollback_transaction(context: Context) -> None:
|
||||
"""Rollback the UnitOfWork transaction."""
|
||||
session = context.uow_session
|
||||
session.rollback()
|
||||
context.transaction_rolled_back = True
|
||||
|
||||
|
||||
@then("the trace should not exist after rollback")
|
||||
def step_trace_not_exist_after_rollback(context: Context) -> None:
|
||||
"""Verify the trace was rolled back and no longer exists."""
|
||||
# Create a new session to check the database state
|
||||
new_session = context.sqla_factory()
|
||||
row = (
|
||||
new_session.query(LLMTraceModel)
|
||||
.filter_by(trace_id=context.trace.trace_id)
|
||||
.first()
|
||||
)
|
||||
assert row is None, "Trace should not exist after rollback"
|
||||
new_session.close()
|
||||
|
||||
@@ -78,7 +78,7 @@ class LLMTraceRepository:
|
||||
timestamp=trace.timestamp.isoformat(),
|
||||
)
|
||||
session.add(model)
|
||||
session.commit()
|
||||
session.flush()
|
||||
except (SQLAlchemyDatabaseError, OperationalError) as exc:
|
||||
session.rollback()
|
||||
raise DatabaseError(f"Failed to save LLM trace: {exc}") from exc
|
||||
|
||||
Reference in New Issue
Block a user