mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-06 00:01:24 +00:00
feat: read a fully failed batch's error file instead of reporting "empty output file id", so a generation job that had every request refused names the provider's actual reason in the admin panel rather than an internal guard message
This commit is contained in:
@@ -168,7 +168,7 @@ func (s *service) PollBatches(ctx context.Context) error {
|
||||
|
||||
// pollBatchJob reconciles a single batch job.
|
||||
func (s *service) pollBatchJob(ctx context.Context, job *models.WarmupGenerationJob) error {
|
||||
status, outputFileID, counts, err := s.gen.GetBatch(ctx, job.BatchID)
|
||||
status, outputFileID, errorFileID, counts, err := s.gen.GetBatch(ctx, job.BatchID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -177,7 +177,14 @@ func (s *service) pollBatchJob(ctx context.Context, job *models.WarmupGeneration
|
||||
switch status {
|
||||
case "completed":
|
||||
job.BatchOutputFileID = outputFileID
|
||||
return s.ingestBatch(ctx, job, outputFileID, counts)
|
||||
// A batch whose requests all failed completes with no output file; the
|
||||
// refusals are in the error file, which has the same JSONL shape. Read
|
||||
// it instead of failing on the empty id, or the reason is never seen.
|
||||
resultsFileID := outputFileID
|
||||
if resultsFileID == "" {
|
||||
resultsFileID = errorFileID
|
||||
}
|
||||
return s.ingestBatch(ctx, job, resultsFileID, counts)
|
||||
case "failed", "expired", "cancelled":
|
||||
now := time.Now()
|
||||
job.Status = "failed"
|
||||
@@ -287,6 +294,11 @@ func (s *service) ingestBatch(ctx context.Context, job *models.WarmupGenerationJ
|
||||
job.Status = "failed"
|
||||
if job.Error == "" {
|
||||
job.Error = fmt.Sprintf("all %d batch results failed", job.FailedCount)
|
||||
// One line's reason is worth more than the count: when every
|
||||
// request is refused it is the same reason for all of them.
|
||||
if reason := firstResultError(results); reason != "" {
|
||||
job.Error += ": " + reason
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := s.repo.UpdateGenerationJob(ctx, job); err != nil {
|
||||
@@ -355,3 +367,14 @@ func (s *service) CancelBatch(ctx context.Context, jobID uuid.UUID) error {
|
||||
}
|
||||
return s.repo.UpdateGenerationJob(ctx, job)
|
||||
}
|
||||
|
||||
// firstResultError returns the first non-empty per-line error in a batch's
|
||||
// results, used to name the reason a fully failed batch produced nothing.
|
||||
func firstResultError(results []generation.BatchResult) string {
|
||||
for i := range results {
|
||||
if results[i].Err != "" {
|
||||
return results[i].Err
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -112,19 +112,22 @@ func (c *GenerationClient) SubmitBatch(ctx context.Context, requests []BatchRequ
|
||||
return batch.ID, file.ID, nil
|
||||
}
|
||||
|
||||
// GetBatch returns the current status, output file ID (when completed), and
|
||||
// request counts for a batch.
|
||||
func (c *GenerationClient) GetBatch(ctx context.Context, batchID string) (status, outputFileID string, counts BatchCounts, err error) {
|
||||
// GetBatch returns the current status, the output file ID (present when at
|
||||
// least one request succeeded), the error file ID (present when at least one
|
||||
// failed), and the request counts for a batch. A batch whose requests all
|
||||
// failed still completes, with output empty and every refusal in the error
|
||||
// file, so callers must read that one to learn why.
|
||||
func (c *GenerationClient) GetBatch(ctx context.Context, batchID string) (status, outputFileID, errorFileID string, counts BatchCounts, err error) {
|
||||
batch, err := c.client.Batches.Get(ctx, batchID)
|
||||
if err != nil {
|
||||
return "", "", BatchCounts{}, err
|
||||
return "", "", "", BatchCounts{}, err
|
||||
}
|
||||
counts = BatchCounts{
|
||||
Completed: int(batch.RequestCounts.Completed),
|
||||
Failed: int(batch.RequestCounts.Failed),
|
||||
Total: int(batch.RequestCounts.Total),
|
||||
}
|
||||
return string(batch.Status), batch.OutputFileID, counts, nil
|
||||
return string(batch.Status), batch.OutputFileID, batch.ErrorFileID, counts, nil
|
||||
}
|
||||
|
||||
// CancelBatch requests cancellation of an in-flight batch.
|
||||
@@ -180,7 +183,12 @@ func parseBatchOutputLine(raw string) BatchResult {
|
||||
return res
|
||||
}
|
||||
if line.Response.StatusCode < 200 || line.Response.StatusCode >= 300 {
|
||||
// An error line carries {"body":{"error":{"message":...}}}, which does
|
||||
// not fit ChatCompletion; without this the reason is reduced to a code.
|
||||
res.Err = fmt.Sprintf("response status %d", line.Response.StatusCode)
|
||||
if msg := batchLineErrorMessage(raw); msg != "" {
|
||||
res.Err += ": " + msg
|
||||
}
|
||||
return res
|
||||
}
|
||||
if len(line.Response.Body.Choices) == 0 {
|
||||
@@ -204,3 +212,21 @@ type namedReader struct {
|
||||
}
|
||||
|
||||
func (n namedReader) Name() string { return n.name }
|
||||
|
||||
// batchLineErrorMessage pulls the provider's message out of a failed batch
|
||||
// line, whose body is an error object rather than a chat completion.
|
||||
func batchLineErrorMessage(raw string) string {
|
||||
var line struct {
|
||||
Response struct {
|
||||
Body struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
} `json:"body"`
|
||||
} `json:"response"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(raw), &line); err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(line.Response.Body.Error.Message)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user