ml
Build and Publish / Metadata (push) Successful in 2s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 1m3s

This commit is contained in:
mats committed 2026-09-19 06:50:19 +00:00
1 parent 21f04d696b
commit 8f5c267444
26 files changed
+3039 -10

No files matched your search

+240
View File
@@ -0,0 +1,240 @@
// issue-model trains and queries an explainable prototype model for normalized
// events. It uses deterministic pattern signatures as weak labels until human
// labels are available.
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"log"
"math"
"os"
"sort"
"strconv"
"strings"
)
type event struct {
EventID string `json:"eventId"`
Service string `json:"service"`
Kind string `json:"kind"`
Source string `json:"source"`
IsError uint8 `json:"isError"`
StatusClass string `json:"statusClass,omitempty"`
StatusCode uint16 `json:"statusCode,omitempty"`
Route string `json:"route,omitempty"`
SpanName string `json:"spanName,omitempty"`
MetricName string `json:"metricName,omitempty"`
MetricType string `json:"metricType,omitempty"`
ExceptionType string `json:"exceptionType,omitempty"`
Message string `json:"messageNormalized,omitempty"`
StackSignature string `json:"stackSignature,omitempty"`
PatternSignature string `json:"patternSignature"`
}
type prototype struct {
Pattern string `json:"patternSignature"`
Count int `json:"count"`
Tokens map[string]float64 `json:"tokens"`
}
type model struct {
Version string `json:"version"`
FeatureSpec string `json:"featureSpec"`
Prototypes []prototype `json:"prototypes"`
}
type prediction struct {
EventID string `json:"eventId"`
ActualPattern string `json:"actualPattern,omitempty"`
Pattern string `json:"patternSignature"`
Score float64 `json:"score"`
Matches []string `json:"matches"`
Rank int `json:"rank"`
Correct bool `json:"correct,omitempty"`
Accepted bool `json:"accepted"`
}
func main() {
trainPath := flag.String("train", "", "JSONL training sample")
modelPath := flag.String("model", "/tmp/issue-model-v001.json", "model path")
predictPath := flag.String("predict", "", "JSONL events to score")
top := flag.Int("top", 3, "number of predictions per event")
minScore := flag.Float64("min-score", 0.75, "minimum top similarity to accept a match")
minMargin := flag.Float64("min-margin", 0.05, "minimum gap between first and second match")
flag.Parse()
if *trainPath == "" && *predictPath == "" {
log.Fatal("provide -train or -predict")
}
if *trainPath != "" {
trained := train(*trainPath)
writeJSON(*modelPath, trained)
fmt.Printf("trained prototypes=%d model=%s\n", len(trained.Prototypes), *modelPath)
}
if *predictPath != "" {
var trained model
readJSON(*modelPath, &trained)
predict(*predictPath, trained, *top, *minScore, *minMargin)
}
}
func train(path string) model {
groups := map[string]*prototype{}
forEachEvent(path, func(item event) {
if item.PatternSignature == "" {
return
}
p := groups[item.PatternSignature]
if p == nil {
p = &prototype{Pattern: item.PatternSignature, Tokens: map[string]float64{}}
groups[item.PatternSignature] = p
}
p.Count++
for token := range features(item) {
p.Tokens[token]++
}
})
result := model{Version: "v1", FeatureSpec: "categorical tokens excluding IDs, timestamps, trace IDs, IPs, and raw values"}
for _, p := range groups {
for token, value := range p.Tokens {
p.Tokens[token] = value / float64(p.Count)
}
result.Prototypes = append(result.Prototypes, *p)
}
sort.Slice(result.Prototypes, func(i, j int) bool { return result.Prototypes[i].Pattern < result.Prototypes[j].Pattern })
return result
}
func predict(path string, trained model, top int, minScore, minMargin float64) {
if top < 1 {
top = 1
}
if len(trained.Prototypes) == 0 {
log.Fatal("model contains no prototypes; train it with a non-empty normalized sample")
}
forEachEvent(path, func(item event) {
input := features(item)
results := make([]prediction, 0, len(trained.Prototypes))
for _, p := range trained.Prototypes {
score, matches := similarity(input, p.Tokens)
results = append(results, prediction{EventID: item.EventID, Pattern: p.Pattern, Score: score, Matches: matches})
}
sort.Slice(results, func(i, j int) bool {
if results[i].Score != results[j].Score {
return results[i].Score > results[j].Score
}
return results[i].Pattern < results[j].Pattern
})
if len(results) > top {
results = results[:top]
}
margin := results[0].Score
if len(results) > 1 {
margin -= results[1].Score
}
for i := range results {
results[i].ActualPattern = item.PatternSignature
results[i].Rank = i + 1
results[i].Correct = results[i].Pattern == item.PatternSignature
results[i].Accepted = results[0].Score >= minScore && margin >= minMargin
}
for _, result := range results {
writeLine(result)
}
})
}
func features(item event) map[string]struct{} {
result := map[string]struct{}{}
add := func(key, value string) {
if value != "" {
result[key+"="+value] = struct{}{}
}
}
add("service", item.Service)
add("kind", item.Kind)
add("source", item.Source)
add("status", item.StatusClass)
if item.StatusCode > 0 {
add("statusCode", strconv.Itoa(int(item.StatusCode)))
}
add("route", item.Route)
add("span", item.SpanName)
add("metric", item.MetricName)
add("metricType", item.MetricType)
add("exception", item.ExceptionType)
add("message", item.Message)
add("stack", item.StackSignature)
return result
}
func similarity(input map[string]struct{}, prototype map[string]float64) (float64, []string) {
if len(input) == 0 || len(prototype) == 0 {
return 0, nil
}
var dot, inputNorm, protoNorm float64
matches := []string{}
for token := range input {
inputNorm++
if weight, ok := prototype[token]; ok {
dot += weight
matches = append(matches, token)
}
}
for _, weight := range prototype {
protoNorm += weight * weight
}
if protoNorm == 0 {
return 0, matches
}
score := dot / math.Sqrt(inputNorm*protoNorm)
sort.Strings(matches)
return score, matches
}
func forEachEvent(path string, callback func(event)) {
file, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Buffer(make([]byte, 64*1024), 4*1024*1024)
for scanner.Scan() {
var item event
if err := json.Unmarshal(scanner.Bytes(), &item); err != nil {
log.Fatal(err)
}
callback(item)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
func writeJSON(path string, value any) {
data, err := json.MarshalIndent(value, "", " ")
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile(path, data, 0600); err != nil {
log.Fatal(err)
}
}
func readJSON(path string, value any) {
data, err := os.ReadFile(path)
if err != nil {
log.Fatal(err)
}
if err := json.Unmarshal(data, value); err != nil {
log.Fatal(err)
}
}
func writeLine(value any) {
data, err := json.Marshal(value)
if err != nil {
log.Fatal(err)
}
fmt.Println(strings.TrimSpace(string(data)))
}