Files
llm-intelligence/scripts/live_pricing_smoke_runner.go
phamnazage-jpg 256975e10c feat(audit): add pricing signature guards and reporting
Add snapshot, signature, and drift guard support for Vertex AI, Cloudflare Workers AI, and Perplexity API, backed by a queryable audit table and recent-window view.

This commit also wires the audit query layer into daily signal materialization and report generation so structure drift becomes a first-class signal instead of a log-only artifact.
2026-05-15 22:34:22 +08:00

223 lines
6.1 KiB
Go

//go:build llm_script
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
}