Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/maximhq/bifrost/core/providers/elevenlabs"
"github.com/maximhq/bifrost/core/providers/fireworks"
"github.com/maximhq/bifrost/core/providers/gemini"
"github.com/maximhq/bifrost/core/providers/gigachat"
"github.com/maximhq/bifrost/core/providers/groq"
"github.com/maximhq/bifrost/core/providers/huggingface"
"github.com/maximhq/bifrost/core/providers/mistral"
Expand Down Expand Up @@ -4499,6 +4500,8 @@ func (bifrost *Bifrost) createBaseProvider(providerKey schemas.ModelProvider, co
return wafer.NewWaferProvider(config, bifrost.logger)
case schemas.Gemini:
return gemini.NewGeminiProvider(config, bifrost.logger), nil
case schemas.GigaChat:
return gigachat.NewGigaChatProvider(config, bifrost.logger)
case schemas.OpenRouter:
return openrouter.NewOpenRouterProvider(config, bifrost.logger), nil
case schemas.Elevenlabs:
Expand Down
20 changes: 20 additions & 0 deletions core/bifrost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,26 @@ func TestHandleProviderRequest_OCROperationNotAllowed(t *testing.T) {
}
}

func TestCreateBaseProvider_GigaChatCustomProvider(t *testing.T) {
const customProvider = schemas.ModelProvider("custom-gigachat")
config := &schemas.ProviderConfig{
CustomProviderConfig: &schemas.CustomProviderConfig{
BaseProviderType: schemas.GigaChat,
},
}

provider, err := (&Bifrost{}).createBaseProvider(customProvider, config)
if err != nil {
t.Fatalf("expected GigaChat custom-provider base to be constructed: %v", err)
}
if got := provider.GetProviderKey(); got != customProvider {
t.Fatalf("provider key mismatch: got %q, want %q", got, customProvider)
}
if got := config.CustomProviderConfig.CustomProviderKey; got != string(customProvider) {
t.Fatalf("custom provider key mismatch: got %q, want %q", got, customProvider)
}
}

// Test that transientServerStatusCodes are properly defined.
// These are upstream-side failures unrelated to the credential — the same key is retried.
func TestTransientServerStatusCodes(t *testing.T) {
Expand Down
6 changes: 6 additions & 0 deletions core/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
- fix: apply GigaChat file-list limit and cursor pagination locally [@krakenalt](https://github.com/krakenalt)
- fix: guard GigaChat batch output downloads against an empty key set [@krakenalt](https://github.com/krakenalt)
- fix: keep GigaChat batch pagination provider-local without widening shared batch response schemas [@krakenalt](https://github.com/krakenalt)
- fix: finalize GigaChat Chat Completions and Responses streams across normal and large-response passthrough paths [@krakenalt](https://github.com/krakenalt)
- fix: cache GigaChat TLS clients without hot-path certificate file reads [@krakenalt](https://github.com/krakenalt)
- fix: tighten GigaChat attachment retries, auth cache cleanup, file-list validation, structured output handling, and batch key configuration [@krakenalt](https://github.com/krakenalt)
- feat: support Gemini's server-side `toolCall`/`toolResponse` parts with `thoughtSignature` round-trip fidelity - server-side search rounds now surface as `web_search_call` items carrying their own call ID and queries, unmapped tool types are preserved on the native round-trip instead of being dropped, and each `thoughtSignature` appears exactly once across the reconstructed parts so Gemini accepts the replayed turn
- feat: async 3D generation on Runware via `/videos` plus a raw `/runware_passthrough` route - `taskType` is now read from extra_params so any Runware async task can be driven through `/videos` (the 16:9 1080p width/height defaults now apply only to `videoInference`), `outputs.files[].url` is surfaced as `VideoOutput` URLs with the content type derived from the file extension, and the passthrough route forwards raw task arrays for capabilities with no first-class Bifrost surface such as upscaling and background removal
- feat: surface Runware's provider-reported per-task `cost` across image, video/3D and passthrough so pricing uses the exact figure verbatim instead of a datasheet estimate - this matters for task types like 3D that have no datasheet rate; when no cost is reported the behavior is unchanged
Expand Down
110 changes: 110 additions & 0 deletions core/internal/llmtests/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func (account *ComprehensiveTestAccount) GetConfiguredProviders() ([]schemas.Mod
schemas.Cerebras,
schemas.DeepSeek,
schemas.Gemini,
schemas.GigaChat,
schemas.OpenRouter,
schemas.HuggingFace,
schemas.Nebius,
Expand Down Expand Up @@ -220,6 +221,37 @@ func replicateProviderTestKeys() []schemas.Key {
}
}

func gigaChatProviderTestKey() schemas.Key {
keyConfig := &schemas.GigaChatKeyConfig{
Scope: getEnvWithDefault("GIGACHAT_SCOPE", schemas.DefaultGigaChatScope),
AuthURL: os.Getenv("GIGACHAT_AUTH_URL"),
BaseURL: os.Getenv("GIGACHAT_BASE_URL"),
CABundleFile: os.Getenv("GIGACHAT_CA_BUNDLE_FILE"),
}
if certFile, keyFile := os.Getenv("GIGACHAT_CERT_FILE"), os.Getenv("GIGACHAT_KEY_FILE"); certFile != "" && keyFile != "" {
keyConfig.CertFile = certFile
keyConfig.KeyFile = keyFile
}

switch {
case os.Getenv("GIGACHAT_ACCESS_TOKEN") != "":
keyConfig.AccessToken = schemas.NewSecretVar("env.GIGACHAT_ACCESS_TOKEN")
case os.Getenv("GIGACHAT_USER") != "" && os.Getenv("GIGACHAT_PASSWORD") != "" && os.Getenv("GIGACHAT_BASE_URL") != "":
keyConfig.User = schemas.NewSecretVar("env.GIGACHAT_USER")
keyConfig.Password = schemas.NewSecretVar("env.GIGACHAT_PASSWORD")
default:
keyConfig.Credentials = schemas.NewSecretVar("env.GIGACHAT_CREDENTIALS")
}

return schemas.Key{
Name: "gigachat-inference",
Models: []string{"*"},
Weight: 1.0,
UseForBatchAPI: bifrost.Ptr(true),
GigaChatKeyConfig: keyConfig,
}
}

// GetKeysForProvider returns the API keys and associated models for a given provider.
func (account *ComprehensiveTestAccount) GetKeysForProvider(ctx context.Context, providerKey schemas.ModelProvider) ([]schemas.Key, error) {
switch providerKey {
Expand Down Expand Up @@ -503,6 +535,10 @@ func (account *ComprehensiveTestAccount) GetKeysForProvider(ctx context.Context,
UseForBatchAPI: bifrost.Ptr(true),
},
}, nil
case schemas.GigaChat:
return []schemas.Key{
gigaChatProviderTestKey(),
}, nil
case schemas.OpenRouter:
return []schemas.Key{
{
Expand Down Expand Up @@ -912,6 +948,20 @@ func (account *ComprehensiveTestAccount) GetConfigForProvider(providerKey schema
BufferSize: 20,
},
}, nil
case schemas.GigaChat:
return &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: os.Getenv("GIGACHAT_BASE_URL"),
DefaultRequestTimeoutInSeconds: 120,
MaxRetries: 10,
RetryBackoffInitial: 1 * time.Second,
RetryBackoffMax: 20 * time.Second,
},
ConcurrencyAndBufferSize: schemas.ConcurrencyAndBufferSize{
Concurrency: Concurrency,
BufferSize: 10,
},
}, nil
case schemas.OpenRouter:
return &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
Expand Down Expand Up @@ -1021,6 +1071,65 @@ func (account *ComprehensiveTestAccount) GetConfigForProvider(providerKey schema
}
}

// GigaChatComprehensiveTestConfig returns the provider checklist scenarios for GigaChat.
func GigaChatComprehensiveTestConfig() ComprehensiveTestConfig {
return ComprehensiveTestConfig{
Provider: schemas.GigaChat,
ChatModel: getEnvWithDefault("GIGACHAT_CHAT_MODEL", "GigaChat-2"),
TextModel: "",
EmbeddingModel: getEnvWithDefault("GIGACHAT_EMBEDDING_MODEL", "Embeddings"),
Scenarios: TestScenarios{
TextCompletion: false,
TextCompletionStream: false,
SimpleChat: true,
CompletionStream: true,
MultiTurnConversation: true,
ToolCalls: true,
ToolCallsStreaming: true,
MultipleToolCalls: false,
MultipleToolCallsStreaming: false,
End2EndToolCalling: true,
AutomaticFunctionCall: true,
ImageURL: false,
ImageBase64: false,
MultipleImages: false,
FileBase64: false,
FileURL: false,
CompleteEnd2End: true,
SpeechSynthesis: false,
SpeechSynthesisStream: false,
Transcription: false,
TranscriptionStream: false,
Embedding: true,
Reasoning: false, // Partial passthrough only; the generic suite sends unsupported Responses reasoning fields.
ListModels: true,
ImageGeneration: false,
ImageGenerationStream: false,
ImageEdit: false,
ImageEditStream: false,
ImageVariation: false,
ImageVariationStream: false,
BatchCreate: true,
BatchList: true,
BatchRetrieve: true,
BatchCancel: false,
BatchResults: true,
FileUpload: true,
FileList: true,
FileRetrieve: true,
FileDelete: true,
FileContent: true,
FileBatchInput: true,
CountTokens: true,
StructuredOutputs: true,
WebSearchTool: false,
PassthroughAPI: false,
WebSocketResponses: false,
Realtime: false,
},
}
}

// AllProviderConfigs contains test configurations for all providers
var AllProviderConfigs = []ComprehensiveTestConfig{
{
Expand Down Expand Up @@ -1542,6 +1651,7 @@ var AllProviderConfigs = []ComprehensiveTestConfig{
{Provider: schemas.OpenAI, Model: "gpt-4o-mini"},
},
},
GigaChatComprehensiveTestConfig(),
{
Provider: schemas.OpenRouter,
ChatModel: "openai/gpt-4o",
Expand Down
Loading