@mzhub/promptc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +224 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +111 -0
- package/dist/cli.js.map +1 -0
- package/dist/compiler/BootstrapFewShot.d.ts +13 -0
- package/dist/compiler/BootstrapFewShot.d.ts.map +1 -0
- package/dist/compiler/BootstrapFewShot.js +93 -0
- package/dist/compiler/BootstrapFewShot.js.map +1 -0
- package/dist/compiler/CandidatePool.d.ts +10 -0
- package/dist/compiler/CandidatePool.d.ts.map +1 -0
- package/dist/compiler/CandidatePool.js +29 -0
- package/dist/compiler/CandidatePool.js.map +1 -0
- package/dist/compiler/CompiledProgram.d.ts +43 -0
- package/dist/compiler/CompiledProgram.d.ts.map +1 -0
- package/dist/compiler/CompiledProgram.js +41 -0
- package/dist/compiler/CompiledProgram.js.map +1 -0
- package/dist/compiler/InstructionRewrite.d.ts +19 -0
- package/dist/compiler/InstructionRewrite.d.ts.map +1 -0
- package/dist/compiler/InstructionRewrite.js +117 -0
- package/dist/compiler/InstructionRewrite.js.map +1 -0
- package/dist/compiler/index.d.ts +8 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +5 -0
- package/dist/compiler/index.js.map +1 -0
- package/dist/compiler/types.d.ts +41 -0
- package/dist/compiler/types.d.ts.map +1 -0
- package/dist/compiler/types.js +2 -0
- package/dist/compiler/types.js.map +1 -0
- package/dist/eval/exactMatch.d.ts +5 -0
- package/dist/eval/exactMatch.d.ts.map +1 -0
- package/dist/eval/exactMatch.js +58 -0
- package/dist/eval/exactMatch.js.map +1 -0
- package/dist/eval/index.d.ts +5 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +3 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/llmJudge.d.ts +9 -0
- package/dist/eval/llmJudge.d.ts.map +1 -0
- package/dist/eval/llmJudge.js +33 -0
- package/dist/eval/llmJudge.js.map +1 -0
- package/dist/eval/types.d.ts +2 -0
- package/dist/eval/types.d.ts.map +1 -0
- package/dist/eval/types.js +2 -0
- package/dist/eval/types.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/program/ChainOfThought.d.ts +6 -0
- package/dist/program/ChainOfThought.d.ts.map +1 -0
- package/dist/program/ChainOfThought.js +44 -0
- package/dist/program/ChainOfThought.js.map +1 -0
- package/dist/program/Predict.d.ts +6 -0
- package/dist/program/Predict.d.ts.map +1 -0
- package/dist/program/Predict.js +33 -0
- package/dist/program/Predict.js.map +1 -0
- package/dist/program/Program.d.ts +33 -0
- package/dist/program/Program.d.ts.map +1 -0
- package/dist/program/Program.js +28 -0
- package/dist/program/Program.js.map +1 -0
- package/dist/program/index.d.ts +5 -0
- package/dist/program/index.d.ts.map +1 -0
- package/dist/program/index.js +4 -0
- package/dist/program/index.js.map +1 -0
- package/dist/providers/anthropic.d.ts +10 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +40 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/cerebras.d.ts +10 -0
- package/dist/providers/cerebras.d.ts.map +1 -0
- package/dist/providers/cerebras.js +39 -0
- package/dist/providers/cerebras.js.map +1 -0
- package/dist/providers/google.d.ts +10 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +42 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/groq.d.ts +10 -0
- package/dist/providers/groq.d.ts.map +1 -0
- package/dist/providers/groq.js +42 -0
- package/dist/providers/groq.js.map +1 -0
- package/dist/providers/index.d.ts +11 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +31 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/ollama.d.ts +9 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +39 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +10 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +42 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +25 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/runtime/cache.d.ts +18 -0
- package/dist/runtime/cache.d.ts.map +1 -0
- package/dist/runtime/cache.js +45 -0
- package/dist/runtime/cache.js.map +1 -0
- package/dist/runtime/concurrency.d.ts +7 -0
- package/dist/runtime/concurrency.d.ts.map +1 -0
- package/dist/runtime/concurrency.js +14 -0
- package/dist/runtime/concurrency.js.map +1 -0
- package/dist/runtime/costTracker.d.ts +24 -0
- package/dist/runtime/costTracker.d.ts.map +1 -0
- package/dist/runtime/costTracker.js +37 -0
- package/dist/runtime/costTracker.js.map +1 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/retry.d.ts +10 -0
- package/dist/runtime/retry.d.ts.map +1 -0
- package/dist/runtime/retry.js +39 -0
- package/dist/runtime/retry.js.map +1 -0
- package/dist/schema/defineSchema.d.ts +18 -0
- package/dist/schema/defineSchema.d.ts.map +1 -0
- package/dist/schema/defineSchema.js +27 -0
- package/dist/schema/defineSchema.js.map +1 -0
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/index.js.map +1 -0
- package/examples/README.md +42 -0
- package/examples/load-compiled.ts +62 -0
- package/examples/multi-provider.ts +77 -0
- package/examples/name-extractor.ts +113 -0
- package/examples/qa-system.ts +98 -0
- package/package.json +62 -0
- package/src/cli.ts +122 -0
- package/src/compiler/BootstrapFewShot.ts +149 -0
- package/src/compiler/CandidatePool.ts +39 -0
- package/src/compiler/CompiledProgram.ts +112 -0
- package/src/compiler/InstructionRewrite.ts +200 -0
- package/src/compiler/index.ts +19 -0
- package/src/compiler/types.ts +46 -0
- package/src/eval/exactMatch.ts +65 -0
- package/src/eval/index.ts +4 -0
- package/src/eval/llmJudge.ts +45 -0
- package/src/eval/types.ts +4 -0
- package/src/index.ts +71 -0
- package/src/program/ChainOfThought.ts +59 -0
- package/src/program/Predict.ts +47 -0
- package/src/program/Program.ts +64 -0
- package/src/program/index.ts +4 -0
- package/src/providers/anthropic.ts +55 -0
- package/src/providers/cerebras.ts +53 -0
- package/src/providers/google.ts +57 -0
- package/src/providers/groq.ts +57 -0
- package/src/providers/index.ts +50 -0
- package/src/providers/ollama.ts +54 -0
- package/src/providers/openai.ts +57 -0
- package/src/providers/types.ts +27 -0
- package/src/runtime/cache.ts +65 -0
- package/src/runtime/concurrency.ts +21 -0
- package/src/runtime/costTracker.ts +58 -0
- package/src/runtime/index.ts +8 -0
- package/src/runtime/retry.ts +59 -0
- package/src/schema/defineSchema.ts +44 -0
- package/src/schema/index.ts +2 -0
- package/tests/candidatePool.test.ts +46 -0
- package/tests/evaluators.test.ts +69 -0
- package/tests/runtime.test.ts +106 -0
- package/tests/schema.test.ts +59 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LLMProvider,
|
|
3
|
+
CompletionParams,
|
|
4
|
+
CompletionResult,
|
|
5
|
+
ProviderConfig,
|
|
6
|
+
} from "./types.js";
|
|
7
|
+
|
|
8
|
+
export class OllamaProvider implements LLMProvider {
|
|
9
|
+
name = "ollama";
|
|
10
|
+
defaultModel: string;
|
|
11
|
+
private baseUrl: string;
|
|
12
|
+
|
|
13
|
+
constructor(config: ProviderConfig = {}) {
|
|
14
|
+
this.baseUrl = config.baseUrl || "http://localhost:11434";
|
|
15
|
+
this.defaultModel = config.defaultModel || "llama3.2";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async complete(params: CompletionParams): Promise<CompletionResult> {
|
|
19
|
+
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify({
|
|
25
|
+
model: params.model || this.defaultModel,
|
|
26
|
+
prompt: params.prompt,
|
|
27
|
+
stream: false,
|
|
28
|
+
options: {
|
|
29
|
+
temperature: params.temperature ?? 0.7,
|
|
30
|
+
num_predict: params.maxTokens ?? 1024,
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const error = await response.text();
|
|
37
|
+
throw new Error(`Ollama API error: ${response.status} - ${error}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const data = (await response.json()) as {
|
|
41
|
+
response: string;
|
|
42
|
+
prompt_eval_count?: number;
|
|
43
|
+
eval_count?: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
content: data.response,
|
|
48
|
+
usage: {
|
|
49
|
+
inputTokens: data.prompt_eval_count || 0,
|
|
50
|
+
outputTokens: data.eval_count || 0,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LLMProvider,
|
|
3
|
+
CompletionParams,
|
|
4
|
+
CompletionResult,
|
|
5
|
+
ProviderConfig,
|
|
6
|
+
} from "./types.js";
|
|
7
|
+
|
|
8
|
+
export class OpenAIProvider implements LLMProvider {
|
|
9
|
+
name = "openai";
|
|
10
|
+
defaultModel: string;
|
|
11
|
+
private apiKey: string;
|
|
12
|
+
private baseUrl: string;
|
|
13
|
+
|
|
14
|
+
constructor(config: ProviderConfig) {
|
|
15
|
+
this.apiKey = config.apiKey || process.env.OPENAI_API_KEY || "";
|
|
16
|
+
this.baseUrl = config.baseUrl || "https://api.openai.com/v1";
|
|
17
|
+
this.defaultModel = config.defaultModel || "gpt-4o-mini";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async complete(params: CompletionParams): Promise<CompletionResult> {
|
|
21
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
model: params.model || this.defaultModel,
|
|
29
|
+
messages: [{ role: "user", content: params.prompt }],
|
|
30
|
+
temperature: params.temperature ?? 0.7,
|
|
31
|
+
max_tokens: params.maxTokens ?? 1024,
|
|
32
|
+
response_format:
|
|
33
|
+
params.responseFormat === "json"
|
|
34
|
+
? { type: "json_object" }
|
|
35
|
+
: undefined,
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const error = await response.text();
|
|
41
|
+
throw new Error(`OpenAI API error: ${response.status} - ${error}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const data = (await response.json()) as {
|
|
45
|
+
choices: Array<{ message: { content: string } }>;
|
|
46
|
+
usage: { prompt_tokens: number; completion_tokens: number };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
content: data.choices[0].message.content,
|
|
51
|
+
usage: {
|
|
52
|
+
inputTokens: data.usage.prompt_tokens,
|
|
53
|
+
outputTokens: data.usage.completion_tokens,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface CompletionParams {
|
|
2
|
+
prompt: string;
|
|
3
|
+
model?: string;
|
|
4
|
+
temperature?: number;
|
|
5
|
+
maxTokens?: number;
|
|
6
|
+
responseFormat?: "text" | "json";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CompletionResult {
|
|
10
|
+
content: string;
|
|
11
|
+
usage: {
|
|
12
|
+
inputTokens: number;
|
|
13
|
+
outputTokens: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LLMProvider {
|
|
18
|
+
name: string;
|
|
19
|
+
defaultModel: string;
|
|
20
|
+
complete(params: CompletionParams): Promise<CompletionResult>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ProviderConfig {
|
|
24
|
+
apiKey?: string;
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
defaultModel?: string;
|
|
27
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export interface CacheOptions {
|
|
2
|
+
maxSize?: number;
|
|
3
|
+
ttlMs?: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface CacheEntry<T> {
|
|
7
|
+
value: T;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class PromptCache<T> {
|
|
12
|
+
private cache = new Map<string, CacheEntry<T>>();
|
|
13
|
+
private maxSize: number;
|
|
14
|
+
private ttlMs: number;
|
|
15
|
+
|
|
16
|
+
constructor(options: CacheOptions = {}) {
|
|
17
|
+
this.maxSize = options.maxSize ?? 1000;
|
|
18
|
+
this.ttlMs = options.ttlMs ?? 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private makeKey(prompt: string, input: unknown): string {
|
|
22
|
+
return `${prompt}::${JSON.stringify(input)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get(prompt: string, input: unknown): T | undefined {
|
|
26
|
+
const key = this.makeKey(prompt, input);
|
|
27
|
+
const entry = this.cache.get(key);
|
|
28
|
+
|
|
29
|
+
if (!entry) return undefined;
|
|
30
|
+
|
|
31
|
+
if (this.ttlMs > 0 && Date.now() - entry.timestamp > this.ttlMs) {
|
|
32
|
+
this.cache.delete(key);
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return entry.value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
set(prompt: string, input: unknown, value: T): void {
|
|
40
|
+
const key = this.makeKey(prompt, input);
|
|
41
|
+
|
|
42
|
+
if (this.cache.size >= this.maxSize) {
|
|
43
|
+
const firstKey = this.cache.keys().next().value;
|
|
44
|
+
if (firstKey) this.cache.delete(firstKey);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.cache.set(key, { value, timestamp: Date.now() });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
has(prompt: string, input: unknown): boolean {
|
|
51
|
+
return this.get(prompt, input) !== undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
clear(): void {
|
|
55
|
+
this.cache.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get size(): number {
|
|
59
|
+
return this.cache.size;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createCache<T>(options: CacheOptions = {}): PromptCache<T> {
|
|
64
|
+
return new PromptCache<T>(options);
|
|
65
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import pLimit from "p-limit";
|
|
2
|
+
|
|
3
|
+
export interface ConcurrencyManager {
|
|
4
|
+
run<T>(fn: () => Promise<T>): Promise<T>;
|
|
5
|
+
get pending(): number;
|
|
6
|
+
get active(): number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createConcurrencyManager(limit: number): ConcurrencyManager {
|
|
10
|
+
const limiter = pLimit(limit);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
run: <T>(fn: () => Promise<T>) => limiter(fn),
|
|
14
|
+
get pending() {
|
|
15
|
+
return limiter.pendingCount;
|
|
16
|
+
},
|
|
17
|
+
get active() {
|
|
18
|
+
return limiter.activeCount;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface TokenUsage {
|
|
2
|
+
inputTokens: number;
|
|
3
|
+
outputTokens: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CostEstimate {
|
|
7
|
+
inputTokens: number;
|
|
8
|
+
outputTokens: number;
|
|
9
|
+
totalTokens: number;
|
|
10
|
+
estimatedCalls: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class CostTracker {
|
|
14
|
+
private totalInputTokens = 0;
|
|
15
|
+
private totalOutputTokens = 0;
|
|
16
|
+
private callCount = 0;
|
|
17
|
+
|
|
18
|
+
record(usage: TokenUsage): void {
|
|
19
|
+
this.totalInputTokens += usage.inputTokens;
|
|
20
|
+
this.totalOutputTokens += usage.outputTokens;
|
|
21
|
+
this.callCount++;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get total(): TokenUsage & { totalTokens: number; calls: number } {
|
|
25
|
+
return {
|
|
26
|
+
inputTokens: this.totalInputTokens,
|
|
27
|
+
outputTokens: this.totalOutputTokens,
|
|
28
|
+
totalTokens: this.totalInputTokens + this.totalOutputTokens,
|
|
29
|
+
calls: this.callCount,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
exceedsBudget(maxTokens: number): boolean {
|
|
34
|
+
return this.totalInputTokens + this.totalOutputTokens > maxTokens;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
reset(): void {
|
|
38
|
+
this.totalInputTokens = 0;
|
|
39
|
+
this.totalOutputTokens = 0;
|
|
40
|
+
this.callCount = 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function estimateCost(
|
|
45
|
+
candidateCount: number,
|
|
46
|
+
testCaseCount: number,
|
|
47
|
+
avgTokensPerCall: number = 500
|
|
48
|
+
): CostEstimate {
|
|
49
|
+
const estimatedCalls = candidateCount * testCaseCount;
|
|
50
|
+
const totalTokens = estimatedCalls * avgTokensPerCall;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
inputTokens: Math.round(totalTokens * 0.7),
|
|
54
|
+
outputTokens: Math.round(totalTokens * 0.3),
|
|
55
|
+
totalTokens,
|
|
56
|
+
estimatedCalls,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createConcurrencyManager } from "./concurrency.js";
|
|
2
|
+
export type { ConcurrencyManager } from "./concurrency.js";
|
|
3
|
+
export { CostTracker, estimateCost } from "./costTracker.js";
|
|
4
|
+
export type { TokenUsage, CostEstimate } from "./costTracker.js";
|
|
5
|
+
export { withRetry, createRetryWrapper } from "./retry.js";
|
|
6
|
+
export type { RetryOptions } from "./retry.js";
|
|
7
|
+
export { PromptCache, createCache } from "./cache.js";
|
|
8
|
+
export type { CacheOptions } from "./cache.js";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface RetryOptions {
|
|
2
|
+
maxRetries?: number;
|
|
3
|
+
initialDelayMs?: number;
|
|
4
|
+
maxDelayMs?: number;
|
|
5
|
+
backoffMultiplier?: number;
|
|
6
|
+
retryOn?: (error: Error) => boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const defaultRetryOptions: Required<RetryOptions> = {
|
|
10
|
+
maxRetries: 3,
|
|
11
|
+
initialDelayMs: 1000,
|
|
12
|
+
maxDelayMs: 30000,
|
|
13
|
+
backoffMultiplier: 2,
|
|
14
|
+
retryOn: (error: Error) => {
|
|
15
|
+
const message = error.message.toLowerCase();
|
|
16
|
+
return (
|
|
17
|
+
message.includes("rate limit") ||
|
|
18
|
+
message.includes("429") ||
|
|
19
|
+
message.includes("503") ||
|
|
20
|
+
message.includes("timeout")
|
|
21
|
+
);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function sleep(ms: number): Promise<void> {
|
|
26
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function withRetry<T>(
|
|
30
|
+
fn: () => Promise<T>,
|
|
31
|
+
options: RetryOptions = {}
|
|
32
|
+
): Promise<T> {
|
|
33
|
+
const opts = { ...defaultRetryOptions, ...options };
|
|
34
|
+
let lastError: Error | null = null;
|
|
35
|
+
let delay = opts.initialDelayMs;
|
|
36
|
+
|
|
37
|
+
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
38
|
+
try {
|
|
39
|
+
return await fn();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
42
|
+
|
|
43
|
+
if (attempt === opts.maxRetries || !opts.retryOn(lastError)) {
|
|
44
|
+
throw lastError;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await sleep(delay);
|
|
48
|
+
delay = Math.min(delay * opts.backoffMultiplier, opts.maxDelayMs);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw lastError;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createRetryWrapper(
|
|
56
|
+
options: RetryOptions = {}
|
|
57
|
+
): <T>(fn: () => Promise<T>) => Promise<T> {
|
|
58
|
+
return <T>(fn: () => Promise<T>) => withRetry(fn, options);
|
|
59
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z, ZodObject, ZodRawShape } from "zod";
|
|
2
|
+
|
|
3
|
+
export interface SchemaDefinition<
|
|
4
|
+
I extends ZodRawShape,
|
|
5
|
+
O extends ZodRawShape
|
|
6
|
+
> {
|
|
7
|
+
description: string;
|
|
8
|
+
inputs: I;
|
|
9
|
+
outputs: O;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class Schema<I extends ZodRawShape, O extends ZodRawShape> {
|
|
13
|
+
public readonly inputSchema: ZodObject<I>;
|
|
14
|
+
public readonly outputSchema: ZodObject<O>;
|
|
15
|
+
public readonly description: string;
|
|
16
|
+
|
|
17
|
+
constructor(definition: SchemaDefinition<I, O>) {
|
|
18
|
+
this.description = definition.description;
|
|
19
|
+
this.inputSchema = z.object(definition.inputs);
|
|
20
|
+
this.outputSchema = z.object(definition.outputs);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
validateInput(input: unknown): z.infer<ZodObject<I>> {
|
|
24
|
+
return this.inputSchema.parse(input);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
validateOutput(output: unknown): z.infer<ZodObject<O>> {
|
|
28
|
+
return this.outputSchema.parse(output);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getInputKeys(): string[] {
|
|
32
|
+
return Object.keys(this.inputSchema.shape);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getOutputKeys(): string[] {
|
|
36
|
+
return Object.keys(this.outputSchema.shape);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function defineSchema<I extends ZodRawShape, O extends ZodRawShape>(
|
|
41
|
+
definition: SchemaDefinition<I, O>
|
|
42
|
+
): Schema<I, O> {
|
|
43
|
+
return new Schema(definition);
|
|
44
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { CandidatePool } from "../src/compiler/CandidatePool.js";
|
|
3
|
+
|
|
4
|
+
describe("CandidatePool", () => {
|
|
5
|
+
const trainset = [
|
|
6
|
+
{ input: { text: "A" }, output: { name: "Alice" } },
|
|
7
|
+
{ input: { text: "B" }, output: { name: "Bob" } },
|
|
8
|
+
{ input: { text: "C" }, output: { name: "Charlie" } },
|
|
9
|
+
{ input: { text: "D" }, output: { name: "Diana" } },
|
|
10
|
+
{ input: { text: "E" }, output: { name: "Eve" } },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
it("should generate candidates with correct count", () => {
|
|
14
|
+
const pool = new CandidatePool(trainset);
|
|
15
|
+
const candidates = pool.generateFewShotCandidates(3, 2);
|
|
16
|
+
|
|
17
|
+
expect(candidates.length).toBe(3);
|
|
18
|
+
candidates.forEach((c) => expect(c.length).toBe(2));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should be deterministic with seed", () => {
|
|
22
|
+
const pool1 = new CandidatePool(trainset, 42);
|
|
23
|
+
const pool2 = new CandidatePool(trainset, 42);
|
|
24
|
+
|
|
25
|
+
const candidates1 = pool1.generateFewShotCandidates(5, 3);
|
|
26
|
+
const candidates2 = pool2.generateFewShotCandidates(5, 3);
|
|
27
|
+
|
|
28
|
+
expect(candidates1).toEqual(candidates2);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should generate different candidates without seed", () => {
|
|
32
|
+
const pool = new CandidatePool(trainset);
|
|
33
|
+
const c1 = pool.generateFewShotCandidates(10, 2);
|
|
34
|
+
const c2 = pool.generateFewShotCandidates(10, 2);
|
|
35
|
+
|
|
36
|
+
const same = c1.every((_, i) => c1[i].every((ex, j) => ex === c2[i][j]));
|
|
37
|
+
expect(same).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should get validation set with correct ratio", () => {
|
|
41
|
+
const pool = new CandidatePool(trainset);
|
|
42
|
+
const validationSet = pool.getValidationSet(0.4);
|
|
43
|
+
|
|
44
|
+
expect(validationSet.length).toBe(2);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
exactMatch,
|
|
4
|
+
partialMatch,
|
|
5
|
+
arrayOverlap,
|
|
6
|
+
} from "../src/eval/exactMatch.js";
|
|
7
|
+
|
|
8
|
+
describe("exactMatch", () => {
|
|
9
|
+
const evaluator = exactMatch<{ name: string }>();
|
|
10
|
+
|
|
11
|
+
it("should return 1.0 for identical objects", () => {
|
|
12
|
+
const score = evaluator({ name: "Alice" }, { name: "Alice" });
|
|
13
|
+
expect(score).toBe(1.0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should return 0.0 for different objects", () => {
|
|
17
|
+
const score = evaluator({ name: "Alice" }, { name: "Bob" });
|
|
18
|
+
expect(score).toBe(0.0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should handle arrays", () => {
|
|
22
|
+
const arrEval = exactMatch<string[]>();
|
|
23
|
+
expect(arrEval(["a", "b"], ["a", "b"])).toBe(1.0);
|
|
24
|
+
expect(arrEval(["a", "b"], ["a", "c"])).toBe(0.0);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("partialMatch", () => {
|
|
29
|
+
const evaluator = partialMatch<{ a: number; b: number; c: number }>();
|
|
30
|
+
|
|
31
|
+
it("should return 1.0 when all fields match", () => {
|
|
32
|
+
const score = evaluator({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 });
|
|
33
|
+
expect(score).toBe(1.0);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should return fraction for partial matches", () => {
|
|
37
|
+
const score = evaluator({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 4 });
|
|
38
|
+
expect(score).toBeCloseTo(2 / 3);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should return 0.0 for no matches", () => {
|
|
42
|
+
const score = evaluator({ a: 9, b: 9, c: 9 }, { a: 1, b: 2, c: 3 });
|
|
43
|
+
expect(score).toBe(0.0);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("arrayOverlap", () => {
|
|
48
|
+
const evaluator = arrayOverlap<string>();
|
|
49
|
+
|
|
50
|
+
it("should return 1.0 for identical arrays", () => {
|
|
51
|
+
const score = evaluator(["a", "b", "c"], ["a", "b", "c"]);
|
|
52
|
+
expect(score).toBe(1.0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should return Jaccard similarity", () => {
|
|
56
|
+
const score = evaluator(["a", "b"], ["b", "c"]);
|
|
57
|
+
expect(score).toBeCloseTo(1 / 3);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should return 0.0 for no overlap", () => {
|
|
61
|
+
const score = evaluator(["a", "b"], ["c", "d"]);
|
|
62
|
+
expect(score).toBe(0.0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should handle empty arrays", () => {
|
|
66
|
+
expect(evaluator([], [])).toBe(1.0);
|
|
67
|
+
expect(evaluator(["a"], [])).toBe(0.0);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { withRetry } from "../src/runtime/retry.js";
|
|
3
|
+
import { PromptCache } from "../src/runtime/cache.js";
|
|
4
|
+
import { CostTracker } from "../src/runtime/costTracker.js";
|
|
5
|
+
|
|
6
|
+
describe("withRetry", () => {
|
|
7
|
+
it("should succeed on first try", async () => {
|
|
8
|
+
let attempts = 0;
|
|
9
|
+
const result = await withRetry(async () => {
|
|
10
|
+
attempts++;
|
|
11
|
+
return "success";
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
expect(result).toBe("success");
|
|
15
|
+
expect(attempts).toBe(1);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should retry on failure and eventually succeed", async () => {
|
|
19
|
+
let attempts = 0;
|
|
20
|
+
const result = await withRetry(
|
|
21
|
+
async () => {
|
|
22
|
+
attempts++;
|
|
23
|
+
if (attempts < 3) {
|
|
24
|
+
throw new Error("rate limit");
|
|
25
|
+
}
|
|
26
|
+
return "success";
|
|
27
|
+
},
|
|
28
|
+
{ maxRetries: 3, initialDelayMs: 10 }
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(result).toBe("success");
|
|
32
|
+
expect(attempts).toBe(3);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should throw after max retries", async () => {
|
|
36
|
+
let attempts = 0;
|
|
37
|
+
await expect(
|
|
38
|
+
withRetry(
|
|
39
|
+
async () => {
|
|
40
|
+
attempts++;
|
|
41
|
+
throw new Error("rate limit");
|
|
42
|
+
},
|
|
43
|
+
{ maxRetries: 2, initialDelayMs: 10 }
|
|
44
|
+
)
|
|
45
|
+
).rejects.toThrow("rate limit");
|
|
46
|
+
|
|
47
|
+
expect(attempts).toBe(3);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("PromptCache", () => {
|
|
52
|
+
it("should cache and retrieve values", () => {
|
|
53
|
+
const cache = new PromptCache<string>();
|
|
54
|
+
cache.set("prompt1", { key: "value" }, "result1");
|
|
55
|
+
|
|
56
|
+
expect(cache.get("prompt1", { key: "value" })).toBe("result1");
|
|
57
|
+
expect(cache.get("prompt1", { key: "other" })).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should respect maxSize", () => {
|
|
61
|
+
const cache = new PromptCache<string>({ maxSize: 2 });
|
|
62
|
+
cache.set("p1", {}, "r1");
|
|
63
|
+
cache.set("p2", {}, "r2");
|
|
64
|
+
cache.set("p3", {}, "r3");
|
|
65
|
+
|
|
66
|
+
expect(cache.size).toBe(2);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should check has correctly", () => {
|
|
70
|
+
const cache = new PromptCache<number>();
|
|
71
|
+
cache.set("p", {}, 42);
|
|
72
|
+
|
|
73
|
+
expect(cache.has("p", {})).toBe(true);
|
|
74
|
+
expect(cache.has("other", {})).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("CostTracker", () => {
|
|
79
|
+
it("should track token usage", () => {
|
|
80
|
+
const tracker = new CostTracker();
|
|
81
|
+
tracker.record({ inputTokens: 100, outputTokens: 50 });
|
|
82
|
+
tracker.record({ inputTokens: 200, outputTokens: 100 });
|
|
83
|
+
|
|
84
|
+
const total = tracker.total;
|
|
85
|
+
expect(total.inputTokens).toBe(300);
|
|
86
|
+
expect(total.outputTokens).toBe(150);
|
|
87
|
+
expect(total.totalTokens).toBe(450);
|
|
88
|
+
expect(total.calls).toBe(2);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should check budget", () => {
|
|
92
|
+
const tracker = new CostTracker();
|
|
93
|
+
tracker.record({ inputTokens: 400, outputTokens: 100 });
|
|
94
|
+
|
|
95
|
+
expect(tracker.exceedsBudget(1000)).toBe(false);
|
|
96
|
+
expect(tracker.exceedsBudget(400)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should reset", () => {
|
|
100
|
+
const tracker = new CostTracker();
|
|
101
|
+
tracker.record({ inputTokens: 100, outputTokens: 50 });
|
|
102
|
+
tracker.reset();
|
|
103
|
+
|
|
104
|
+
expect(tracker.total.totalTokens).toBe(0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { defineSchema } from "../src/schema/defineSchema.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
describe("defineSchema", () => {
|
|
6
|
+
it("should create a schema with description, inputs, and outputs", () => {
|
|
7
|
+
const schema = defineSchema({
|
|
8
|
+
description: "Extract names from text",
|
|
9
|
+
inputs: { text: z.string() },
|
|
10
|
+
outputs: { names: z.array(z.string()) },
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(schema.description).toBe("Extract names from text");
|
|
14
|
+
expect(schema.getInputKeys()).toEqual(["text"]);
|
|
15
|
+
expect(schema.getOutputKeys()).toEqual(["names"]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should validate correct input", () => {
|
|
19
|
+
const schema = defineSchema({
|
|
20
|
+
description: "Test",
|
|
21
|
+
inputs: { text: z.string() },
|
|
22
|
+
outputs: { result: z.number() },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const result = schema.validateInput({ text: "hello" });
|
|
26
|
+
expect(result.text).toBe("hello");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should throw on invalid input", () => {
|
|
30
|
+
const schema = defineSchema({
|
|
31
|
+
description: "Test",
|
|
32
|
+
inputs: { text: z.string() },
|
|
33
|
+
outputs: { result: z.number() },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(() => schema.validateInput({ text: 123 })).toThrow();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should validate correct output", () => {
|
|
40
|
+
const schema = defineSchema({
|
|
41
|
+
description: "Test",
|
|
42
|
+
inputs: { text: z.string() },
|
|
43
|
+
outputs: { names: z.array(z.string()) },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const result = schema.validateOutput({ names: ["Alice", "Bob"] });
|
|
47
|
+
expect(result.names).toEqual(["Alice", "Bob"]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should throw on invalid output", () => {
|
|
51
|
+
const schema = defineSchema({
|
|
52
|
+
description: "Test",
|
|
53
|
+
inputs: { text: z.string() },
|
|
54
|
+
outputs: { names: z.array(z.string()) },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(() => schema.validateOutput({ names: "not an array" })).toThrow();
|
|
58
|
+
});
|
|
59
|
+
});
|