Skip to content

Commit da15e6d

Browse files
chore: add disk based accuracy storage for local runs
1 parent 0516ea3 commit da15e6d

File tree

4 files changed

+137
-10
lines changed

4 files changed

+137
-10
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ state.json
1111

1212
tests/tmp
1313
coverage
14+
.accuracy-snapshots
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import path from "path";
2+
import fs from "fs/promises";
3+
import { fileURLToPath } from "url";
4+
import {
5+
AccuracyRunStatus,
6+
AccuracySnapshotEntry,
7+
AccuracySnapshotEntrySchema,
8+
AccuracySnapshotStorage,
9+
} from "./snapshot-storage.js";
10+
const __dirname = fileURLToPath(import.meta.url);
11+
const rootDir = path.resolve(__dirname, "..", "..", "..", "..", "..");
12+
const snapshotsDir = path.resolve(rootDir, ".accuracy-snapshots");
13+
export const snapshotFilePath = path.resolve(snapshotsDir, "snapshots.json");
14+
15+
export class DiskSnapshotStorage implements AccuracySnapshotStorage {
16+
private constructor(
17+
private readonly accuracyRunId: string,
18+
private readonly commitSHA: string
19+
) {}
20+
21+
async createSnapshotEntry(
22+
snapshotEntry: Pick<
23+
AccuracySnapshotEntry,
24+
| "provider"
25+
| "requestedModel"
26+
| "test"
27+
| "prompt"
28+
| "toolCallingAccuracy"
29+
| "expectedToolCalls"
30+
| "actualToolCalls"
31+
| "llmResponseTime"
32+
| "tokensUsage"
33+
| "respondingModel"
34+
| "text"
35+
| "messages"
36+
>
37+
): Promise<void> {
38+
const snapshotWithMeta: AccuracySnapshotEntry = {
39+
...snapshotEntry,
40+
commitSHA: this.commitSHA,
41+
accuracyRunId: this.accuracyRunId,
42+
accuracyRunStatus: AccuracyRunStatus.InProgress,
43+
createdOn: Date.now(),
44+
};
45+
46+
await this.appendAccuracySnapshot(snapshotWithMeta);
47+
}
48+
49+
async getLatestSnapshotsForCommit(commit: string): Promise<AccuracySnapshotEntry[]> {
50+
const snapshot = await this.readSnapshot();
51+
const entries = snapshot
52+
.filter((entry) => {
53+
return entry.commitSHA === commit && entry.accuracyRunStatus === AccuracyRunStatus.Done;
54+
})
55+
.sort((a, b) => b.createdOn - a.createdOn);
56+
const latestRunId = entries[0]?.accuracyRunId;
57+
return latestRunId ? snapshot.filter((entry) => entry.accuracyRunId === latestRunId) : [];
58+
}
59+
60+
async accuracyRunFinished(): Promise<void> {
61+
const snapshot = await this.readSnapshot();
62+
const updatedSnapshot = snapshot.map((entry) => {
63+
if (entry.accuracyRunId === this.accuracyRunId) {
64+
return {
65+
...entry,
66+
accuracyRunStatus: AccuracyRunStatus.Done,
67+
};
68+
}
69+
70+
return entry;
71+
});
72+
await this.writeSnapshot(updatedSnapshot);
73+
}
74+
75+
close(): Promise<void> {
76+
return Promise.resolve();
77+
}
78+
79+
private async appendAccuracySnapshot(entry: AccuracySnapshotEntry): Promise<void> {
80+
for (let attempt = 0; attempt < 5; attempt++) {
81+
try {
82+
const snapshot = await this.readSnapshot();
83+
snapshot.unshift(entry);
84+
await this.writeSnapshot(snapshot);
85+
return;
86+
} catch (e) {
87+
if (attempt < 4) {
88+
await this.waitFor(100 + Math.random() * 200);
89+
} else {
90+
throw e;
91+
}
92+
}
93+
}
94+
}
95+
96+
private async writeSnapshot(snapshot: AccuracySnapshotEntry[]): Promise<void> {
97+
const tmp = `${snapshotFilePath}~${Date.now()}`;
98+
await fs.writeFile(tmp, JSON.stringify(snapshot, null, 2));
99+
await fs.rename(tmp, snapshotFilePath);
100+
}
101+
102+
private async readSnapshot(): Promise<AccuracySnapshotEntry[]> {
103+
try {
104+
const raw = await fs.readFile(snapshotFilePath, "utf8");
105+
return AccuracySnapshotEntrySchema.array().parse(JSON.parse(raw));
106+
} catch (e: unknown) {
107+
if ((e as { code: string }).code === "ENOENT") {
108+
return [];
109+
}
110+
throw e;
111+
}
112+
}
113+
114+
private waitFor(ms: number) {
115+
return new Promise((resolve) => setTimeout(resolve, ms));
116+
}
117+
118+
static async getStorage(commitSHA: string, accuracyRunId: string) {
119+
await fs.mkdir(snapshotsDir, { recursive: true });
120+
return new DiskSnapshotStorage(commitSHA, accuracyRunId);
121+
}
122+
}

tests/accuracy/sdk/accuracy-snapshot-storage/get-snapshot-storage.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getCommitSHA } from "../git-info.js";
2+
import { DiskSnapshotStorage } from "./disk-snapshot-storage.js";
23
import { MongoDBSnapshotStorage } from "./mdb-snapshot-storage.js";
34
import { AccuracySnapshotStorage } from "./snapshot-storage.js";
45

@@ -15,5 +16,8 @@ export async function getAccuracySnapshotStorage(): Promise<AccuracySnapshotStor
1516
throw new Error("Cannot create AccuracySnapshotStorage without a commitSHA.");
1617
}
1718

18-
return MongoDBSnapshotStorage.getStorage(commitSHA, accuracyRunId);
19+
return (
20+
MongoDBSnapshotStorage.getStorage(commitSHA, accuracyRunId) ??
21+
(await DiskSnapshotStorage.getStorage(commitSHA, accuracyRunId))
22+
);
1923
}

tests/accuracy/sdk/accuracy-snapshot-storage/mdb-snapshot-storage.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ export class MongoDBSnapshotStorage implements AccuracySnapshotStorage {
5858
}
5959

6060
async getLatestSnapshotsForCommit(commit: string): Promise<AccuracySnapshotEntry[]> {
61-
const latestRunId = await this.getLastRunIdForCommit(commit);
61+
const latestRunId = await this.getLatestAccuracyRunForCommit(commit);
6262
return latestRunId ? this.getSnapshotEntriesForRunId(latestRunId) : [];
6363
}
6464

65-
private async getLastRunIdForCommit(commit: string): Promise<string | undefined> {
65+
private async getLatestAccuracyRunForCommit(commit: string): Promise<string | undefined> {
6666
const document = await this.snapshotCollection.findOne(
67-
{ commit: commit },
67+
{ commit: commit, accuracyRunStatus: AccuracyRunStatus.Done },
6868
{ sort: { createdOn: -1 }, projection: { accuracyRunId: 1 } }
6969
);
7070

@@ -83,12 +83,16 @@ export class MongoDBSnapshotStorage implements AccuracySnapshotStorage {
8383
);
8484
}
8585

86-
static getStorage(commitSHA: string, accuracyRunId: string): MongoDBSnapshotStorage {
86+
async close(): Promise<void> {
87+
await this.client.close();
88+
}
89+
90+
static getStorage(commitSHA: string, accuracyRunId: string): MongoDBSnapshotStorage | null {
8791
const mongodbUrl = process.env.MDB_ACCURACY_MDB_URL;
8892
const database = process.env.MDB_ACCURACY_MDB_DB;
8993
const collection = process.env.MDB_ACCURACY_MDB_COLLECTION;
9094
if (!mongodbUrl || !database || !collection) {
91-
throw new Error("Cannot create MongoDBAccuracySnapshot storage without relevant configuration provided");
95+
return null;
9296
}
9397

9498
return new MongoDBSnapshotStorage({
@@ -99,8 +103,4 @@ export class MongoDBSnapshotStorage implements AccuracySnapshotStorage {
99103
accuracyRunId,
100104
});
101105
}
102-
103-
async close(): Promise<void> {
104-
await this.client.close();
105-
}
106106
}

0 commit comments

Comments
 (0)