fix
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/aiprovider"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/config"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/db"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/platformsettings"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/processing"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "config: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
ctx := context.Background()
|
||||
pool, err := db.NewPool(ctx, cfg.DatabaseURL, db.PoolOptions{
|
||||
MaxConns: int32(cfg.DBMaxConns), MinConns: int32(cfg.DBMinConns),
|
||||
MaxConnLifetime: cfg.DBMaxConnLifetime, MaxConnLifetimeJitter: cfg.DBMaxConnLifetimeJitter,
|
||||
MaxConnIdleTime: cfg.DBMaxConnIdleTime, HealthCheckPeriod: cfg.DBHealthCheckPeriod,
|
||||
StatementTimeout: cfg.DBStatementTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "db: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
platEnv := platformsettings.EnvConfig{
|
||||
AppEncryptionKey: cfg.AppEncryptionKey, CredentialsEncryptionKey: cfg.CredentialsEncryptionKey,
|
||||
TokenSigningSecret: cfg.TokenSigningSecret, DatabaseURL: cfg.DatabaseURL,
|
||||
OpenAIAPIKey: cfg.OpenAIAPIKey, OpenAIBaseURL: cfg.OpenAIBaseURL, OpenAIModel: cfg.OpenAIModel,
|
||||
}
|
||||
plat := platformsettings.NewService(pool, platEnv)
|
||||
ai := aiprovider.NewService(pool, aiprovider.EnvConfig{
|
||||
AppEncryptionKey: cfg.AppEncryptionKey, CredentialsEncryptionKey: cfg.CredentialsEncryptionKey,
|
||||
TokenSigningSecret: cfg.TokenSigningSecret, DatabaseURL: cfg.DatabaseURL,
|
||||
OpenAIAPIKey: cfg.OpenAIAPIKey, OpenAIBaseURL: cfg.OpenAIBaseURL, OpenAIModel: cfg.OpenAIModel,
|
||||
})
|
||||
ai.Platform = plat
|
||||
|
||||
companyID := uuid.MustParse("604f23a8-b66e-4b21-8b45-0d72b68f4790")
|
||||
roleCfg, err := plat.ResolveAIConfig(ctx, platformsettings.AIRoleProcessing)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ResolveAIConfig: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
completer, mode, byok, err := ai.ResolveCompleterForRole(ctx, companyID, aiprovider.RoleProcessing)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ResolveCompleter: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
client, ok := completer.(*processing.OpenAIClient)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "not OpenAIClient: %T\n", completer)
|
||||
os.Exit(2)
|
||||
}
|
||||
base := strings.TrimRight(strings.TrimSpace(client.BaseURL), "/")
|
||||
model := strings.TrimSpace(client.Model)
|
||||
if processing.IsMockOrLoopbackBaseURL(base) {
|
||||
fmt.Println("FAIL mock/loopback")
|
||||
os.Exit(3)
|
||||
}
|
||||
|
||||
fmt.Printf("resolved source=%s provider=%s base=%s model=%s mode=%s byok=%v key_len=%d app_max_tokens_enhance=%d retry=%d http_timeout=240s\n",
|
||||
roleCfg.Source, roleCfg.Provider, base, model, mode, byok, len(client.APIKey),
|
||||
processing.MaxTokensEnhance, processing.MaxTokensEnhanceRetry)
|
||||
|
||||
// Optional: adjust model id if gateway wants green/ prefix (prior resolve used both).
|
||||
models := []string{model}
|
||||
if !strings.HasPrefix(model, "green/") {
|
||||
models = append(models, "green/"+model)
|
||||
}
|
||||
|
||||
size := "tiny"
|
||||
if len(os.Args) > 1 {
|
||||
size = strings.ToLower(strings.TrimSpace(os.Args[1]))
|
||||
}
|
||||
maxTok := 256
|
||||
prompt := "Reply with exactly: OK"
|
||||
timeout := 60 * time.Second
|
||||
switch size {
|
||||
case "tiny":
|
||||
maxTok = 256
|
||||
prompt = "Reply with exactly one word: OK"
|
||||
timeout = 60 * time.Second
|
||||
case "medium":
|
||||
maxTok = 3072
|
||||
prompt = "Write a short JSON object with keys title and description for product Sony WH-1000XM5 headphones. description must be 2 short HTML paragraphs. Keep under 400 words."
|
||||
timeout = 90 * time.Second
|
||||
case "large":
|
||||
maxTok = 16384
|
||||
prompt = "Write a long JSON object with keys title, description, attributes. description must be multi-section Slovenian HTML with several h2+p and a ul list for Sony WH-1000XM5. Be thorough."
|
||||
timeout = 120 * time.Second
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "usage: %s [tiny|medium|large]\n", os.Args[0])
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
useModel := models[0]
|
||||
if len(os.Args) > 2 && strings.TrimSpace(os.Args[2]) != "" {
|
||||
useModel = strings.TrimSpace(os.Args[2])
|
||||
}
|
||||
|
||||
bodyObj := map[string]any{
|
||||
"model": useModel,
|
||||
"temperature": 0.2,
|
||||
"max_tokens": maxTok,
|
||||
"messages": []map[string]string{
|
||||
{"role": "system", "content": "You are a concise assistant."},
|
||||
{"role": "user", "content": prompt},
|
||||
},
|
||||
}
|
||||
raw, _ := json.Marshal(bodyObj)
|
||||
url := base + "/chat/completions"
|
||||
fmt.Printf("probe size=%s model=%s max_tokens=%d timeout=%s url=%s prompt_len=%d\n",
|
||||
size, useModel, maxTok, timeout, url, len(prompt))
|
||||
|
||||
reqCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
req, err := http.NewRequestWithContext(reqCtx, http.MethodPost, url, bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "new request: %v\n", err)
|
||||
os.Exit(4)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+client.APIKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
// Separate dial vs header wait so we can tell "hung with no headers" from slow body.
|
||||
headerTO := 25 * time.Second
|
||||
if timeout < headerTO {
|
||||
headerTO = timeout
|
||||
}
|
||||
httpClient := &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: &http.Transport{
|
||||
ResponseHeaderTimeout: headerTO,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
start := time.Now()
|
||||
resp, err := httpClient.Do(req)
|
||||
elapsed := time.Since(start).Round(time.Millisecond)
|
||||
if err != nil {
|
||||
fmt.Printf("RESULT ok=false elapsed=%s header_timeout=%s err=%v\n", elapsed, headerTO, err)
|
||||
os.Exit(5)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||
snip := strings.TrimSpace(string(b))
|
||||
if len(snip) > 500 {
|
||||
snip = snip[:500] + "…"
|
||||
}
|
||||
fmt.Printf("RESULT ok=%t status=%d elapsed=%s body_len=%d snippet=%s\n",
|
||||
resp.StatusCode >= 200 && resp.StatusCode < 300, resp.StatusCode, elapsed, len(b), snip)
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
os.Exit(6)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user