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.
109 lines
3.1 KiB
Go
109 lines
3.1 KiB
Go
//go:build llm_script
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
defaultCloudflarePricingFetchURL = "https://developers.cloudflare.com/workers-ai/platform/pricing/index.md"
|
|
defaultCloudflarePricingSourceURL = "https://developers.cloudflare.com/workers-ai/platform/pricing/"
|
|
)
|
|
|
|
func parseCloudflarePricingCatalog(raw string) ([]officialPricingRecord, error) {
|
|
section, ok := extractCloudflareLLMPricingSection(raw)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected cloudflare pricing content")
|
|
}
|
|
|
|
lines := strings.Split(section, "\n")
|
|
records := make([]officialPricingRecord, 0)
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if !strings.HasPrefix(line, "| @cf/") {
|
|
continue
|
|
}
|
|
parts := strings.Split(line, "|")
|
|
if len(parts) < 4 {
|
|
continue
|
|
}
|
|
modelPath := strings.Trim(strings.TrimSpace(parts[1]), "`")
|
|
priceCell := strings.TrimSpace(parts[2])
|
|
prices := extractCloudflarePrices(priceCell)
|
|
if len(prices) < 2 {
|
|
continue
|
|
}
|
|
providerName := providerFromModelPath(strings.TrimPrefix(modelPath, "@cf/"))
|
|
providerNameCn, providerCountry, providerWebsite := providerMetadata(providerName)
|
|
record := officialPricingRecord{
|
|
ModelID: normalizeExternalID("cloudflare", modelPath),
|
|
ModelName: modelPath,
|
|
ProviderName: providerName,
|
|
ProviderNameCn: providerNameCn,
|
|
ProviderCountry: providerCountry,
|
|
ProviderWebsite: providerWebsite,
|
|
OperatorName: "Cloudflare Workers AI",
|
|
OperatorNameCn: "Cloudflare Workers AI",
|
|
OperatorCountry: "US",
|
|
OperatorWebsite: "https://developers.cloudflare.com/workers-ai/",
|
|
OperatorType: "cloud",
|
|
Region: "global",
|
|
Currency: "USD",
|
|
InputPrice: prices[0],
|
|
OutputPrice: prices[1],
|
|
SourceURL: defaultCloudflarePricingSourceURL,
|
|
ModelSourceURL: defaultCloudflarePricingSourceURL,
|
|
DateConfidence: "unknown",
|
|
DateSourceKind: "official_pricing",
|
|
Modality: detectModality(modelPath),
|
|
}
|
|
record.IsFree = record.InputPrice == 0 && record.OutputPrice == 0
|
|
records = append(records, record)
|
|
}
|
|
if len(records) == 0 {
|
|
return nil, fmt.Errorf("no cloudflare llm pricing rows found")
|
|
}
|
|
return records, nil
|
|
}
|
|
|
|
func extractCloudflarePrices(raw string) []float64 {
|
|
fields := strings.Split(raw, "$")
|
|
prices := make([]float64, 0, 3)
|
|
for _, field := range fields[1:] {
|
|
value := strings.TrimSpace(field)
|
|
end := strings.Index(value, " per ")
|
|
if end == -1 {
|
|
continue
|
|
}
|
|
prices = append(prices, mustParseSubscriptionPrice(value[:end]))
|
|
}
|
|
return prices
|
|
}
|
|
|
|
func extractCloudflareLLMPricingSection(raw string) (string, bool) {
|
|
lines := strings.Split(raw, "\n")
|
|
start := -1
|
|
end := len(lines)
|
|
for i, line := range lines {
|
|
trimmed := strings.TrimSpace(line)
|
|
if !strings.HasPrefix(trimmed, "## ") {
|
|
continue
|
|
}
|
|
title := strings.ToLower(strings.TrimSpace(strings.TrimPrefix(trimmed, "## ")))
|
|
if start == -1 {
|
|
if strings.Contains(title, "llm") && strings.Contains(title, "pricing") {
|
|
start = i
|
|
}
|
|
continue
|
|
}
|
|
end = i
|
|
break
|
|
}
|
|
if start == -1 {
|
|
return "", false
|
|
}
|
|
return strings.Join(lines[start:end], "\n"), true
|
|
}
|