fix: make docs i18n use gpt-5.4 overrides

This commit is contained in:
Peter Steinberger 2026-03-16 07:02:44 +00:00
parent 3c6a49b27e
commit edab939f4d
6 changed files with 140 additions and 17 deletions

View File

@ -73,8 +73,8 @@ func startDocsPiClient(ctx context.Context, options docsPiClientOptions) (*docsP
args := append([]string{}, command.Args...)
args = append(args,
"--mode", "rpc",
"--provider", "anthropic",
"--model", modelVersion,
"--provider", docsPiProvider(),
"--model", docsPiModel(),
"--thinking", options.Thinking,
"--no-session",
)

View File

@ -64,8 +64,8 @@ func processFile(ctx context.Context, translator *PiTranslator, tm *TranslationM
TextHash: seg.TextHash,
Text: seg.Text,
Translated: translated,
Provider: providerName,
Model: modelVersion,
Provider: docsPiProvider(),
Model: docsPiModel(),
SrcLang: srcLang,
TgtLang: tgtLang,
UpdatedAt: time.Now().UTC().Format(time.RFC3339),
@ -121,8 +121,8 @@ func encodeFrontMatter(frontData map[string]any, relPath string, source []byte)
frontData["x-i18n"] = map[string]any{
"source_path": relPath,
"source_hash": hashBytes(source),
"provider": providerName,
"model": modelVersion,
"provider": docsPiProvider(),
"model": docsPiModel(),
"workflow": workflowVersion,
"generated_at": time.Now().UTC().Format(time.RFC3339),
}
@ -191,8 +191,8 @@ func translateSnippet(ctx context.Context, translator *PiTranslator, tm *Transla
TextHash: textHash,
Text: textValue,
Translated: translated,
Provider: providerName,
Model: modelVersion,
Provider: docsPiProvider(),
Model: docsPiModel(),
SrcLang: srcLang,
TgtLang: tgtLang,
UpdatedAt: time.Now().UTC().Format(time.RFC3339),

View File

@ -4,14 +4,16 @@ import (
"context"
"errors"
"fmt"
"os"
"strings"
"time"
)
const (
translateMaxAttempts = 3
translateBaseDelay = 15 * time.Second
translatePromptTimeout = 2 * time.Minute
translateMaxAttempts = 3
translateBaseDelay = 15 * time.Second
defaultPromptTimeout = 2 * time.Minute
envDocsI18nPromptTimeout = "OPENCLAW_DOCS_I18N_PROMPT_TIMEOUT"
)
var errEmptyTranslation = errors.New("empty translation")
@ -112,10 +114,16 @@ func isRetryableTranslateError(err error) bool {
if err == nil {
return false
}
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
return false
}
if errors.Is(err, errEmptyTranslation) {
return true
}
message := strings.ToLower(err.Error())
if strings.Contains(message, "authentication failed") {
return false
}
return strings.Contains(message, "placeholder missing") || strings.Contains(message, "rate limit") || strings.Contains(message, "429")
}
@ -142,7 +150,7 @@ type promptRunner interface {
}
func runPrompt(ctx context.Context, client promptRunner, message string) (string, error) {
promptCtx, cancel := context.WithTimeout(ctx, translatePromptTimeout)
promptCtx, cancel := context.WithTimeout(ctx, docsI18nPromptTimeout())
defer cancel()
result, err := client.Prompt(promptCtx, message)
@ -171,3 +179,15 @@ func normalizeThinking(value string) string {
return "high"
}
}
func docsI18nPromptTimeout() time.Duration {
value := strings.TrimSpace(os.Getenv(envDocsI18nPromptTimeout))
if value == "" {
return defaultPromptTimeout
}
parsed, err := time.ParseDuration(value)
if err != nil || parsed <= 0 {
return defaultPromptTimeout
}
return parsed
}

View File

@ -50,11 +50,35 @@ func TestRunPromptAddsTimeout(t *testing.T) {
}
remaining := time.Until(deadline)
if remaining <= time.Minute || remaining > translatePromptTimeout {
if remaining <= time.Minute || remaining > docsI18nPromptTimeout() {
t.Fatalf("unexpected timeout window %s", remaining)
}
}
func TestDocsI18nPromptTimeoutUsesEnvOverride(t *testing.T) {
t.Setenv(envDocsI18nPromptTimeout, "5m")
if got := docsI18nPromptTimeout(); got != 5*time.Minute {
t.Fatalf("expected 5m timeout, got %s", got)
}
}
func TestIsRetryableTranslateErrorRejectsDeadlineExceeded(t *testing.T) {
t.Parallel()
if isRetryableTranslateError(context.DeadlineExceeded) {
t.Fatal("deadline exceeded should not retry")
}
}
func TestIsRetryableTranslateErrorRejectsAuthenticationFailures(t *testing.T) {
t.Parallel()
if isRetryableTranslateError(errors.New(`Authentication failed for "openai"`)) {
t.Fatal("auth failures should not retry")
}
}
func TestRunPromptIncludesStderr(t *testing.T) {
t.Parallel()

View File

@ -10,13 +10,24 @@ import (
)
const (
workflowVersion = 15
providerName = "pi"
modelVersion = "claude-opus-4-6"
workflowVersion = 15
docsI18nEngineName = "pi"
envDocsI18nProvider = "OPENCLAW_DOCS_I18N_PROVIDER"
envDocsI18nModel = "OPENCLAW_DOCS_I18N_MODEL"
defaultOpenAIModel = "gpt-5.4"
defaultAnthropicModel = "claude-opus-4-6"
defaultFallbackProvider = "openai"
defaultFallbackModelName = defaultOpenAIModel
)
func cacheNamespace() string {
return fmt.Sprintf("wf=%d|provider=%s|model=%s", workflowVersion, providerName, modelVersion)
return fmt.Sprintf(
"wf=%d|engine=%s|provider=%s|model=%s",
workflowVersion,
docsI18nEngineName,
docsPiProvider(),
docsPiModel(),
)
}
func cacheKey(namespace, srcLang, tgtLang, segmentID, textHash string) string {
@ -40,6 +51,33 @@ func normalizeText(text string) string {
return strings.Join(strings.Fields(strings.TrimSpace(text)), " ")
}
func docsPiProvider() string {
if value := strings.TrimSpace(os.Getenv(envDocsI18nProvider)); value != "" {
return value
}
if strings.TrimSpace(os.Getenv("OPENAI_API_KEY")) != "" {
return "openai"
}
if strings.TrimSpace(os.Getenv("ANTHROPIC_API_KEY")) != "" {
return "anthropic"
}
return defaultFallbackProvider
}
func docsPiModel() string {
if value := strings.TrimSpace(os.Getenv(envDocsI18nModel)); value != "" {
return value
}
switch docsPiProvider() {
case "anthropic":
return defaultAnthropicModel
case "openai":
return defaultOpenAIModel
default:
return defaultFallbackModelName
}
}
func segmentID(relPath, textHash string) string {
shortHash := textHash
if len(shortHash) > 16 {

View File

@ -0,0 +1,41 @@
package main
import "testing"
func TestDocsPiProviderPrefersExplicitOverride(t *testing.T) {
t.Setenv(envDocsI18nProvider, "anthropic")
t.Setenv("OPENAI_API_KEY", "openai-key")
t.Setenv("ANTHROPIC_API_KEY", "anthropic-key")
if got := docsPiProvider(); got != "anthropic" {
t.Fatalf("expected anthropic override, got %q", got)
}
}
func TestDocsPiProviderPrefersOpenAIEnvWhenAvailable(t *testing.T) {
t.Setenv(envDocsI18nProvider, "")
t.Setenv("OPENAI_API_KEY", "openai-key")
t.Setenv("ANTHROPIC_API_KEY", "anthropic-key")
if got := docsPiProvider(); got != "openai" {
t.Fatalf("expected openai provider, got %q", got)
}
}
func TestDocsPiModelUsesProviderDefault(t *testing.T) {
t.Setenv(envDocsI18nProvider, "anthropic")
t.Setenv(envDocsI18nModel, "")
if got := docsPiModel(); got != defaultAnthropicModel {
t.Fatalf("expected anthropic default model, got %q", got)
}
}
func TestDocsPiModelPrefersExplicitOverride(t *testing.T) {
t.Setenv(envDocsI18nProvider, "openai")
t.Setenv(envDocsI18nModel, "gpt-5.2")
if got := docsPiModel(); got != "gpt-5.2" {
t.Fatalf("expected explicit model override, got %q", got)
}
}