Skip to content

Commit 4f5a21c

Browse files
committed
gemini-sdk-batch-support
1 parent 80b55ad commit 4f5a21c

File tree

17 files changed

+1414
-42
lines changed

17 files changed

+1414
-42
lines changed

core/bifrost.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,84 @@ func (bifrost *Bifrost) BatchResultsRequest(ctx context.Context, req *schemas.Bi
12601260
return response, nil
12611261
}
12621262

1263+
// BatchDeleteRequest deletes a batch job.
1264+
func (bifrost *Bifrost) BatchDeleteRequest(ctx context.Context, req *schemas.BifrostBatchDeleteRequest) (*schemas.BifrostBatchDeleteResponse, *schemas.BifrostError) {
1265+
if req == nil {
1266+
return nil, &schemas.BifrostError{
1267+
IsBifrostError: false,
1268+
Error: &schemas.ErrorField{
1269+
Message: "batch delete request is nil",
1270+
},
1271+
}
1272+
}
1273+
if req.Provider == "" {
1274+
return nil, &schemas.BifrostError{
1275+
IsBifrostError: false,
1276+
Error: &schemas.ErrorField{
1277+
Message: "provider is required for batch delete request",
1278+
},
1279+
}
1280+
}
1281+
if req.BatchID == "" {
1282+
return nil, &schemas.BifrostError{
1283+
IsBifrostError: false,
1284+
Error: &schemas.ErrorField{
1285+
Message: "batch_id is required for batch delete request",
1286+
},
1287+
}
1288+
}
1289+
if ctx == nil {
1290+
ctx = bifrost.ctx
1291+
}
1292+
1293+
provider := bifrost.getProviderByKey(req.Provider)
1294+
if provider == nil {
1295+
return nil, &schemas.BifrostError{
1296+
IsBifrostError: false,
1297+
Error: &schemas.ErrorField{
1298+
Message: "provider not found for batch delete request",
1299+
},
1300+
}
1301+
}
1302+
1303+
config, err := bifrost.account.GetConfigForProvider(req.Provider)
1304+
if err != nil {
1305+
return nil, newBifrostErrorFromMsg(fmt.Sprintf("failed to get config for provider %s: %v", req.Provider, err.Error()))
1306+
}
1307+
if config == nil {
1308+
return nil, newBifrostErrorFromMsg(fmt.Sprintf("config is nil for provider %s", req.Provider))
1309+
}
1310+
1311+
// Determine the base provider type for key requirement checks
1312+
baseProvider := req.Provider
1313+
if config.CustomProviderConfig != nil && config.CustomProviderConfig.BaseProviderType != "" {
1314+
baseProvider = config.CustomProviderConfig.BaseProviderType
1315+
}
1316+
1317+
var key schemas.Key
1318+
if providerRequiresKey(baseProvider, config.CustomProviderConfig) {
1319+
keys, keyErr := bifrost.getAllSupportedKeys(&ctx, req.Provider, baseProvider)
1320+
if keyErr != nil {
1321+
return nil, newBifrostError(keyErr)
1322+
}
1323+
if len(keys) > 0 {
1324+
key = keys[0]
1325+
}
1326+
}
1327+
1328+
response, bifrostErr := executeRequestWithRetries(&ctx, config, func() (*schemas.BifrostBatchDeleteResponse, *schemas.BifrostError) {
1329+
return provider.BatchDelete(ctx, key, req)
1330+
}, schemas.BatchDeleteRequest, req.Provider, "")
1331+
if bifrostErr != nil {
1332+
bifrostErr.ExtraFields = schemas.BifrostErrorExtraFields{
1333+
RequestType: schemas.BatchDeleteRequest,
1334+
Provider: req.Provider,
1335+
}
1336+
return nil, bifrostErr
1337+
}
1338+
return response, nil
1339+
}
1340+
12631341
// FileUploadRequest uploads a file to the specified provider.
12641342
func (bifrost *Bifrost) FileUploadRequest(ctx context.Context, req *schemas.BifrostFileUploadRequest) (*schemas.BifrostFileUploadResponse, *schemas.BifrostError) {
12651343
if req == nil {

core/providers/anthropic/batch.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,3 +377,8 @@ func formatAnthropicTimestamp(unixTime int64) string {
377377
}
378378
return time.Unix(unixTime, 0).UTC().Format(time.RFC3339)
379379
}
380+
381+
// BatchDelete is not supported by Anthropic provider.
382+
func (provider *AnthropicProvider) BatchDelete(ctx context.Context, key schemas.Key, request *schemas.BifrostBatchDeleteRequest) (*schemas.BifrostBatchDeleteResponse, *schemas.BifrostError) {
383+
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchDeleteRequest, provider.GetProviderKey())
384+
}

core/providers/cerebras/cerebras.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,8 @@ func (provider *CerebrasProvider) BatchCancel(_ context.Context, _ schemas.Key,
260260
func (provider *CerebrasProvider) BatchResults(_ context.Context, _ schemas.Key, _ *schemas.BifrostBatchResultsRequest) (*schemas.BifrostBatchResultsResponse, *schemas.BifrostError) {
261261
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchResultsRequest, provider.GetProviderKey())
262262
}
263+
264+
// BatchDelete is not supported by Cerebras provider.
265+
func (provider *CerebrasProvider) BatchDelete(ctx context.Context, key schemas.Key, request *schemas.BifrostBatchDeleteRequest) (*schemas.BifrostBatchDeleteResponse, *schemas.BifrostError) {
266+
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchDeleteRequest, provider.GetProviderKey())
267+
}

core/providers/cohere/cohere.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,12 @@ func (provider *CohereProvider) BatchResults(_ context.Context, _ schemas.Key, _
866866
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchResultsRequest, provider.GetProviderKey())
867867
}
868868

869+
// BatchDelete is not supported by Cohere provider.
870+
func (provider *CohereProvider) BatchDelete(ctx context.Context, key schemas.Key, request *schemas.BifrostBatchDeleteRequest) (*schemas.BifrostBatchDeleteResponse, *schemas.BifrostError) {
871+
return nil, providerUtils.NewUnsupportedOperationError(schemas.BatchDeleteRequest, provider.GetProviderKey())
872+
}
873+
874+
869875
// FileUpload is not supported by Cohere provider.
870876
func (provider *CohereProvider) FileUpload(_ context.Context, _ schemas.Key, _ *schemas.BifrostFileUploadRequest) (*schemas.BifrostFileUploadResponse, *schemas.BifrostError) {
871877
return nil, providerUtils.NewUnsupportedOperationError(schemas.FileUploadRequest, provider.GetProviderKey())

core/providers/gemini/batch.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,146 @@ func extractGeminiUsageMetadata(geminiResponse *GenerateContentResponse) (int, i
240240
}
241241
return inputTokens, outputTokens, totalTokens
242242
}
243+
244+
// ==================== SDK RESPONSE CONVERTERS ====================
245+
// These functions convert Bifrost batch responses to Google GenAI SDK format.
246+
247+
// ToGeminiJobState converts Bifrost batch status to Gemini SDK job state.
248+
func ToGeminiJobState(status schemas.BatchStatus) string {
249+
switch status {
250+
case schemas.BatchStatusValidating:
251+
return GeminiJobStatePending
252+
case schemas.BatchStatusInProgress:
253+
return GeminiJobStateRunning
254+
case schemas.BatchStatusFinalizing:
255+
return GeminiJobStateRunning
256+
case schemas.BatchStatusCompleted:
257+
return GeminiJobStateSucceeded
258+
case schemas.BatchStatusFailed:
259+
return GeminiJobStateFailed
260+
case schemas.BatchStatusCancelling:
261+
return GeminiJobStateCancelling
262+
case schemas.BatchStatusCancelled:
263+
return GeminiJobStateCancelled
264+
case schemas.BatchStatusExpired:
265+
return GeminiJobStateFailed
266+
default:
267+
return GeminiJobStatePending
268+
}
269+
}
270+
271+
// ToGeminiBatchJobResponse converts a BifrostBatchCreateResponse to Gemini SDK format.
272+
func ToGeminiBatchJobResponse(resp *schemas.BifrostBatchCreateResponse) *GeminiBatchJobResponseSDK {
273+
if resp == nil {
274+
return nil
275+
}
276+
277+
result := &GeminiBatchJobResponseSDK{
278+
Name: resp.ID,
279+
State: ToGeminiJobState(resp.Status),
280+
}
281+
282+
// Add metadata if available
283+
if resp.CreatedAt > 0 {
284+
result.Metadata = &GeminiBatchMetadata{
285+
Name: resp.ID,
286+
State: ToGeminiJobState(resp.Status),
287+
CreateTime: time.Unix(resp.CreatedAt, 0).Format(time.RFC3339),
288+
BatchStats: &GeminiBatchStats{
289+
RequestCount: resp.RequestCounts.Total,
290+
PendingRequestCount: resp.RequestCounts.Total - resp.RequestCounts.Completed,
291+
SuccessfulRequestCount: resp.RequestCounts.Completed - resp.RequestCounts.Failed,
292+
},
293+
}
294+
}
295+
296+
return result
297+
}
298+
299+
// ToGeminiBatchRetrieveResponse converts a BifrostBatchRetrieveResponse to Gemini SDK format.
300+
func ToGeminiBatchRetrieveResponse(resp *schemas.BifrostBatchRetrieveResponse) *GeminiBatchJobResponseSDK {
301+
if resp == nil {
302+
return nil
303+
}
304+
305+
result := &GeminiBatchJobResponseSDK{
306+
Name: resp.ID,
307+
State: ToGeminiJobState(resp.Status),
308+
}
309+
310+
// Add metadata
311+
result.Metadata = &GeminiBatchMetadata{
312+
Name: resp.ID,
313+
State: ToGeminiJobState(resp.Status),
314+
CreateTime: time.Unix(resp.CreatedAt, 0).Format(time.RFC3339),
315+
BatchStats: &GeminiBatchStats{
316+
RequestCount: resp.RequestCounts.Total,
317+
PendingRequestCount: resp.RequestCounts.Total - resp.RequestCounts.Completed,
318+
SuccessfulRequestCount: resp.RequestCounts.Completed - resp.RequestCounts.Failed,
319+
},
320+
}
321+
322+
if resp.CompletedAt != nil {
323+
result.Metadata.EndTime = time.Unix(*resp.CompletedAt, 0).Format(time.RFC3339)
324+
}
325+
326+
// Add output file info if available
327+
if resp.OutputFileID != nil {
328+
result.Dest = &GeminiBatchDest{
329+
FileName: *resp.OutputFileID,
330+
}
331+
}
332+
333+
return result
334+
}
335+
336+
// ToGeminiBatchListResponse converts a BifrostBatchListResponse to Gemini SDK format.
337+
func ToGeminiBatchListResponse(resp *schemas.BifrostBatchListResponse) *GeminiBatchListResponseSDK {
338+
if resp == nil {
339+
return nil
340+
}
341+
342+
jobs := make([]GeminiBatchJobResponseSDK, 0, len(resp.Data))
343+
for _, batch := range resp.Data {
344+
job := GeminiBatchJobResponseSDK{
345+
Name: batch.ID,
346+
State: ToGeminiJobState(batch.Status),
347+
}
348+
349+
// Add metadata
350+
job.Metadata = &GeminiBatchMetadata{
351+
Name: batch.ID,
352+
State: ToGeminiJobState(batch.Status),
353+
CreateTime: time.Unix(batch.CreatedAt, 0).Format(time.RFC3339),
354+
BatchStats: &GeminiBatchStats{
355+
RequestCount: batch.RequestCounts.Total,
356+
PendingRequestCount: batch.RequestCounts.Total - batch.RequestCounts.Completed,
357+
SuccessfulRequestCount: batch.RequestCounts.Completed - batch.RequestCounts.Failed,
358+
},
359+
}
360+
361+
jobs = append(jobs, job)
362+
}
363+
364+
result := &GeminiBatchListResponseSDK{
365+
BatchJobs: jobs,
366+
}
367+
368+
if resp.NextCursor != nil {
369+
result.NextPageToken = *resp.NextCursor
370+
}
371+
372+
return result
373+
}
374+
375+
// ToGeminiBatchCancelResponse converts a BifrostBatchCancelResponse to Gemini SDK format.
376+
func ToGeminiBatchCancelResponse(resp *schemas.BifrostBatchCancelResponse) *GeminiBatchJobResponseSDK {
377+
if resp == nil {
378+
return nil
379+
}
380+
381+
return &GeminiBatchJobResponseSDK{
382+
Name: resp.ID,
383+
State: ToGeminiJobState(resp.Status),
384+
}
385+
}

core/providers/gemini/gemini.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,6 +2101,65 @@ func (provider *GeminiProvider) BatchResults(ctx context.Context, key schemas.Ke
21012101
return batchResultsResp, nil
21022102
}
21032103

2104+
// BatchDelete deletes a batch job for Gemini.
2105+
func (provider *GeminiProvider) BatchDelete(ctx context.Context, key schemas.Key, request *schemas.BifrostBatchDeleteRequest) (*schemas.BifrostBatchDeleteResponse, *schemas.BifrostError) {
2106+
if err := providerUtils.CheckOperationAllowed(schemas.Gemini, provider.customProviderConfig, schemas.BatchDeleteRequest); err != nil {
2107+
return nil, err
2108+
}
2109+
2110+
providerName := provider.GetProviderKey()
2111+
2112+
if request.BatchID == "" {
2113+
return nil, providerUtils.NewBifrostOperationError("batch_id is required", nil, providerName)
2114+
}
2115+
2116+
// Create HTTP request
2117+
req := fasthttp.AcquireRequest()
2118+
resp := fasthttp.AcquireResponse()
2119+
defer fasthttp.ReleaseRequest(req)
2120+
defer fasthttp.ReleaseResponse(resp)
2121+
2122+
// Build URL for delete operation
2123+
batchID := request.BatchID
2124+
var url string
2125+
if strings.HasPrefix(batchID, "batches/") {
2126+
url = fmt.Sprintf("%s/%s", provider.networkConfig.BaseURL, batchID)
2127+
} else {
2128+
url = fmt.Sprintf("%s/batches/%s", provider.networkConfig.BaseURL, batchID)
2129+
}
2130+
2131+
provider.logger.Debug("gemini batch delete url: " + url)
2132+
providerUtils.SetExtraHeaders(ctx, req, provider.networkConfig.ExtraHeaders, nil)
2133+
req.SetRequestURI(url)
2134+
req.Header.SetMethod(http.MethodDelete)
2135+
if key.Value != "" {
2136+
req.Header.Set("x-goog-api-key", key.Value)
2137+
}
2138+
req.Header.SetContentType("application/json")
2139+
2140+
// Make request
2141+
latency, bifrostErr := providerUtils.MakeRequestWithContext(ctx, provider.client, req, resp)
2142+
if bifrostErr != nil {
2143+
return nil, bifrostErr
2144+
}
2145+
2146+
// Handle response
2147+
if resp.StatusCode() != fasthttp.StatusOK && resp.StatusCode() != fasthttp.StatusNoContent {
2148+
return nil, parseGeminiError(resp)
2149+
}
2150+
2151+
return &schemas.BifrostBatchDeleteResponse{
2152+
ID: request.BatchID,
2153+
Object: "batch",
2154+
Deleted: true,
2155+
ExtraFields: schemas.BifrostResponseExtraFields{
2156+
RequestType: schemas.BatchDeleteRequest,
2157+
Provider: providerName,
2158+
Latency: latency.Milliseconds(),
2159+
},
2160+
}, nil
2161+
}
2162+
21042163
// FileUpload uploads a file to Gemini.
21052164
func (provider *GeminiProvider) FileUpload(ctx context.Context, key schemas.Key, request *schemas.BifrostFileUploadRequest) (*schemas.BifrostFileUploadResponse, *schemas.BifrostError) {
21062165
if err := providerUtils.CheckOperationAllowed(schemas.Gemini, provider.customProviderConfig, schemas.FileUploadRequest); err != nil {

0 commit comments

Comments
 (0)