@mgvdev/nestjs-ai 0.1.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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +655 -0
  3. package/dist/ai-module-options.interface-B5X4AFLC.d.cts +192 -0
  4. package/dist/ai-module-options.interface-Ca6y_MY2.d.ts +192 -0
  5. package/dist/conversation-store.interface-CtQY-qcc.d.cts +30 -0
  6. package/dist/conversation-store.interface-CtQY-qcc.d.ts +30 -0
  7. package/dist/index.cjs +3209 -0
  8. package/dist/index.cjs.map +1 -0
  9. package/dist/index.d.cts +1441 -0
  10. package/dist/index.d.ts +1441 -0
  11. package/dist/index.js +3163 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/stream-to-socket-fJphU0WN.d.cts +47 -0
  14. package/dist/stream-to-socket-fJphU0WN.d.ts +47 -0
  15. package/dist/testing.cjs +2395 -0
  16. package/dist/testing.cjs.map +1 -0
  17. package/dist/testing.d.cts +46 -0
  18. package/dist/testing.d.ts +46 -0
  19. package/dist/testing.js +2391 -0
  20. package/dist/testing.js.map +1 -0
  21. package/dist/typeorm.cjs +115 -0
  22. package/dist/typeorm.cjs.map +1 -0
  23. package/dist/typeorm.d.cts +44 -0
  24. package/dist/typeorm.d.ts +44 -0
  25. package/dist/typeorm.js +113 -0
  26. package/dist/typeorm.js.map +1 -0
  27. package/dist/websocket.cjs +154 -0
  28. package/dist/websocket.cjs.map +1 -0
  29. package/dist/websocket.d.cts +25 -0
  30. package/dist/websocket.d.ts +25 -0
  31. package/dist/websocket.js +152 -0
  32. package/dist/websocket.js.map +1 -0
  33. package/documentation/README.md +44 -0
  34. package/documentation/agents-and-tools.md +102 -0
  35. package/documentation/api-reference.md +117 -0
  36. package/documentation/configuration.md +87 -0
  37. package/documentation/content-safety.md +51 -0
  38. package/documentation/embeddings-and-rag.md +105 -0
  39. package/documentation/evals-and-testing.md +56 -0
  40. package/documentation/getting-started.md +96 -0
  41. package/documentation/guardrails-events-telemetry.md +72 -0
  42. package/documentation/jobs-and-realtime.md +69 -0
  43. package/documentation/memory.md +101 -0
  44. package/documentation/multimodal.md +49 -0
  45. package/documentation/orchestration-and-mcp.md +68 -0
  46. package/documentation/prompts.md +51 -0
  47. package/documentation/reliability.md +90 -0
  48. package/documentation/structured-output-and-streaming.md +81 -0
  49. package/package.json +146 -0
  50. package/skill/nestjs-ai/SKILL.md +154 -0
@@ -0,0 +1,117 @@
1
+ # API reference
2
+
3
+ All exports are from `@mgvdev/nestjs-ai` unless a subpath is noted.
4
+
5
+ ## Module
6
+
7
+ | Export | Kind | Notes |
8
+ | --- | --- | --- |
9
+ | `AiModule` | module | `forRoot` / `forRootAsync` / `forFeature` |
10
+ | `AI_MODULE_OPTIONS` | token | injected options |
11
+ | `DEFAULT_MAX_STEPS` | const | default tool-loop bound (5) |
12
+
13
+ ### Tokens
14
+
15
+ `CONVERSATION_STORE` · `VECTOR_STORE` · `AI_CACHE` · `APPROVAL_GATE` ·
16
+ `AGENT_QUEUE` · `RATE_LIMITER` · `RERANKER`.
17
+
18
+ ## Agents & tools
19
+
20
+ | Export | Kind |
21
+ | --- | --- |
22
+ | `@Agent(options)` | class decorator |
23
+ | `AiAgent` | base class (`.run` / `.stream`) |
24
+ | `AgentExecutorService` | runtime |
25
+ | `@Tool(options)` | method decorator |
26
+ | `ToolRegistry` | discovered-tool registry |
27
+ | `AiService` | raw `generateText`/`streamText`/`generateObject`/`streamObject` |
28
+ | `ProviderRegistry` | model resolution |
29
+
30
+ Types: `AgentOptions`, `AgentRunOptions`, `AgentResult`, `ToolOptions`,
31
+ `ToolMetadata`, `ToolEntry`, `ToolRef`, `AiMessage`, `AiInput`, `toMessages`.
32
+
33
+ ## Embeddings & RAG
34
+
35
+ | Export | Kind |
36
+ | --- | --- |
37
+ | `EmbeddingsService` | `embed` / `embedMany` |
38
+ | `RagService` | `ingest` / `retrieve` |
39
+ | `createRetrievalTool(rag, opts)` | tool factory |
40
+ | `InMemoryVectorStore` | default store |
41
+ | `PgVectorStore` | Postgres/pgvector (structural `pg` Pool) |
42
+ | `QdrantVectorStore` | Qdrant (structural client) |
43
+ | `PineconeVectorStore` | Pinecone (structural index) |
44
+ | `HeuristicReranker` / `ModelReranker` | reranking |
45
+ | `splitText(text, size, overlap)` | chunker |
46
+
47
+ Types: `VectorStore`, `VectorDocument`, `VectorQueryResult`, `VectorQueryOptions`,
48
+ `IngestItem`, `IngestOptions`, `RetrieveOptions`, `Reranker`.
49
+
50
+ ## Multimodal
51
+
52
+ `ImageService` · `SpeechService` · `TranscriptionService`. Option types:
53
+ `GenerateImageOptions`, `GenerateSpeechOptions`, `TranscribeOptions`, `AudioInput`.
54
+
55
+ ## Prompts
56
+
57
+ `PromptRegistry` · `interpolate(template, vars)`. Types: `PromptDefinition`,
58
+ `PromptRef`.
59
+
60
+ ## Memory
61
+
62
+ `InMemoryConversationStore` · `SemanticMemory`. Interface: `ConversationStore`.
63
+ Subpath `@mgvdev/nestjs-ai/typeorm`: `TypeOrmConversationStore`,
64
+ `ConversationMessageEntity`. Main: `PrismaConversationStore`,
65
+ `PrismaConversationDelegate`.
66
+
67
+ ## Guardrails, events & safety
68
+
69
+ | Export | Kind |
70
+ | --- | --- |
71
+ | `@Guardrail()` | class decorator |
72
+ | `GuardrailRegistry` | runs the chain |
73
+ | `AiEventEmitter` / `AI_EVENTS` | events |
74
+ | `PiiRedactionGuardrail` / `createPiiRedactionGuardrail` | PII |
75
+ | `redactPii` / `redactMessages` / `DEFAULT_PII_PATTERNS` | helpers |
76
+ | `createModerationGuardrail` / `ContentBlockedError` | moderation |
77
+
78
+ Types: `Guardrail` (as `GuardrailContract`), `GuardrailContext`, event payloads.
79
+
80
+ ## Orchestration & MCP
81
+
82
+ `AgentRegistry` · `createAgentTool` · `McpService`. Types: `AgentEntry`,
83
+ `AgentToolOptions`, `McpClientLike`, `McpToolInfo`, `McpCallResult`.
84
+
85
+ ## Reliability
86
+
87
+ | Export | Kind |
88
+ | --- | --- |
89
+ | `createFallbackModel(models, opts)` | composite model |
90
+ | `InMemoryAiCache` / `createCacheMiddleware` / `cacheKey` | caching |
91
+ | `InMemoryRateLimiter` / `RateLimitGuardrail` / `RateLimitedError` | rate limit |
92
+ | `AutoApproveGate` / `DenyApproveGate` / `ToolApprovalDeniedError` | approval |
93
+ | `UsageTracker` / `BudgetGuard` / `BudgetExceededError` | cost |
94
+ | `costOf` / `DEFAULT_PRICING` / `bareModelId` | pricing |
95
+
96
+ Interfaces: `AiCache`, `RateLimiter`, `ApprovalGate`, `ApprovalContext`. Types:
97
+ `ModelPricing`, `PricingTable`, `UsageTotals`, `UsageRecord`.
98
+
99
+ ## Jobs, HTTP & realtime
100
+
101
+ | Export | Kind |
102
+ | --- | --- |
103
+ | `AgentJobsModule` / `AgentQueueService` / `AgentJobProcessor` | BullMQ |
104
+ | `pipeAgentStream` / `AgentStreamInterceptor` | HTTP/SSE |
105
+ | `streamAgentToSocket` | WebSocket helper (main entry) |
106
+ | `AgentGateway` | WebSocket gateway (`@mgvdev/nestjs-ai/websocket`) |
107
+
108
+ Types: `AgentJobData`, `QueueLike`, `AgentJobsOptions`, `PipeAgentStreamOptions`,
109
+ `SocketLike`, `AgentRunMessage`.
110
+
111
+ ## Evals & testing
112
+
113
+ `EvalRunner` · `createLlmJudge` · `defaultJudge`. Types: `EvalCase`, `EvalScore`,
114
+ `EvalResult`, `EvalReport`, `Judge`, `RunnableAgent`, `JudgeAi`.
115
+
116
+ Subpath `@mgvdev/nestjs-ai/testing`: `createTestingAiModule`, `createMockModel`,
117
+ `createEmbeddingMock`.
@@ -0,0 +1,87 @@
1
+ # Configuration
2
+
3
+ ## `AiModule.forRoot(options)`
4
+
5
+ ```ts
6
+ AiModule.forRoot({
7
+ providers: {
8
+ openai: { apiKey: '…', baseURL?: '…', headers?: {} },
9
+ anthropic: { apiKey: '…' },
10
+ google: { apiKey: '…' },
11
+ },
12
+ defaultModel: 'openai:gpt-4o', // string | string[] (fallback chain)
13
+ defaultEmbeddingModel: 'openai:text-embedding-3-small',
14
+ defaultImageModel: 'openai:dall-e-3',
15
+ defaultSpeechModel: 'openai:tts-1',
16
+ defaultTranscriptionModel: 'openai:whisper-1',
17
+ defaultMaxSteps: 5,
18
+ maxRetries: 2,
19
+
20
+ // memory
21
+ conversationStore: MyStore, // class | { useClass | useFactory | useValue }
22
+ // rag
23
+ vectorStore: { useFactory: () => new PgVectorStore(pool) },
24
+ rerankingModel: 'cohere:rerank-v3.5',
25
+ // prompts / guardrails
26
+ prompts: [{ name: 'support', template: 'Help {{user}}.' }],
27
+ guardrails: [MyGuardrail],
28
+ // reliability
29
+ cache: InMemoryAiCache,
30
+ cacheTtlMs: 60_000,
31
+ approvalGate: MyApprovalGate,
32
+ rateLimiter: { useValue: new InMemoryRateLimiter({ capacity: 10, refillTokens: 10, intervalMs: 60_000 }) },
33
+ // cost
34
+ pricing: { 'gpt-4o': { input: 2.5, output: 10 } },
35
+ maxCostPerConversation: 1.0,
36
+ // telemetry
37
+ telemetry: { isEnabled: true, functionId: 'my-app' },
38
+ });
39
+ ```
40
+
41
+ Every field is optional. Providers you don't list are simply unavailable; their
42
+ `@ai-sdk/*` package only needs to be installed if you configure that provider.
43
+
44
+ ## `AiModule.forRootAsync(options)`
45
+
46
+ Build options from injected dependencies (e.g. `ConfigService`):
47
+
48
+ ```ts
49
+ AiModule.forRootAsync({
50
+ imports: [ConfigModule],
51
+ inject: [ConfigService],
52
+ useFactory: (config: ConfigService) => ({
53
+ providers: { openai: { apiKey: config.getOrThrow('OPENAI_API_KEY') } },
54
+ defaultModel: 'openai:gpt-4o',
55
+ }),
56
+ });
57
+ ```
58
+
59
+ ## `AiModule.forFeature(feature)`
60
+
61
+ Convenience registration so agents/tools/guardrails don't have to be added to a
62
+ consuming module's own `providers`:
63
+
64
+ ```ts
65
+ AiModule.forFeature({
66
+ agents: [SupportAgent],
67
+ tools: [WeatherTools],
68
+ guardrails: [ProfanityGuard],
69
+ prompts: [{ name: 'greeting', template: 'Hi {{name}}' }],
70
+ });
71
+ ```
72
+
73
+ Discovery finds providers globally regardless of where they're registered — but
74
+ they **must** be registered somewhere (here, or in your own module `providers`).
75
+
76
+ ## Model ids
77
+
78
+ - `"provider:model"` — explicit, e.g. `"openai:gpt-4o"`, `"anthropic:claude-sonnet-4"`.
79
+ - Bare `"model"` — works only when a single provider is configured, or when
80
+ `defaultModel` carries the provider prefix.
81
+ - `string[]` — a **fallback chain**: each is tried in order (see
82
+ [Reliability](./reliability.md)).
83
+
84
+ ## Environment
85
+
86
+ AI SDK v7 requires **Node.js ≥ 22**. Provider SDKs also read their own env vars
87
+ (e.g. `OPENAI_API_KEY`) if you omit `apiKey`.
@@ -0,0 +1,51 @@
1
+ # Content safety
2
+
3
+ Two guardrails: **PII redaction** and **moderation**. Both integrate with the
4
+ guardrail pipeline — register them as providers or via `forFeature({ guardrails })`.
5
+
6
+ ## PII redaction
7
+
8
+ Redacts emails, phone numbers, credit cards, and US SSNs from the messages sent
9
+ to the model.
10
+
11
+ ```ts
12
+ import { PiiRedactionGuardrail } from '@mgvdev/nestjs-ai';
13
+
14
+ AiModule.forFeature({ guardrails: [PiiRedactionGuardrail] });
15
+ ```
16
+
17
+ Custom patterns / replacement:
18
+
19
+ ```ts
20
+ import { createPiiRedactionGuardrail } from '@mgvdev/nestjs-ai';
21
+
22
+ const MyRedaction = createPiiRedactionGuardrail({
23
+ patterns: [/\bACC-\d{6}\b/g],
24
+ replacement: '[HIDDEN]',
25
+ });
26
+ AiModule.forFeature({ guardrails: [MyRedaction] });
27
+ ```
28
+
29
+ Standalone helpers are exported too: `redactPii(text, patterns?, replacement?)`
30
+ and `redactMessages(messages, …)`.
31
+
32
+ ## Moderation
33
+
34
+ Blocks a run when the input contains a deny-listed term or fails a custom check.
35
+
36
+ ```ts
37
+ import { createModerationGuardrail } from '@mgvdev/nestjs-ai';
38
+
39
+ const Moderation = createModerationGuardrail({
40
+ blocked: ['secret-project', 'internal-only'],
41
+ moderate: async (text) => (await callModerationApi(text)).flagged, // optional
42
+ });
43
+ AiModule.forFeature({ guardrails: [Moderation] });
44
+ ```
45
+
46
+ A blocked run throws `ContentBlockedError`.
47
+
48
+ ## Ordering
49
+
50
+ Guardrails run in registration order. Put PII redaction before moderation if you
51
+ want moderation to see the redacted text, or after if it should see the raw text.
@@ -0,0 +1,105 @@
1
+ # Embeddings & RAG
2
+
3
+ ## Embeddings
4
+
5
+ ```ts
6
+ import { EmbeddingsService } from '@mgvdev/nestjs-ai';
7
+
8
+ @Injectable()
9
+ export class SearchService {
10
+ constructor(private readonly embeddings: EmbeddingsService) {}
11
+
12
+ async run(docs: string[]) {
13
+ const { embedding } = await this.embeddings.embed('hello');
14
+ const { embeddings } = await this.embeddings.embedMany(docs);
15
+ return embeddings;
16
+ }
17
+ }
18
+ ```
19
+
20
+ Model resolves from `defaultEmbeddingModel` or the `model` option. When a
21
+ [cache](./reliability.md#caching) is configured, single `embed` calls are cached.
22
+
23
+ ## RAG
24
+
25
+ `RagService` chunks, embeds, and stores documents, then retrieves the most
26
+ relevant chunks for a query.
27
+
28
+ ```ts
29
+ await this.rag.ingest(
30
+ [{ id: 'handbook', content: longText, metadata: { source: 'hr' } }],
31
+ { chunkSize: 1000, chunkOverlap: 100 },
32
+ );
33
+
34
+ const hits = await this.rag.retrieve('vacation policy', { topK: 4 });
35
+ // hits: { id, content, score, metadata }[]
36
+ ```
37
+
38
+ ### Expose retrieval to an agent
39
+
40
+ A `@Tool` method that calls `RagService`:
41
+
42
+ ```ts
43
+ @Injectable()
44
+ export class KnowledgeTools {
45
+ constructor(private readonly rag: RagService) {}
46
+
47
+ @Tool({ description: 'Search the handbook', schema: z.object({ query: z.string() }) })
48
+ async search({ query }: { query: string }) {
49
+ const hits = await this.rag.retrieve(query);
50
+ return hits.map((h) => h.content).join('\n\n');
51
+ }
52
+ }
53
+ ```
54
+
55
+ Or use the factory:
56
+
57
+ ```ts
58
+ import { createRetrievalTool } from '@mgvdev/nestjs-ai';
59
+ const tool = createRetrievalTool(rag, { topK: 5 });
60
+ await this.ai.generateText({ model: 'openai:gpt-4o', tools: { search: tool }, prompt });
61
+ ```
62
+
63
+ ## Reranking
64
+
65
+ Fetch more candidates then rerank down to `topK`:
66
+
67
+ ```ts
68
+ const hits = await this.rag.retrieve('query', { topK: 4, rerank: true }); // heuristic default
69
+ ```
70
+
71
+ - `rerank: true` uses the configured default (`HeuristicReranker`, no dependency).
72
+ - Pass a `Reranker` to override; `ModelReranker` uses the AI SDK `rerank()` with a
73
+ rerank-capable provider (`rerankingModel`, e.g. Cohere).
74
+
75
+ ## Vector stores
76
+
77
+ The default store is `InMemoryVectorStore` (cosine similarity). Swap it via
78
+ `vectorStore`:
79
+
80
+ ```ts
81
+ // pgvector — pass your own `pg` Pool
82
+ AiModule.forRoot({ vectorStore: { useFactory: () => new PgVectorStore(pool, { table: 'ai_documents' }) } });
83
+
84
+ // Qdrant
85
+ AiModule.forRoot({ vectorStore: { useFactory: () => new QdrantVectorStore(qdrant, { collection: 'docs' }) } });
86
+
87
+ // Pinecone
88
+ AiModule.forRoot({ vectorStore: { useFactory: () => new PineconeVectorStore(index) } });
89
+ ```
90
+
91
+ Implement the `VectorStore` interface (`upsert` / `query` / `delete` / `clear`)
92
+ for any other backend.
93
+
94
+ ### pgvector schema
95
+
96
+ ```sql
97
+ CREATE EXTENSION IF NOT EXISTS vector;
98
+ CREATE TABLE ai_documents (
99
+ id text PRIMARY KEY,
100
+ content text NOT NULL,
101
+ embedding vector(1536) NOT NULL,
102
+ metadata jsonb
103
+ );
104
+ CREATE INDEX ON ai_documents USING hnsw (embedding vector_cosine_ops);
105
+ ```
@@ -0,0 +1,56 @@
1
+ # Evals & testing
2
+
3
+ ## Testing
4
+
5
+ Never hit real providers in tests. Use `@mgvdev/nestjs-ai/testing` (needs the
6
+ optional peer `msw`, which `ai/test` requires).
7
+
8
+ ```ts
9
+ import { createTestingAiModule, createMockModel } from '@mgvdev/nestjs-ai/testing';
10
+
11
+ it('answers', async () => {
12
+ const app = await createTestingAiModule({
13
+ model: createMockModel('mocked answer'),
14
+ providers: [SupportAgent],
15
+ });
16
+ const { text } = await app.get(SupportAgent).run('hi');
17
+ expect(text).toBe('mocked answer');
18
+ await app.close();
19
+ });
20
+ ```
21
+
22
+ - `createMockModel(reply | replies[])` — a mock language model; an array yields
23
+ one reply per successive call (multi-step).
24
+ - `createEmbeddingMock(fn)` — a mock embedding model from `value => number[]`.
25
+ - `createTestingAiModule({ model, embedding, providers, imports, aiOptions })` —
26
+ boots `AiModule` with `ProviderRegistry` overridden to serve the mocks.
27
+
28
+ To exercise tool calls or multi-step loops with full control, build a
29
+ `MockLanguageModelV3` from `ai/test` directly and pass it via
30
+ `createTestingAiModule({ model })`.
31
+
32
+ ## Evals (LLM-as-judge)
33
+
34
+ `EvalRunner` runs an agent over a set of cases and scores each output.
35
+
36
+ ```ts
37
+ import { EvalRunner, createLlmJudge } from '@mgvdev/nestjs-ai';
38
+
39
+ const report = await this.evals.run(
40
+ supportAgent,
41
+ [
42
+ { input: 'capital of France?', expected: 'Paris' },
43
+ { input: 'summarize X', rubric: 'accurate and concise' },
44
+ ],
45
+ { judge: createLlmJudge(this.ai, { scale: 5, passThreshold: 0.6 }) },
46
+ );
47
+
48
+ // report.averageScore, report.passRate, report.results[]
49
+ ```
50
+
51
+ - **Default judge** (no `judge` option): substring match against `case.expected`.
52
+ - **`createLlmJudge(ai, { model?, scale?, passThreshold? })`**: an LLM scores each
53
+ output 0..`scale` (normalized to `[0, 1]`) using the case's `rubric` / `expected`.
54
+
55
+ Each `EvalResult` has `{ name, input, output, score, passed, reasoning? }`.
56
+ `EvalReport` aggregates `averageScore` and `passRate`.
@@ -0,0 +1,96 @@
1
+ # Getting started
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm install @mgvdev/nestjs-ai ai zod
7
+ npm install @ai-sdk/openai # and/or @ai-sdk/anthropic, @ai-sdk/google
8
+ ```
9
+
10
+ `@nestjs/common`, `@nestjs/core`, `reflect-metadata`, and `rxjs` are already in
11
+ every Nest app. Ensure `reflect-metadata` is imported once at your app entry.
12
+
13
+ ## Register the module
14
+
15
+ ```ts
16
+ import { Module } from '@nestjs/common';
17
+ import { AiModule } from '@mgvdev/nestjs-ai';
18
+
19
+ @Module({
20
+ imports: [
21
+ AiModule.forRoot({
22
+ providers: { openai: { apiKey: process.env.OPENAI_API_KEY } },
23
+ defaultModel: 'openai:gpt-4o',
24
+ }),
25
+ ],
26
+ })
27
+ export class AppModule {}
28
+ ```
29
+
30
+ `AiModule` is global — its services are injectable everywhere.
31
+
32
+ ## Your first agent
33
+
34
+ ```ts
35
+ // support.agent.ts
36
+ import { Agent, AiAgent } from '@mgvdev/nestjs-ai';
37
+
38
+ @Agent({ model: 'openai:gpt-4o', system: 'You are a concise assistant.' })
39
+ export class SupportAgent extends AiAgent {}
40
+ ```
41
+
42
+ ```ts
43
+ // support.module.ts
44
+ import { Module } from '@nestjs/common';
45
+ import { AiModule } from '@mgvdev/nestjs-ai';
46
+ import { SupportAgent } from './support.agent';
47
+
48
+ @Module({ imports: [AiModule.forFeature({ agents: [SupportAgent] })] })
49
+ export class SupportModule {}
50
+ ```
51
+
52
+ ```ts
53
+ // support.service.ts
54
+ import { Injectable } from '@nestjs/common';
55
+ import { SupportAgent } from './support.agent';
56
+
57
+ @Injectable()
58
+ export class SupportService {
59
+ constructor(private readonly agent: SupportAgent) {}
60
+
61
+ ask(question: string) {
62
+ return this.agent.run(question).then((r) => r.text);
63
+ }
64
+ }
65
+ ```
66
+
67
+ ## Add a tool
68
+
69
+ ```ts
70
+ import { Injectable } from '@nestjs/common';
71
+ import { Tool } from '@mgvdev/nestjs-ai';
72
+ import { z } from 'zod';
73
+
74
+ @Injectable()
75
+ export class ClockTools {
76
+ @Tool({ description: 'Get the current time', schema: z.object({}) })
77
+ now() {
78
+ return new Date().toISOString();
79
+ }
80
+ }
81
+ ```
82
+
83
+ Reference it on the agent and register both:
84
+
85
+ ```ts
86
+ @Agent({ model: 'openai:gpt-4o', system: '…', tools: [ClockTools] })
87
+ export class SupportAgent extends AiAgent {}
88
+
89
+ AiModule.forFeature({ agents: [SupportAgent], tools: [ClockTools] });
90
+ ```
91
+
92
+ ## Next
93
+
94
+ - [Configuration](./configuration.md) — providers, defaults, async config.
95
+ - [Agents & tools](./agents-and-tools.md) — the full agent/tool model.
96
+ - [Evals & testing](./evals-and-testing.md) — test without hitting providers.
@@ -0,0 +1,72 @@
1
+ # Guardrails, events & telemetry
2
+
3
+ ## Guardrails
4
+
5
+ A guardrail inspects, mutates, or blocks agent runs and tool calls. Throw from
6
+ any hook to abort.
7
+
8
+ ```ts
9
+ import { Guardrail, type GuardrailContext } from '@mgvdev/nestjs-ai';
10
+
11
+ @Guardrail()
12
+ export class ProfanityGuard {
13
+ beforeRun(ctx: GuardrailContext) {
14
+ // ctx.messages is mutable; throw to block
15
+ if (isProfane(ctx.messages)) throw new ForbiddenException('Blocked');
16
+ }
17
+ afterRun(ctx: GuardrailContext, result) { /* inspect the result */ }
18
+ onToolCall(tool: string, args: unknown) { /* veto specific tool calls */ }
19
+ }
20
+ ```
21
+
22
+ Register as a provider (auto-discovered) or via
23
+ `AiModule.forRoot({ guardrails: [ProfanityGuard] })` /
24
+ `AiModule.forFeature({ guardrails })`.
25
+
26
+ Hooks run in registration order:
27
+ - `beforeRun(ctx)` — before generation; mutate `ctx.messages` or throw.
28
+ - `afterRun(ctx, result)` — after generation.
29
+ - `onToolCall(tool, args)` — before each tool executes; throw to block.
30
+
31
+ Built-in guardrails ship for cost budgets, rate limiting, PII redaction, and
32
+ moderation — see [Reliability](./reliability.md) and
33
+ [Content safety](./content-safety.md).
34
+
35
+ ## Events
36
+
37
+ With `@nestjs/event-emitter` installed and `EventEmitterModule.forRoot()`
38
+ imported, the library emits lifecycle events (no-op otherwise).
39
+
40
+ ```ts
41
+ import { AI_EVENTS } from '@mgvdev/nestjs-ai';
42
+
43
+ @Injectable()
44
+ export class AiListener {
45
+ @OnEvent(AI_EVENTS.agentRunFinish)
46
+ onFinish(payload: { agent: string; result: AgentResult }) { /* … */ }
47
+
48
+ @OnEvent(AI_EVENTS.usage)
49
+ onUsage(record: { model: string; cost: number; inputTokens: number }) { /* … */ }
50
+ }
51
+ ```
52
+
53
+ Event names:
54
+
55
+ | Constant | Name | Payload |
56
+ | --- | --- | --- |
57
+ | `agentRunStart` | `ai.agent.run.start` | `{ agent, input, options }` |
58
+ | `agentRunFinish` | `ai.agent.run.finish` | `{ agent, result }` |
59
+ | `agentRunError` | `ai.agent.run.error` | `{ agent, error }` |
60
+ | `toolCall` | `ai.tool.call` | `{ tool, args }` |
61
+ | `toolResult` | `ai.tool.result` | `{ tool, args, result }` |
62
+ | `streamFinish` | `ai.stream.finish` | `{ agent }` |
63
+ | `usage` | `ai.usage` | `{ model, agent?, inputTokens, outputTokens, cost }` |
64
+
65
+ ## Telemetry (OpenTelemetry)
66
+
67
+ Forward spans to the AI SDK's `experimental_telemetry` (requires an OTel setup in
68
+ your app):
69
+
70
+ ```ts
71
+ AiModule.forRoot({ telemetry: { isEnabled: true, functionId: 'support-agent' } });
72
+ ```
@@ -0,0 +1,69 @@
1
+ # Background jobs & realtime
2
+
3
+ ## Background jobs (BullMQ)
4
+
5
+ Run agents asynchronously. `AgentJobsModule` is a separate module (not part of
6
+ the global `AiModule`); it needs the optional peer `bullmq` and a Redis instance.
7
+
8
+ ```ts
9
+ import { AgentJobsModule } from '@mgvdev/nestjs-ai';
10
+
11
+ @Module({
12
+ imports: [
13
+ AgentJobsModule.forRoot({
14
+ connection: { host: 'localhost', port: 6379 },
15
+ queueName: 'ai-agent-runs', // optional
16
+ runWorker: true, // start a worker in this process (default)
17
+ }),
18
+ ],
19
+ })
20
+ export class JobsModule {}
21
+ ```
22
+
23
+ Enqueue a run:
24
+
25
+ ```ts
26
+ constructor(private readonly queue: AgentQueueService) {}
27
+
28
+ const jobId = await this.queue.enqueue({
29
+ agent: 'SupportAgent', // agent class name (resolved via AgentRegistry)
30
+ input: 'summarize ticket 42',
31
+ options: { conversationId: 't-42' },
32
+ });
33
+ ```
34
+
35
+ The worker resolves the agent from `AgentRegistry`, runs it, and returns the
36
+ `AgentResult` as the job's return value (BullMQ stores it). Query status via the
37
+ BullMQ job API.
38
+
39
+ For fine control, inject `AgentJobProcessor` and call `run(data)` yourself from a
40
+ custom worker.
41
+
42
+ ## Realtime (WebSocket)
43
+
44
+ Stream agent responses over socket.io. From `@mgvdev/nestjs-ai/websocket` (needs
45
+ `@nestjs/websockets`, `@nestjs/platform-socket.io`, `socket.io`).
46
+
47
+ ```ts
48
+ import { AgentGateway } from '@mgvdev/nestjs-ai/websocket';
49
+
50
+ @Module({ providers: [AgentGateway] })
51
+ export class RealtimeModule {}
52
+ ```
53
+
54
+ Protocol:
55
+ - Client emits `agent:run` `{ agent, input }`.
56
+ - Server streams `agent:chunk` `{ delta }` per token, then `agent:done` `{ text }`.
57
+ - Errors arrive as `agent:error` `{ message }`.
58
+
59
+ ```js
60
+ socket.emit('agent:run', { agent: 'ChatAgent', input: 'Hello' });
61
+ socket.on('agent:chunk', ({ delta }) => append(delta));
62
+ socket.on('agent:done', ({ text }) => finalize(text));
63
+ ```
64
+
65
+ ### Framework-agnostic helper
66
+
67
+ `streamAgentToSocket(streamResult, socket, options?)` (from the main entry)
68
+ forwards a stream to any object with an `emit` method — use it to build your own
69
+ gateway or adapter.