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.
This commit is contained in:
150
scripts/perplexity_pricing_lib.go
Normal file
150
scripts/perplexity_pricing_lib.go
Normal file
@@ -0,0 +1,150 @@
|
||||
//go:build llm_script
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPerplexityPricingFetchURL = "https://docs.perplexity.ai/docs/agent-api/models.md"
|
||||
defaultPerplexityPricingSourceURL = "https://docs.perplexity.ai/docs/agent-api/models"
|
||||
)
|
||||
|
||||
var markdownLinkPattern = regexp.MustCompile(`\[(.*?)\]\((https://[^)]+)\)`)
|
||||
|
||||
func parsePerplexityPricingCatalog(raw string) ([]officialPricingRecord, error) {
|
||||
lines := strings.Split(raw, "\n")
|
||||
records := make([]officialPricingRecord, 0)
|
||||
header := []string(nil)
|
||||
modelIndex := -1
|
||||
inputIndex := -1
|
||||
outputIndex := -1
|
||||
docIndex := -1
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(line, "|") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := splitMarkdownTableRow(line)
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
if header == nil {
|
||||
header = parts
|
||||
modelIndex, inputIndex, outputIndex, docIndex = detectPerplexityTableColumns(parts)
|
||||
continue
|
||||
}
|
||||
if isMarkdownTableSeparator(parts) {
|
||||
continue
|
||||
}
|
||||
if modelIndex < 0 || inputIndex < 0 || outputIndex < 0 || modelIndex >= len(parts) || inputIndex >= len(parts) || outputIndex >= len(parts) {
|
||||
continue
|
||||
}
|
||||
|
||||
modelPath := strings.Trim(parts[modelIndex], "`")
|
||||
inputCell := parts[inputIndex]
|
||||
outputCell := parts[outputIndex]
|
||||
inputPrice, ok := firstDollarPrice(inputCell)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
outputPrice, ok := firstDollarPrice(outputCell)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
sourceURL := defaultPerplexityPricingSourceURL
|
||||
if docIndex >= 0 && docIndex < len(parts) {
|
||||
if matches := markdownLinkPattern.FindStringSubmatch(parts[docIndex]); len(matches) == 3 {
|
||||
sourceURL = matches[2]
|
||||
}
|
||||
}
|
||||
providerName := providerFromModelPath(modelPath)
|
||||
providerNameCn, providerCountry, providerWebsite := providerMetadata(providerName)
|
||||
record := officialPricingRecord{
|
||||
ModelID: normalizeExternalID("perplexity", modelPath),
|
||||
ModelName: modelPath,
|
||||
ProviderName: providerName,
|
||||
ProviderNameCn: providerNameCn,
|
||||
ProviderCountry: providerCountry,
|
||||
ProviderWebsite: providerWebsite,
|
||||
OperatorName: "Perplexity API",
|
||||
OperatorNameCn: "Perplexity API",
|
||||
OperatorCountry: "US",
|
||||
OperatorWebsite: "https://docs.perplexity.ai",
|
||||
OperatorType: "relay",
|
||||
Region: "global",
|
||||
Currency: "USD",
|
||||
InputPrice: inputPrice,
|
||||
OutputPrice: outputPrice,
|
||||
SourceURL: defaultPerplexityPricingSourceURL,
|
||||
ModelSourceURL: sourceURL,
|
||||
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("unexpected perplexity pricing content")
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func splitMarkdownTableRow(line string) []string {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
trimmed = strings.TrimPrefix(trimmed, "|")
|
||||
trimmed = strings.TrimSuffix(trimmed, "|")
|
||||
if trimmed == "" {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(trimmed, "|")
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
result = append(result, strings.TrimSpace(part))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func detectPerplexityTableColumns(header []string) (int, int, int, int) {
|
||||
modelIndex := -1
|
||||
inputIndex := -1
|
||||
outputIndex := -1
|
||||
docIndex := -1
|
||||
for i, col := range header {
|
||||
lower := strings.ToLower(strings.TrimSpace(col))
|
||||
switch {
|
||||
case strings.Contains(lower, "model") && modelIndex == -1:
|
||||
modelIndex = i
|
||||
case strings.Contains(lower, "input") && strings.Contains(lower, "price") && inputIndex == -1:
|
||||
inputIndex = i
|
||||
case strings.Contains(lower, "output") && strings.Contains(lower, "price") && outputIndex == -1:
|
||||
outputIndex = i
|
||||
case (strings.Contains(lower, "documentation") || strings.Contains(lower, "docs")) && docIndex == -1:
|
||||
docIndex = i
|
||||
}
|
||||
}
|
||||
return modelIndex, inputIndex, outputIndex, docIndex
|
||||
}
|
||||
|
||||
func isMarkdownTableSeparator(parts []string) bool {
|
||||
if len(parts) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed == "" {
|
||||
return false
|
||||
}
|
||||
for _, ch := range trimmed {
|
||||
if ch != '-' && ch != ':' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user