Skip to content
Merged
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
7 changes: 2 additions & 5 deletions config/livereview.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ url = "https://gitea.example.com"
[ai.langchain]
# Model name (varies by provider)
# Ollama: "llama3.2", "codellama", "mistral", etc.
# Google: "gemini-2.0-flash", "gemini-2.5-pro", etc.
Comment thread
Amazing-Stardom marked this conversation as resolved.
# Google: "gemini-3.8-flash", "gemini-3.5-pro", etc.
# OpenAI: "gpt-4", "gpt-3.5-turbo", etc.
model = "gemini-2.0-flash"
Comment thread
Amazing-Stardom marked this conversation as resolved.
model = "gemini-3.8-flash"

# Provider type: "ollama", "google", "openai", "anthropic"
# provider_type = "ollama"
Expand All @@ -55,9 +55,6 @@ model = "gemini-2.0-flash"
# Default: http://localhost:11434
# base_url = "http://localhost:11434"

# Temperature for generation (0.0 = deterministic, 1.0 = creative)
Comment thread
Amazing-Stardom marked this conversation as resolved.
temperature = 0.2

# Maximum tokens per batch for processing
max_tokens_per_batch = 8000

Expand Down
36 changes: 36 additions & 0 deletions docs/gemini_3.8_migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Gemini 3.8 Flash Migration & Parameter Deprecation Fixes

**Date**: October 10, 2026

## Overview
Google AI Studio deprecated the `gemini-3.5-flash` model, automatically routing to `3.6-flash`, while announcing the general availability of `gemini-3.8-flash`. Furthermore, the Gemini 3.x models officially deprecated all sampling parameters (`temperature`, `top_p`, `top_k`) and the `thinking_budget` variable. Sending these parameters now strictly returns a `400 INVALID_ARGUMENT` error.

This document serves as the permanent record of the codebase updates applied to ensure LiveReview remains 100% compliant with the Gemini 3.8 API rules without crashing.

## Changes Implemented

### 1. `langchaingo` Integration Sanitization
**File**: `internal/ai/langchain/provider.go`
- **Issue**: The underlying `github.com/tmc/langchaingo/llms/googleai` SDK sets a default temperature of `0.5`, and LiveReview was actively passing an `effectiveTemperature` payload downstream. Additionally, a linter flagged a long string of boolean `OR` comparisons for provider checking as a maintainability risk.
- **Fix**: Created a localized `buildCallOptions()` helper function mimicking the behavior of `internal/aiconnectors/connector.go`. This dynamically intercepts calls to `GenerateFromSinglePrompt` and `AggregateAndCombineOutputs`. If the active provider is `gemini`, `googleai`, `gemini-enterprise`, or `vertex` (now cleanly verified using a `switch` statement), it entirely skips attaching the `llms.WithTemperature()` option to the request, protecting the payload.

### 2. PreLivi Python Script Fix
**File**: `scripts/prelivi/interpretation.py`
- **Issue**: The direct REST call to `generativelanguage.googleapis.com` had a hardcoded `"temperature": 0.2` injected into the `generationConfig` block.
- **Fix**: Deleted the `temperature` key entirely. The request now solely depends on `maxOutputTokens`.

### 3. Native Gemini Connector
**File**: `internal/ai/gemini/gemini.go`
- **Issue**: Historical references to `temperature` still existed in the struct logging (`p.Temperature`) and initialization fields, which caused build compilation errors when purged.
- **Fix**: Dropped the temperature assignment and `fmt.Fprintf(f, "Temperature: %f\n", p.Temperature)` logging. The `internal/ai/gemini` package compiles and runs without expecting sampling configs.

### 4. Configuration Hygiene
**Files**: `config/livereview.toml.example`, `internal/config/config.go`
- **Issue**: Example TOML configs and the automatic config generator still pointed to `gemini-2.0-flash` and generated sample configurations with `temperature = 0.2`.
- **Fix**: Refactored the sample generator and TOML files to use `gemini-3.8-flash` as the standard, actively scrubbing out the temperature variables so new deployments don't try to submit invalid configs.

### 5. Cleanup
- `internal/ai/gemini/gemini.go.bak` and `internal/ai/gemini/.gemini.go.broken` were removed as they were dead-tracked code still reflecting the old temperature logic.

## Note on Database Migrations
We deliberately opted **not** to force a database-level SQL migration over the `ai_connectors` table for existing users configured to `3.5-flash` or `3.6-flash`. Because Google handles routing on their backend seamlessly, active user settings have been preserved as-is.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ require (
github.com/slack-go/slack v0.27.0
github.com/stephenafamo/goldmark-pdf v0.4.2
github.com/stretchr/testify v1.12.1
github.com/tmc/langchaingo v0.1.14
Comment thread
Amazing-Stardom marked this conversation as resolved.
github.com/tmc/langchaingo v0.1.15
github.com/wasilibs/go-pgquery v0.0.0-20260728010200-155ebad2880e
github.com/xuri/excelize/v2 v2.11.1-0.20261009041834-958cda5363be
github.com/yalue/onnxruntime_go v1.32.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -571,8 +571,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
github.com/tmc/langchaingo v0.1.14 h1:o1qWBPigAIuFvrG6cjTFo0cZPFEZ47ZqpOYMjM15yZc=
github.com/tmc/langchaingo v0.1.14/go.mod h1:aKKYXYoqhIDEv7WKdpnnCLRaqXic69cX9MnDUk72378=
github.com/tmc/langchaingo v0.1.15 h1:zbRfW3RiX8SSX9ck0P1y3Q7FV6VRwusRSKFykaZOh/4=
github.com/tmc/langchaingo v0.1.15/go.mod h1:aKKYXYoqhIDEv7WKdpnnCLRaqXic69cX9MnDUk72378=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
Expand Down
22 changes: 2 additions & 20 deletions internal/ai/gemini/gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
)

// DefaultModel is the Gemini model used when no model is configured.
const DefaultModel = "gemini-3.6-flash"
Comment thread
Amazing-Stardom marked this conversation as resolved.
const DefaultModel = "gemini-3.8-flash"

type GeminiProvider struct {
TestableFields
Expand All @@ -32,15 +32,13 @@ type GeminiProvider struct {
type GeminiConfig struct {
APIKey string `koanf:"api_key"`
Model string `koanf:"model"`
Temperature float64 `koanf:"temperature"`
Comment thread
Amazing-Stardom marked this conversation as resolved.
MaxTokensPerBatch int `koanf:"max_tokens_per_batch"`
}

// TestableFields exposes fields for testing
type TestableFields struct {
APIKey string
Model string
Temperature float64
MaxTokensPerBatch int
Comment thread
Amazing-Stardom marked this conversation as resolved.
HTTPClient *http.Client
}
Expand All @@ -58,10 +56,6 @@ func New(config GeminiConfig) (*GeminiProvider, error) {
config.Model = DefaultModel
}

if config.Temperature == 0 {
config.Temperature = 0.4 // Increased from 0.2 to encourage more thorough analysis
}

if config.MaxTokensPerBatch == 0 {
Comment thread
Amazing-Stardom marked this conversation as resolved.
config.MaxTokensPerBatch = 10000
}
Expand All @@ -72,7 +66,6 @@ func New(config GeminiConfig) (*GeminiProvider, error) {
TestableFields: TestableFields{
APIKey: config.APIKey,
Model: config.Model,
Temperature: config.Temperature,
Comment thread
Amazing-Stardom marked this conversation as resolved.
MaxTokensPerBatch: config.MaxTokensPerBatch,
HTTPClient: &http.Client{},
},
Expand Down Expand Up @@ -106,10 +99,7 @@ type safetySettings struct {
}

type generationConfig struct {
Temperature float64 `json:"temperature"`
MaxOutputTokens int `json:"maxOutputTokens"`
TopK int `json:"topK"`
TopP float64 `json:"topP"`
MaxOutputTokens int `json:"maxOutputTokens"`
Comment thread
Amazing-Stardom marked this conversation as resolved.
}

// reviewResponse represents a response from the Gemini API
Expand Down Expand Up @@ -201,10 +191,7 @@ func (p *GeminiProvider) callGeminiAPI(ctx context.Context, prompt string) (stri
},
},
GenerationConfig: generationConfig{
Temperature: p.Temperature,
MaxOutputTokens: 8192, // Increased from 4096 to allow for more detailed reviews
Comment thread
Amazing-Stardom marked this conversation as resolved.
TopK: 40,
TopP: 0.95,
},
}

Expand Down Expand Up @@ -1439,10 +1426,6 @@ func (p *GeminiProvider) Configure(config map[string]interface{}) error {
p.TestableFields.Model = model
}

if temp, ok := config["temperature"].(float64); ok && temp > 0 {
p.TestableFields.Temperature = temp
}

if maxTokens, ok := config["max_tokens_per_batch"].(int); ok && maxTokens > 0 {
Comment thread
Amazing-Stardom marked this conversation as resolved.
p.TestableFields.MaxTokensPerBatch = maxTokens
} else if maxTokens, ok := config["max_tokens_per_batch"].(float64); ok && maxTokens > 0 {
Expand Down Expand Up @@ -1483,7 +1466,6 @@ func (p *GeminiProvider) logRequestAndResponse(prompt string, reqJSON []byte, re
fmt.Fprintf(f, "=== GEMINI API REQUEST LOG ===\n")
fmt.Fprintf(f, "Timestamp: %s\n", fmt.Sprintf("%d", os.Getpid()))
fmt.Fprintf(f, "Model: %s\n", p.Model)
fmt.Fprintf(f, "Temperature: %f\n", p.Temperature)
Comment thread
Amazing-Stardom marked this conversation as resolved.
fmt.Fprintf(f, "\n--- PROMPT ---\n")
fmt.Fprintf(f, "%s\n", prompt)
fmt.Fprintf(f, "\n--- REQUEST JSON ---\n")
Expand Down
26 changes: 21 additions & 5 deletions internal/ai/langchain/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,23 @@ func (p *LangchainProvider) effectiveTemperature() float64 {
return normalizeTemperature(p.temperature, p.temperatureSet)
}

// buildCallOptions creates a slice of CallOption with the provider's default model configuration.
Comment thread
Amazing-Stardom marked this conversation as resolved.
func (p *LangchainProvider) buildCallOptions(extra ...llms.CallOption) []llms.CallOption {
var opts []llms.CallOption

isGemini := false
switch strings.ToLower(p.providerType) {
Comment thread
Amazing-Stardom marked this conversation as resolved.
case "gemini", "googleai", "gemini-enterprise", "vertex":
isGemini = true
}

if !isGemini {
Comment thread
Amazing-Stardom marked this conversation as resolved.
opts = append(opts, llms.WithTemperature(p.effectiveTemperature()))
}
opts = append(opts, extra...)
return opts
}

func isOpenAISeriesModel(model string) bool {
model = strings.TrimSpace(strings.ToLower(model))
if len(model) < 2 || model[0] != 'o' {
Expand Down Expand Up @@ -1151,7 +1168,7 @@ func (p *LangchainProvider) reviewCodeBatchFormatted(ctx context.Context, diffs
timeoutCtx,
p.llm,
prompt,
llms.WithTemperature(effectiveTemp),
p.buildCallOptions()...,
Comment thread
Amazing-Stardom marked this conversation as resolved.
)
if callErr == nil {
responseBuilder.WriteString(out)
Expand Down Expand Up @@ -1208,8 +1225,7 @@ func (p *LangchainProvider) reviewCodeBatchFormatted(ctx context.Context, diffs
timeoutCtx,
p.llm,
prompt,
llms.WithTemperature(effectiveTemp),
llms.WithStreamingFunc(streamingFunc),
p.buildCallOptions(llms.WithStreamingFunc(streamingFunc))...,
Comment thread
Amazing-Stardom marked this conversation as resolved.
)
fmt.Printf("[STREAM DEBUG] GenerateFromSinglePrompt returned, err=%v\n", err)
}
Expand Down Expand Up @@ -1270,7 +1286,7 @@ func (p *LangchainProvider) reviewCodeBatchFormatted(ctx context.Context, diffs
fbCtx,
p.llm,
prompt,
llms.WithTemperature(effectiveTemp),
p.buildCallOptions()...,
Comment thread
Amazing-Stardom marked this conversation as resolved.
)
close(waitingDone)

Expand Down Expand Up @@ -1471,7 +1487,7 @@ func (p *LangchainProvider) ReviewCodeWithBatching(ctx context.Context, diffs []
ctx,
p.llm,
successfulBatches,
llms.WithTemperature(p.effectiveTemperature()),
p.buildCallOptions()...,
Comment thread
Amazing-Stardom marked this conversation as resolved.
)
if err != nil {
return nil, fmt.Errorf("failed to aggregate results from %d successful batches: %w", len(successfulBatches), err)
Expand Down
15 changes: 11 additions & 4 deletions internal/aiconnectors/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,19 +596,26 @@ func (t *atlasLoggingTransport) RoundTrip(req *http.Request) (*http.Response, er
// buildCallOptions creates a slice of llms.CallOption with the connector's
// default model configuration, appending any extra options.
func (c *Connector) buildCallOptions(options ...llms.CallOption) []llms.CallOption {
callOptions := []llms.CallOption{
Comment thread
Amazing-Stardom marked this conversation as resolved.
llms.WithTemperature(c.options.ModelConfig.Temperature),
var callOptions []llms.CallOption

// Google AI Studio deprecated sampling parameters for Gemini 3.x models.
Comment thread
Amazing-Stardom marked this conversation as resolved.
// Sending them will soon return a 400 INVALID_ARGUMENT error.
isGemini := c.provider == ProviderGemini || c.provider == ProviderGeminiEnterprise
Comment thread
Amazing-Stardom marked this conversation as resolved.

if !isGemini {
Comment thread
Amazing-Stardom marked this conversation as resolved.
callOptions = append(callOptions, llms.WithTemperature(c.options.ModelConfig.Temperature))
}

if c.options.ModelConfig.Model != "" {
callOptions = append(callOptions, llms.WithModel(c.options.ModelConfig.Model))
}
if c.options.ModelConfig.MaxTokens > 0 {
callOptions = append(callOptions, llms.WithMaxTokens(c.options.ModelConfig.MaxTokens))
}
if c.options.ModelConfig.TopP > 0 {
if c.options.ModelConfig.TopP > 0 && !isGemini {
Comment thread
Amazing-Stardom marked this conversation as resolved.
callOptions = append(callOptions, llms.WithTopP(c.options.ModelConfig.TopP))
}
if c.options.ModelConfig.TopK > 0 {
if c.options.ModelConfig.TopK > 0 && !isGemini {
Comment thread
Amazing-Stardom marked this conversation as resolved.
callOptions = append(callOptions, llms.WithTopK(int(c.options.ModelConfig.TopK)))
}
callOptions = append(callOptions, options...)
Expand Down
2 changes: 1 addition & 1 deletion internal/aiconnectors/models_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ var defaultProviderModels = map[string]string{
"gemini": gemini.DefaultModel,
"deepseek": "deepseek-v4-flash",
"cohere": "command-a",
"openrouter": "deepseek/deepseek-v4-flash",
Comment thread
Amazing-Stardom marked this conversation as resolved.
"openrouter": "deepseek/deepseek-pro-v4",
"atlas": "deepseek-ai/deepseek-v4-flash",
}

Expand Down
1 change: 0 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ token = "your-gitlab-token"
[ai.gemini]
api_key = "your-gemini-api-key"
model = "%s"
temperature = 0.2
Comment thread
Amazing-Stardom marked this conversation as resolved.
`, gemini.DefaultModel)

return os.WriteFile(configPath, []byte(sampleConfig), 0644)
Expand Down
1 change: 0 additions & 1 deletion scripts/prelivi/interpretation.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ def call_gemini(api_keys: list[str], user_message: str) -> tuple[dict, dict]:
"system_instruction": {"parts": [{"text": SYSTEM_PROMPT}]},
"contents": [{"role": "user", "parts": [{"text": user_message}]}],
"generationConfig": {
"temperature": 0.2,
Comment thread
Amazing-Stardom marked this conversation as resolved.
"maxOutputTokens": 8192,
"responseMimeType": "application/json"
}
Expand Down
8 changes: 5 additions & 3 deletions ui/src/pages/AIProviders/AIProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,17 @@ const providerUIMetadata: AIProvider[] = [
url: 'https://ai.google.dev/',
description: 'High quality, multimodal reasoning with balanced cost and performance.',
icon: <Icons.Google />,
apiKeyPlaceholder: 'gemini-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
Comment thread
Amazing-Stardom marked this conversation as resolved.
apiKeyPlaceholder: 'gemini-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
defaultModel: 'gemini-3.8-flash'
},
{
id: 'gemini-enterprise',
name: 'Gemini Enterprise',
url: 'https://cloud.google.com/vertex-ai',
description: 'Enterprise-grade LLM services via GCP Vertex AI with IAM authentication.',
icon: <Icons.Google />,
apiKeyPlaceholder: 'Paste Service Account JSON content here'
apiKeyPlaceholder: 'Paste Service Account JSON content here',
Comment thread
Amazing-Stardom marked this conversation as resolved.
defaultModel: 'gemini-3.8-flash'
},
{
id: 'deepseek',
Expand All @@ -71,7 +73,7 @@ const providerUIMetadata: AIProvider[] = [
id: 'openrouter',
name: 'OpenRouter',
url: 'https://openrouter.ai/',
description: 'Bring your own key and choose any OpenRouter model. Defaults to the free DeepSeek route.',
description: 'Bring your own key and choose any OpenRouter model.',
Comment thread
Amazing-Stardom marked this conversation as resolved.
icon: <Icons.OpenRouter />,
apiKeyPlaceholder: 'sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
baseURLPlaceholder: 'https://openrouter.ai/api/v1'
Expand Down
4 changes: 3 additions & 1 deletion ui/src/pages/AIProviders/components/ConnectorForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ const ConnectorForm: React.FC<ConnectorFormProps> = ({
const usesCustomModel = !!currentModelValue && !providerModels.includes(currentModelValue);
const shouldShowCustomModelInput = customModelMode || usesCustomModel;

// Models come straight from the provider API catalog (dynamic), filtered only by
Comment thread
Amazing-Stardom marked this conversation as resolved.
// the search box. No client-side pruning: whatever the backend returns is shown.
const filteredModels = React.useMemo(() => {
if (!searchQuery) return providerModels;
return providerModels.filter((model: string) =>
Expand Down Expand Up @@ -522,7 +524,7 @@ const ConnectorForm: React.FC<ConnectorFormProps> = ({
/>
)}
<p className="text-xs text-slate-400">
{providerDetails.id === 'openrouter' ? 'OpenRouter model ID (defaults to free DeepSeek route)' : providerDetails.id === 'atlas' ? 'Atlas Cloud model ID (defaults to DeepSeek-V3)' : 'Select a model or enter a custom model ID'}
{providerDetails.id === 'openrouter' ? 'OpenRouter model ID' : providerDetails.id === 'atlas' ? 'Atlas Cloud model ID (defaults to DeepSeek-V3)' : 'Select a model or enter a custom model ID'}
Comment thread
Amazing-Stardom marked this conversation as resolved.
</p>
</div>
)}
Expand Down
Loading