mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-11 03:37:40 +08:00
refactor: replace processed output comparison with baseline-based validation in SSE simulator
This commit is contained in:
@@ -26,51 +26,29 @@ type CaptureSummary struct {
|
||||
FinishedTokenCount int `json:"finished_token_count,omitempty"`
|
||||
}
|
||||
|
||||
type ProcessedSummary struct {
|
||||
Kind string `json:"kind"`
|
||||
File string `json:"file"`
|
||||
TextFile string `json:"text_file,omitempty"`
|
||||
StatusCode int `json:"status_code"`
|
||||
ContentType string `json:"content_type,omitempty"`
|
||||
ResponseBytes int `json:"response_bytes"`
|
||||
ContainsReferenceMarkers bool `json:"contains_reference_markers,omitempty"`
|
||||
ReferenceMarkerCount int `json:"reference_marker_count,omitempty"`
|
||||
ContainsFinishedToken bool `json:"contains_finished_token,omitempty"`
|
||||
FinishedTokenCount int `json:"finished_token_count,omitempty"`
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
SampleID string `json:"sample_id"`
|
||||
CapturedAtUTC string `json:"captured_at_utc"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Request any `json:"request"`
|
||||
Capture CaptureSummary `json:"capture"`
|
||||
Processed ProcessedSummary `json:"processed"`
|
||||
SampleID string `json:"sample_id"`
|
||||
CapturedAtUTC string `json:"captured_at_utc"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Request any `json:"request"`
|
||||
Capture CaptureSummary `json:"capture"`
|
||||
}
|
||||
|
||||
type PersistOptions struct {
|
||||
RootDir string
|
||||
SampleID string
|
||||
Source string
|
||||
Request any
|
||||
Capture CaptureSummary
|
||||
UpstreamBody []byte
|
||||
ProcessedBody []byte
|
||||
ProcessedKind string
|
||||
ProcessedFile string
|
||||
ProcessedStatusCode int
|
||||
ProcessedContentType string
|
||||
ProcessedText string
|
||||
RootDir string
|
||||
SampleID string
|
||||
Source string
|
||||
Request any
|
||||
Capture CaptureSummary
|
||||
UpstreamBody []byte
|
||||
}
|
||||
|
||||
type SavedSample struct {
|
||||
SampleID string
|
||||
Dir string
|
||||
MetaPath string
|
||||
UpstreamPath string
|
||||
ProcessedPath string
|
||||
OutputPath string
|
||||
Meta Meta
|
||||
SampleID string
|
||||
Dir string
|
||||
MetaPath string
|
||||
UpstreamPath string
|
||||
Meta Meta
|
||||
}
|
||||
|
||||
func Persist(opts PersistOptions) (SavedSample, error) {
|
||||
@@ -81,9 +59,6 @@ func Persist(opts PersistOptions) (SavedSample, error) {
|
||||
if len(opts.UpstreamBody) == 0 {
|
||||
return SavedSample{}, errors.New("upstream body is required")
|
||||
}
|
||||
if len(opts.ProcessedBody) == 0 {
|
||||
return SavedSample{}, errors.New("processed body is required")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(root, 0o755); err != nil {
|
||||
return SavedSample{}, fmt.Errorf("create root dir: %w", err)
|
||||
@@ -114,56 +89,17 @@ func Persist(opts PersistOptions) (SavedSample, error) {
|
||||
return SavedSample{}, fmt.Errorf("write upstream stream: %w", err)
|
||||
}
|
||||
|
||||
processedKind := strings.TrimSpace(opts.ProcessedKind)
|
||||
if processedKind == "" {
|
||||
processedKind = inferProcessedKind(opts.ProcessedContentType)
|
||||
}
|
||||
processedFile := strings.TrimSpace(opts.ProcessedFile)
|
||||
if processedFile == "" {
|
||||
processedFile = defaultProcessedFile(processedKind, opts.ProcessedContentType)
|
||||
}
|
||||
if processedFile == "" {
|
||||
cleanup()
|
||||
return SavedSample{}, errors.New("processed file name is required")
|
||||
}
|
||||
processedPath := filepath.Join(tempDir, processedFile)
|
||||
if err := os.WriteFile(processedPath, opts.ProcessedBody, 0o644); err != nil {
|
||||
cleanup()
|
||||
return SavedSample{}, fmt.Errorf("write processed output: %w", err)
|
||||
}
|
||||
|
||||
processedText := opts.ProcessedText
|
||||
if processedText == "" {
|
||||
processedText = extractProcessedVisibleText(opts.ProcessedBody, processedKind, opts.ProcessedContentType)
|
||||
}
|
||||
textPath := filepath.Join(tempDir, "openai.output.txt")
|
||||
if err := os.WriteFile(textPath, []byte(processedText), 0o644); err != nil {
|
||||
cleanup()
|
||||
return SavedSample{}, fmt.Errorf("write processed text: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
capture := opts.Capture
|
||||
capture.ResponseBytes = len(opts.UpstreamBody)
|
||||
capture.ContainsReferenceMarkers, capture.ReferenceMarkerCount, capture.ContainsFinishedToken, capture.FinishedTokenCount = analyzeBytes(opts.UpstreamBody)
|
||||
|
||||
processed := ProcessedSummary{
|
||||
Kind: processedKind,
|
||||
File: processedFile,
|
||||
TextFile: "openai.output.txt",
|
||||
StatusCode: opts.ProcessedStatusCode,
|
||||
ContentType: strings.TrimSpace(opts.ProcessedContentType),
|
||||
ResponseBytes: len(opts.ProcessedBody),
|
||||
}
|
||||
processed.ContainsReferenceMarkers, processed.ReferenceMarkerCount, processed.ContainsFinishedToken, processed.FinishedTokenCount = analyzeBytes(opts.ProcessedBody)
|
||||
|
||||
meta := Meta{
|
||||
SampleID: sampleID,
|
||||
CapturedAtUTC: now.Format(time.RFC3339),
|
||||
Source: strings.TrimSpace(opts.Source),
|
||||
Request: opts.Request,
|
||||
Capture: capture,
|
||||
Processed: processed,
|
||||
}
|
||||
metaBytes, err := json.MarshalIndent(meta, "", " ")
|
||||
if err != nil {
|
||||
@@ -182,13 +118,11 @@ func Persist(opts PersistOptions) (SavedSample, error) {
|
||||
}
|
||||
|
||||
return SavedSample{
|
||||
SampleID: sampleID,
|
||||
Dir: finalDir,
|
||||
MetaPath: filepath.Join(finalDir, "meta.json"),
|
||||
UpstreamPath: filepath.Join(finalDir, "upstream.stream.sse"),
|
||||
ProcessedPath: filepath.Join(finalDir, processedFile),
|
||||
OutputPath: filepath.Join(finalDir, "openai.output.txt"),
|
||||
Meta: meta,
|
||||
SampleID: sampleID,
|
||||
Dir: finalDir,
|
||||
MetaPath: filepath.Join(finalDir, "meta.json"),
|
||||
UpstreamPath: filepath.Join(finalDir, "upstream.stream.sse"),
|
||||
Meta: meta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -226,33 +160,6 @@ func DefaultSampleID(prefix string) string {
|
||||
return fmt.Sprintf("%s-%s", prefix, time.Now().UTC().Format("20060102T150405Z"))
|
||||
}
|
||||
|
||||
func inferProcessedKind(contentType string) string {
|
||||
ct := strings.ToLower(strings.TrimSpace(contentType))
|
||||
switch {
|
||||
case strings.Contains(ct, "text/event-stream"):
|
||||
return "stream"
|
||||
case strings.Contains(ct, "application/json"):
|
||||
return "json"
|
||||
default:
|
||||
return "stream"
|
||||
}
|
||||
}
|
||||
|
||||
func defaultProcessedFile(kind, contentType string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(kind)) {
|
||||
case "json":
|
||||
return "openai.response.json"
|
||||
case "stream":
|
||||
return "openai.stream.sse"
|
||||
}
|
||||
switch inferProcessedKind(contentType) {
|
||||
case "json":
|
||||
return "openai.response.json"
|
||||
default:
|
||||
return "openai.stream.sse"
|
||||
}
|
||||
}
|
||||
|
||||
func uniqueSampleID(root, base string) (string, error) {
|
||||
if base == "" {
|
||||
base = DefaultSampleID("capture")
|
||||
|
||||
@@ -35,9 +35,6 @@ func TestPersistWritesSampleFilesAndMeta(t *testing.T) {
|
||||
},
|
||||
UpstreamBody: []byte("data: {\"v\":\"hello [reference:1]\"}\n\n" +
|
||||
"data: {\"v\":\"FINISHED\",\"p\":\"response/status\"}\n\n"),
|
||||
ProcessedBody: []byte("data: {\"choices\":[{\"delta\":{\"content\":\"hello\"},\"index\":0}],\"created\":1,\"id\":\"id\",\"model\":\"m\",\"object\":\"chat.completion.chunk\"}\n\n"),
|
||||
ProcessedStatusCode: 200,
|
||||
ProcessedContentType: "text/event-stream",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Persist failed: %v", err)
|
||||
@@ -52,14 +49,11 @@ func TestPersistWritesSampleFilesAndMeta(t *testing.T) {
|
||||
if _, err := os.Stat(saved.UpstreamPath); err != nil {
|
||||
t.Fatalf("upstream file missing: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(saved.ProcessedPath); err != nil {
|
||||
t.Fatalf("processed file missing: %v", err)
|
||||
if _, err := os.Stat(filepath.Join(saved.Dir, "openai.stream.sse")); !os.IsNotExist(err) {
|
||||
t.Fatalf("unexpected processed stream file: %v", err)
|
||||
}
|
||||
if saved.OutputPath != filepath.Join(saved.Dir, "openai.output.txt") {
|
||||
t.Fatalf("unexpected processed text path: %s", saved.OutputPath)
|
||||
}
|
||||
if _, err := os.Stat(saved.OutputPath); err != nil {
|
||||
t.Fatalf("processed text file missing: %v", err)
|
||||
if _, err := os.Stat(filepath.Join(saved.Dir, "openai.output.txt")); !os.IsNotExist(err) {
|
||||
t.Fatalf("unexpected processed text file: %v", err)
|
||||
}
|
||||
|
||||
metaBytes, err := os.ReadFile(saved.MetaPath)
|
||||
@@ -79,16 +73,7 @@ func TestPersistWritesSampleFilesAndMeta(t *testing.T) {
|
||||
if meta.Capture.FinishedTokenCount != 1 {
|
||||
t.Fatalf("expected one finished token, got %+v", meta.Capture)
|
||||
}
|
||||
if meta.Processed.File != "openai.stream.sse" {
|
||||
t.Fatalf("expected stream processed file, got %+v", meta.Processed)
|
||||
}
|
||||
if meta.Processed.TextFile != "openai.output.txt" {
|
||||
t.Fatalf("expected text file metadata, got %+v", meta.Processed)
|
||||
}
|
||||
if meta.Processed.ResponseBytes == 0 {
|
||||
t.Fatalf("expected processed bytes to be recorded, got %+v", meta.Processed)
|
||||
}
|
||||
if !strings.HasSuffix(saved.ProcessedPath, filepath.Join(saved.SampleID, "openai.stream.sse")) {
|
||||
t.Fatalf("unexpected processed path: %s", saved.ProcessedPath)
|
||||
if strings.Contains(string(metaBytes), "\"processed\"") {
|
||||
t.Fatalf("meta should not include processed payload: %s", string(metaBytes))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user