generated from john/python-template
fix: retry execution attempt number conflicts
Co-authored-by: Copilot App <[email protected]>
This commit is contained in:
co-authored by
Copilot App
parent
f9261a1af3
commit
2093eb6fb3
@@ -62,6 +62,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_PROMPT_FILE = "transcribe_document.md"
|
||||
JSON_OBJECT_ADAPTER = TypeAdapter(dict[str, JsonValue])
|
||||
MAX_EXECUTION_ATTEMPT_NUMBER_RETRIES = 3
|
||||
|
||||
|
||||
class PromptExecution(BaseModel):
|
||||
@@ -533,54 +534,72 @@ class SourceService(ServiceBase):
|
||||
|
||||
finish_time = finished_at or datetime.now(UTC)
|
||||
start_time = started_at or finish_time
|
||||
attempt_number = (
|
||||
await _session.exec(
|
||||
select(func.max(ExecutionAttempt.attempt_number))
|
||||
.where(ExecutionAttempt.job_id == job_id)
|
||||
.where(ExecutionAttempt.source_id == source_id)
|
||||
)
|
||||
).one()
|
||||
transport = transport_evidence or TransportEvidence(response_received=False)
|
||||
manifest_payload = request_manifest.model_dump(mode="json") if request_manifest is not None else None
|
||||
software_payload = (
|
||||
request_manifest.software.model_dump(mode="json") if request_manifest is not None else None
|
||||
)
|
||||
attempt = ExecutionAttempt(
|
||||
job_source_id=job_source.id,
|
||||
job_id=job_id,
|
||||
source_id=source_id,
|
||||
attempt_number=(attempt_number or 0) + 1,
|
||||
status=outcome,
|
||||
provider=provider or job.provider or self.settings.provider.value,
|
||||
model=model or job.model,
|
||||
request_manifest=manifest_payload,
|
||||
request_manifest_sha256=request_manifest.digest() if request_manifest is not None else None,
|
||||
request_manifest_schema_version=(
|
||||
request_manifest.schema_version if request_manifest is not None else None
|
||||
),
|
||||
response_received=transport.response_received,
|
||||
transport_status_code=transport.status_code,
|
||||
transport_body=transport.body,
|
||||
transport_content_type=transport.content_type,
|
||||
transport_content_encoding=transport.content_encoding,
|
||||
transport_safe_headers=transport.safe_headers or None,
|
||||
router_request_id=transport.request_id,
|
||||
router_generation_id=transport.generation_id,
|
||||
sdk_response_snapshot=raw_response_payload,
|
||||
normalized_metadata=attempt_metadata,
|
||||
software_context=software_payload,
|
||||
raw_transcription=text,
|
||||
error_category=error_category,
|
||||
error_detail=error_detail,
|
||||
failure_phase=failure_phase,
|
||||
started_at=start_time,
|
||||
finished_at=finish_time,
|
||||
duration_ms=duration_ms
|
||||
if duration_ms is not None
|
||||
else max(0, int((finish_time - start_time).total_seconds() * 1000)),
|
||||
)
|
||||
_session.add(attempt)
|
||||
await _session.flush()
|
||||
attempt: ExecutionAttempt | None = None
|
||||
for attempt_retry in range(1, MAX_EXECUTION_ATTEMPT_NUMBER_RETRIES + 1):
|
||||
latest_attempt_number = (
|
||||
await _session.exec(
|
||||
select(func.max(ExecutionAttempt.attempt_number))
|
||||
.where(ExecutionAttempt.job_id == job_id)
|
||||
.where(ExecutionAttempt.source_id == source_id)
|
||||
)
|
||||
).one()
|
||||
candidate = ExecutionAttempt(
|
||||
job_source_id=job_source.id,
|
||||
job_id=job_id,
|
||||
source_id=source_id,
|
||||
attempt_number=(latest_attempt_number or 0) + 1,
|
||||
status=outcome,
|
||||
provider=provider or job.provider or self.settings.provider.value,
|
||||
model=model or job.model,
|
||||
request_manifest=manifest_payload,
|
||||
request_manifest_sha256=request_manifest.digest() if request_manifest is not None else None,
|
||||
request_manifest_schema_version=(
|
||||
request_manifest.schema_version if request_manifest is not None else None
|
||||
),
|
||||
response_received=transport.response_received,
|
||||
transport_status_code=transport.status_code,
|
||||
transport_body=transport.body,
|
||||
transport_content_type=transport.content_type,
|
||||
transport_content_encoding=transport.content_encoding,
|
||||
transport_safe_headers=transport.safe_headers or None,
|
||||
router_request_id=transport.request_id,
|
||||
router_generation_id=transport.generation_id,
|
||||
sdk_response_snapshot=raw_response_payload,
|
||||
normalized_metadata=attempt_metadata,
|
||||
software_context=software_payload,
|
||||
raw_transcription=text,
|
||||
error_category=error_category,
|
||||
error_detail=error_detail,
|
||||
failure_phase=failure_phase,
|
||||
started_at=start_time,
|
||||
finished_at=finish_time,
|
||||
duration_ms=duration_ms
|
||||
if duration_ms is not None
|
||||
else max(0, int((finish_time - start_time).total_seconds() * 1000)),
|
||||
)
|
||||
try:
|
||||
async with _session.begin_nested():
|
||||
_session.add(candidate)
|
||||
await _session.flush()
|
||||
attempt = candidate
|
||||
break
|
||||
except IntegrityError:
|
||||
logger.warning(
|
||||
"Execution attempt number conflict job_id=%s source_id=%s retry=%s/%s",
|
||||
job_id,
|
||||
source_id,
|
||||
attempt_retry,
|
||||
MAX_EXECUTION_ATTEMPT_NUMBER_RETRIES,
|
||||
)
|
||||
continue
|
||||
|
||||
if attempt is None:
|
||||
raise self._execution_attempt_conflict(job_id=job_id, source_id=source_id)
|
||||
|
||||
if text is not None and source.raw_transcription is None and source.preferred_execution_attempt_id is None:
|
||||
source.raw_transcription = text
|
||||
@@ -597,6 +616,17 @@ class SourceService(ServiceBase):
|
||||
suggestion="Use the existing job-source link instead of creating a duplicate.",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _execution_attempt_conflict(*, job_id: UUID, source_id: UUID) -> TranscriptionError:
|
||||
return TranscriptionError(
|
||||
(
|
||||
f"Failed to allocate an execution attempt number for Source {source_id} in Job {job_id} "
|
||||
"after bounded retries"
|
||||
),
|
||||
category=ErrorCategory.CONFLICT,
|
||||
suggestion="Retry the transcription. If it repeats, investigate concurrent worker activity.",
|
||||
)
|
||||
|
||||
async def upsert_revision_for_source(
|
||||
self,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user