@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.
- package/LICENSE +21 -0
- package/README.md +655 -0
- package/dist/ai-module-options.interface-B5X4AFLC.d.cts +192 -0
- package/dist/ai-module-options.interface-Ca6y_MY2.d.ts +192 -0
- package/dist/conversation-store.interface-CtQY-qcc.d.cts +30 -0
- package/dist/conversation-store.interface-CtQY-qcc.d.ts +30 -0
- package/dist/index.cjs +3209 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1441 -0
- package/dist/index.d.ts +1441 -0
- package/dist/index.js +3163 -0
- package/dist/index.js.map +1 -0
- package/dist/stream-to-socket-fJphU0WN.d.cts +47 -0
- package/dist/stream-to-socket-fJphU0WN.d.ts +47 -0
- package/dist/testing.cjs +2395 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +46 -0
- package/dist/testing.d.ts +46 -0
- package/dist/testing.js +2391 -0
- package/dist/testing.js.map +1 -0
- package/dist/typeorm.cjs +115 -0
- package/dist/typeorm.cjs.map +1 -0
- package/dist/typeorm.d.cts +44 -0
- package/dist/typeorm.d.ts +44 -0
- package/dist/typeorm.js +113 -0
- package/dist/typeorm.js.map +1 -0
- package/dist/websocket.cjs +154 -0
- package/dist/websocket.cjs.map +1 -0
- package/dist/websocket.d.cts +25 -0
- package/dist/websocket.d.ts +25 -0
- package/dist/websocket.js +152 -0
- package/dist/websocket.js.map +1 -0
- package/documentation/README.md +44 -0
- package/documentation/agents-and-tools.md +102 -0
- package/documentation/api-reference.md +117 -0
- package/documentation/configuration.md +87 -0
- package/documentation/content-safety.md +51 -0
- package/documentation/embeddings-and-rag.md +105 -0
- package/documentation/evals-and-testing.md +56 -0
- package/documentation/getting-started.md +96 -0
- package/documentation/guardrails-events-telemetry.md +72 -0
- package/documentation/jobs-and-realtime.md +69 -0
- package/documentation/memory.md +101 -0
- package/documentation/multimodal.md +49 -0
- package/documentation/orchestration-and-mcp.md +68 -0
- package/documentation/prompts.md +51 -0
- package/documentation/reliability.md +90 -0
- package/documentation/structured-output-and-streaming.md +81 -0
- package/package.json +146 -0
- package/skill/nestjs-ai/SKILL.md +154 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Memory
|
|
2
|
+
|
|
3
|
+
Two complementary systems: **conversation history** (raw turns) and **semantic
|
|
4
|
+
memory** (embedding-based recall).
|
|
5
|
+
|
|
6
|
+
## Conversation history
|
|
7
|
+
|
|
8
|
+
Pass a `conversationId` to load prior turns before a run and persist the new
|
|
9
|
+
exchange after.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
await agent.run('What about tomorrow?', { conversationId: user.id });
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The default `ConversationStore` is in-memory. Provide your own by implementing
|
|
16
|
+
the interface:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
interface ConversationStore {
|
|
20
|
+
load(conversationId: string): Promise<AiMessage[]>;
|
|
21
|
+
append(conversationId: string, messages: AiMessage[]): Promise<void>;
|
|
22
|
+
clear(conversationId: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### TypeORM store
|
|
27
|
+
|
|
28
|
+
From `@mgvdev/nestjs-ai/typeorm` (needs `typeorm` + `@nestjs/typeorm`):
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { getRepositoryToken, TypeOrmModule } from '@nestjs/typeorm';
|
|
32
|
+
import { ConversationMessageEntity, TypeOrmConversationStore } from '@mgvdev/nestjs-ai/typeorm';
|
|
33
|
+
|
|
34
|
+
@Module({
|
|
35
|
+
imports: [
|
|
36
|
+
TypeOrmModule.forFeature([ConversationMessageEntity]),
|
|
37
|
+
AiModule.forRoot({
|
|
38
|
+
providers: { openai: { apiKey } },
|
|
39
|
+
conversationStore: {
|
|
40
|
+
useFactory: (repo) => new TypeOrmConversationStore(repo),
|
|
41
|
+
inject: [getRepositoryToken(ConversationMessageEntity)],
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
export class AppModule {}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Prisma store
|
|
50
|
+
|
|
51
|
+
No build-time dependency — pass a model delegate:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { PrismaConversationStore } from '@mgvdev/nestjs-ai';
|
|
55
|
+
|
|
56
|
+
AiModule.forRoot({
|
|
57
|
+
conversationStore: {
|
|
58
|
+
useFactory: (prisma: PrismaService) => new PrismaConversationStore(prisma.aiMessage),
|
|
59
|
+
inject: [PrismaService],
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```prisma
|
|
65
|
+
model AiMessage {
|
|
66
|
+
id String @id @default(cuid())
|
|
67
|
+
conversationId String
|
|
68
|
+
role String
|
|
69
|
+
content Json
|
|
70
|
+
position Int
|
|
71
|
+
createdAt DateTime @default(now())
|
|
72
|
+
@@index([conversationId])
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Semantic memory
|
|
77
|
+
|
|
78
|
+
`SemanticMemory` stores snippets per conversation as embeddings and recalls the
|
|
79
|
+
most relevant ones — useful for long-term facts beyond raw turns.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
await this.memory.remember(conversationId, 'The user prefers dark mode.');
|
|
83
|
+
|
|
84
|
+
// recall relevant snippets:
|
|
85
|
+
const hits = await this.memory.recall(conversationId, 'settings', { topK: 3 });
|
|
86
|
+
|
|
87
|
+
// summarize a conversation and store it (requires AiService):
|
|
88
|
+
await this.memory.rememberConversation(conversationId, messages, { summarize: true });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Automatic recall in a run
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
await agent.run('what are my settings?', {
|
|
95
|
+
conversationId,
|
|
96
|
+
recall: { topK: 3 }, // recalled snippets are prepended to the system prompt
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Semantic memory uses the configured `VectorStore` (isolated per conversation via
|
|
101
|
+
metadata), so it works with in-memory, pgvector, Qdrant, or Pinecone.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Multimodal
|
|
2
|
+
|
|
3
|
+
Three injectable services wrap the AI SDK's image, speech, and transcription
|
|
4
|
+
APIs. Set `defaultImageModel` / `defaultSpeechModel` /
|
|
5
|
+
`defaultTranscriptionModel`, or pass `model` per call.
|
|
6
|
+
|
|
7
|
+
## Image generation
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { ImageService } from '@mgvdev/nestjs-ai';
|
|
11
|
+
|
|
12
|
+
const { image, images } = await this.images.generate('a fox in the snow', {
|
|
13
|
+
model: 'openai:dall-e-3',
|
|
14
|
+
size: '1024x1024',
|
|
15
|
+
aspectRatio: '16:9',
|
|
16
|
+
n: 1,
|
|
17
|
+
});
|
|
18
|
+
// image: GeneratedFile (base64 / bytes)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Speech (text-to-speech)
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { SpeechService } from '@mgvdev/nestjs-ai';
|
|
25
|
+
|
|
26
|
+
const { audio } = await this.speech.generate('Hello there', {
|
|
27
|
+
model: 'openai:tts-1',
|
|
28
|
+
voice: 'alloy',
|
|
29
|
+
outputFormat: 'mp3',
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Transcription (speech-to-text)
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { TranscriptionService } from '@mgvdev/nestjs-ai';
|
|
37
|
+
|
|
38
|
+
const { text, segments } = await this.transcription.transcribe(audioBuffer, {
|
|
39
|
+
model: 'openai:whisper-1',
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`audio` accepts a `Uint8Array`, `ArrayBuffer`, `Buffer`, base64 string, or `URL`.
|
|
44
|
+
|
|
45
|
+
## Provider support
|
|
46
|
+
|
|
47
|
+
Availability depends on the provider SDK. OpenAI supports image, speech, and
|
|
48
|
+
transcription; other providers vary. A clear error is thrown when a configured
|
|
49
|
+
provider does not support the requested capability.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Orchestration & MCP
|
|
2
|
+
|
|
3
|
+
## Multi-agent orchestration
|
|
4
|
+
|
|
5
|
+
Reference an `@Agent` class in another agent's `tools` to delegate to it
|
|
6
|
+
(supervisor / handoff). The sub-agent's output text is returned to the caller.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
@Agent({ model: 'openai:gpt-4o', system: 'Research facts.' })
|
|
10
|
+
export class ResearchAgent extends AiAgent {}
|
|
11
|
+
|
|
12
|
+
@Agent({ model: 'openai:gpt-4o', system: 'Write the final answer.' })
|
|
13
|
+
export class WriterAgent extends AiAgent {}
|
|
14
|
+
|
|
15
|
+
@Agent({
|
|
16
|
+
model: 'openai:gpt-4o',
|
|
17
|
+
system: 'Coordinate specialists to answer the user.',
|
|
18
|
+
tools: [ResearchAgent, WriterAgent], // sub-agents used as tools
|
|
19
|
+
})
|
|
20
|
+
export class SupervisorAgent extends AiAgent {}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Register all three as providers. The supervisor calls each sub-agent by its class
|
|
24
|
+
name.
|
|
25
|
+
|
|
26
|
+
### `AgentRegistry`
|
|
27
|
+
|
|
28
|
+
Indexes all agents by class name — used by orchestration and background jobs.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
this.agents.get('ResearchAgent'); // instance
|
|
32
|
+
this.agents.all(); // AgentEntry[]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### `createAgentTool`
|
|
36
|
+
|
|
37
|
+
Wrap an agent as a tool manually:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { createAgentTool } from '@mgvdev/nestjs-ai';
|
|
41
|
+
const tool = createAgentTool(researchAgent, { name: 'research', description: '…' });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## MCP (Model Context Protocol)
|
|
45
|
+
|
|
46
|
+
Adapt tools from an MCP server into an agent tool set. Bring your own client from
|
|
47
|
+
`@modelcontextprotocol/sdk` — any object with `listTools` / `callTool` / `close`.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { McpService } from '@mgvdev/nestjs-ai';
|
|
51
|
+
|
|
52
|
+
// connect once (e.g. on module init)
|
|
53
|
+
const toolSet = await this.mcp.connect('filesystem', mcpClient);
|
|
54
|
+
|
|
55
|
+
// use the tools
|
|
56
|
+
await this.ai.generateText({ model: 'openai:gpt-4o', tools: toolSet, prompt });
|
|
57
|
+
|
|
58
|
+
// or fetch later / merge
|
|
59
|
+
this.mcp.getToolSet('filesystem');
|
|
60
|
+
this.mcp.getAllTools();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Clients are closed automatically on module destroy. MCP tools are exposed to the
|
|
64
|
+
model via the AI SDK's `dynamicTool`, with input schemas passed through from the
|
|
65
|
+
server's JSON schema.
|
|
66
|
+
|
|
67
|
+
> AI SDK v7 does not ship an MCP client; use `@modelcontextprotocol/sdk` directly
|
|
68
|
+
> to construct the transport + client, then hand it to `McpService`.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Prompt registry
|
|
2
|
+
|
|
3
|
+
Register named, versioned prompt templates and render them with `{{var}}`
|
|
4
|
+
interpolation.
|
|
5
|
+
|
|
6
|
+
## Register
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
AiModule.forRoot({
|
|
10
|
+
prompts: [
|
|
11
|
+
{ name: 'support', version: 'v1', template: 'Help {{user}} with {{topic}}.' },
|
|
12
|
+
{ name: 'support', version: 'v2', template: 'Assist {{user}} regarding {{topic}}.' },
|
|
13
|
+
],
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Also via `AiModule.forFeature({ prompts })`, or imperatively:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
this.prompts.register({ name: 'greeting', template: 'Hi {{name}}!' });
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Render
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
this.prompts.render('support', { user: 'Ada', topic: 'billing' });
|
|
27
|
+
// latest version by default; pass { version: 'v1' } to pin
|
|
28
|
+
this.prompts.render('support', { user: 'Ada', topic: 'billing' }, { version: 'v1' });
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Unknown variables throw, so typos are caught early. Duplicate `(name, version)`
|
|
32
|
+
registration throws.
|
|
33
|
+
|
|
34
|
+
## Use as an agent's system prompt
|
|
35
|
+
|
|
36
|
+
Resolve the system prompt per call from the registry:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
await agent.run(question, {
|
|
40
|
+
systemPrompt: { name: 'support', vars: { user, topic }, version: 'v2' },
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This overrides the agent's static `system`.
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
- `register(def)` / `registerAll(defs)`
|
|
49
|
+
- `render(name, vars?, { version? })`
|
|
50
|
+
- `get(name, version?)` — the raw `PromptDefinition`
|
|
51
|
+
- `has(name)`
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Reliability
|
|
2
|
+
|
|
3
|
+
Fallback models, retries, caching, rate limiting, tool approval, and cost budgets.
|
|
4
|
+
|
|
5
|
+
## Fallback models & retries
|
|
6
|
+
|
|
7
|
+
Pass a **model array** to configure a fallback chain — each model is tried in
|
|
8
|
+
order when the previous one throws.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
@Agent({ model: ['openai:gpt-4o', 'anthropic:claude-sonnet-4'] })
|
|
12
|
+
export class ResilientAgent extends AiAgent {}
|
|
13
|
+
|
|
14
|
+
// or per call, plus SDK-native retries:
|
|
15
|
+
await agent.run(prompt, { model: ['openai:gpt-4o', 'openai:gpt-4o-mini'], maxRetries: 3 });
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Build a composite model manually with `createFallbackModel(models, { shouldRetry })`.
|
|
19
|
+
|
|
20
|
+
## Caching
|
|
21
|
+
|
|
22
|
+
Memoize model responses (via middleware) and single embeddings.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
AiModule.forRoot({
|
|
26
|
+
providers: { openai: { apiKey } },
|
|
27
|
+
cache: InMemoryAiCache, // or a class/factory/value implementing AiCache
|
|
28
|
+
cacheTtlMs: 60_000,
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Implement `AiCache` (`get` / `set`) for Redis or another backend. Cache keys are
|
|
33
|
+
derived from the model id and call parameters.
|
|
34
|
+
|
|
35
|
+
## Rate limiting
|
|
36
|
+
|
|
37
|
+
Throttle runs per conversation (or `"global"`).
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
AiModule.forRoot({
|
|
41
|
+
providers: { openai: { apiKey } },
|
|
42
|
+
rateLimiter: {
|
|
43
|
+
useValue: new InMemoryRateLimiter({ capacity: 10, refillTokens: 10, intervalMs: 60_000 }),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A throttled run throws `RateLimitedError`. Implement `RateLimiter`
|
|
49
|
+
(`consume(key, cost?)`) for a distributed limiter.
|
|
50
|
+
|
|
51
|
+
## Tool approval (human-in-the-loop)
|
|
52
|
+
|
|
53
|
+
Flag a tool with `requiresApproval` and register an `ApprovalGate`.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
@Tool({ description: 'Delete a record', schema: z.object({ id: z.string() }), requiresApproval: true })
|
|
57
|
+
deleteRecord({ id }: { id: string }) { /* … */ }
|
|
58
|
+
|
|
59
|
+
@Injectable()
|
|
60
|
+
class QueueApprovalGate implements ApprovalGate {
|
|
61
|
+
async requestApproval({ tool, args }: ApprovalContext) {
|
|
62
|
+
return askAHuman(tool, args); // resolve true to allow, false to block
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
AiModule.forRoot({ providers: { openai: { apiKey } }, approvalGate: QueueApprovalGate });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Defaults `AutoApproveGate` / `DenyApproveGate` are provided. A denied call throws
|
|
70
|
+
`ToolApprovalDeniedError`.
|
|
71
|
+
|
|
72
|
+
## Cost tracking & budgets
|
|
73
|
+
|
|
74
|
+
Token usage and USD cost are tracked per conversation. Set a budget to block
|
|
75
|
+
runaway spend.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
AiModule.forRoot({
|
|
79
|
+
providers: { openai: { apiKey } },
|
|
80
|
+
maxCostPerConversation: 0.5, // blocks the next run once $0.50 is reached
|
|
81
|
+
pricing: { 'gpt-4o': { input: 2.5, output: 10 } }, // override defaults ($/1M tokens)
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// inspect anytime
|
|
85
|
+
const { cost, inputTokens, runs } = this.usage.totals(conversationId);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Listen with `@OnEvent('ai.usage')`. The budget check throws `BudgetExceededError`.
|
|
89
|
+
Costs use `DEFAULT_PRICING` (a small built-in table) merged with your `pricing`
|
|
90
|
+
overrides; unknown models cost `0`.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Structured output & streaming
|
|
2
|
+
|
|
3
|
+
## Structured output
|
|
4
|
+
|
|
5
|
+
Declare an `output` schema on the agent (or pass `schema` per call) to receive a
|
|
6
|
+
validated object instead of text.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
@Agent({
|
|
10
|
+
model: 'openai:gpt-4o',
|
|
11
|
+
system: 'Extract the order details.',
|
|
12
|
+
output: z.object({ product: z.string(), quantity: z.number() }),
|
|
13
|
+
})
|
|
14
|
+
export class OrderAgent extends AiAgent {}
|
|
15
|
+
|
|
16
|
+
const { object } = await orderAgent.run<{ product: string; quantity: number }>(text);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Per call:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
const { object } = await agent.run(input, {
|
|
23
|
+
schema: z.object({ sentiment: z.enum(['positive', 'neutral', 'negative']) }),
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Structured-output runs use `generateObject` under the hood; the result's
|
|
28
|
+
`object` is validated against the schema.
|
|
29
|
+
|
|
30
|
+
## Streaming
|
|
31
|
+
|
|
32
|
+
`.stream()` returns the raw Vercel AI SDK stream result. Iterate `textStream` or
|
|
33
|
+
pipe it to an HTTP response.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const result = agent.stream('Tell me a story');
|
|
37
|
+
for await (const delta of result.textStream) {
|
|
38
|
+
process.stdout.write(delta);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Stream to an HTTP response
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { pipeAgentStream } from '@mgvdev/nestjs-ai';
|
|
46
|
+
|
|
47
|
+
@Post('chat')
|
|
48
|
+
chat(@Body('prompt') prompt: string, @Res() res: Response) {
|
|
49
|
+
pipeAgentStream(agent.stream(prompt), res, { protocol: 'ui' }); // 'ui' | 'text'
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- `'ui'` → AI SDK UI message stream (for `useChat` on the frontend).
|
|
54
|
+
- `'text'` → a plain text stream.
|
|
55
|
+
|
|
56
|
+
### With an interceptor
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { AgentStreamInterceptor } from '@mgvdev/nestjs-ai';
|
|
60
|
+
|
|
61
|
+
@UseInterceptors(new AgentStreamInterceptor({ protocol: 'ui' }))
|
|
62
|
+
@Post('chat')
|
|
63
|
+
chat(@Body('prompt') prompt: string) {
|
|
64
|
+
return agent.stream(prompt); // returned stream result is piped automatically
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Streaming structured objects
|
|
69
|
+
|
|
70
|
+
When the agent has an `output` schema, `.stream()` returns a `streamObject`
|
|
71
|
+
result — iterate `partialObjectStream` for progressive objects.
|
|
72
|
+
|
|
73
|
+
### Realtime over WebSocket
|
|
74
|
+
|
|
75
|
+
See [Background jobs & realtime](./jobs-and-realtime.md) for `AgentGateway`
|
|
76
|
+
(`@mgvdev/nestjs-ai/websocket`).
|
|
77
|
+
|
|
78
|
+
## Persistence during streaming
|
|
79
|
+
|
|
80
|
+
When you pass a `conversationId`, the new user turn and the model's response are
|
|
81
|
+
persisted on stream finish (`onFinish`), just like `run()`.
|
package/package.json
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mgvdev/nestjs-ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Structured AI toolkit for NestJS — agents, tools, structured output, streaming and embeddings on top of the Vercel AI SDK.",
|
|
5
|
+
"author": "Maxence Guyonvarho",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nestjs",
|
|
9
|
+
"ai",
|
|
10
|
+
"llm",
|
|
11
|
+
"agents",
|
|
12
|
+
"tools",
|
|
13
|
+
"openai",
|
|
14
|
+
"anthropic",
|
|
15
|
+
"vercel-ai-sdk",
|
|
16
|
+
"embeddings"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.cjs",
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"require": "./dist/index.cjs"
|
|
27
|
+
},
|
|
28
|
+
"./typeorm": {
|
|
29
|
+
"types": "./dist/typeorm.d.ts",
|
|
30
|
+
"import": "./dist/typeorm.js",
|
|
31
|
+
"require": "./dist/typeorm.cjs"
|
|
32
|
+
},
|
|
33
|
+
"./websocket": {
|
|
34
|
+
"types": "./dist/websocket.d.ts",
|
|
35
|
+
"import": "./dist/websocket.js",
|
|
36
|
+
"require": "./dist/websocket.cjs"
|
|
37
|
+
},
|
|
38
|
+
"./testing": {
|
|
39
|
+
"types": "./dist/testing.d.ts",
|
|
40
|
+
"import": "./dist/testing.js",
|
|
41
|
+
"require": "./dist/testing.cjs"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"documentation",
|
|
47
|
+
"skill",
|
|
48
|
+
"README.md",
|
|
49
|
+
"LICENSE"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"dev": "tsup --watch",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"prepublishOnly": "npm run build"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
61
|
+
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
|
62
|
+
"@nestjs/event-emitter": "^2.0.0 || ^3.0.0",
|
|
63
|
+
"@nestjs/typeorm": "^10.0.0 || ^11.0.0",
|
|
64
|
+
"ai": "^7.0.0",
|
|
65
|
+
"reflect-metadata": "^0.1.13 || ^0.2.0",
|
|
66
|
+
"rxjs": "^7.0.0",
|
|
67
|
+
"typeorm": "^0.3.0 || ^1.0.0",
|
|
68
|
+
"zod": "^3.23.0 || ^4.0.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"@ai-sdk/openai": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"@ai-sdk/anthropic": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@ai-sdk/google": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"@nestjs/event-emitter": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"@nestjs/typeorm": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"typeorm": {
|
|
87
|
+
"optional": true
|
|
88
|
+
},
|
|
89
|
+
"bullmq": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"pg": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"@modelcontextprotocol/sdk": {
|
|
96
|
+
"optional": true
|
|
97
|
+
},
|
|
98
|
+
"@nestjs/websockets": {
|
|
99
|
+
"optional": true
|
|
100
|
+
},
|
|
101
|
+
"@nestjs/platform-socket.io": {
|
|
102
|
+
"optional": true
|
|
103
|
+
},
|
|
104
|
+
"socket.io": {
|
|
105
|
+
"optional": true
|
|
106
|
+
},
|
|
107
|
+
"@qdrant/js-client-rest": {
|
|
108
|
+
"optional": true
|
|
109
|
+
},
|
|
110
|
+
"@pinecone-database/pinecone": {
|
|
111
|
+
"optional": true
|
|
112
|
+
},
|
|
113
|
+
"msw": {
|
|
114
|
+
"optional": true
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"devDependencies": {
|
|
118
|
+
"@ai-sdk/anthropic": "^3.0.95",
|
|
119
|
+
"@ai-sdk/google": "^3.0.90",
|
|
120
|
+
"@ai-sdk/openai": "^3.0.82",
|
|
121
|
+
"@nestjs/common": "^11.0.0",
|
|
122
|
+
"@nestjs/core": "^11.0.0",
|
|
123
|
+
"@nestjs/event-emitter": "^3.1.0",
|
|
124
|
+
"@nestjs/platform-socket.io": "^11.1.28",
|
|
125
|
+
"@nestjs/testing": "^11.0.0",
|
|
126
|
+
"@nestjs/typeorm": "^11.0.3",
|
|
127
|
+
"@nestjs/websockets": "^11.1.28",
|
|
128
|
+
"@swc/core": "^1.15.43",
|
|
129
|
+
"@types/node": "^22.0.0",
|
|
130
|
+
"ai": "^7.0.17",
|
|
131
|
+
"better-sqlite3": "^12.11.1",
|
|
132
|
+
"msw": "^2.15.0",
|
|
133
|
+
"reflect-metadata": "^0.2.2",
|
|
134
|
+
"rxjs": "^7.8.1",
|
|
135
|
+
"socket.io": "^4.8.3",
|
|
136
|
+
"tsup": "^8.3.0",
|
|
137
|
+
"typeorm": "^1.0.0",
|
|
138
|
+
"typescript": "^5.6.0",
|
|
139
|
+
"unplugin-swc": "^1.5.9",
|
|
140
|
+
"vitest": "^2.1.0",
|
|
141
|
+
"zod": "^3.23.0"
|
|
142
|
+
},
|
|
143
|
+
"engines": {
|
|
144
|
+
"node": ">=22"
|
|
145
|
+
}
|
|
146
|
+
}
|