@nestjs/ai 0.0.1 → 0.0.2

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.
Files changed (36) hide show
  1. package/README.md +1 -1
  2. package/dist/ai.constants.d.ts +8 -0
  3. package/dist/ai.constants.js +10 -0
  4. package/dist/ai.module-definition.d.ts +2 -2
  5. package/dist/ai.module-definition.js +14 -5
  6. package/dist/ai.module.js +13 -4
  7. package/dist/decorators/index.d.ts +1 -0
  8. package/dist/decorators/index.js +1 -0
  9. package/dist/decorators/inject-embedding-model.decorator.d.ts +10 -0
  10. package/dist/decorators/inject-embedding-model.decorator.js +16 -0
  11. package/dist/index.d.ts +4 -3
  12. package/dist/index.js +2 -1
  13. package/dist/interfaces/ai-embedding.interface.d.ts +79 -0
  14. package/dist/interfaces/ai-embedding.interface.js +1 -0
  15. package/dist/interfaces/ai-module-options.interface.d.ts +31 -9
  16. package/dist/interfaces/ai-result.interface.d.ts +4 -1
  17. package/dist/interfaces/index.d.ts +1 -0
  18. package/dist/interfaces/index.js +1 -0
  19. package/dist/models/ai-embedding-models.service.d.ts +31 -0
  20. package/dist/models/ai-embedding-models.service.js +138 -0
  21. package/dist/models/ai-models.service.js +3 -4
  22. package/dist/models/model-registry.util.d.ts +6 -0
  23. package/dist/models/model-registry.util.js +21 -0
  24. package/dist/models/model-requests.d.ts +3 -0
  25. package/dist/models/model-requests.js +8 -0
  26. package/dist/runtime/embedding-call.d.ts +38 -0
  27. package/dist/runtime/embedding-call.js +120 -0
  28. package/dist/runtime/model-policy.d.ts +14 -9
  29. package/dist/runtime/model-policy.js +7 -7
  30. package/dist/services/ai.service.d.ts +14 -1
  31. package/dist/services/ai.service.js +51 -7
  32. package/dist/testing/fake-embedding-model.d.ts +57 -0
  33. package/dist/testing/fake-embedding-model.js +82 -0
  34. package/dist/testing/index.d.ts +7 -1
  35. package/dist/testing/index.js +9 -1
  36. package/package.json +17 -2
package/README.md CHANGED
@@ -23,7 +23,7 @@
23
23
 
24
24
  ## Description
25
25
 
26
- Language models for [Nest](https://github.com/nestjs/nest): named models through dependency injection, model calls with timeouts, retries and fallback models (run on [`@nestjs/resilience`](https://github.com/nestjs/resilience) policies, or your own), budgets and usage events, structured output validated by a schema, the application's own provider methods as agent functions its agents call (through Nest's guards, interceptors, pipes and exception filters, as the user the agent acts for), answers streamed to the browser on Express, Fastify and the Node adapter, and durable agent runs on [`@nestjs/workflows`](https://github.com/nestjs/workflows) (`@nestjs/ai/workflows`) that survive restarts and wait for a person's approval. You write an agent function with `@AgentFunction()`, and the model is offered it as a tool; a method can be an MCP tool too, served by [`@nestjs/mcp`](https://github.com/nestjs/mcp), with its `@Tool()` stacked on it. Comes with a fake model for tests. Built on the [AI SDK](https://ai-sdk.dev) (`ai`) and its provider packages.
26
+ Language models for [Nest](https://github.com/nestjs/nest): named models through dependency injection, model calls with timeouts, retries and fallback models (run on [`@nestjs/resilience`](https://github.com/nestjs/resilience) policies, or your own), budgets and usage events, structured output validated by a schema, the application's own provider methods as agent functions its agents call (through Nest's guards, interceptors, pipes and exception filters, as the user the agent acts for), answers streamed to the browser on Express, Fastify and the Node adapter, and durable agent runs on [`@nestjs/workflows`](https://github.com/nestjs/workflows) (`@nestjs/ai/workflows`) that survive restarts and wait for a person's approval. You write an agent function with `@AgentFunction()`, and the model is offered it as a tool; a method can be an MCP tool too, served by [`@nestjs/mcp`](https://github.com/nestjs/mcp), with its `@Tool()` stacked on it. Embedding models have their own named registry, dependency injection, resilient single and batch calls, and usage events. Comes with fake language and embedding models for tests. Built on the [AI SDK](https://ai-sdk.dev) (`ai`) and its provider packages.
27
27
 
28
28
  ## Installation
29
29
 
@@ -18,5 +18,13 @@ export declare const AI_PARAMS_METADATA = "__routeArguments__";
18
18
  export declare const DEFAULT_MODEL_NAME = "default";
19
19
  /** Models that replace configured ones by name, from `@nestjs/ai/testing`'s `overrideModels()`. Internal. */
20
20
  export declare const AI_MODEL_OVERRIDES: unique symbol;
21
+ /**
22
+ * Embedding-model replacements from `@nestjs/ai/testing`'s `overrideEmbeddingModels()`. Internal.
23
+ */
24
+ export declare const AI_EMBEDDING_MODEL_OVERRIDES: unique symbol;
21
25
  /** The injection token of the model called `name`, which `@InjectModel(name)` injects. */
22
26
  export declare function getModelToken(name?: string): string;
27
+ /**
28
+ * The injection token of the embedding model called `name`, independent of language-model names.
29
+ */
30
+ export declare function getEmbeddingModelToken(name?: string): string;
@@ -18,7 +18,17 @@ export const AI_PARAMS_METADATA = '__routeArguments__';
18
18
  export const DEFAULT_MODEL_NAME = 'default';
19
19
  /** Models that replace configured ones by name, from `@nestjs/ai/testing`'s `overrideModels()`. Internal. */
20
20
  export const AI_MODEL_OVERRIDES = Symbol('AI_MODEL_OVERRIDES');
21
+ /**
22
+ * Embedding-model replacements from `@nestjs/ai/testing`'s `overrideEmbeddingModels()`. Internal.
23
+ */
24
+ export const AI_EMBEDDING_MODEL_OVERRIDES = Symbol('AI_EMBEDDING_MODEL_OVERRIDES');
21
25
  /** The injection token of the model called `name`, which `@InjectModel(name)` injects. */
22
26
  export function getModelToken(name = DEFAULT_MODEL_NAME) {
23
27
  return `nestjs:ai:model:${name}`;
24
28
  }
29
+ /**
30
+ * The injection token of the embedding model called `name`, independent of language-model names.
31
+ */
32
+ export function getEmbeddingModelToken(name = DEFAULT_MODEL_NAME) {
33
+ return `nestjs:ai:embedding-model:${name}`;
34
+ }
@@ -6,8 +6,8 @@ export type AiModuleRootOptions = AiModuleOptions & {
6
6
  isGlobal?: boolean;
7
7
  };
8
8
  /**
9
- * What `forRootAsync()` takes: `modelNames` and `isGlobal` at the top level, and one of `useFactory`, `useClass` or
10
- * `useExisting` for the options.
9
+ * What `forRootAsync()` takes: `modelNames`, `embeddingModelNames` and `isGlobal` at the top level, and one of
10
+ * `useFactory`, `useClass` or `useExisting` for the options.
11
11
  */
12
12
  export type AiModuleAsyncOptions = ConfigurableModuleAsyncOptions<AiModuleOptions, 'createAiOptions'> & AiModuleStructure & {
13
13
  isGlobal?: boolean;
@@ -1,8 +1,9 @@
1
1
  import { ConfigurableModuleBuilder, Scope } from '@nestjs/common';
2
2
  import { INQUIRER } from '@nestjs/core';
3
- import { AI_MODULE_OPTIONS, getModelToken } from './ai.constants.js';
3
+ import { AI_MODULE_OPTIONS, getEmbeddingModelToken, getModelToken } from './ai.constants.js';
4
4
  import { AiModels } from './models/ai-models.service.js';
5
- import { requestedModelNames } from './models/model-requests.js';
5
+ import { AiEmbeddingModels } from './models/ai-embedding-models.service.js';
6
+ import { requestedEmbeddingModelNames, requestedModelNames } from './models/model-requests.js';
6
7
  export { AI_MODULE_OPTIONS };
7
8
  export const { ConfigurableModuleClass, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = new ConfigurableModuleBuilder({
8
9
  moduleName: 'Ai',
@@ -10,7 +11,7 @@ export const { ConfigurableModuleClass, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = new
10
11
  })
11
12
  .setClassMethodName('forRoot')
12
13
  .setFactoryMethodName('createAiOptions')
13
- .setExtras({ isGlobal: false, modelNames: undefined }, (definition, { isGlobal, modelNames }) => {
14
+ .setExtras({ isGlobal: false, modelNames: undefined, embeddingModelNames: undefined }, (definition, { isGlobal, modelNames, embeddingModelNames }) => {
14
15
  const names = new Set([...(modelNames ?? []), ...requestedModelNames()]);
15
16
  // Transient, so that each class that injects a model is known (INQUIRER), and one that asks for a model the
16
17
  // module does not configure is named in the error.
@@ -20,11 +21,19 @@ export const { ConfigurableModuleClass, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = new
20
21
  inject: [AiModels, INQUIRER],
21
22
  useFactory: (models, inquirer) => models.injected(name, inquirer),
22
23
  }));
24
+ const embeddingNames = new Set([...(embeddingModelNames ?? []), ...requestedEmbeddingModelNames()]);
25
+ const embeddingTokens = [...embeddingNames].map((name) => ({
26
+ provide: getEmbeddingModelToken(name),
27
+ scope: Scope.TRANSIENT,
28
+ inject: [AiEmbeddingModels, INQUIRER],
29
+ useFactory: (models, inquirer) => models.injected(name, inquirer),
30
+ }));
31
+ const allTokens = [...tokens, ...embeddingTokens];
23
32
  return {
24
33
  ...definition,
25
34
  global: isGlobal,
26
- providers: [...(definition.providers ?? []), ...tokens],
27
- exports: [...(definition.exports ?? []), ...tokens.map((provider) => provider.provide)],
35
+ providers: [...(definition.providers ?? []), ...allTokens],
36
+ exports: [...(definition.exports ?? []), ...allTokens.map((provider) => provider.provide)],
28
37
  };
29
38
  })
30
39
  .build();
package/dist/ai.module.js CHANGED
@@ -6,9 +6,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { Module } from '@nestjs/common';
8
8
  import { DiscoveryModule } from '@nestjs/core';
9
- import { AI_MODEL_OVERRIDES } from './ai.constants.js';
9
+ import { AI_EMBEDDING_MODEL_OVERRIDES, AI_MODEL_OVERRIDES } from './ai.constants.js';
10
10
  import { ConfigurableModuleClass, } from './ai.module-definition.js';
11
11
  import { AiModels } from './models/ai-models.service.js';
12
+ import { AiEmbeddingModels } from './models/ai-embedding-models.service.js';
12
13
  import { AiService } from './services/ai.service.js';
13
14
  import { AiTools } from './tools/ai-tools.service.js';
14
15
  /**
@@ -20,7 +21,8 @@ let AiModule = class AiModule extends ConfigurableModuleClass {
20
21
  static forRoot(options) {
21
22
  // `@InjectModel()` may ask for every model `forRoot()` names.
22
23
  const modelNames = Object.keys(options?.models ?? {});
23
- return super.forRoot({ ...options, modelNames });
24
+ const embeddingModelNames = Object.keys(options?.embeddingModels ?? {});
25
+ return super.forRoot({ ...options, modelNames, embeddingModelNames });
24
26
  }
25
27
  static forRootAsync(options) {
26
28
  return super.forRootAsync(options);
@@ -29,8 +31,15 @@ let AiModule = class AiModule extends ConfigurableModuleClass {
29
31
  AiModule = __decorate([
30
32
  Module({
31
33
  imports: [DiscoveryModule],
32
- providers: [AiModels, AiTools, AiService, { provide: AI_MODEL_OVERRIDES, useValue: null }],
33
- exports: [AiModels, AiTools, AiService],
34
+ providers: [
35
+ AiModels,
36
+ AiEmbeddingModels,
37
+ AiTools,
38
+ AiService,
39
+ { provide: AI_MODEL_OVERRIDES, useValue: null },
40
+ { provide: AI_EMBEDDING_MODEL_OVERRIDES, useValue: null },
41
+ ],
42
+ exports: [AiModels, AiEmbeddingModels, AiTools, AiService],
34
43
  })
35
44
  ], AiModule);
36
45
  export { AiModule };
@@ -1,2 +1,3 @@
1
1
  export * from './agent-function.decorator.js';
2
2
  export * from './inject-model.decorator.js';
3
+ export * from './inject-embedding-model.decorator.js';
@@ -1,2 +1,3 @@
1
1
  export * from './agent-function.decorator.js';
2
2
  export * from './inject-model.decorator.js';
3
+ export * from './inject-embedding-model.decorator.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Injects the configured `EmbeddingModel` called `name` (default `'default'`), for code that calls
3
+ * `ai` directly. Direct SDK calls do not get `AiService`'s resilience or usage events. An unknown
4
+ * name fails at startup.
5
+ *
6
+ * ```ts
7
+ * constructor(@InjectEmbeddingModel('documents') private readonly model: EmbeddingModel) {}
8
+ * ```
9
+ */
10
+ export declare function InjectEmbeddingModel(name?: string): PropertyDecorator & ParameterDecorator;
@@ -0,0 +1,16 @@
1
+ import { Inject } from '@nestjs/common';
2
+ import { DEFAULT_MODEL_NAME, getEmbeddingModelToken } from '../ai.constants.js';
3
+ import { recordEmbeddingModelRequest } from '../models/model-requests.js';
4
+ /**
5
+ * Injects the configured `EmbeddingModel` called `name` (default `'default'`), for code that calls
6
+ * `ai` directly. Direct SDK calls do not get `AiService`'s resilience or usage events. An unknown
7
+ * name fails at startup.
8
+ *
9
+ * ```ts
10
+ * constructor(@InjectEmbeddingModel('documents') private readonly model: EmbeddingModel) {}
11
+ * ```
12
+ */
13
+ export function InjectEmbeddingModel(name = DEFAULT_MODEL_NAME) {
14
+ recordEmbeddingModelRequest(name);
15
+ return Inject(getEmbeddingModelToken(name));
16
+ }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  export { AiModule } from './ai.module.js';
2
2
  export { AI_MODULE_OPTIONS, type AiModuleAsyncOptions, type AiModuleRootOptions, type AiOptionsFactory, } from './ai.module-definition.js';
3
- export { DEFAULT_MODEL_NAME, getModelToken } from './ai.constants.js';
4
- export type { AiBudget, AiCallDefaults, AiCallSettings, AiModelDefinition, AiModelPricing, AiModuleOptions, AiModuleStructure, } from './interfaces/index.js';
3
+ export { DEFAULT_MODEL_NAME, getEmbeddingModelToken, getModelToken } from './ai.constants.js';
4
+ export type { AiBudget, AiCallDefaults, AiCallSettings, AiModelDefinition, AiModelPricing, AiModuleOptions, AiModuleStructure, AiResilienceSettings, } from './interfaces/index.js';
5
5
  export { AiModels } from './models/ai-models.service.js';
6
+ export { AiEmbeddingModels } from './models/ai-embedding-models.service.js';
6
7
  export { AiService } from './services/ai.service.js';
7
8
  export { AiTextStream, type AiStreamResponseOptions, type AiUIMessageStreamOptions, } from './runtime/text-stream.js';
8
- export type { AiCallOptions, AiModelInfo, AiModelReference, AiObjectCallOptions, AiObjectResult, AiSchema, AiStep, AiTextResult, AiToolCall, AiToolChoice, AiToolResult, AiUsage, AiUsageEvent, InferSchemaOutput, } from './interfaces/index.js';
9
+ export type { AiCallOptions, AiEmbeddingCallOptions, AiEmbeddingModelDefinition, AiEmbeddingModelPricing, AiEmbeddingModelReference, AiEmbeddingSettings, AiEmbeddingUsageEvent, AiEmbedManyOptions, AiEmbedManyResult, AiEmbedOptions, AiEmbedResult, AiModelInfo, AiModelReference, AiObjectCallOptions, AiObjectResult, AiSchema, AiStep, AiTextResult, AiToolCall, AiToolChoice, AiToolResult, AiUsage, AiUsageEvent, InferSchemaOutput, } from './interfaces/index.js';
9
10
  export * from './decorators/index.js';
10
11
  export { AiExecutionContext, type AiContextType } from './context/ai-execution-context.js';
11
12
  export { AiTools, type AiToolEntry } from './tools/ai-tools.service.js';
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  // Module and options
2
2
  export { AiModule } from './ai.module.js';
3
3
  export { AI_MODULE_OPTIONS, } from './ai.module-definition.js';
4
- export { DEFAULT_MODEL_NAME, getModelToken } from './ai.constants.js';
4
+ export { DEFAULT_MODEL_NAME, getEmbeddingModelToken, getModelToken } from './ai.constants.js';
5
5
  // Models and model calls
6
6
  export { AiModels } from './models/ai-models.service.js';
7
+ export { AiEmbeddingModels } from './models/ai-embedding-models.service.js';
7
8
  export { AiService } from './services/ai.service.js';
8
9
  export { AiTextStream, } from './runtime/text-stream.js';
9
10
  // Models by injection (`@InjectModel()`); agent functions (`@AgentFunction()`), which the model is offered as tools:
@@ -0,0 +1,79 @@
1
+ import type { EmbeddingModel, EmbeddingModelUsage, EmbedManyResult, EmbedResult } from 'ai';
2
+ import type { AiResilienceSettings } from './ai-module-options.interface.js';
3
+ import type { AiModelInfo } from './ai-result.interface.js';
4
+ /** Embedding settings, independent of language-model generation defaults. */
5
+ export interface AiEmbeddingSettings extends AiResilienceSettings {
6
+ /** Provider-specific options, by provider name. */
7
+ providerOptions?: Record<string, Record<string, any>>;
8
+ /** HTTP headers sent to the embedding provider. */
9
+ headers?: Record<string, string>;
10
+ /** Maximum concurrent SDK batch requests within one `embedMany()` operation. Default 4. */
11
+ maxParallelCalls?: number;
12
+ }
13
+ /** Price per million input tokens, in the unit used for reported `cost`. */
14
+ export interface AiEmbeddingModelPricing {
15
+ input: number;
16
+ }
17
+ /** A registered SDK embedding model with its own embedding settings. */
18
+ export interface AiEmbeddingModelDefinition extends AiEmbeddingSettings {
19
+ model: EmbeddingModel;
20
+ pricing?: AiEmbeddingModelPricing;
21
+ }
22
+ /** A registered embedding name, or an SDK embedding-model object passed directly to a call. */
23
+ export type AiEmbeddingModelReference = string | Exclude<EmbeddingModel, string>;
24
+ /** Options shared by single and batch embedding calls. */
25
+ export interface AiEmbeddingCallOptions extends AiEmbeddingSettings {
26
+ /** Name in `embeddingModels` (default `'default'`), or a direct SDK model object. */
27
+ model?: AiEmbeddingModelReference;
28
+ /**
29
+ * Cancels in-flight provider requests and retry backoff. `timeout` covers one provider request,
30
+ * so this is how to set a deadline for the whole operation (`AbortSignal.timeout(ms)`).
31
+ */
32
+ abortSignal?: AbortSignal;
33
+ /** Names this operation in usage and resilience events. */
34
+ label?: string;
35
+ /** Called after a successful operation, after the module's embedding usage listener. */
36
+ onEmbeddingUsage?: (event: AiEmbeddingUsageEvent) => void | Promise<void>;
37
+ }
38
+ export interface AiEmbedOptions extends AiEmbeddingCallOptions {
39
+ value: string;
40
+ }
41
+ export interface AiEmbedManyOptions extends AiEmbeddingCallOptions {
42
+ values: string[];
43
+ }
44
+ /** SDK single-embedding result with the resolved model and attempt count. */
45
+ export interface AiEmbedResult extends EmbedResult {
46
+ model: AiModelInfo;
47
+ /** Provider requests made, retries included. */
48
+ attempts: number;
49
+ /** Cost of the reported usage; absent without model pricing. */
50
+ cost?: number;
51
+ }
52
+ /**
53
+ * SDK batch result, in input order. A failed operation rejects rather than returning partial
54
+ * results.
55
+ */
56
+ export interface AiEmbedManyResult extends EmbedManyResult {
57
+ model: AiModelInfo;
58
+ /** Provider requests made, retries included. */
59
+ attempts: number;
60
+ /** Cost of the reported usage; absent without model pricing. */
61
+ cost?: number;
62
+ }
63
+ /**
64
+ * One successful embedding operation. Not a billing ledger: a provider may bill a request that
65
+ * timed out, and an operation that fails reports nothing, including the requests it completed.
66
+ * Input contents and output vectors are excluded.
67
+ */
68
+ export interface AiEmbeddingUsageEvent {
69
+ operation: 'embed' | 'embedMany';
70
+ label?: string;
71
+ model: AiModelInfo;
72
+ inputCount: number;
73
+ usage: EmbeddingModelUsage;
74
+ /** Provider requests made, retries included. */
75
+ attempts: number;
76
+ /** Total duration of the operation, including retries and backoff. */
77
+ durationMs: number;
78
+ cost?: number;
79
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,7 @@
1
1
  import type { Duration, ResiliencePolicy, RetryOptions } from '@nestjs/resilience';
2
- import type { LanguageModel } from 'ai';
2
+ import type { EmbeddingModel, LanguageModel } from 'ai';
3
3
  import type { AiUsageEvent } from './ai-usage.interface.js';
4
+ import type { AiEmbeddingModelDefinition, AiEmbeddingSettings, AiEmbeddingUsageEvent } from './ai-embedding.interface.js';
4
5
  /** Prices per million tokens, in a unit of your choice: what `cost` is reported in. */
5
6
  export interface AiModelPricing {
6
7
  input: number;
@@ -15,12 +16,12 @@ export interface AiBudget {
15
16
  /** In the unit of the models' `pricing`. A model without pricing counts as free. */
16
17
  maxCost?: number;
17
18
  }
18
- /** Settings a model, the module's `defaults` and each call can set; the call's win, then the model's. */
19
- export interface AiCallSettings {
19
+ /** Resilience settings shared by language-model and embedding calls. */
20
+ export interface AiResilienceSettings {
20
21
  /**
21
22
  * The longest a model call may take to answer: its whole response for `generateText()` and `generateObject()`,
22
- * its first chunk for `streamText()`, until its tools start for a step that calls them. Applied to each attempt,
23
- * as a `TimeoutPolicy`. Default `'60s'`; `false` for none.
23
+ * its first chunk for `streamText()`, until its tools start for a step that calls them, one provider request for
24
+ * an embedding. Applied to each attempt, as a `TimeoutPolicy`. Default `'60s'`; `false` for none.
24
25
  */
25
26
  timeout?: Duration | false;
26
27
  /**
@@ -28,15 +29,19 @@ export interface AiCallSettings {
28
29
  * first included), `backoff` (default from `'1s'`, doubling, up to `'30s'`, full jitter), `retryIf`. Retried by
29
30
  * default: what the provider marks retryable (408, 409, 429, 5xx and network failures) and timeouts; a
30
31
  * `retry-after` the provider sends is honored, and one longer than `backoff.maxDelay` moves on to the fallback.
31
- * `false` for none.
32
+ * An embedding call has no fallback, and retries only the provider request that failed. `false` for none.
32
33
  */
33
34
  retry?: RetryOptions | false;
34
35
  /**
35
36
  * A policy of your own (`ResiliencePolicy.wrap(breaker, retry, timeout)`), or the name of a preset of
36
37
  * `ResilienceService` (`ResilienceModule.forRoot({ presets })`), in place of `retry` and `timeout`: a circuit
37
- * breaker or a bulkhead shared with the rest of the application. Fallback models are applied around it.
38
+ * breaker or a bulkhead shared with the rest of the application. Fallback models are applied around it. An
39
+ * embedding call runs it around each provider request, so a bulkhead or a rate limit counts requests.
38
40
  */
39
41
  resilience?: ResiliencePolicy | string;
42
+ }
43
+ /** Settings a language model, the module's `defaults` and each call can set; the call's win, then the model's. */
44
+ export interface AiCallSettings extends AiResilienceSettings {
40
45
  /** Generation settings passed to the model. */
41
46
  maxOutputTokens?: number;
42
47
  temperature?: number;
@@ -51,7 +56,7 @@ export interface AiModelDefinition extends AiCallSettings {
51
56
  /** The models tried, in order, when this one fails after its retries: names of other models. */
52
57
  fallback?: string | string[];
53
58
  }
54
- /** Defaults for every call. `maxSteps` and `budget` are also those of durable runs (`AiAgents.run()`). */
59
+ /** Defaults for language-model calls. `maxSteps` and `budget` are also those of durable runs (`AiAgents.run()`). */
55
60
  export interface AiCallDefaults extends AiCallSettings {
56
61
  /** Model calls one call may make while the model asks for tools. Default 10 (25 for a durable run). */
57
62
  maxSteps?: number;
@@ -64,10 +69,22 @@ export interface AiModuleOptions {
64
69
  * `'openai/gpt-5'`), or a definition with its pricing, fallback and settings. Calls that name no model use
65
70
  * `default`.
66
71
  */
67
- models: Record<string, LanguageModel | AiModelDefinition>;
72
+ models?: Record<string, LanguageModel | AiModelDefinition>;
68
73
  defaults?: AiCallDefaults;
74
+ /**
75
+ * Named embedding models, independent of `models`. At least one of the two registries must be
76
+ * nonempty.
77
+ */
78
+ embeddingModels?: Record<string, EmbeddingModel | AiEmbeddingModelDefinition>;
79
+ /**
80
+ * Embedding-only defaults. They do not inherit generation settings, headers or options from
81
+ * `defaults`.
82
+ */
83
+ embeddingDefaults?: AiEmbeddingSettings;
69
84
  /** Called after every model call that answered, with its tokens and cost. Errors it throws are logged. */
70
85
  onUsage?: (event: AiUsageEvent) => void | Promise<void>;
86
+ /** Called once after a successful embedding operation. Errors it throws are logged. */
87
+ onEmbeddingUsage?: (event: AiEmbeddingUsageEvent) => void | Promise<void>;
71
88
  /**
72
89
  * The modules whose providers are searched for agent functions (`@AgentFunction()` methods). Default: every module
73
90
  * of the application.
@@ -81,6 +98,11 @@ export interface AiModuleStructure {
81
98
  * classes the module's file imports): for classes loaded later, such as lazily loaded modules.
82
99
  */
83
100
  modelNames?: string[];
101
+ /**
102
+ * Embedding-model counterpart of `modelNames`, for injection sites loaded after module
103
+ * definition.
104
+ */
105
+ embeddingModelNames?: string[];
84
106
  }
85
107
  /** What a class passed to `forRootAsync({ useClass })` implements. */
86
108
  export interface AiOptionsFactory {
@@ -2,7 +2,10 @@ import type { FinishReason, ModelMessage } from 'ai';
2
2
  import type { AiUsage } from './ai-usage.interface.js';
3
3
  /** Which model answered. */
4
4
  export interface AiModelInfo {
5
- /** Its name in `forRoot({ models })`; `undefined` for a `LanguageModel` passed to the call. */
5
+ /**
6
+ * Its name in `models` or `embeddingModels`; `undefined` for an SDK model object passed directly
7
+ * to the call.
8
+ */
6
9
  name?: string;
7
10
  provider: string;
8
11
  modelId: string;
@@ -1,5 +1,6 @@
1
1
  export * from './agent-function.interface.js';
2
2
  export * from './ai-call-options.interface.js';
3
+ export * from './ai-embedding.interface.js';
3
4
  export * from './ai-module-options.interface.js';
4
5
  export * from './ai-result.interface.js';
5
6
  export * from './ai-schema.interface.js';
@@ -1,5 +1,6 @@
1
1
  export * from './agent-function.interface.js';
2
2
  export * from './ai-call-options.interface.js';
3
+ export * from './ai-embedding.interface.js';
3
4
  export * from './ai-module-options.interface.js';
4
5
  export * from './ai-result.interface.js';
5
6
  export * from './ai-schema.interface.js';
@@ -0,0 +1,31 @@
1
+ import type { EmbeddingModel } from 'ai';
2
+ import type { AiEmbeddingModelPricing, AiEmbeddingModelReference, AiEmbeddingSettings } from '../interfaces/ai-embedding.interface.js';
3
+ import type { AiModuleOptions } from '../interfaces/ai-module-options.interface.js';
4
+ import type { AiModelInfo } from '../interfaces/ai-result.interface.js';
5
+ /** One resolved embedding model. Internal; embedding calls never switch to fallback models. */
6
+ export interface ResolvedEmbeddingModel {
7
+ info: AiModelInfo;
8
+ model: EmbeddingModel;
9
+ pricing?: AiEmbeddingModelPricing;
10
+ settings: AiEmbeddingSettings;
11
+ }
12
+ /** The configured embedding models, independent of language-model names. */
13
+ export declare class AiEmbeddingModels {
14
+ private readonly models;
15
+ constructor(options: AiModuleOptions, overrides?: Record<string, EmbeddingModel> | null);
16
+ names(): string[];
17
+ /** The SDK model, for direct calls without `AiService`'s resilience or usage events. */
18
+ get(name?: string): EmbeddingModel;
19
+ has(name: string): boolean;
20
+ /**
21
+ * @internal What `@InjectEmbeddingModel()` injects; unknown names fail during module
22
+ * initialization.
23
+ */
24
+ injected(name: string, inquirer: unknown): EmbeddingModel;
25
+ /**
26
+ * @internal Resolves a name or an SDK object. Direct objects have no registered settings or
27
+ * pricing.
28
+ */
29
+ resolve(reference: AiEmbeddingModelReference | undefined): ResolvedEmbeddingModel;
30
+ private known;
31
+ }
@@ -0,0 +1,138 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { Inject, Injectable, Optional } from '@nestjs/common';
14
+ import { AI_EMBEDDING_MODEL_OVERRIDES, AI_MODULE_OPTIONS, DEFAULT_MODEL_NAME, } from '../ai.constants.js';
15
+ import { AiConfigurationError } from '../errors/ai.errors.js';
16
+ import { assertModelRegistries } from './model-registry.util.js';
17
+ /** The configured embedding models, independent of language-model names. */
18
+ let AiEmbeddingModels = class AiEmbeddingModels {
19
+ constructor(options, overrides) {
20
+ this.models = new Map();
21
+ assertModelRegistries(options);
22
+ assertSettings(options.embeddingDefaults, 'AiModule: `embeddingDefaults`');
23
+ for (const [name, entry] of Object.entries(options.embeddingModels ?? {})) {
24
+ const { model, pricing, ...settings } = isDefinition(entry) ? entry : { model: entry };
25
+ assertModel(model, `AiModule: the embedding model "${name}"`);
26
+ assertSettings(settings, `AiModule: the embedding model "${name}"`);
27
+ if (pricing !== undefined &&
28
+ (typeof pricing !== 'object' ||
29
+ pricing === null ||
30
+ !Number.isFinite(pricing.input) ||
31
+ pricing.input < 0)) {
32
+ throw new AiConfigurationError(`AiModule: the embedding model "${name}" pricing.input must be ` +
33
+ 'a finite nonnegative number.');
34
+ }
35
+ const used = overrides?.[name] ?? model;
36
+ assertModel(used, `AiModule: the replacement embedding model "${name}"`);
37
+ this.models.set(name, {
38
+ info: { name, ...identify(used) },
39
+ model: used,
40
+ ...(pricing !== undefined && { pricing }),
41
+ settings,
42
+ });
43
+ }
44
+ for (const name of Object.keys(overrides ?? {})) {
45
+ if (!this.models.has(name)) {
46
+ throw new AiConfigurationError(`overrideEmbeddingModels(): there is no embedding model "${name}" to override. ` +
47
+ this.known());
48
+ }
49
+ }
50
+ }
51
+ names() {
52
+ return [...this.models.keys()];
53
+ }
54
+ /** The SDK model, for direct calls without `AiService`'s resilience or usage events. */
55
+ get(name = DEFAULT_MODEL_NAME) {
56
+ return this.resolve(name).model;
57
+ }
58
+ has(name) {
59
+ return this.models.has(name);
60
+ }
61
+ /**
62
+ * @internal What `@InjectEmbeddingModel()` injects; unknown names fail during module
63
+ * initialization.
64
+ */
65
+ injected(name, inquirer) {
66
+ const resolved = this.models.get(name);
67
+ if (!resolved) {
68
+ const requester = inquirer?.constructor
69
+ ?.name;
70
+ throw new AiConfigurationError(`${requester ? `${requester}: ` : ''}@InjectEmbeddingModel('${name}') ` +
71
+ 'asks for an embedding model AiModule does not configure. ' +
72
+ `${this.known()} Add "${name}" to \`embeddingModels\` ` +
73
+ '(in forRootAsync(), to what the factory returns).');
74
+ }
75
+ return resolved.model;
76
+ }
77
+ /**
78
+ * @internal Resolves a name or an SDK object. Direct objects have no registered settings or
79
+ * pricing.
80
+ */
81
+ resolve(reference) {
82
+ if (reference === undefined || typeof reference === 'string') {
83
+ const name = reference ?? DEFAULT_MODEL_NAME;
84
+ const resolved = this.models.get(name);
85
+ if (!resolved) {
86
+ throw new AiConfigurationError(`There is no embedding model named "${name}". ${this.known()}`);
87
+ }
88
+ return resolved;
89
+ }
90
+ assertModel(reference, 'The embedding `model` option');
91
+ return { info: identify(reference), model: reference, settings: {} };
92
+ }
93
+ known() {
94
+ return `Configured embedding models: ${this.names().join(', ')}.`;
95
+ }
96
+ };
97
+ AiEmbeddingModels = __decorate([
98
+ Injectable(),
99
+ __param(0, Inject(AI_MODULE_OPTIONS)),
100
+ __param(1, Optional()),
101
+ __param(1, Inject(AI_EMBEDDING_MODEL_OVERRIDES)),
102
+ __metadata("design:paramtypes", [Object, Object])
103
+ ], AiEmbeddingModels);
104
+ export { AiEmbeddingModels };
105
+ function isDefinition(entry) {
106
+ return (typeof entry === 'object' &&
107
+ entry !== null &&
108
+ 'model' in entry &&
109
+ !('specificationVersion' in entry));
110
+ }
111
+ function assertModel(model, where) {
112
+ const valid = (typeof model === 'string' && model.length > 0) ||
113
+ (typeof model === 'object' &&
114
+ model !== null &&
115
+ typeof model.doEmbed === 'function');
116
+ if (!valid) {
117
+ throw new AiConfigurationError(`${where} is not an embedding model. ` +
118
+ 'Pass an embedding model from an AI SDK provider package.');
119
+ }
120
+ }
121
+ function assertSettings(settings, where) {
122
+ if (settings === undefined) {
123
+ return;
124
+ }
125
+ if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) {
126
+ throw new AiConfigurationError(`${where} must contain embedding settings.`);
127
+ }
128
+ if (settings.maxParallelCalls !== undefined &&
129
+ (!Number.isSafeInteger(settings.maxParallelCalls) || settings.maxParallelCalls < 1)) {
130
+ throw new AiConfigurationError(`${where} maxParallelCalls must be a finite positive safe integer.`);
131
+ }
132
+ }
133
+ /** Registered string ids are resolved by the SDK's default gateway provider. */
134
+ function identify(model) {
135
+ return typeof model === 'string'
136
+ ? { provider: 'gateway', modelId: model }
137
+ : { provider: model.provider, modelId: model.modelId };
138
+ }
@@ -13,14 +13,13 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
13
13
  import { Inject, Injectable, Optional } from '@nestjs/common';
14
14
  import { AiConfigurationError } from '../errors/ai.errors.js';
15
15
  import { AI_MODEL_OVERRIDES, AI_MODULE_OPTIONS, DEFAULT_MODEL_NAME } from '../ai.constants.js';
16
+ import { assertModelRegistries } from './model-registry.util.js';
16
17
  /** The configured models, by name. */
17
18
  let AiModels = class AiModels {
18
19
  constructor(options, overrides) {
19
20
  this.models = new Map();
20
- const models = options?.models;
21
- if (typeof models !== 'object' || models === null || Object.keys(models).length === 0) {
22
- throw new AiConfigurationError('AiModule: `models` must name at least one model, e.g. { default: anthropic("claude-opus-5-5") }.');
23
- }
21
+ assertModelRegistries(options);
22
+ const models = options.models ?? {};
24
23
  for (const [name, entry] of Object.entries(models)) {
25
24
  const { model, pricing, fallback, ...settings } = isDefinition(entry) ? entry : { model: entry };
26
25
  assertModel(model, `AiModule: the model "${name}"`);
@@ -0,0 +1,6 @@
1
+ import type { AiModuleOptions } from '../interfaces/ai-module-options.interface.js';
2
+ /**
3
+ * Checks registry structure without contacting a provider. Either kind may be omitted, but not
4
+ * both.
5
+ */
6
+ export declare function assertModelRegistries(options: AiModuleOptions): void;
@@ -0,0 +1,21 @@
1
+ import { AiConfigurationError } from '../errors/ai.errors.js';
2
+ /**
3
+ * Checks registry structure without contacting a provider. Either kind may be omitted, but not
4
+ * both.
5
+ */
6
+ export function assertModelRegistries(options) {
7
+ let count = 0;
8
+ for (const key of ['models', 'embeddingModels']) {
9
+ const entries = options?.[key];
10
+ if (entries === undefined) {
11
+ continue;
12
+ }
13
+ if (typeof entries !== 'object' || entries === null || Array.isArray(entries)) {
14
+ throw new AiConfigurationError(`AiModule: \`${key}\` must be a record of named models.`);
15
+ }
16
+ count += Object.keys(entries).length;
17
+ }
18
+ if (count === 0) {
19
+ throw new AiConfigurationError('AiModule: configure at least one model in `models` or `embeddingModels`.');
20
+ }
21
+ }
@@ -1,2 +1,5 @@
1
1
  export declare function recordModelRequest(name: string): void;
2
2
  export declare function requestedModelNames(): string[];
3
+ /** Embedding injection requests use a separate namespace, even when the names are identical. */
4
+ export declare function recordEmbeddingModelRequest(name: string): void;
5
+ export declare function requestedEmbeddingModelNames(): string[];