@sebastiantuyu/agest 0.3.3-next.3 → 0.3.3-next.5

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,3 +1,5 @@
1
1
  export { langchain } from "./langchain";
2
2
  export { remote } from "./remote";
3
3
  export type { RemoteAdapterOptions } from "./remote";
4
+ export { createTrace, summarizeEvents } from "./tracing";
5
+ export type { Trace } from "./tracing";
@@ -1,2 +1,3 @@
1
1
  export { langchain } from "./langchain";
2
2
  export { remote } from "./remote";
3
+ export { createTrace, summarizeEvents } from "./tracing";
@@ -1,5 +1,4 @@
1
- import { computeCost } from "../pricing";
2
- import { createTracingHandle } from "./tracing";
1
+ import { createTracingHandle, summarizeEvents } from "./tracing";
3
2
  /**
4
3
  * Adapter for LangChain runnables and agents.
5
4
  *
@@ -221,48 +220,5 @@ function extractTokensFromMessage(msg) {
221
220
  };
222
221
  }
223
222
  function summarizeRun(input) {
224
- const modelEvents = input.events.filter((e) => e.kind === "model");
225
- let inputTokens = 0;
226
- let outputTokens = 0;
227
- let providerCost = 0;
228
- let hasProviderCost = false;
229
- let hasTableCost = false;
230
- let tableCost = 0;
231
- let hasTokens = false;
232
- for (const e of modelEvents) {
233
- if (e.tokens) {
234
- hasTokens = true;
235
- inputTokens += e.tokens.input;
236
- outputTokens += e.tokens.output;
237
- }
238
- if (e.cost?.source === "provider" && e.cost.totalUsd != null) {
239
- hasProviderCost = true;
240
- providerCost += e.cost.totalUsd;
241
- }
242
- else if (e.cost?.source === "table" && e.cost.totalUsd != null) {
243
- hasTableCost = true;
244
- tableCost += e.cost.totalUsd;
245
- }
246
- }
247
- let tokens = hasTokens ? { input: inputTokens, output: outputTokens } : undefined;
248
- if (!tokens && input.fallbackTokens)
249
- tokens = input.fallbackTokens;
250
- // Pick cost: provider > table > recompute from fallback tokens
251
- let cost;
252
- if (hasProviderCost) {
253
- cost = { totalUsd: providerCost, source: "provider" };
254
- }
255
- else if (hasTableCost) {
256
- cost = { totalUsd: tableCost, source: "table" };
257
- }
258
- else if (tokens && input.model) {
259
- const computed = computeCost({
260
- model: input.model,
261
- inputTokens: tokens.input,
262
- outputTokens: tokens.output,
263
- });
264
- if (computed.source !== "unavailable")
265
- cost = computed;
266
- }
267
- return { tokens, cost };
223
+ return summarizeEvents(input.events, input.model, input.fallbackTokens);
268
224
  }
@@ -1,4 +1,4 @@
1
- import type { TimelineEvent } from "../types";
1
+ import type { TimelineEvent, CostBreakdown } from "../types";
2
2
  export interface TracingHandle {
3
3
  /** Pass this into `runnable.invoke(..., { callbacks: [handler.callbacks] })` */
4
4
  callbacks: any[];
@@ -17,3 +17,57 @@ export interface TracingHandle {
17
17
  * than throwing — the underlying agent run must not be broken by tracing.
18
18
  */
19
19
  export declare function createTracingHandle(baselineMs: number): Promise<TracingHandle>;
20
+ export interface Trace {
21
+ /**
22
+ * Attach to your top-level LangChain/LangGraph call:
23
+ * `await graph.invoke(input, { callbacks: trace.callbacks })`.
24
+ * Callbacks propagate to nested nodes automatically.
25
+ */
26
+ callbacks: any[];
27
+ /**
28
+ * Collect the captured timeline plus aggregated tokens and cost. Call once
29
+ * after the run completes; the result is memoized so repeat calls are safe.
30
+ * Spread the result into your `AgentResponse.metadata` to surface the
31
+ * per-scene cost/timeline waterfall in the report.
32
+ */
33
+ collect(): {
34
+ events: TimelineEvent[];
35
+ tokens?: {
36
+ input: number;
37
+ output: number;
38
+ };
39
+ cost?: CostBreakdown;
40
+ };
41
+ }
42
+ /**
43
+ * Public tracing helper for custom executors (i.e. agents not wired through
44
+ * the `langchain()` adapter). Create one per scene run, hand its `callbacks`
45
+ * to your LangChain/LangGraph invocation, then spread `collect()` into the
46
+ * response metadata.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const trace = await createTrace({ model: env.OPENROUTER_MODEL });
51
+ * const plan = await generatePlan(input, { callbacks: trace.callbacks });
52
+ * return { text: render(plan), metadata: { model, tools, ...trace.collect() } };
53
+ * ```
54
+ */
55
+ export declare function createTrace(opts?: {
56
+ model?: string;
57
+ }): Promise<Trace>;
58
+ /**
59
+ * Aggregate token counts and cost across a timeline's model events.
60
+ * Provider-reported cost wins; otherwise the table-derived cost; otherwise
61
+ * cost is recomputed from `model` and the summed tokens. `fallbackTokens` is
62
+ * used only when no model event carried usage.
63
+ */
64
+ export declare function summarizeEvents(events: TimelineEvent[], model?: string, fallbackTokens?: {
65
+ input: number;
66
+ output: number;
67
+ }): {
68
+ tokens?: {
69
+ input: number;
70
+ output: number;
71
+ };
72
+ cost?: CostBreakdown;
73
+ };
@@ -1,4 +1,5 @@
1
1
  import { computeCost } from "../pricing";
2
+ import { logger } from "../logger";
2
3
  /**
3
4
  * Creates a LangChain callback handler that records every LLM and tool
4
5
  * invocation as a `TimelineEvent`. Returns a handle whose `drain()` method
@@ -15,7 +16,9 @@ export async function createTracingHandle(baselineMs) {
15
16
  try {
16
17
  ({ BaseCallbackHandler } = await import("@langchain/core/callbacks/base"));
17
18
  }
18
- catch {
19
+ catch (err) {
20
+ logger.debug(`[agest] tracing disabled: could not load @langchain/core/callbacks/base — ` +
21
+ `install @langchain/core as a peer to capture per-scene cost/timeline. (${err.message})`);
19
22
  return { callbacks: [], drain: () => ({ events: [] }) };
20
23
  }
21
24
  const events = [];
@@ -125,6 +128,86 @@ export async function createTracingHandle(baselineMs) {
125
128
  },
126
129
  };
127
130
  }
131
+ /**
132
+ * Public tracing helper for custom executors (i.e. agents not wired through
133
+ * the `langchain()` adapter). Create one per scene run, hand its `callbacks`
134
+ * to your LangChain/LangGraph invocation, then spread `collect()` into the
135
+ * response metadata.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * const trace = await createTrace({ model: env.OPENROUTER_MODEL });
140
+ * const plan = await generatePlan(input, { callbacks: trace.callbacks });
141
+ * return { text: render(plan), metadata: { model, tools, ...trace.collect() } };
142
+ * ```
143
+ */
144
+ export async function createTrace(opts) {
145
+ const baseline = performance.now();
146
+ const handle = await createTracingHandle(baseline);
147
+ let collected;
148
+ return {
149
+ callbacks: handle.callbacks,
150
+ collect() {
151
+ if (collected)
152
+ return collected;
153
+ const drained = handle.drain();
154
+ const { tokens, cost } = summarizeEvents(drained.events, opts?.model ?? drained.modelName);
155
+ collected = { events: drained.events, tokens, cost };
156
+ return collected;
157
+ },
158
+ };
159
+ }
160
+ /**
161
+ * Aggregate token counts and cost across a timeline's model events.
162
+ * Provider-reported cost wins; otherwise the table-derived cost; otherwise
163
+ * cost is recomputed from `model` and the summed tokens. `fallbackTokens` is
164
+ * used only when no model event carried usage.
165
+ */
166
+ export function summarizeEvents(events, model, fallbackTokens) {
167
+ const modelEvents = events.filter((e) => e.kind === "model");
168
+ let inputTokens = 0;
169
+ let outputTokens = 0;
170
+ let providerCost = 0;
171
+ let hasProviderCost = false;
172
+ let hasTableCost = false;
173
+ let tableCost = 0;
174
+ let hasTokens = false;
175
+ for (const e of modelEvents) {
176
+ if (e.tokens) {
177
+ hasTokens = true;
178
+ inputTokens += e.tokens.input;
179
+ outputTokens += e.tokens.output;
180
+ }
181
+ if (e.cost?.source === "provider" && e.cost.totalUsd != null) {
182
+ hasProviderCost = true;
183
+ providerCost += e.cost.totalUsd;
184
+ }
185
+ else if (e.cost?.source === "table" && e.cost.totalUsd != null) {
186
+ hasTableCost = true;
187
+ tableCost += e.cost.totalUsd;
188
+ }
189
+ }
190
+ let tokens = hasTokens ? { input: inputTokens, output: outputTokens } : undefined;
191
+ if (!tokens && fallbackTokens)
192
+ tokens = fallbackTokens;
193
+ let cost;
194
+ if (hasProviderCost) {
195
+ cost = { totalUsd: providerCost, source: "provider" };
196
+ }
197
+ else if (hasTableCost) {
198
+ cost = { totalUsd: tableCost, source: "table" };
199
+ }
200
+ else if (tokens && model) {
201
+ const computed = computeCost({
202
+ model,
203
+ inputTokens: tokens.input,
204
+ outputTokens: tokens.output,
205
+ });
206
+ if (computed.source !== "unavailable")
207
+ cost = computed;
208
+ }
209
+ return { tokens, cost };
210
+ }
128
211
  function now() {
129
212
  return performance.now();
130
213
  }
package/dist/index.d.ts CHANGED
@@ -3,11 +3,13 @@ import { SceneBuilder } from "./context";
3
3
  export { expect } from "./assertions";
4
4
  export { logger } from "./logger";
5
5
  export { defineConfig } from "./config";
6
+ export { createTrace, summarizeEvents } from "./adapters/tracing";
7
+ export type { Trace } from "./adapters/tracing";
6
8
  export type { AgestConfig, JudgeConfig, JudgeExecutor } from "./config";
7
9
  export type { LogLevel } from "./logger";
8
10
  export type { AgentExpectation, AgentMatchers } from "./assertions";
9
11
  export type { JudgeCriteria } from "./judge";
10
- export type { AgentExecutor, ExecutorOptions, AgentResponse, AgentReport, SceneResult, RunResult, JudgeVerdict, JudgeResult, HookFn, } from "./types";
12
+ export type { AgentExecutor, ExecutorOptions, AgentResponse, AgentReport, SceneResult, RunResult, JudgeVerdict, JudgeResult, HookFn, TimelineEvent, TimelineEventKind, CostBreakdown, CostSource, } from "./types";
11
13
  export interface AgentOptions {
12
14
  name?: string;
13
15
  }
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { AgentContext, setContext, getContext } from "./context";
2
2
  export { expect } from "./assertions";
3
3
  export { logger } from "./logger";
4
4
  export { defineConfig } from "./config";
5
+ export { createTrace, summarizeEvents } from "./adapters/tracing";
5
6
  export function scene(prompt) {
6
7
  return getContext().registerScene(prompt);
7
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sebastiantuyu/agest",
3
- "version": "0.3.3-next.3",
3
+ "version": "0.3.3-next.5",
4
4
  "description": "A testing library for agents",
5
5
  "repository": {
6
6
  "type": "git",
@@ -58,5 +58,13 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "@supercharge/promise-pool": "3.3.0"
61
+ },
62
+ "peerDependencies": {
63
+ "@langchain/core": ">=0.3.0 <2.0.0"
64
+ },
65
+ "peerDependenciesMeta": {
66
+ "@langchain/core": {
67
+ "optional": true
68
+ }
61
69
  }
62
70
  }