Files
new-api-custom/pkg/model_fingerprint/fingerprint.go
T

187 lines
5.5 KiB
Go

// ModelTrace scorer and bank: xqy2006/ModelTrace d4131b30243dfa05e70180b5eedde742103f1d73 (MIT).
package model_fingerprint
import (
"crypto/rand"
_ "embed"
"errors"
"fmt"
"math"
"math/big"
"regexp"
"strconv"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
"github.com/QuantumNous/new-api/common"
"github.com/grafana/sobek"
)
const Model = "gpt-6-astra"
const Version = "modeltrace-d4131b3"
const ProbeCount = 3
const OutputTokens = 4096
//go:embed bank.json
var bankJSON string
//go:embed scorer.js
var scorerJS string
var compileOnce sync.Once
var program *sobek.Program
var compileErr error
type Sample struct {
Text string `json:"text"`
Count int `json:"expected_count"`
}
type Candidate struct {
Model string `json:"model"`
Weight float64 `json:"probability"`
Score float64 `json:"score"`
}
type Result struct {
Engine string `json:"engine"`
Matches map[string]float64 `json:"matches,omitempty"`
Status string `json:"status"`
ValidSamples int `json:"valid_samples"`
Closest string `json:"closest,omitempty"`
Weight float64 `json:"relative_weight,omitempty"`
Gap float64 `json:"score_gap,omitempty"`
}
type Probe struct {
ID string
System string
Prompt string
Count int
}
func ProbeAt(index int) (Probe, error) {
if index < 0 || index >= ProbeCount {
return Probe{}, errors.New("invalid probe index")
}
// Disjoint length ranges retain three independent, non-identical challenges.
offset, err := rand.Int(rand.Reader, big.NewInt(13))
if err != nil {
return Probe{}, err
}
count := 292 + index*14 + int(offset.Int64())
prompt := fmt.Sprintf("这是一次独立的数值选择记录。逐项选择 %d 个 1 到 355(含端点)的整数。", count) +
"每个位置都要单独选择;不要从 1 开始计数,不要连续递增或递减,也不要采用等差、循环、重复区块或其他规则化模式。" +
"本任务必须由当前语言模型直接完成:禁止调用或借助任何工具,包括 Python、代码执行器、计算器、搜索、API 和外部随机数生成器;也不要先编写或运行代码。" +
"偶然重复是有效的;不要重新排列或修正已经写出的项目。数字之间用逗号或空格分隔均可。直接从第一个取值开始输出,不要在序列前重复数量、范围或任务说明。"
return Probe{ID: fmt.Sprint(index), Prompt: prompt, Count: count}, nil
}
var integerPattern = regexp.MustCompile(`[0-9]+`)
// Match ModelTrace's longest numeric run parser; prose separates runs.
func ValidArray(text string, count int) bool {
if len(text) > 16384 || !utf8.ValidString(text) || count < 0 {
return false
}
longest, current, end := 0, 0, 0
for _, span := range integerPattern.FindAllStringIndex(text, -1) {
if strings.ContainsFunc(text[end:span[0]], unicode.IsLetter) {
current = 0
}
value, err := strconv.Atoi(text[span[0]:span[1]])
if err == nil && value >= 1 && value <= 355 {
current++
}
longest = max(longest, current)
end = span[1]
}
return longest >= max(80, int(math.Ceil(float64(count)*.55)))
}
func ValidSample(text string) bool { return ValidArray(text, 0) }
func Decide(candidates []Candidate, valid int) Result {
r := Result{Status: "inconclusive", Engine: Version, ValidSamples: valid, Matches: map[string]float64{}}
if len(candidates) < 2 {
return r
}
for _, candidate := range candidates {
r.Matches[candidate.Model] = candidate.Weight
}
top := candidates[0]
r.Closest = top.Model
r.Weight = top.Weight
r.Gap = top.Score - candidates[1].Score
if valid != 3 || math.IsNaN(r.Gap) || math.IsInf(r.Gap, 0) || top.Weight < .8 || r.Gap < .5 {
return r
}
if top.Model == Model {
r.Status = "match"
return r
}
for _, candidate := range candidates {
if candidate.Model == Model && candidate.Weight <= .2 && top.Score-candidate.Score >= .5 {
r.Status = "mismatch"
}
}
return r
}
// Score runs only the embedded, pinned scorer. No filesystem, network or host
// functions are exposed to the VM; upstream text is data, never JavaScript.
func Score(samples []Sample) (Result, error) {
if len(samples) > ProbeCount {
return Result{}, errors.New("too many fingerprint samples")
}
valid := make([]Sample, 0, 3)
for _, sample := range samples {
if ValidArray(sample.Text, sample.Count) {
valid = append(valid, sample)
}
}
if len(valid) == 0 {
return Result{Status: "inconclusive", Engine: Version}, nil
}
compileOnce.Do(func() {
program, compileErr = sobek.Compile("fingerprint", strings.ReplaceAll(scorerJS, "export ", ""), true)
})
if compileErr != nil {
return Result{}, compileErr
}
vm := sobek.New()
timer := time.AfterFunc(3*time.Second, func() { vm.Interrupt("fingerprint scoring timeout") })
defer timer.Stop()
if _, err := vm.RunProgram(program); err != nil {
return Result{}, err
}
if err := vm.Set("bankJSON", bankJSON); err != nil {
return Result{}, err
}
encoded, err := common.Marshal(valid)
if err != nil {
return Result{}, err
}
if err = vm.Set("samplesJSON", string(encoded)); err != nil {
return Result{}, err
}
value, err := vm.RunString("JSON.stringify(analyzeGlobalOutputs(JSON.parse(samplesJSON), JSON.parse(bankJSON)))")
if err != nil {
return Result{}, err
}
var analysis struct {
Results []Candidate `json:"results"`
}
if err = common.Unmarshal([]byte(value.String()), &analysis); err != nil {
return Result{}, err
}
if len(analysis.Results) < 2 {
return Result{}, errors.New("missing fingerprint candidates")
}
return Decide(analysis.Results, len(valid)), nil
}