@spendgraph/prompt 0.7.0 → 0.8.1

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.
@@ -1 +1 @@
1
- import{makePrompt as l,noRecorder as d}from"../prompt/index.js";import{applyVariables as i}from"../render/index.js";let u=!1;function c(o){return(o.inputTokens??0)===0&&(o.outputTokens??0)===0}function k(){u||(u=!0,console.warn("[prompt] call() recorded a call 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."))}function m(o,t,n){const a=n.onEmptyTokens??k,r=s=>{try{n.onReportError?.(s)}catch{}};return{enabled:!0,write(s,e){e.status!=="failed"&&(c(e)&&a(e),o.ingest.send([{eventId:e.rolloutId,model:e.model,inputTokens:e.inputTokens??0,outputTokens:e.outputTokens??0,cacheReadTokens:e.cacheReadTokens,cacheWriteTokens:e.cacheWriteTokens,citationTokens:e.citationTokens,reasoningTokens:e.reasoningTokens,metadata:{...n.metadata,prompt:t}}]).catch(r))}}}function f(o,t={}){const n=o.variables??{},a=(o.blocks??[]).map(s=>({title:i(s.title,n),body:i(s.body,n)})),r=i(o.question??"",n);return l({id:o.name,name:o.name,slug:null,blocks:a,question:r,fields:o.fields??[],versionId:null,models:o.models??[]},"custom",t.sdk?m(t.sdk,o.name,t):d)}export{f as buildCustomPrompt};
1
+ import{makePrompt as l,noRecorder as c}from"../prompt/index.js";import{applyVariables as i}from"../render/index.js";let u=!1;function k(o){return(o.inputTokens??0)===0&&(o.outputTokens??0)===0}function m(){u||(u=!0,console.warn("[prompt] call() recorded a call 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."))}function p(o,t,n){const s=n.onEmptyTokens??m,a=r=>{try{n.onReportError?.(r)}catch{}};return{enabled:!0,write(r,e){if(e.status!=="failed")return k(e)&&s(e),o.ingest.priced({eventId:e.rolloutId,model:e.model,inputTokens:e.inputTokens??0,outputTokens:e.outputTokens??0,cacheReadTokens:e.cacheReadTokens,cacheWriteTokens:e.cacheWriteTokens,citationTokens:e.citationTokens,reasoningTokens:e.reasoningTokens,metadata:{...n.metadata,prompt:t}}).catch(d=>{a(d)})}}}function b(o,t={}){const n=o.variables??{},s=(o.blocks??[]).map(r=>({title:i(r.title,n),body:i(r.body,n)})),a=i(o.question??"",n);return l({id:o.name,name:o.name,slug:null,blocks:s,question:a,fields:o.fields??[],versionId:null,models:o.models??[]},"custom",t.sdk?p(t.sdk,o.name,t):c)}export{b as buildCustomPrompt};
@@ -9,9 +9,9 @@ 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 { PullCache } from "./cache/index.js";
12
13
  export type { Worded } from "./fingerprint/index.js";
13
14
  export { fingerprint } from "./fingerprint/index.js";
14
- export { PullCache } from "./cache/index.js";
15
15
  export type { PromptShape, Recorder } from "./prompt/index.js";
16
16
  export { bind, makePrompt, noRecorder } from "./prompt/index.js";
17
17
  export { pullWithCache } from "./pull/index.js";
package/dist/internals.js CHANGED
@@ -1 +1 @@
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};
1
+ import{serializeFields as o,validateFields as t}from"@spendgraph/sdk";import{Budget as m}from"./budget/index.js";import{PullCache as l}from"./cache/index.js";import{fingerprint as a}from"./fingerprint/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,l as PullCache,x as bind,a as fingerprint,n as makePrompt,g as mapLimit,C as newRolloutId,u as noRecorder,h as pullWithCache,o as serializeFields,t as validateFields};
@@ -1 +1 @@
1
- import{serializeFields as R}from"@spendgraph/sdk";import{findVariables as S,renderMessages as h}from"../render/index.js";import{recordable as v}from"../render/record.js";import{newRolloutId as E}from"../run/id.js";import{fingerprint as $}from"../fingerprint/index.js";import{bind as j}from"./bind.js";const x={enabled:!1,write:()=>{}},A=50;let D=!1;function z(e){D||e.length<=A||(D=!0,console.warn(`[prompt] ${e.length} messages is over the ${A} a rollout can record. The call still runs; the rollout will be rejected. Trim the history you pass.`))}function O(e,r,l){const s=Object.keys(e).filter(i=>l.has(i)&&!r.has(i));return s.length===0||console.warn(`[prompt] ${s.map(i=>`{${i}}`).join(", ")} appears in this prompt and you passed a value for it, but no field declares it, so it was not substituted and the placeholder goes to the model as written. Add it to the prompt's fields.`),s}function P(e){for(let r=e.length-1;r>=0;r--)if(e[r].role==="user")return e[r].content;return""}function L(e,r,l=x){const s=new Set(e.fields.map(t=>t.name)),i=new Set(S(e.blocks,e.question));let w=!1;const u=(t={})=>(w||(w=O(t,s,i).length>0),R(t,e.fields)),q=(t={},f={})=>h(e.blocks,e.question,u(t),f.history),b={id:e.id,name:e.name,slug:e.slug,fields:e.fields,versionId:e.versionId,fingerprint:$({name:e.name,blocks:e.blocks,question:e.question}),models:e.models,source:r,serialize:u,format:q,async call(t,f,d={}){const k=u(t),a=h(e.blocks,e.question,k,d.history);z(a);const m=d.tools?.trace(d.query??P(a)),T=d.rolloutId??E(),g=l.enabled&&(d.report??!0),y=Date.now(),I=()=>({rolloutId:T,versionId:e.versionId,fields:k,rendered:v(a)});let o;try{o=await f({messages:a,turn:m})}catch(n){if(g){const c=m?.record();l.write(e.id,{...I(),model:"unknown",output:"",status:"failed",error:n instanceof Error?n.message:String(n),latencyMs:Date.now()-y,offeredTools:c?.offeredTools,steps:c?.steps})}throw n}const M=Date.now()-y;let p;if(g){const n=m?.record(),c=l.write(e.id,{...I(),model:o.model,output:o.output,status:o.status??"completed",error:o.error,inputTokens:o.inputTokens,outputTokens:o.outputTokens,cacheReadTokens:o.cacheReadTokens,cacheWriteTokens:o.cacheWriteTokens,citationTokens:o.citationTokens,reasoningTokens:o.reasoningTokens,latencyMs:M,caseId:o.caseId,seed:o.seed,offeredTools:o.offeredTools??n?.offeredTools,steps:o.steps??n?.steps});p=c instanceof Promise?c:void 0}return{...o,rolloutId:T,...p?{pricing:p}:{}}},bind:t=>j(b,t)};return b}export{A as MAX_RECORDED_MESSAGES,L as makePrompt,x as noRecorder};
1
+ import{serializeFields as R}from"@spendgraph/sdk";import{fingerprint as S}from"../fingerprint/index.js";import{findVariables as v,renderMessages as h}from"../render/index.js";import{recordable as E}from"../render/record.js";import{newRolloutId as $}from"../run/id.js";import{bind as j}from"./bind.js";const x={enabled:!1,write:()=>{}},A=50;let D=!1;function z(e){D||e.length<=A||(D=!0,console.warn(`[prompt] ${e.length} messages is over the ${A} a rollout can record. The call still runs; the rollout will be rejected. Trim the history you pass.`))}function O(e,r,l){const s=Object.keys(e).filter(i=>l.has(i)&&!r.has(i));return s.length===0||console.warn(`[prompt] ${s.map(i=>`{${i}}`).join(", ")} appears in this prompt and you passed a value for it, but no field declares it, so it was not substituted and the placeholder goes to the model as written. Add it to the prompt's fields.`),s}function P(e){for(let r=e.length-1;r>=0;r--)if(e[r].role==="user")return e[r].content;return""}function L(e,r,l=x){const s=new Set(e.fields.map(t=>t.name)),i=new Set(v(e.blocks,e.question));let w=!1;const u=(t={})=>(w||(w=O(t,s,i).length>0),R(t,e.fields)),q=(t={},f={})=>h(e.blocks,e.question,u(t),f.history),b={id:e.id,name:e.name,slug:e.slug,fields:e.fields,versionId:e.versionId,fingerprint:S({name:e.name,blocks:e.blocks,question:e.question}),models:e.models,source:r,serialize:u,format:q,async call(t,f,d={}){const k=u(t),a=h(e.blocks,e.question,k,d.history);z(a);const m=d.tools?.trace(d.query??P(a)),T=d.rolloutId??$(),g=l.enabled&&(d.report??!0),y=Date.now(),I=()=>({rolloutId:T,versionId:e.versionId,fields:k,rendered:E(a)});let o;try{o=await f({messages:a,turn:m})}catch(n){if(g){const c=m?.record();l.write(e.id,{...I(),model:"unknown",output:"",status:"failed",error:n instanceof Error?n.message:String(n),latencyMs:Date.now()-y,offeredTools:c?.offeredTools,steps:c?.steps})}throw n}const M=Date.now()-y;let p;if(g){const n=m?.record(),c=l.write(e.id,{...I(),model:o.model,output:o.output,status:o.status??"completed",error:o.error,inputTokens:o.inputTokens,outputTokens:o.outputTokens,cacheReadTokens:o.cacheReadTokens,cacheWriteTokens:o.cacheWriteTokens,citationTokens:o.citationTokens,reasoningTokens:o.reasoningTokens,latencyMs:M,caseId:o.caseId,seed:o.seed,offeredTools:o.offeredTools??n?.offeredTools,steps:o.steps??n?.steps});p=c instanceof Promise?c:void 0}return{...o,rolloutId:T,...p?{pricing:p}:{}}},bind:t=>j(b,t)};return b}export{A as MAX_RECORDED_MESSAGES,L as makePrompt,x as noRecorder};
package/docs/caching.mdx CHANGED
@@ -1,5 +1,5 @@
1
1
  export const meta = {
2
- title: "Caching — spendgraph docs",
2
+ title: "Caching: spendgraph docs",
3
3
  description:
4
4
  "`pull` serves a stale copy immediately and refreshes behind the caller. Invalidating has to drop both handles, because the same prompt sits under its uuid and its slug at once.",
5
5
  };
@@ -10,7 +10,7 @@ export const meta = {
10
10
 
11
11
  ## Invalidating drops both handles
12
12
 
13
- `promote` invalidates for you. Doing it by hand needs to drop **both** handles, not just the one passed: `pull` keys on whatever string it was given, so the same prompt sits under its uuid and its slug at once — and `promote` is only reachable with a uuid while the dashboard teaches pulling by slug. Dropping one left the other warm for a full TTL, still rendering the wording the promotion replaced.
13
+ `promote` invalidates for you. Doing it by hand needs to drop **both** handles, not just the one passed: `pull` keys on whatever string it was given, so the same prompt sits under its uuid and its slug at once, and `promote` is only reachable with a uuid while the dashboard teaches pulling by slug. Dropping one left the other warm for a full TTL, still rendering the wording the promotion replaced.
14
14
 
15
15
  ```ts
16
16
  await prompts.promote(promptId, versionId); // invalidates
@@ -35,7 +35,7 @@ new PromptClient({
35
35
  });
36
36
  ```
37
37
 
38
- `cache.identify` is not yours to set — the client fixes it to the prompt's id and slug, because an invalidation arriving under one handle has to reach a fetch started under the other.
38
+ `cache.identify` is not yours to set, the client fixes it to the prompt's id and slug, because an invalidation arriving under one handle has to reach a fetch started under the other.
39
39
 
40
40
  <Callout tone="trap" title="The dataset is not cached with the prompt">
41
41
  Cases change independently of the wording, so they are fetched fresh. A cached prompt and a stale case list would score today's answers against yesterday's questions and report it as an improvement.
@@ -43,4 +43,4 @@ new PromptClient({
43
43
 
44
44
  ## A reply that is not a prompt
45
45
 
46
- `pull` refuses a response carrying no prompt, naming the handle and the two things worth checking — that the base URL points at a spendgraph app, and that the key can read this project. A prompt that is thin but real is still served, with the absent parts as their empty values.
46
+ `pull` refuses a response carrying no prompt, naming the handle and the two things worth checking: that the base URL points at a spendgraph app, and that the key can read this project. A prompt that is thin but real is still served, with the absent parts as their empty values.
package/docs/calling.mdx CHANGED
@@ -1,5 +1,5 @@
1
1
  export const meta = {
2
- title: "Calling a prompt — spendgraph docs",
2
+ title: "Calling a prompt: spendgraph docs",
3
3
  description:
4
4
  "`call` renders, runs the model call you give it, and records what happened around it. What was sent is built once and shared by the success and failure paths.",
5
5
  };
@@ -17,7 +17,7 @@ const reply = await stored.call({ audience: "students", max_words: 50 }, async (
17
17
 
18
18
  It fills in the version, fields, messages, latency and status; you return the text, model and tokens.
19
19
 
20
- **Return the tokens** or every cost on the prompt reads as zero — the first call that forgets warns once.
20
+ **Return the tokens** or every cost on the prompt reads as zero, the first call that forgets warns once.
21
21
 
22
22
  ## One record, two paths
23
23
 
@@ -29,11 +29,11 @@ A callback that throws is recorded as failed and then rethrown: a provider call
29
29
 
30
30
  Recording is on for every prompt and is fire-and-forget. There is no client-level switch: pass `{ report: false }` on the call that should not be recorded, which is where you know whether it should.
31
31
 
32
- Fire-and-forget means nothing catches a send that fails, and what is lost is spend nothing downstream can reconstruct. **`onReportError` is where that arrives** — on the client for a rollout, and on `buildCustomPrompt` for a custom prompt's usage. A client passes its own down to what it builds.
32
+ Fire-and-forget means nothing catches a send that fails, and what is lost is spend nothing downstream can reconstruct. **`onReportError` is where that arrives**, on the client for a rollout, and on `buildCustomPrompt` for a custom prompt's usage. A client passes its own down to what it builds.
33
33
 
34
34
  ## Tools
35
35
 
36
- `call` and `invoke` accept anything that hands out a turn — [`toolbus()`](/docs/tools/overview), or your own.
36
+ `call` and `invoke` accept anything that hands out a turn: [`toolbus()`](/spendgraph/tools/overview), or your own.
37
37
 
38
38
  ```ts
39
39
  await prompt.call(values, ({ messages, turn }) => call(messages, turn), { tools: bus });
@@ -42,7 +42,7 @@ await prompt.call(values, ({ messages, turn }) => call(messages, turn), { tools:
42
42
  The callback gets the turn **fully typed**; the rollout gets the tools offered and the steps taken. Which tools were offered is recorded as well as which were called: without it a rollout cannot tell "chose not to" from "was never offered", which are opposite bugs.
43
43
 
44
44
  <Callout tone="trap" title="The recording never blocks the call, and never breaks it">
45
- Reporting happens beside your call, not in front of it: a report that fails reaches `onReportError` rather than your `catch`. That is deliberate — a metering outage must not take an app down — but it does mean a missing rollout is silent unless you wired that callback.
45
+ Reporting happens beside your call, not in front of it: a report that fails reaches `onReportError` rather than your `catch`. That is deliberate: a metering outage must not take an app down, but it does mean a missing rollout is silent unless you wired that callback.
46
46
  </Callout>
47
47
 
48
48
  ## Binding a model
package/docs/datasets.mdx CHANGED
@@ -1,7 +1,7 @@
1
1
  export const meta = {
2
- title: "Versions and datasets — spendgraph docs",
2
+ title: "Versions and datasets: spendgraph docs",
3
3
  description:
4
- "Cases sit on one side of a wall the server decides, so a case never moves sides. setCases replaces the whole set, because a dataset has one answer at a time.",
4
+ "Cases sit on one side of a wall the server decides, so a case never moves sides. SetCases replaces the whole set, because a dataset has one answer at a time.",
5
5
  };
6
6
 
7
7
  # Versions and datasets
@@ -14,7 +14,7 @@ await prompts.setCases(promptId, cases);
14
14
 
15
15
  ## A case sits on one side of a wall
16
16
 
17
- Every case is either `feedback` or `held_out`, and **the server decides which** — it is not a field you set.
17
+ Every case is either `feedback` or `held_out`, and **the server decides which**: it is not a field you set.
18
18
 
19
19
  ```
20
20
  feedback ──▶ what an optimiser is allowed to read and learn from
@@ -36,7 +36,7 @@ summary.problem; // why this cannot be optimised against yet, or null
36
36
 
37
37
  Not a merge, and deliberately.
38
38
 
39
- A dataset is a **set** — "which cases am I evaluating against" has one answer at a time. A merge would leave no way to remove a case, and a stale case nobody can delete quietly biases every run after it.
39
+ A dataset is a **set**: "which cases am I evaluating against" has one answer at a time. A merge would leave no way to remove a case, and a stale case nobody can delete quietly biases every run after it.
40
40
 
41
41
  ```ts
42
42
  await prompts.setCases(promptId, [
@@ -46,7 +46,7 @@ await prompts.setCases(promptId, [
46
46
 
47
47
  Send the set you want to have. Read it back with `cases()` if you need to confirm what landed.
48
48
 
49
- <Callout tone="trap" title="A case id is not a label — it decides which side of the wall the case lands on">
49
+ <Callout tone="trap" title="A case id is not a label: it decides which side of the wall the case lands on">
50
50
  The split is derived from the id, so renaming a case re-rolls it: a case that was held out can come back as feedback, and whatever proposes changes may now read what it is scored against. Nothing in the data would say so. Change the text, keep the id.
51
51
  </Callout>
52
52
 
@@ -56,6 +56,6 @@ Send the set you want to have. Read it back with `cases()` if you need to confir
56
56
  await prompts.versions(promptId, { origin: "user", limit: 20 });
57
57
  ```
58
58
 
59
- `origin` separates a version somebody wrote from one a run produced, which matters once an optimiser is writing candidates — otherwise the history reads as though a person made forty edits in a minute.
59
+ `origin` separates a version somebody wrote from one a run produced, which matters once an optimiser is writing candidates: otherwise the history reads as though a person made forty edits in a minute.
60
60
 
61
- Promoting a version is on [Caching](/docs/prompt/caching), because the part worth knowing is what it invalidates.
61
+ Promoting a version is on [Caching](/spendgraph/prompt/caching), because the part worth knowing is what it invalidates.
package/docs/fields.mdx CHANGED
@@ -1,12 +1,12 @@
1
1
  export const meta = {
2
- title: "Fields — spendgraph docs",
2
+ title: "Fields: spendgraph docs",
3
3
  description:
4
4
  "A placeholder is declared as a FieldSpec. A missing required field throws before anything is sent, and a value with nowhere to land is said rather than dropped.",
5
5
  };
6
6
 
7
7
  # Fields
8
8
 
9
- A `{placeholder}` is declared as a `FieldSpec` — a name, a type, whether it is required, and how it renders. A missing required field throws **before anything is sent**.
9
+ A `{placeholder}` is declared as a `FieldSpec`, a name, a type, whether it is required, and how it renders. A missing required field throws **before anything is sent**.
10
10
 
11
11
  What it throws is `FieldValidationError`, re-exported from the SDK so a caller has one name to catch whether the failure came from a prompt, a tool argument or a graph input:
12
12
 
@@ -39,7 +39,7 @@ field declares it, so it was not substituted and the placeholder goes to the
39
39
  model as written. Add it to the prompt's fields.
40
40
  ```
41
41
 
42
- All three conditions have to hold — the value was supplied, the placeholder is in the wording, and no field declares it — so an unrelated value and an unsupplied placeholder both stay quiet.
42
+ All three conditions have to hold: the value was supplied, the placeholder is in the wording, and no field declares it, so an unrelated value and an unsupplied placeholder both stay quiet.
43
43
 
44
44
  ## Substitution reads own properties only
45
45
 
@@ -58,6 +58,6 @@ prompt.format(values, { history });
58
58
  await prompt.call(values, ({ messages }) => call(messages), { history });
59
59
  ```
60
60
 
61
- History is passed through **verbatim** — it is a record of what was already said, not a template, so `{placeholders}` inside it are left alone. A `system` turn in the history is dropped: the prompt owns that turn, and two of them is a different request from the one the prompt describes.
61
+ History is passed through **verbatim**: it is a record of what was already said, not a template, so `{placeholders}` inside it are left alone. A `system` turn in the history is dropped: the prompt owns that turn, and two of them is a different request from the one the prompt describes.
62
62
 
63
- A rollout records at most 50 messages, which is what the route accepts. A longer conversation still runs; only its rollout is refused, and that refusal reaches `onReportError` rather than your call — so the first time it happens the package warns, once, while somebody is still watching. `MAX_RECORDED_MESSAGES` is exported if you want to trim before it bites.
63
+ A rollout records at most 50 messages, which is what the route accepts. A longer conversation still runs; only its rollout is refused, and that refusal reaches `onReportError` rather than your call, so the first time it happens the package warns, once, while somebody is still watching. `MAX_RECORDED_MESSAGES` is exported if you want to trim before it bites.
package/docs/overview.mdx CHANGED
@@ -1,5 +1,5 @@
1
1
  export const meta = {
2
- title: "Prompts — spendgraph docs",
2
+ title: "Prompts: spendgraph docs",
3
3
  description:
4
4
  "Pull a stored prompt or write one in code, render it, and record what it cost. Both hand back the same object, so nothing downstream has to know which it was given.",
5
5
  };
@@ -8,7 +8,7 @@ export const meta = {
8
8
 
9
9
  The wording that produced last week's answer is not the wording in the file today, and a prompt edited since is a score nobody can reproduce.
10
10
 
11
- Pull a prompt from the dashboard or write one in code, render it, and record what the call cost — with the exact messages that were sent kept beside the reply.
11
+ Pull a prompt from the dashboard or write one in code, render it, and record what the call cost, with the exact messages that were sent kept beside the reply.
12
12
 
13
13
  ```sh
14
14
  npm install @spendgraph/prompt
@@ -16,6 +16,8 @@ npm install @spendgraph/prompt
16
16
 
17
17
  ## Two ways in, one prompt
18
18
 
19
+ <PromptSources />
20
+
19
21
  `pullPrompt` fetches the wording from the server. `buildCustomPrompt` declares it in code. Both hand back the same `Prompt`, so `format`, `serialize` and `call` behave identically and nothing downstream has to know which it was given.
20
22
 
21
23
  ```ts
@@ -39,7 +41,7 @@ const local = buildCustomPrompt({
39
41
  });
40
42
  ```
41
43
 
42
- `pull` takes an id or a slug — the server resolves either. `pullPrompt` is the same fetch as a standalone function, for a caller that wants one prompt and no client to hold.
44
+ `pull` takes an id or a slug, the server resolves either. `pullPrompt` is the same fetch as a standalone function, for a caller that wants one prompt and no client to hold.
43
45
 
44
46
  ## Running one
45
47
 
@@ -55,7 +57,7 @@ answer.output;
55
57
  answer.rolloutId;
56
58
  ```
57
59
 
58
- `bind` asks for a **shape, not an import** — anything with `call(messages)` returning an outcome satisfies `Model`, and an `LlmReply` already is one. Neither package depends on the other.
60
+ `bind` asks for a **shape, not an import**: anything with `call(messages)` returning an outcome satisfies `Model`, and an `LlmReply` already is one. Neither package depends on the other.
59
61
 
60
62
  ## What each kind records
61
63
 
@@ -64,15 +66,15 @@ A **server** prompt has a row and a version behind it, so a call writes a rollou
64
66
  Read `prompt.source` to tell them apart.
65
67
 
66
68
  <Callout tone="trap" title="A custom prompt has no version, so its spend is usage rather than a rollout">
67
- `buildCustomPrompt` records the call and the cost exactly as a stored one does — but there is no row and no version, so nothing ties the number to a wording you can go back to. Fine while you are exploring; not what you want under a score you intend to compare next month.
69
+ `buildCustomPrompt` records the call and the cost exactly as a stored one does, but there is no row and no version, so nothing ties the number to a wording you can go back to. Fine while you are exploring; not what you want under a score you intend to compare next month.
68
70
  </Callout>
69
71
 
70
72
  ## Where to go next
71
73
 
72
74
  | | |
73
75
  | --- | --- |
74
- | [Fields](/docs/prompt/fields) | placeholders, types, and what is substituted |
75
- | [Calling a prompt](/docs/prompt/calling) | `call`, history, tools, and what gets recorded |
76
- | [Caching](/docs/prompt/caching) | serving stale, refreshing behind, invalidating both handles |
77
- | [Running server-side](/docs/prompt/running) | `run`, `sample`, `runAll`, and the spend ceiling |
78
- | [Versions and datasets](/docs/prompt/datasets) | the split the server decides, and why it does |
76
+ | [Fields](/spendgraph/prompt/fields) | placeholders, types, and what is substituted |
77
+ | [Calling a prompt](/spendgraph/prompt/calling) | `call`, history, tools, and what gets recorded |
78
+ | [Caching](/spendgraph/prompt/caching) | serving stale, refreshing behind, invalidating both handles |
79
+ | [Running server-side](/spendgraph/prompt/running) | `run`, `sample`, `runAll`, and the spend ceiling |
80
+ | [Versions and datasets](/spendgraph/prompt/datasets) | the split the server decides, and why it does |
package/docs/running.mdx CHANGED
@@ -1,5 +1,5 @@
1
1
  export const meta = {
2
- title: "Running server-side — spendgraph docs",
2
+ title: "Running server-side: spendgraph docs",
3
3
  description:
4
4
  "run, sample and runAll execute the prompt where the spec, rendering and pricing already live. The batch path, and the ceiling that stops it running away.",
5
5
  };
@@ -18,9 +18,9 @@ await prompts.runAll(promptId, values); // one rollout per saved model
18
18
 
19
19
  ## Every seed, and every model, comes back
20
20
 
21
- `sample` always returns **k results**. A failed seed is present with `status: "failed"` rather than missing — three results for k = 5 would let a metric compute over three and report better consistency than the run showed.
21
+ `sample` always returns **k results**. A failed seed is present with `status: "failed"` rather than missing, three results for k = 5 would let a metric compute over three and report better consistency than the run showed.
22
22
 
23
- `runAll` does the same across models. One slug with a typo used to take the whole comparison with it — and not stop the calls already queued, which still left, landed and were billed after the caller had been told the run failed. Now each model comes back in the order asked, a failed one carrying its own name and the provider's message.
23
+ `runAll` does the same across models. One slug with a typo used to take the whole comparison with it, and not stop the calls already queued, which still left, landed and were billed after the caller had been told the run failed. Now each model comes back in the order asked, a failed one carrying its own name and the provider's message.
24
24
 
25
25
  A **budget stop** is the exception: it throws `BudgetExceededError`, carrying what was spent and what the ceiling was. Money running out says nothing about a prompt or a model, and recording those as failures would put the wallet into `pass^k`.
26
26
 
@@ -44,12 +44,12 @@ new PromptClient({ apiKey, baseUrl, maxCostMicros: 5_000_000 }); // $5
44
44
  new PromptClient({ apiKey, baseUrl, maxCostMicros: null }); // deliberately none
45
45
  ```
46
46
 
47
- `0` is kept, because spending nothing is a thing a caller can mean. A ceiling that is `NaN` or negative is **refused**: `NaN >= NaN` is false, so a `NaN` limit never fires and the guard is silently not there — and `Number(process.env.MAX_COST_MICROS)` on an unset variable is `NaN`, which is how it arrives without anyone typing it.
47
+ `0` is kept, because spending nothing is a thing a caller can mean. A ceiling that is `NaN` or negative is **refused**: `NaN >= NaN` is false, so a `NaN` limit never fires and the guard is silently not there, and `Number(process.env.MAX_COST_MICROS)` on an unset variable is `NaN`, which is how it arrives without anyone typing it.
48
48
 
49
49
  <Callout tone="trap" title="Omit `maxCostMicros` and the client warns once, then spends without a ceiling">
50
- `runAll` fans out across every saved model and `sample` multiplies that by k, so the bill is a product of two numbers neither of which is in front of you. Pass a ceiling, or pass `null` to say you meant it — the warning appears once per process and is easy to scroll past.
50
+ `runAll` fans out across every saved model and `sample` multiplies that by k, so the bill is a product of two numbers neither of which is in front of you. Pass a ceiling, or pass `null` to say you meant it, the warning appears once per process and is easy to scroll past.
51
51
  </Callout>
52
52
 
53
53
  ## What you run against
54
54
 
55
- Cases and versions have their own page: [Versions and datasets](/docs/prompt/datasets).
55
+ Cases and versions have their own page: [Versions and datasets](/spendgraph/prompt/datasets).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spendgraph/prompt",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Pull a stored prompt or build one in code, render it, and record what it cost.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -44,11 +44,12 @@
44
44
  "README.md"
45
45
  ],
46
46
  "dependencies": {
47
- "@spendgraph/sdk": "^0.7.0"
47
+ "@spendgraph/sdk": "^0.8.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@spendgraph/llms": "^0.7.0",
51
- "@spendgraph/tools": "^0.7.0",
50
+ "@spendgraph/config": "0.8.1",
51
+ "@spendgraph/llms": "^0.8.1",
52
+ "@spendgraph/tools": "^0.8.1",
52
53
  "typescript": "^5"
53
54
  },
54
55
  "engines": {
@@ -59,6 +60,7 @@
59
60
  },
60
61
  "scripts": {
61
62
  "build": "rm -rf dist && tsc -p tsconfig.json --emitDeclarationOnly && tsc -p tsconfig.json --declaration false --removeComments && node ../../scripts/minify.mjs dist",
62
- "test": "vitest run"
63
+ "test": "vitest run",
64
+ "typecheck": "tsc -p tsconfig.tests.json"
63
65
  }
64
66
  }