@posthog/ai 8.15.1 → 8.16.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 CHANGED
@@ -84,3 +84,148 @@ The SDK's synchronous `close()` method also starts finalization; await `return()
84
84
  ## Questions?
85
85
 
86
86
  ### [Check out our community page.](https://posthog.com/posts)
87
+
88
+ ## Datasets, scorers, and offline evaluations
89
+
90
+ Import the server-only API from `@posthog/ai/evaluations`. It has no model-provider runtime
91
+ requirements and does not require a `posthog-node` analytics client. Every network method
92
+ returns a Promise that resolves only after a validated server acknowledgment; there is no
93
+ capture queue or flush step. Your application executes models and evaluators.
94
+
95
+ ```ts
96
+ import { OfflineEvaluations, EvaluationItem } from '@posthog/ai/evaluations'
97
+
98
+ const evaluations = new OfflineEvaluations({
99
+ projectId: 123,
100
+ secretKey: process.env.POSTHOG_PERSONAL_API_KEY!,
101
+ // host: 'https://eu.posthog.com',
102
+ // timeoutMs: 15_000,
103
+ // maxRetries: 3, // additional attempts, only for operations safe to replay
104
+ })
105
+
106
+ try {
107
+ const snapshot = await evaluations.datasets.snapshot(datasetId)
108
+ const scorer = await evaluations.scorers.get(scorerId)
109
+ const experiment = await evaluations.createExperiment({
110
+ name: 'Answer quality',
111
+ datasetRevisionId: snapshot.revisionId,
112
+ runSource: 'ci',
113
+ })
114
+
115
+ for await (const row of snapshot) {
116
+ // Implement these functions in your application.
117
+ const output = await runApplication(row.input)
118
+ const value = await scoreOutput(output, row.expectedOutput)
119
+ await experiment.uploadResult({
120
+ item: EvaluationItem.fromDatasetItem(row, { output }),
121
+ scorerVersionId: scorer.currentVersionId,
122
+ value,
123
+ })
124
+ }
125
+ await experiment.complete()
126
+ } finally {
127
+ evaluations.close()
128
+ }
129
+ ```
130
+
131
+ `datasets` provides create/get/list/update/archive/restore, item management, revision/version
132
+ history, and `snapshot`. Item mutations require the current `baseVersion`. Supply a stable
133
+ `clientItemId` to make item creation retryable. Snapshots pin both the numeric revision and its
134
+ UUID before iteration. `snapshot.iterItems({ pageSize, signal })` fetches one page at a time;
135
+ repeated iteration starts again at the same revision. Keep the client open until reads finish.
136
+ Use the snapshot's `revisionId` for the experiment, even when individual items were introduced
137
+ in earlier revisions. Source output and trace provenance never become a new execution's output.
138
+
139
+ `scorers` provides create/get/list/update/createVersion/getVersion/listVersions. Configurations
140
+ are typed by `kind`: `boolean`, `numeric`, or `categorical`. Store `currentVersionId` to pin a
141
+ configuration. Metadata updates do not create versions; explicit `createVersion` does, including
142
+ when the configuration is unchanged. Optional `baseVersion` detects conflicting version writes.
143
+ Scorer names are not unique. A result with `status: 'ok'` means the evaluator succeeded; its score
144
+ may still fail the pinned scorer's passing rule. Other statuses are `error`, `skipped`, and
145
+ `not_applicable`, and carry no score.
146
+
147
+ | Operation | Credential and scope |
148
+ | -------------------------------------- | -------------------------------------------------------------------- |
149
+ | Dataset reads/writes | Personal key: `dataset:read` / `dataset:write` |
150
+ | Scorer reads/writes | Personal key: `llm_analytics:read` / `llm_analytics:write` |
151
+ | Experiment create/upload/complete/fail | Personal or project secret key: `offline_evaluation_ingestion:write` |
152
+ | Experiment tags | Personal key: `evaluation:read` / `evaluation:write` |
153
+
154
+ Feature availability and permissions are enforced by the service. Known ingestion hosts map to
155
+ the app API host; custom host path prefixes are preserved. Pagination exposes `nextOffset` or,
156
+ for scorer versions, `nextCursor`. Retain the original filters when requesting the next page.
157
+
158
+ ### Recovery, cancellation, and payloads
159
+
160
+ `resumeExperiment(id)` is local and sends no request. Save creation `submission` and item/result
161
+ declarations before relying on process recovery. Use `EvaluationItem.toJSONString()` and
162
+ `EvaluationResult.toJSONString()` to save the exact **snake_case wire JSON** compatible with
163
+ Python, including large integers. Save attached items separately:
164
+
165
+ ```ts
166
+ import { EvaluationItem, EvaluationResult } from '@posthog/ai/evaluations'
167
+
168
+ const savedItem = item.toJSONString()
169
+ const savedResult = result.toJSONString()
170
+ // Persist these strings directly; JSON.stringify would wrap them in another JSON string.
171
+ const restoredItem = EvaluationItem.fromJSON(JSON.parse(savedItem))
172
+ const restoredResult = EvaluationResult.fromJSON(JSON.parse(savedResult), { item: restoredItem })
173
+ ```
174
+
175
+ `toJSON()` returns a detached object for inspection and JavaScript-only round trips.
176
+ `JSON.stringify()` can write a different integer outside JavaScript's safe integer range,
177
+ even when the original value is exactly representable, so use `toJSONString()` for portable
178
+ saved declarations. Public options and records use camelCase; arbitrary payload/metadata keys
179
+ are never renamed.
180
+
181
+ `EvaluationAPIError` exposes `status`, `code`, `detail`, `attr`, `errors`, `response`,
182
+ `retryAfter` (seconds), and `persistence`: `not_sent`, `rejected`, or `unknown`. Creation errors
183
+ also expose `submission`, which can be passed directly to `createExperiment` to reuse the ID
184
+ and start time. Error messages omit payloads and credentials. Structured details are explicit
185
+ accessors for callers; treat them as potentially sensitive when logging.
186
+
187
+ `uploadResults` takes a finite array of immutable `EvaluationResult` declarations. The whole
188
+ array is validated before I/O, then uploaded sequentially in requests of at most 1,000 items,
189
+ 1,000 results, and 5 MiB of UTF-8 JSON. Memory usage is proportional to the supplied array, so
190
+ submit caller-sized batches. Operations on one experiment handle are serialized.
191
+
192
+ ```ts
193
+ import { BulkUploadError } from '@posthog/ai/evaluations'
194
+
195
+ try {
196
+ await experiment.uploadResults(results)
197
+ } catch (error) {
198
+ if (!(error instanceof BulkUploadError)) throw error
199
+ // Earlier chunks are acknowledged; this includes the failed, possibly committed chunk.
200
+ // Persist or inspect this work before deciding when to retry.
201
+ console.log(error.failedIndex, error.persistence)
202
+ await experiment.uploadResults(error.pendingResults)
203
+ }
204
+ ```
205
+
206
+ Exact replay preserves identities. Changed content for an existing identity conflicts. Ordinary
207
+ dataset writes, scorer writes, and experiment tag updates are not automatically replayed. Never
208
+ create a fresh ID merely because an acknowledgment was lost. `complete()` and `fail()` are explicit;
209
+ closing the client or cancelling a request does neither. Pass `{ signal }` as a network method's
210
+ request options, or within listing/snapshot options. Native cancellation propagates without a
211
+ retry and does not prove an in-flight write was unpersisted. Retain declarations for replay.
212
+
213
+ Cancelled creation rejects with `ExperimentCreationAbortedError`, a `DOMException` named
214
+ `AbortError` (or the original DOMException name, such as `TimeoutError`). Its `cause` is the
215
+ original cancellation reason, and `submission` is a defensive copy of the prepared creation
216
+ fields. Save that submission to replay with the same ID and start time. Each cancelled creation
217
+ has its own recovery data, including when several requests share one `AbortSignal`.
218
+
219
+ Omitted fields, explicit nulls, empty payloads, false, and zero remain distinct. `undefined`
220
+ omits a top-level optional option, but is rejected inside payload JSON. Payloads accept plain
221
+ JSON values without coercion, cycles, accessors, invalid Unicode, NUL characters, or non-finite
222
+ numbers. Maximum nesting is 32 containers; item payloads are limited to 1 MiB and result payloads
223
+ to 256 KiB, measured in UTF-8 bytes including their payload object.
224
+
225
+ Counts accept safe integer `number` values or nonnegative signed-64-bit `bigint`; large returned
226
+ counts remain `bigint`. Use a lossless JSON codec when persisting submissions or receipts containing
227
+ bigint counts. Numeric scores accept finite numbers; use `bigint` for integer inputs outside the
228
+ safe number range, which are accepted only when exactly representable as binary64. Unsafe integer
229
+ `number` score inputs are rejected because their original precision cannot be recovered. Bigints
230
+ are not accepted in arbitrary JSON payloads. An unrepresentable integer in returned dataset JSON
231
+ raises an invalid-response error rather than silently rounding it.
@@ -3,7 +3,7 @@ let _google_adk = require("@google/adk");
3
3
  let uuid = require("uuid");
4
4
  let _posthog_core = require("@posthog/core");
5
5
  //#region package.json
6
- var version = "8.15.1";
6
+ var version = "8.16.0";
7
7
  //#endregion
8
8
  //#region src/captureAiEvent.ts
9
9
  /** @internal */
@@ -532,7 +532,7 @@ const captureAiGeneration = async (client, options) => {
532
532
  /** Map Gemini usage metadata to PostHog's provider-agnostic token fields. */
533
533
  function mapGeminiUsage(metadata, additionalUsage = {}) {
534
534
  return {
535
- inputTokens: metadata?.promptTokenCount ?? 0,
535
+ inputTokens: (metadata?.promptTokenCount ?? 0) + (metadata?.toolUsePromptTokenCount ?? 0),
536
536
  outputTokens: metadata?.candidatesTokenCount ?? 0,
537
537
  reasoningTokens: metadata?.thoughtsTokenCount ?? 0,
538
538
  cacheReadInputTokens: metadata?.cachedContentTokenCount ?? 0,