223 lines
6.1 KiB
Go
223 lines
6.1 KiB
Go
//go:build llm_script && !scripts_pkg
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type pricingSmokeCheck struct {
|
|
Name string
|
|
URL string
|
|
Run func() (string, error)
|
|
}
|
|
|
|
type pricingSmokeSummary struct {
|
|
Source string
|
|
ModelCount int
|
|
Operator string
|
|
}
|
|
|
|
type pricingSmokeResult struct {
|
|
Name string
|
|
URL string
|
|
Source string
|
|
Operator string
|
|
ModelCount int
|
|
DurationMS int64
|
|
Success bool
|
|
Error string
|
|
}
|
|
|
|
func main() {
|
|
loadSubscriptionImportEnv()
|
|
|
|
var timeoutSeconds int
|
|
var vertexURL string
|
|
var cloudflareURL string
|
|
var perplexityURL string
|
|
var vertexFixture string
|
|
var cloudflareFixture string
|
|
var perplexityFixture string
|
|
|
|
flag.IntVar(&timeoutSeconds, "timeout", 20, "请求超时(秒)")
|
|
flag.StringVar(&vertexURL, "vertex-url", defaultVertexPricingURL, "Vertex AI 官方价格页")
|
|
flag.StringVar(&cloudflareURL, "cloudflare-url", defaultCloudflarePricingFetchURL, "Cloudflare Workers AI 官方价格页")
|
|
flag.StringVar(&perplexityURL, "perplexity-url", defaultPerplexityPricingFetchURL, "Perplexity API 官方模型页")
|
|
flag.StringVar(&vertexFixture, "vertex-fixture", "", "Vertex AI fixture 文件")
|
|
flag.StringVar(&cloudflareFixture, "cloudflare-fixture", "", "Cloudflare fixture 文件")
|
|
flag.StringVar(&perplexityFixture, "perplexity-fixture", "", "Perplexity fixture 文件")
|
|
flag.Parse()
|
|
|
|
timeout := time.Duration(timeoutSeconds) * time.Second
|
|
checks := []pricingSmokeCheck{
|
|
buildVertexSmokeCheck(vertexURL, vertexFixture, timeout),
|
|
buildCloudflareSmokeCheck(cloudflareURL, cloudflareFixture, timeout),
|
|
buildPerplexitySmokeCheck(perplexityURL, perplexityFixture, timeout),
|
|
}
|
|
results := runPricingSmokeChecks(checks, time.Now)
|
|
renderPricingSmokeTextReport(os.Stdout, results, time.Now())
|
|
if hasFailedPricingSmoke(results) {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func buildVertexSmokeCheck(url, fixture string, timeout time.Duration) pricingSmokeCheck {
|
|
return pricingSmokeCheck{
|
|
Name: "Vertex",
|
|
URL: url,
|
|
Run: func() (string, error) {
|
|
var out bytes.Buffer
|
|
err := runVertexPricingImport(vertexPricingImportConfig{
|
|
URL: url,
|
|
Fixture: fixture,
|
|
DryRun: true,
|
|
Timeout: timeout,
|
|
}, nil, &out)
|
|
return strings.TrimSpace(out.String()), err
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildCloudflareSmokeCheck(url, fixture string, timeout time.Duration) pricingSmokeCheck {
|
|
return pricingSmokeCheck{
|
|
Name: "Cloudflare",
|
|
URL: url,
|
|
Run: func() (string, error) {
|
|
var out bytes.Buffer
|
|
err := runCloudflarePricingImport(cloudflarePricingImportConfig{
|
|
URL: url,
|
|
Fixture: fixture,
|
|
DryRun: true,
|
|
Timeout: timeout,
|
|
}, nil, &out)
|
|
return strings.TrimSpace(out.String()), err
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildPerplexitySmokeCheck(url, fixture string, timeout time.Duration) pricingSmokeCheck {
|
|
return pricingSmokeCheck{
|
|
Name: "Perplexity",
|
|
URL: url,
|
|
Run: func() (string, error) {
|
|
var out bytes.Buffer
|
|
err := runPerplexityPricingImport(perplexityPricingImportConfig{
|
|
URL: url,
|
|
Fixture: fixture,
|
|
DryRun: true,
|
|
Timeout: timeout,
|
|
}, nil, &out)
|
|
return strings.TrimSpace(out.String()), err
|
|
},
|
|
}
|
|
}
|
|
|
|
func runPricingSmokeChecks(checks []pricingSmokeCheck, now func() time.Time) []pricingSmokeResult {
|
|
results := make([]pricingSmokeResult, 0, len(checks))
|
|
for _, check := range checks {
|
|
start := now()
|
|
result := pricingSmokeResult{
|
|
Name: check.Name,
|
|
URL: check.URL,
|
|
}
|
|
summaryLine, err := check.Run()
|
|
result.DurationMS = now().Sub(start).Milliseconds()
|
|
if err != nil {
|
|
result.Error = err.Error()
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
summary, ok := parsePricingSmokeSummaryLine(summaryLine)
|
|
if !ok {
|
|
result.Error = fmt.Sprintf("invalid dry-run summary: %q", summaryLine)
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
result.Success = true
|
|
result.Source = summary.Source
|
|
result.Operator = summary.Operator
|
|
result.ModelCount = summary.ModelCount
|
|
results = append(results, result)
|
|
}
|
|
return results
|
|
}
|
|
|
|
func parsePricingSmokeSummaryLine(line string) (pricingSmokeSummary, bool) {
|
|
fields := strings.Fields(strings.TrimSpace(line))
|
|
if len(fields) == 0 {
|
|
return pricingSmokeSummary{}, false
|
|
}
|
|
|
|
source := ""
|
|
modelCount := -1
|
|
operatorParts := make([]string, 0)
|
|
capturingOperator := false
|
|
for _, field := range fields {
|
|
switch {
|
|
case strings.HasPrefix(field, "source="):
|
|
source = strings.TrimPrefix(field, "source=")
|
|
capturingOperator = false
|
|
case strings.HasPrefix(field, "models="):
|
|
value := strings.TrimPrefix(field, "models=")
|
|
parsed, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return pricingSmokeSummary{}, false
|
|
}
|
|
modelCount = parsed
|
|
capturingOperator = false
|
|
case strings.HasPrefix(field, "operator="):
|
|
capturingOperator = true
|
|
operatorParts = append(operatorParts, strings.TrimPrefix(field, "operator="))
|
|
case strings.HasPrefix(field, "dry_run="), strings.HasPrefix(field, "table_rows="):
|
|
capturingOperator = false
|
|
default:
|
|
if capturingOperator {
|
|
operatorParts = append(operatorParts, field)
|
|
}
|
|
}
|
|
}
|
|
if source == "" || modelCount < 0 || len(operatorParts) == 0 {
|
|
return pricingSmokeSummary{}, false
|
|
}
|
|
return pricingSmokeSummary{
|
|
Source: source,
|
|
ModelCount: modelCount,
|
|
Operator: strings.Join(operatorParts, " "),
|
|
}, true
|
|
}
|
|
|
|
func renderPricingSmokeTextReport(out io.Writer, results []pricingSmokeResult, now time.Time) {
|
|
passed := 0
|
|
failed := 0
|
|
_, _ = fmt.Fprintf(out, "=== Live Pricing Smoke Report (%s) ===\n", now.Format("2006-01-02 15:04"))
|
|
for _, result := range results {
|
|
if result.Success {
|
|
passed++
|
|
_, _ = fmt.Fprintf(out, "PASS %s source=%s models=%d operator=%s duration_ms=%d url=%s\n",
|
|
result.Name, result.Source, result.ModelCount, result.Operator, result.DurationMS, result.URL)
|
|
continue
|
|
}
|
|
failed++
|
|
_, _ = fmt.Fprintf(out, "FAIL %s duration_ms=%d error=%s url=%s\n",
|
|
result.Name, result.DurationMS, result.Error, result.URL)
|
|
}
|
|
_, _ = fmt.Fprintf(out, "Summary: %d passed, %d failed\n", passed, failed)
|
|
}
|
|
|
|
func hasFailedPricingSmoke(results []pricingSmokeResult) bool {
|
|
for _, result := range results {
|
|
if !result.Success {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|