@lunora/ai 1.0.0-alpha.20 → 1.0.0-alpha.21

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.
@@ -113,6 +113,20 @@ const modelIdOf = (model) => {
113
113
  const id = model.modelId;
114
114
  return typeof id === "string" && id.length > 0 ? id : void 0;
115
115
  };
116
+ const embedCostOf = (providerMetadata) => {
117
+ if (typeof providerMetadata !== "object" || providerMetadata === null) {
118
+ return void 0;
119
+ }
120
+ for (const bag of Object.values(providerMetadata)) {
121
+ if (typeof bag === "object" && bag !== null) {
122
+ const { cost } = bag;
123
+ if (typeof cost === "number" && Number.isFinite(cost)) {
124
+ return cost;
125
+ }
126
+ }
127
+ }
128
+ return void 0;
129
+ };
116
130
  const defineRag = (config) => {
117
131
  if (typeof config.index !== "string" || config.index.length === 0) {
118
132
  throw new LunoraError("BAD_REQUEST", "@lunora/ai/rag: `index` must be a non-empty Vectorize index name");
@@ -151,15 +165,25 @@ const defineRag = (config) => {
151
165
  const embedText = async (text) => {
152
166
  model ??= resolveEmbeddingModel(config.embeddingModel, context.ai);
153
167
  const resolvedModel = model;
154
- const run = async () => {
155
- const { embedding } = await embed({ model: resolvedModel, value: text });
168
+ const run = async (span) => {
169
+ const { embedding, providerMetadata, usage } = await embed({ model: resolvedModel, value: text });
170
+ if (span !== void 0) {
171
+ const inputTokens = usage.tokens;
172
+ if (typeof inputTokens === "number" && Number.isFinite(inputTokens)) {
173
+ span.setAttribute("gen_ai.usage.input_tokens", inputTokens);
174
+ }
175
+ const cost = embedCostOf(providerMetadata);
176
+ if (cost !== void 0) {
177
+ span.setAttribute("gen_ai.usage.cost", cost);
178
+ }
179
+ }
156
180
  return embedding;
157
181
  };
158
182
  if (tracer === void 0) {
159
183
  return run();
160
184
  }
161
185
  const modelId = modelIdOf(resolvedModel);
162
- return tracer("ai.embed", run, {
186
+ return tracer("ai.embed", (_trace, span) => run(span), {
163
187
  "gen_ai.operation.name": "embeddings",
164
188
  ...modelId === void 0 ? {} : { "gen_ai.request.model": modelId }
165
189
  });
@@ -112,11 +112,15 @@ interface RagContext {
112
112
  /**
113
113
  * Optional `ctx.trace` span factory — an `ActionCtx`'s `ctx.trace` satisfies
114
114
  * it structurally. When present, `defineRag` wraps each embedding-model
115
- * call in a `generation` span (`gen_ai.operation.name: "embeddings"` +
116
- * `gen_ai.request.model`), so the embed shows up on the trace waterfall like
117
- * any other instrumented sub-operation. `unknown` on purposethe same
118
- * decoupling rationale as `auth`: `defineRag` narrows it to a callable and
119
- * runs embeds untraced when it is absent (a hand-built context / test).
115
+ * call in a `generation` span carrying `gen_ai.operation.name: "embeddings"`
116
+ * and `gen_ai.request.model` up front, plus attached post-hoc through the
117
+ * span handle the tracer hands the body`gen_ai.usage.input_tokens` (from
118
+ * the embed result's token usage) and `gen_ai.usage.cost` (probed from the
119
+ * embed result's provider metadata, e.g. AI Gateway) when those are present.
120
+ * So the embed shows up on the trace waterfall with its usage like any other
121
+ * instrumented model call. `unknown` on purpose — the same decoupling
122
+ * rationale as `auth`: `defineRag` narrows it to a callable and runs embeds
123
+ * untraced when it is absent (a hand-built context / test).
120
124
  */
121
125
  trace?: unknown;
122
126
  vectors: RagVectors;
@@ -112,11 +112,15 @@ interface RagContext {
112
112
  /**
113
113
  * Optional `ctx.trace` span factory — an `ActionCtx`'s `ctx.trace` satisfies
114
114
  * it structurally. When present, `defineRag` wraps each embedding-model
115
- * call in a `generation` span (`gen_ai.operation.name: "embeddings"` +
116
- * `gen_ai.request.model`), so the embed shows up on the trace waterfall like
117
- * any other instrumented sub-operation. `unknown` on purposethe same
118
- * decoupling rationale as `auth`: `defineRag` narrows it to a callable and
119
- * runs embeds untraced when it is absent (a hand-built context / test).
115
+ * call in a `generation` span carrying `gen_ai.operation.name: "embeddings"`
116
+ * and `gen_ai.request.model` up front, plus attached post-hoc through the
117
+ * span handle the tracer hands the body`gen_ai.usage.input_tokens` (from
118
+ * the embed result's token usage) and `gen_ai.usage.cost` (probed from the
119
+ * embed result's provider metadata, e.g. AI Gateway) when those are present.
120
+ * So the embed shows up on the trace waterfall with its usage like any other
121
+ * instrumented model call. `unknown` on purpose — the same decoupling
122
+ * rationale as `auth`: `defineRag` narrows it to a callable and runs embeds
123
+ * untraced when it is absent (a hand-built context / test).
120
124
  */
121
125
  trace?: unknown;
122
126
  vectors: RagVectors;
@@ -1,5 +1,5 @@
1
1
  export { default as fixedWindowChunks } from '../packem_shared/fixedWindowChunks-J-WfQDw9.mjs';
2
- export { default as defineRag } from '../packem_shared/defineRag-DgVXmNGw.mjs';
2
+ export { default as defineRag } from '../packem_shared/defineRag-CQ3pnKUh.mjs';
3
3
  export { contentHash, guessMimeTypeFromExtension } from '../packem_shared/contentHash-Cgz5KRGD.mjs';
4
4
  export { default as hybridRank } from '../packem_shared/hybridRank-DPC9c2ON.mjs';
5
5
  export { default as bm25LexicalStore } from '../packem_shared/bm25LexicalStore-BQzWLMqX.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/ai",
3
- "version": "1.0.0-alpha.20",
3
+ "version": "1.0.0-alpha.21",
4
4
  "description": "Workers AI helper for Lunora: provider-agnostic AI SDK access from functions, Workers AI by default",
5
5
  "keywords": [
6
6
  "ai",