@spendgraph/prompt 0.5.0 → 0.7.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/LICENSE +202 -0
- package/README.md +27 -221
- package/dist/budget/budget.d.ts +10 -0
- package/dist/budget/budget.js +1 -31
- package/dist/budget/errors.js +1 -11
- package/dist/budget/index.js +1 -2
- package/dist/build/build.d.ts +11 -2
- package/dist/build/build.js +1 -57
- package/dist/build/index.js +1 -1
- package/dist/cache/cache.d.ts +16 -0
- package/dist/cache/cache.js +1 -128
- package/dist/cache/index.js +1 -1
- package/dist/client.d.ts +27 -1
- package/dist/client.js +1 -145
- package/dist/fingerprint/fingerprint.d.ts +23 -0
- package/dist/fingerprint/fingerprint.js +1 -0
- package/dist/fingerprint/index.d.ts +2 -0
- package/dist/fingerprint/index.js +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -5
- package/dist/internals.d.ts +4 -2
- package/dist/internals.js +1 -6
- package/dist/pack/index.d.ts +3 -0
- package/dist/pack/index.js +1 -0
- package/dist/pack/pack.d.ts +58 -0
- package/dist/pack/pack.js +1 -0
- package/dist/pack/tokens.d.ts +11 -0
- package/dist/pack/tokens.js +1 -0
- package/dist/prompt/bind.d.ts +13 -18
- package/dist/prompt/bind.js +1 -15
- package/dist/prompt/factory.d.ts +1 -1
- package/dist/prompt/factory.js +1 -100
- package/dist/prompt/index.js +1 -2
- package/dist/pull/index.js +1 -1
- package/dist/pull/pull.js +1 -19
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -3
- package/dist/render/messages.js +1 -14
- package/dist/render/record.d.ts +12 -0
- package/dist/render/record.js +1 -0
- package/dist/render/system.js +4 -13
- package/dist/render/types.d.ts +21 -5
- package/dist/render/types.js +0 -1
- package/dist/render/variables.d.ts +7 -0
- package/dist/render/variables.js +1 -20
- package/dist/run/across-models.d.ts +11 -1
- package/dist/run/across-models.js +1 -5
- package/dist/run/concurrency.d.ts +1 -7
- package/dist/run/concurrency.js +1 -12
- package/dist/run/id.js +1 -3
- package/dist/run/index.js +1 -5
- package/dist/run/once.js +1 -21
- package/dist/run/sample.js +1 -28
- package/dist/types/dataset.js +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types/payload.js +0 -1
- package/dist/types/prompt.d.ts +6 -1
- package/dist/types/prompt.js +0 -1
- package/dist/types/result.js +0 -1
- package/dist/types/rollout.js +0 -1
- package/dist/types/run.js +0 -1
- package/dist/types/trace.js +0 -1
- package/dist/types/version.js +0 -1
- package/docs/caching.mdx +46 -0
- package/docs/calling.mdx +58 -0
- package/docs/datasets.mdx +61 -0
- package/docs/fields.mdx +63 -0
- package/docs/overview.mdx +78 -0
- package/docs/running.mdx +55 -0
- package/package.json +12 -11
package/dist/cache/cache.js
CHANGED
|
@@ -1,128 +1 @@
|
|
|
1
|
-
|
|
2
|
-
store = new Map();
|
|
3
|
-
ttlMs;
|
|
4
|
-
maxSize;
|
|
5
|
-
now;
|
|
6
|
-
refreshing = new Set();
|
|
7
|
-
failures = new Map();
|
|
8
|
-
retryAfterMs;
|
|
9
|
-
onRefreshError;
|
|
10
|
-
identify;
|
|
11
|
-
tick = 0;
|
|
12
|
-
invalidatedAt = new Map();
|
|
13
|
-
clearedAt = -1;
|
|
14
|
-
outstanding = 0;
|
|
15
|
-
constructor(opts = {}) {
|
|
16
|
-
this.ttlMs = (opts.ttlSeconds ?? 300) * 1000;
|
|
17
|
-
this.maxSize = Math.max(1, opts.maxSize ?? 100);
|
|
18
|
-
this.now = opts.now ?? (() => Date.now());
|
|
19
|
-
this.retryAfterMs = (opts.retryAfterSeconds ?? 60) * 1000;
|
|
20
|
-
this.onRefreshError = opts.onRefreshError;
|
|
21
|
-
this.identify = opts.identify;
|
|
22
|
-
}
|
|
23
|
-
get(key) {
|
|
24
|
-
const entry = this.store.get(key);
|
|
25
|
-
if (!entry)
|
|
26
|
-
return undefined;
|
|
27
|
-
this.store.delete(key);
|
|
28
|
-
this.store.set(key, entry);
|
|
29
|
-
const ageMs = this.now() - entry.at;
|
|
30
|
-
return { value: entry.value, stale: ageMs > this.ttlMs, ageMs };
|
|
31
|
-
}
|
|
32
|
-
set(key, value) {
|
|
33
|
-
this.store.delete(key);
|
|
34
|
-
this.store.set(key, { value, at: this.now() });
|
|
35
|
-
while (this.store.size > this.maxSize) {
|
|
36
|
-
const oldest = this.store.keys().next().value;
|
|
37
|
-
if (oldest === undefined)
|
|
38
|
-
break;
|
|
39
|
-
this.store.delete(oldest);
|
|
40
|
-
if (!this.refreshing.has(oldest))
|
|
41
|
-
this.failures.delete(oldest);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
refreshInBackground(key, refresh) {
|
|
45
|
-
if (this.refreshing.has(key))
|
|
46
|
-
return;
|
|
47
|
-
const failedAt = this.failures.get(key);
|
|
48
|
-
if (failedAt !== undefined && this.now() - failedAt < this.retryAfterMs)
|
|
49
|
-
return;
|
|
50
|
-
this.refreshing.add(key);
|
|
51
|
-
const startedAt = this.beginFetch();
|
|
52
|
-
void refresh()
|
|
53
|
-
.then((value) => this.settle(key, value, startedAt))
|
|
54
|
-
.catch((err) => {
|
|
55
|
-
this.abandon(key);
|
|
56
|
-
try {
|
|
57
|
-
this.onRefreshError?.(err, key);
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
.finally(() => {
|
|
63
|
-
this.refreshing.delete(key);
|
|
64
|
-
this.forgetLogIfIdle();
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
beginFetch() {
|
|
68
|
-
this.outstanding++;
|
|
69
|
-
return this.tick;
|
|
70
|
-
}
|
|
71
|
-
settle(key, value, startedAt) {
|
|
72
|
-
this.outstanding--;
|
|
73
|
-
const names = [key, ...(this.identify?.(value) ?? [])];
|
|
74
|
-
const overtaken = startedAt < this.clearedAt ||
|
|
75
|
-
names.some((n) => (this.invalidatedAt.get(n) ?? -1) > startedAt);
|
|
76
|
-
if (!overtaken) {
|
|
77
|
-
this.failures.delete(key);
|
|
78
|
-
this.set(key, value);
|
|
79
|
-
}
|
|
80
|
-
this.forgetLogIfIdle();
|
|
81
|
-
}
|
|
82
|
-
abandon(key) {
|
|
83
|
-
this.outstanding--;
|
|
84
|
-
this.failures.set(key, this.now());
|
|
85
|
-
this.boundFailures();
|
|
86
|
-
this.forgetLogIfIdle();
|
|
87
|
-
}
|
|
88
|
-
invalidate(name) {
|
|
89
|
-
this.invalidatedAt.set(name, ++this.tick);
|
|
90
|
-
}
|
|
91
|
-
forgetLogIfIdle() {
|
|
92
|
-
if (this.outstanding === 0 && this.refreshing.size === 0)
|
|
93
|
-
this.invalidatedAt.clear();
|
|
94
|
-
}
|
|
95
|
-
boundFailures() {
|
|
96
|
-
while (this.failures.size > this.maxSize) {
|
|
97
|
-
const oldest = this.failures.keys().next().value;
|
|
98
|
-
if (oldest === undefined)
|
|
99
|
-
break;
|
|
100
|
-
this.failures.delete(oldest);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
keysWhere(match) {
|
|
104
|
-
const out = [];
|
|
105
|
-
for (const [key, entry] of this.store)
|
|
106
|
-
if (match(entry.value))
|
|
107
|
-
out.push(key);
|
|
108
|
-
return out;
|
|
109
|
-
}
|
|
110
|
-
delete(key) {
|
|
111
|
-
this.store.delete(key);
|
|
112
|
-
this.failures.delete(key);
|
|
113
|
-
this.invalidate(key);
|
|
114
|
-
this.forgetLogIfIdle();
|
|
115
|
-
}
|
|
116
|
-
clear() {
|
|
117
|
-
this.clearedAt = ++this.tick;
|
|
118
|
-
this.store.clear();
|
|
119
|
-
this.failures.clear();
|
|
120
|
-
this.forgetLogIfIdle();
|
|
121
|
-
}
|
|
122
|
-
get size() {
|
|
123
|
-
return this.store.size;
|
|
124
|
-
}
|
|
125
|
-
get trackedKeys() {
|
|
126
|
-
return new Set([...this.invalidatedAt.keys(), ...this.failures.keys()]).size;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
1
|
+
function a(r,e=0,s=Math.random){if(r<=0||e<=0)return r;const t=Math.min(.99,e);return Math.round(r*(1+(s()*2-1)*t))}class o{store=new Map;ttlMs;maxSize;now;refreshing=new Set;failures=new Map;retryAfterMs;onRefreshError;identify;tick=0;invalidatedAt=new Map;clearedAt=-1;outstanding=0;constructor(e={}){this.ttlMs=a((e.ttlSeconds??300)*1e3,e.jitter,e.random),this.maxSize=Math.max(1,e.maxSize??100),this.now=e.now??(()=>Date.now()),this.retryAfterMs=(e.retryAfterSeconds??60)*1e3,this.onRefreshError=e.onRefreshError,this.identify=e.identify}get(e){const s=this.store.get(e);if(!s)return;this.store.delete(e),this.store.set(e,s);const t=this.now()-s.at;return{value:s.value,stale:t>this.ttlMs,ageMs:t}}set(e,s){for(this.store.delete(e),this.store.set(e,{value:s,at:this.now()});this.store.size>this.maxSize;){const t=this.store.keys().next().value;if(t===void 0)break;this.store.delete(t),this.refreshing.has(t)||this.failures.delete(t)}}refreshInBackground(e,s){if(this.refreshing.has(e))return;const t=this.failures.get(e);if(t!==void 0&&this.now()-t<this.retryAfterMs)return;this.refreshing.add(e);const i=this.beginFetch();s().then(n=>this.settle(e,n,i)).catch(n=>{this.abandon(e);try{this.onRefreshError?.(n,e)}catch{}}).finally(()=>{this.refreshing.delete(e),this.forgetLogIfIdle()})}beginFetch(){return this.outstanding++,this.tick}settle(e,s,t){this.outstanding--;const i=[e,...this.identify?.(s)??[]];t<this.clearedAt||i.some(h=>(this.invalidatedAt.get(h)??-1)>t)||(this.failures.delete(e),this.set(e,s)),this.forgetLogIfIdle()}abandon(e){this.outstanding--,this.failures.set(e,this.now()),this.boundFailures(),this.forgetLogIfIdle()}invalidate(e){this.invalidatedAt.set(e,++this.tick)}forgetLogIfIdle(){this.outstanding===0&&this.refreshing.size===0&&this.invalidatedAt.clear()}boundFailures(){for(;this.failures.size>this.maxSize;){const e=this.failures.keys().next().value;if(e===void 0)break;this.failures.delete(e)}}keysWhere(e){const s=[];for(const[t,i]of this.store)e(i.value)&&s.push(t);return s}delete(e){this.store.delete(e),this.failures.delete(e),this.invalidate(e),this.forgetLogIfIdle()}clear(){this.clearedAt=++this.tick,this.store.clear(),this.failures.clear(),this.forgetLogIfIdle()}get size(){return this.store.size}get trackedKeys(){return new Set([...this.invalidatedAt.keys(),...this.failures.keys()]).size}}export{o as PullCache};
|
package/dist/cache/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import{PullCache as o}from"./cache.js";export{o as PullCache};
|
package/dist/client.d.ts
CHANGED
|
@@ -6,7 +6,13 @@ export interface PromptClientOptions extends Omit<ClientOptions, "apiKey" | "bas
|
|
|
6
6
|
/** spendgraph API key (sg_…). Without it, `pull` throws and `report` no-ops. */
|
|
7
7
|
apiKey: string | undefined;
|
|
8
8
|
baseUrl: string;
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* `identify` is not yours to set: the client fixes it to the prompt's id and
|
|
11
|
+
* slug, because an invalidation that arrives under one handle has to reach a
|
|
12
|
+
* fetch started under the other. Naming it in the type and discarding it in
|
|
13
|
+
* the constructor was worse than not offering it.
|
|
14
|
+
*/
|
|
15
|
+
cache?: Omit<CacheOptions<PromptPayload>, "identify">;
|
|
10
16
|
/** Max requests in flight for sample() and runAll(). Default 4. */
|
|
11
17
|
concurrency?: number;
|
|
12
18
|
/**
|
|
@@ -44,6 +50,19 @@ export declare class PromptClient {
|
|
|
44
50
|
* you passed, so pulling the same prompt both ways keeps two entries.
|
|
45
51
|
*/
|
|
46
52
|
pull(handle: string): Promise<Prompt>;
|
|
53
|
+
/**
|
|
54
|
+
* Registers a prompt written in code, and hands back the stored one.
|
|
55
|
+
*
|
|
56
|
+
* The wording goes up as it stands, so what runs afterwards is the text that
|
|
57
|
+
* ran before. What comes back is a server prompt: it has an id, a version,
|
|
58
|
+
* and its calls write rollouts where the built one wrote usage.
|
|
59
|
+
*
|
|
60
|
+
* `fields` are not sent. The app derives them from the `{placeholders}` in
|
|
61
|
+
* the blocks and the question, so a spec whose fields say more than its
|
|
62
|
+
* placeholders do — a default, a range, an enum — loses that here and is
|
|
63
|
+
* worth setting on the prompt afterwards.
|
|
64
|
+
*/
|
|
65
|
+
create(spec: PromptSpec, projectId: string): Promise<Prompt>;
|
|
47
66
|
/** A prompt written in code, wired to record its usage through this client. */
|
|
48
67
|
build(spec: PromptSpec, opts?: Omit<BuildOptions, "sdk">): Prompt;
|
|
49
68
|
/**
|
|
@@ -101,6 +120,13 @@ export declare class PromptClient {
|
|
|
101
120
|
* Invalidates the cache: a promotion that left a warm entry in place would
|
|
102
121
|
* keep sending the old wording for up to a TTL, which is the one moment a
|
|
103
122
|
* caller is actively watching for the change.
|
|
123
|
+
*
|
|
124
|
+
* **This client's cache.** Every other process holding the same prompt keeps
|
|
125
|
+
* serving what it already pulled until its own TTL lapses, which for an edit
|
|
126
|
+
* is what "within a TTL" means and for a rollback is up to five minutes of
|
|
127
|
+
* continued harm — a version is usually rolled back because it is doing some.
|
|
128
|
+
* A fleet that needs the change at once has each instance call `invalidate`
|
|
129
|
+
* when it hears about the promotion, or pull with `cache: { ttlSeconds: 0 }`.
|
|
104
130
|
*/
|
|
105
131
|
promote(promptId: string, versionId: string): Promise<{
|
|
106
132
|
promoted: string;
|
package/dist/client.js
CHANGED
|
@@ -1,145 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Budget } from "./budget/index.js";
|
|
3
|
-
import { buildCustomPrompt } from "./build/index.js";
|
|
4
|
-
import { PullCache } from "./cache/index.js";
|
|
5
|
-
import { makePrompt } from "./prompt/index.js";
|
|
6
|
-
import { pullWithCache } from "./pull/index.js";
|
|
7
|
-
import { newRolloutId } from "./run/id.js";
|
|
8
|
-
import { runAcrossModels, runOnce, sampleRuns } from "./run/index.js";
|
|
9
|
-
const DEFAULT_CONCURRENCY = 4;
|
|
10
|
-
export class PromptClient {
|
|
11
|
-
sdk;
|
|
12
|
-
cache;
|
|
13
|
-
hasKey;
|
|
14
|
-
onReportError;
|
|
15
|
-
concurrency;
|
|
16
|
-
budget;
|
|
17
|
-
warnedAboutTokens = false;
|
|
18
|
-
constructor(opts) {
|
|
19
|
-
const { cache, onReportError, onPullError, concurrency, maxCostMicros, ...clientOpts } = opts;
|
|
20
|
-
this.sdk = new Spendgraph(clientOpts);
|
|
21
|
-
this.cache = new PullCache({
|
|
22
|
-
...cache,
|
|
23
|
-
onRefreshError: cache?.onRefreshError ?? onPullError,
|
|
24
|
-
identify: (p) => [p.id, p.slug].filter((v) => Boolean(v)),
|
|
25
|
-
});
|
|
26
|
-
this.hasKey = Boolean(opts.apiKey);
|
|
27
|
-
this.onReportError = onReportError;
|
|
28
|
-
this.concurrency = Math.max(1, concurrency ?? DEFAULT_CONCURRENCY);
|
|
29
|
-
this.budget = new Budget("maxCostMicros" in opts ? maxCostMicros : undefined);
|
|
30
|
-
}
|
|
31
|
-
recorder() {
|
|
32
|
-
return {
|
|
33
|
-
enabled: true,
|
|
34
|
-
write: (promptId, entry) => {
|
|
35
|
-
this.warnOnEmptyTokens(entry);
|
|
36
|
-
return this.priced(promptId, entry);
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
async pull(handle) {
|
|
41
|
-
const payload = await pullWithCache(this.cache, handle, async () => {
|
|
42
|
-
const res = await this.sdk.prompts.get(handle);
|
|
43
|
-
return res.prompt;
|
|
44
|
-
});
|
|
45
|
-
return makePrompt({
|
|
46
|
-
id: payload.id,
|
|
47
|
-
name: payload.name,
|
|
48
|
-
slug: payload.slug ?? null,
|
|
49
|
-
blocks: payload.blocks,
|
|
50
|
-
question: payload.question,
|
|
51
|
-
fields: payload.fieldSpec ?? [],
|
|
52
|
-
versionId: payload.currentVersionId ?? null,
|
|
53
|
-
models: payload.models ?? [],
|
|
54
|
-
}, "server", this.recorder());
|
|
55
|
-
}
|
|
56
|
-
build(spec, opts = {}) {
|
|
57
|
-
return buildCustomPrompt(spec, { ...opts, sdk: this.sdk });
|
|
58
|
-
}
|
|
59
|
-
warnOnEmptyTokens(outcome) {
|
|
60
|
-
if (this.warnedAboutTokens)
|
|
61
|
-
return;
|
|
62
|
-
if (outcome.status === "failed")
|
|
63
|
-
return;
|
|
64
|
-
if ((outcome.inputTokens ?? 0) > 0 || (outcome.outputTokens ?? 0) > 0)
|
|
65
|
-
return;
|
|
66
|
-
this.warnedAboutTokens = true;
|
|
67
|
-
console.warn("[prompt] trace() recorded a rollout with no tokens. This package never sees " +
|
|
68
|
-
"the provider response, so return inputTokens and outputTokens from your callback " +
|
|
69
|
-
"or every cost on this prompt reads as zero.");
|
|
70
|
-
}
|
|
71
|
-
async run(promptId, values = {}, opts = {}) {
|
|
72
|
-
return runOnce(this.sdk.prompts, promptId, values, opts, this.budget);
|
|
73
|
-
}
|
|
74
|
-
async sample(promptId, values = {}, opts = {}) {
|
|
75
|
-
return sampleRuns(this.sdk.prompts, promptId, values, { concurrency: this.concurrency, ...opts }, this.budget);
|
|
76
|
-
}
|
|
77
|
-
async runAll(promptId, values = {}, opts = {}) {
|
|
78
|
-
const models = opts.models ?? (await this.pull(promptId)).models;
|
|
79
|
-
if (models.length === 0) {
|
|
80
|
-
throw new Error("No models given and the prompt has none saved.");
|
|
81
|
-
}
|
|
82
|
-
return runAcrossModels(this.sdk.prompts, promptId, values, models, { concurrency: this.concurrency, ...opts }, this.budget);
|
|
83
|
-
}
|
|
84
|
-
async cases(promptId) {
|
|
85
|
-
return this.sdk.prompts.cases(promptId);
|
|
86
|
-
}
|
|
87
|
-
async setCases(promptId, cases) {
|
|
88
|
-
return this.sdk.prompts.putCases(promptId, cases);
|
|
89
|
-
}
|
|
90
|
-
spent() {
|
|
91
|
-
return this.budget.spent();
|
|
92
|
-
}
|
|
93
|
-
remaining() {
|
|
94
|
-
return this.budget.remaining();
|
|
95
|
-
}
|
|
96
|
-
async versions(promptId, opts = {}) {
|
|
97
|
-
return this.sdk.prompts.versions(promptId, opts);
|
|
98
|
-
}
|
|
99
|
-
async promote(promptId, versionId) {
|
|
100
|
-
const res = await this.sdk.prompts.promote(promptId, versionId);
|
|
101
|
-
this.invalidate(promptId);
|
|
102
|
-
return res;
|
|
103
|
-
}
|
|
104
|
-
invalidate(promptId) {
|
|
105
|
-
if (!promptId) {
|
|
106
|
-
this.cache.clear();
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
for (const key of this.cache.keysWhere((p) => p.id === promptId || p.slug === promptId)) {
|
|
110
|
-
this.cache.delete(key);
|
|
111
|
-
}
|
|
112
|
-
this.cache.delete(promptId);
|
|
113
|
-
this.cache.invalidate(promptId);
|
|
114
|
-
}
|
|
115
|
-
async priced(promptId, input) {
|
|
116
|
-
const rolloutId = input.rolloutId ?? newRolloutId();
|
|
117
|
-
if (!this.hasKey)
|
|
118
|
-
return undefined;
|
|
119
|
-
try {
|
|
120
|
-
const res = (await this.sdk.prompts.report(promptId, { ...input, rolloutId }));
|
|
121
|
-
if (res.priced === false)
|
|
122
|
-
return undefined;
|
|
123
|
-
return res.rollout?.costMicros;
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
this.onReportError?.(err);
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
async report(promptId, input) {
|
|
131
|
-
const rolloutId = input.rolloutId ?? newRolloutId();
|
|
132
|
-
if (!this.hasKey)
|
|
133
|
-
return rolloutId;
|
|
134
|
-
try {
|
|
135
|
-
await this.sdk.prompts.report(promptId, { ...input, rolloutId });
|
|
136
|
-
}
|
|
137
|
-
catch (err) {
|
|
138
|
-
this.onReportError?.(err);
|
|
139
|
-
}
|
|
140
|
-
return rolloutId;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
export function pullPrompt(handle, opts) {
|
|
144
|
-
return new PromptClient(opts).pull(handle);
|
|
145
|
-
}
|
|
1
|
+
import{Spendgraph as h}from"@spendgraph/sdk";import{Budget as p}from"./budget/index.js";import{buildCustomPrompt as m}from"./build/index.js";import{PullCache as f}from"./cache/index.js";import{makePrompt as k}from"./prompt/index.js";import{pullWithCache as y}from"./pull/index.js";import{newRolloutId as i}from"./run/id.js";import{runAcrossModels as b,runOnce as w,sampleRuns as g}from"./run/index.js";const E=4;function a(o,r){const e=o?.prompt;if(!e||typeof e!="object"||typeof e.id!="string")throw new Error(`Asked for prompt "${r}" and the response carried no prompt. Check the base URL points at a spendgraph app and that the key can read this project.`);return e}class v{sdk;cache;hasKey;onReportError;concurrency;budget;warnedAboutTokens=!1;constructor(r){const{cache:e,onReportError:t,onPullError:s,concurrency:c,maxCostMicros:u,...d}=r;this.sdk=new h(d),this.cache=new f({...e,onRefreshError:e?.onRefreshError??s,identify:n=>[n.id,n.slug].filter(l=>!!l)}),this.hasKey=!!r.apiKey,this.onReportError=t,this.concurrency=Math.max(1,c??E),this.budget=new p("maxCostMicros"in r?u:void 0)}recorder(){return{enabled:!0,write:(r,e)=>(this.warnOnEmptyTokens(e),this.priced(r,e))}}async pull(r){const e=await y(this.cache,r,async()=>{const t=await this.sdk.prompts.get(r);return a(t,r)});return k({id:e.id,name:e.name??e.id,slug:e.slug??null,blocks:e.blocks??[],question:e.question??"",fields:e.fieldSpec??[],versionId:e.currentVersionId??null,models:e.models??[]},"server",this.recorder())}async create(r,e){const t=await this.sdk.prompts.create({projectId:e,name:r.name,...r.blocks?{blocks:r.blocks}:{},...r.question===void 0?{}:{question:r.question},...r.variables?{variables:r.variables}:{},...r.models?{models:r.models}:{}}),s=a(t,r.name);return this.pull(s.id)}build(r,e={}){return m(r,{onReportError:this.onReportError,...e,sdk:this.sdk})}warnOnEmptyTokens(r){this.warnedAboutTokens||r.status!=="failed"&&((r.inputTokens??0)>0||(r.outputTokens??0)>0||(this.warnedAboutTokens=!0,console.warn("[prompt] call() recorded a rollout with no tokens. This package never sees the provider response, so return inputTokens and outputTokens from your callback or every cost on this prompt reads as zero.")))}async run(r,e={},t={}){return w(this.sdk.prompts,r,e,t,this.budget)}async sample(r,e={},t={}){return g(this.sdk.prompts,r,e,{concurrency:this.concurrency,...t},this.budget)}async runAll(r,e={},t={}){const s=t.models??(await this.pull(r)).models;if(s.length===0)throw new Error("No models given and the prompt has none saved.");return b(this.sdk.prompts,r,e,s,{concurrency:this.concurrency,...t},this.budget)}async cases(r){return this.sdk.prompts.cases(r)}async setCases(r,e){return this.sdk.prompts.putCases(r,e)}spent(){return this.budget.spent()}remaining(){return this.budget.remaining()}async versions(r,e={}){return this.sdk.prompts.versions(r,e)}async promote(r,e){const t=await this.sdk.prompts.promote(r,e);return this.invalidate(r),t}invalidate(r){if(!r){this.cache.clear();return}for(const e of this.cache.keysWhere(t=>t.id===r||t.slug===r))this.cache.delete(e);this.cache.delete(r),this.cache.invalidate(r)}async priced(r,e){const t=e.rolloutId??i();if(this.hasKey)try{const s=await this.sdk.prompts.report(r,{...e,rolloutId:t});return s.priced===!1?void 0:s.rollout?.costMicros}catch(s){this.onReportError?.(s);return}}async report(r,e){const t=e.rolloutId??i();if(!this.hasKey)return t;try{await this.sdk.prompts.report(r,{...e,rolloutId:t})}catch(s){this.onReportError?.(s)}return t}}function M(o,r){return new v(r).pull(o)}export{v as PromptClient,M as pullPrompt};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Everything about a prompt that decides what the model is actually sent. */
|
|
2
|
+
export interface Worded {
|
|
3
|
+
name: string;
|
|
4
|
+
blocks: readonly {
|
|
5
|
+
title: string;
|
|
6
|
+
body: string;
|
|
7
|
+
}[];
|
|
8
|
+
question: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A stable id for what a prompt says, so its cost history follows the wording
|
|
12
|
+
* rather than where the wording is kept.
|
|
13
|
+
*
|
|
14
|
+
* Built here or stored on spendgraph, the same text fingerprints the same, so
|
|
15
|
+
* registering a prompt written in code carries its history across instead of
|
|
16
|
+
* starting over under a new version id.
|
|
17
|
+
*
|
|
18
|
+
* Not a cryptographic digest. It is a cache key, and it has to be computed
|
|
19
|
+
* synchronously everywhere this package runs, where `crypto.subtle` is async
|
|
20
|
+
* and `node:crypto` is not there at all. A collision costs two prompts a shared
|
|
21
|
+
* estimate, which is the whole of the damage.
|
|
22
|
+
*/
|
|
23
|
+
export declare function fingerprint(worded: Worded): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const a=2166136261,e=16777619,s="\0",h="";function c(t,n){let o=n>>>0;for(let i=0;i<t.length;i++)o^=t.charCodeAt(i),o=Math.imul(o,16777619)>>>0;return o>>>0}function r(t){return[t.name,...t.blocks.map(n=>`${n.title}\0${n.body}`),t.question].join("")}function g(t){const n=r(t),o=c(n,2166136261).toString(16).padStart(8,"0");return`sig:${c(n,2166136261^n.length+1).toString(16).padStart(8,"0")}${o}`}export{g as fingerprint};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{fingerprint as f}from"./fingerprint.js";export{f as fingerprint};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,9 @@ export type { BuildOptions } from "./build/index.js";
|
|
|
5
5
|
export { buildCustomPrompt } from "./build/index.js";
|
|
6
6
|
export type { PromptClientOptions } from "./client.js";
|
|
7
7
|
export { PromptClient, pullPrompt } from "./client.js";
|
|
8
|
-
export type {
|
|
8
|
+
export type { ContextLayer, LayerFit, PackedContext, PackOptions } from "./pack/index.js";
|
|
9
|
+
export { approxTokens, packContext } from "./pack/index.js";
|
|
10
|
+
export type { Answer, BatchOptions, Chain, InvokeOptions, Model, ModelOptions, } from "./prompt/index.js";
|
|
9
11
|
export { MAX_RECORDED_MESSAGES } from "./prompt/index.js";
|
|
10
|
-
export type { Block, History, Message } from "./render/index.js";
|
|
11
|
-
export type { DatasetCase, DatasetSummary, Prompt, PromptSource, PromptSpec, PromptVersion, RenderOptions, ReportInput, RunOptions, RunResult, Split, TraceHandle, TraceOptions, TraceOutcome, } from "./types/index.js";
|
|
12
|
+
export type { Block, History, Message, MessageCall } from "./render/index.js";
|
|
13
|
+
export type { DatasetCase, DatasetSummary, Prompt, PromptSource, PromptSpec, PromptVersion, Recordable, RenderOptions, ReportInput, RunOptions, RunResult, Split, TraceHandle, TraceOptions, TraceOutcome, TurnRecord, TurnSource, } from "./types/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { BudgetExceededError } from "./budget/index.js";
|
|
3
|
-
export { buildCustomPrompt } from "./build/index.js";
|
|
4
|
-
export { PromptClient, pullPrompt } from "./client.js";
|
|
5
|
-
export { MAX_RECORDED_MESSAGES } from "./prompt/index.js";
|
|
1
|
+
import{FieldValidationError as t}from"@spendgraph/sdk";import{BudgetExceededError as p}from"./budget/index.js";import{buildCustomPrompt as x}from"./build/index.js";import{PromptClient as d,pullPrompt as f}from"./client.js";import{approxTokens as i,packContext as a}from"./pack/index.js";import{MAX_RECORDED_MESSAGES as u}from"./prompt/index.js";export{p as BudgetExceededError,t as FieldValidationError,u as MAX_RECORDED_MESSAGES,d as PromptClient,i as approxTokens,x as buildCustomPrompt,a as packContext,f as pullPrompt};
|
package/dist/internals.d.ts
CHANGED
|
@@ -9,9 +9,11 @@ export type { RolloutInput, RolloutRecord } from "@spendgraph/sdk";
|
|
|
9
9
|
export { serializeFields, validateFields } from "@spendgraph/sdk";
|
|
10
10
|
export { Budget } from "./budget/index.js";
|
|
11
11
|
export type { CacheHit, CacheOptions } from "./cache/index.js";
|
|
12
|
+
export type { Worded } from "./fingerprint/index.js";
|
|
13
|
+
export { fingerprint } from "./fingerprint/index.js";
|
|
12
14
|
export { PullCache } from "./cache/index.js";
|
|
13
|
-
export type {
|
|
15
|
+
export type { PromptShape, Recorder } from "./prompt/index.js";
|
|
14
16
|
export { bind, makePrompt, noRecorder } from "./prompt/index.js";
|
|
15
17
|
export { pullWithCache } from "./pull/index.js";
|
|
16
18
|
export { mapLimit, newRolloutId } from "./run/index.js";
|
|
17
|
-
export type { PromptPayload,
|
|
19
|
+
export type { PromptPayload, Rollout } from "./types/index.js";
|
package/dist/internals.js
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { Budget } from "./budget/index.js";
|
|
3
|
-
export { PullCache } from "./cache/index.js";
|
|
4
|
-
export { bind, makePrompt, noRecorder } from "./prompt/index.js";
|
|
5
|
-
export { pullWithCache } from "./pull/index.js";
|
|
6
|
-
export { mapLimit, newRolloutId } from "./run/index.js";
|
|
1
|
+
import{serializeFields as o,validateFields as t}from"@spendgraph/sdk";import{Budget as m}from"./budget/index.js";import{fingerprint as l}from"./fingerprint/index.js";import{PullCache as a}from"./cache/index.js";import{bind as x,makePrompt as n,noRecorder as u}from"./prompt/index.js";import{pullWithCache as h}from"./pull/index.js";import{mapLimit as g,newRolloutId as C}from"./run/index.js";export{m as Budget,a as PullCache,x as bind,l as fingerprint,n as makePrompt,g as mapLimit,C as newRolloutId,u as noRecorder,h as pullWithCache,o as serializeFields,t as validateFields};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{packContext as r}from"./pack.js";import{approxTokens as t}from"./tokens.js";export{t as approxTokens,r as packContext};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface ContextLayer {
|
|
2
|
+
name: string;
|
|
3
|
+
/** Its parts, the ones most worth keeping first. */
|
|
4
|
+
parts: string[];
|
|
5
|
+
/** Tokens this layer may have. Omit and it shares whatever the others leave. */
|
|
6
|
+
budget?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Kept whole, or the pack says it did not fit.
|
|
9
|
+
*
|
|
10
|
+
* The instructions and the question are the two nothing is worth dropping —
|
|
11
|
+
* a context that trimmed the question to make room for retrieved documents
|
|
12
|
+
* has spent the whole window on the wrong half of the request.
|
|
13
|
+
*/
|
|
14
|
+
required?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface LayerFit {
|
|
17
|
+
name: string;
|
|
18
|
+
kept: string[];
|
|
19
|
+
/** Parts that did not fit. */
|
|
20
|
+
dropped: number;
|
|
21
|
+
tokens: number;
|
|
22
|
+
/** What this layer was allowed: its own budget, or what was left when its turn came. */
|
|
23
|
+
allowed: number;
|
|
24
|
+
}
|
|
25
|
+
export interface PackedContext {
|
|
26
|
+
layers: LayerFit[];
|
|
27
|
+
/** Kept parts by layer name, ready to fill a template. */
|
|
28
|
+
kept: Record<string, string[]>;
|
|
29
|
+
tokens: number;
|
|
30
|
+
/** Room left under the budget. */
|
|
31
|
+
left: number;
|
|
32
|
+
/** The required layers alone did not fit, so the budget was never real. */
|
|
33
|
+
over: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface PackOptions {
|
|
36
|
+
/** Tokens the whole context may use. */
|
|
37
|
+
budget: number;
|
|
38
|
+
/** How a part is measured. Defaults to `approxTokens`. */
|
|
39
|
+
count?: (text: string) => number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fits several competing sources into one context window.
|
|
43
|
+
*
|
|
44
|
+
* A prompt's sources do not compete evenly. The instructions and the question
|
|
45
|
+
* have to be there whatever else is; retrieval is worth a fixed share and no
|
|
46
|
+
* more, because the tenth-best document is rarely worth the history it
|
|
47
|
+
* displaced; and what is left goes to the layers that can take whatever they
|
|
48
|
+
* are given. Assembling in that order is what stops the longest source — nearly
|
|
49
|
+
* always the history — from quietly crowding out the ones that were chosen.
|
|
50
|
+
*
|
|
51
|
+
* Parts within a layer are kept in the order given, so rank before packing:
|
|
52
|
+
* this drops from the end. A part that does not fit is dropped rather than cut
|
|
53
|
+
* in half, since half a retrieved document reads as a whole one to the model.
|
|
54
|
+
*
|
|
55
|
+
* Required layers are taken whole even when they overflow, and `over` says so.
|
|
56
|
+
* The alternative is a context that silently answers a trimmed question.
|
|
57
|
+
*/
|
|
58
|
+
export declare function packContext(layers: ContextLayer[], opts: PackOptions): PackedContext;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{approxTokens as u}from"./tokens.js";function m(e,o){const a=e.parts.reduce((r,t)=>r+o(t),0);return{name:e.name,kept:[...e.parts],dropped:0,tokens:a,allowed:a}}function d(e,o,a){const r=[];let t=0;for(const p of e.parts){const c=a(p);if(t+c>o)break;r.push(p),t+=c}return{name:e.name,kept:r,dropped:e.parts.length-r.length,tokens:t,allowed:o}}function k(e,o){const a=o.count??u,r=new Map;let t=0;for(const n of e.filter(s=>s.required)){const s=m(n,a);r.set(n.name,s),t+=s.tokens}const p=t>o.budget;for(const n of e.filter(s=>!s.required)){const s=Math.max(0,o.budget-t),f=d(n,Math.min(n.budget??s,s),a);r.set(n.name,f),t+=f.tokens}const c=e.map(n=>r.get(n.name));return{layers:c,kept:Object.fromEntries(c.map(n=>[n.name,n.kept])),tokens:t,left:Math.max(0,o.budget-t),over:p}}export{k as packContext};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roughly how many tokens a string is, at four characters each.
|
|
3
|
+
*
|
|
4
|
+
* An estimate, and this package has no tokenizer to make it anything else —
|
|
5
|
+
* every provider's differs, and pulling one in would tie a rendering package to
|
|
6
|
+
* one vendor's vocabulary. Close enough to pack against, and the reason a pack
|
|
7
|
+
* leaves headroom rather than filling the window to its last token.
|
|
8
|
+
*
|
|
9
|
+
* Pass your own counter to `packContext` where the exact number matters.
|
|
10
|
+
*/
|
|
11
|
+
export declare function approxTokens(text: string): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=4;function e(n){return n?Math.ceil(n.length/4):0}export{e as approxTokens};
|
package/dist/prompt/bind.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { Prompt, TraceOutcome } from "../types/index.js";
|
|
1
|
+
import type { Message } from "../render/index.js";
|
|
2
|
+
import type { Prompt, TraceOptions, TraceOutcome } from "../types/index.js";
|
|
3
3
|
/**
|
|
4
4
|
* Anything that can answer a list of messages.
|
|
5
5
|
*
|
|
@@ -17,22 +17,8 @@ export interface ModelOptions {
|
|
|
17
17
|
tools?: unknown;
|
|
18
18
|
onText?: (delta: string) => void;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
history?: History;
|
|
23
|
-
/** A bus to select from. The turn is passed to the model and to the rollout. */
|
|
24
|
-
tools?: {
|
|
25
|
-
trace(query?: string, limit?: number): {
|
|
26
|
-
record(): unknown;
|
|
27
|
-
};
|
|
28
|
-
};
|
|
29
|
-
/** What to select tools on. Defaults to the rendered question. */
|
|
30
|
-
query?: string;
|
|
31
|
-
/** False runs the model and records nothing. */
|
|
32
|
-
report?: boolean;
|
|
33
|
-
/** Supply your own to make a retry safe. */
|
|
34
|
-
rolloutId?: string;
|
|
35
|
-
}
|
|
20
|
+
/** What one invocation may be given; the same options `Prompt.call` takes. */
|
|
21
|
+
export type InvokeOptions = TraceOptions;
|
|
36
22
|
export interface BatchOptions extends InvokeOptions {
|
|
37
23
|
/** Max calls in flight. Default 4. */
|
|
38
24
|
concurrency?: number;
|
|
@@ -49,6 +35,15 @@ export interface Chain {
|
|
|
49
35
|
onText?: (delta: string) => void;
|
|
50
36
|
}): Promise<Answer>;
|
|
51
37
|
/** One call per set of values, concurrency-capped. */
|
|
38
|
+
/**
|
|
39
|
+
* Every row, at most `concurrency` at a time, in the order they were given.
|
|
40
|
+
*
|
|
41
|
+
* A row that throws comes back as a failed `Answer` rather than taking the
|
|
42
|
+
* batch down with it — the point of sending five hundred is that the other
|
|
43
|
+
* four hundred and ninety-nine still land, and they have already been paid
|
|
44
|
+
* for by the time one of them fails. Each carries the `rolloutId` its record
|
|
45
|
+
* was written under, so a failure is traceable to a row rather than to a job.
|
|
46
|
+
*/
|
|
52
47
|
batch(values: Record<string, unknown>[], opts?: BatchOptions): Promise<Answer[]>;
|
|
53
48
|
}
|
|
54
49
|
/**
|
package/dist/prompt/bind.js
CHANGED
|
@@ -1,15 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
const DEFAULT_CONCURRENCY = 4;
|
|
3
|
-
export function bind(prompt, model) {
|
|
4
|
-
const once = (values, opts, streaming) => prompt.call(values, ({ messages, turn }) => {
|
|
5
|
-
const modelOpts = { tools: turn, onText: opts.onText };
|
|
6
|
-
return streaming && model.stream
|
|
7
|
-
? model.stream(messages, modelOpts)
|
|
8
|
-
: model.call(messages, modelOpts);
|
|
9
|
-
}, opts);
|
|
10
|
-
return {
|
|
11
|
-
invoke: (values = {}, opts = {}) => once(values, opts, false),
|
|
12
|
-
stream: (values = {}, opts = {}) => once(values, opts, true),
|
|
13
|
-
batch: (values, opts = {}) => mapLimit(values, opts.concurrency ?? DEFAULT_CONCURRENCY, (v) => once(v, opts, false)),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
1
|
+
import{mapLimit as i}from"../run/concurrency.js";import{newRolloutId as m}from"../run/id.js";const d=4;function w(c,n){const e=(r,t,o)=>c.call(r,({messages:l,turn:s})=>{const a={tools:s,onText:t.onText};return o&&n.stream?n.stream(l,a):n.call(l,a)},t),u=async(r,t)=>{try{return await e(r,t,!1)}catch(o){return{rolloutId:t.rolloutId,output:"",model:"unknown",status:"failed",error:o instanceof Error?o.message:String(o)}}};return{invoke:(r={},t={})=>e(r,t,!1),stream:(r={},t={})=>e(r,t,!0),batch:(r,t={})=>i(r,t.concurrency??d,o=>u(o,{...t,rolloutId:t.rolloutId??m()}))}}export{w as bind};
|
package/dist/prompt/factory.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface PromptShape {
|
|
|
17
17
|
*
|
|
18
18
|
* A server prompt writes a rollout; a custom one has no row to hang one on and
|
|
19
19
|
* writes usage instead. Injecting it is what lets both constructors share one
|
|
20
|
-
* `
|
|
20
|
+
* `call`, so the two can never drift on what "recorded" means.
|
|
21
21
|
*/
|
|
22
22
|
export interface Recorder {
|
|
23
23
|
enabled: boolean;
|