@jaypie/mcp 0.2.2 → 0.2.4
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/dist/index.js +208 -3
- package/dist/index.js.map +1 -1
- package/dist/llm.d.ts +41 -0
- package/package.json +3 -2
- package/prompts/Jaypie_CDK_Constructs_and_Patterns.md +51 -0
- package/prompts/Jaypie_DynamoDB_Package.md +547 -0
- package/prompts/Jaypie_Express_Package.md +91 -0
- package/prompts/Jaypie_Init_Lambda_Package.md +134 -1
- package/prompts/Jaypie_Llm_Calls.md +339 -3
- package/prompts/Jaypie_Llm_Tools.md +43 -12
- package/prompts/Jaypie_Vocabulary_Commander.md +411 -0
- package/prompts/Jaypie_Vocabulary_LLM.md +312 -0
- package/prompts/Jaypie_Vocabulary_Lambda.md +310 -0
- package/prompts/Jaypie_Vocabulary_MCP.md +296 -0
- package/prompts/Jaypie_Vocabulary_Package.md +141 -183
|
@@ -406,3 +406,94 @@ expressHandler automatically sets these headers:
|
|
|
406
406
|
## Datadog Integration
|
|
407
407
|
|
|
408
408
|
When Datadog environment variables are configured, expressHandler automatically submits metrics for each request including status code and path.
|
|
409
|
+
|
|
410
|
+
## Streaming Responses
|
|
411
|
+
|
|
412
|
+
Use `expressStreamHandler` for Server-Sent Events (SSE) streaming responses. Ideal for real-time updates, LLM streaming, and long-running operations.
|
|
413
|
+
|
|
414
|
+
### Basic Streaming Usage
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
import { expressStreamHandler } from "jaypie";
|
|
418
|
+
import type { Request, Response } from "express";
|
|
419
|
+
|
|
420
|
+
const streamRoute = expressStreamHandler(async (req: Request, res: Response) => {
|
|
421
|
+
// Write SSE events directly to response
|
|
422
|
+
res.write("event: message\ndata: {\"text\": \"Hello\"}\n\n");
|
|
423
|
+
res.write("event: message\ndata: {\"text\": \"World\"}\n\n");
|
|
424
|
+
// Handler automatically ends the stream
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
app.get("/stream", streamRoute);
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Streaming with LLM
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import { expressStreamHandler, Llm, createExpressStream } from "jaypie";
|
|
434
|
+
|
|
435
|
+
const llmStreamRoute = expressStreamHandler(async (req: Request, res: Response) => {
|
|
436
|
+
const llm = new Llm("anthropic");
|
|
437
|
+
const stream = llm.stream(req.body.prompt);
|
|
438
|
+
|
|
439
|
+
// createExpressStream pipes LLM chunks as SSE events
|
|
440
|
+
await createExpressStream(stream, res);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
app.post("/chat", llmStreamRoute);
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Stream Handler Options
|
|
447
|
+
|
|
448
|
+
`expressStreamHandler` supports the same lifecycle options as `expressHandler`:
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
import { expressStreamHandler } from "jaypie";
|
|
452
|
+
import type { ExpressStreamHandlerOptions } from "jaypie";
|
|
453
|
+
|
|
454
|
+
const options: ExpressStreamHandlerOptions = {
|
|
455
|
+
name: "myStreamHandler", // Handler name for logging
|
|
456
|
+
contentType: "text/event-stream", // Default SSE content type
|
|
457
|
+
chaos: "low", // Chaos testing level
|
|
458
|
+
secrets: ["API_KEY"], // Secrets to load
|
|
459
|
+
setup: [], // Setup function(s)
|
|
460
|
+
teardown: [], // Teardown function(s)
|
|
461
|
+
validate: [], // Validation function(s)
|
|
462
|
+
locals: {}, // Values to set on req.locals
|
|
463
|
+
unavailable: false, // Return 503 if true
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const handler = expressStreamHandler(async (req, res) => {
|
|
467
|
+
// Streaming logic
|
|
468
|
+
}, options);
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### SSE Headers
|
|
472
|
+
|
|
473
|
+
`expressStreamHandler` automatically sets SSE headers:
|
|
474
|
+
- `Content-Type: text/event-stream`
|
|
475
|
+
- `Cache-Control: no-cache`
|
|
476
|
+
- `Connection: keep-alive`
|
|
477
|
+
- `X-Accel-Buffering: no` (disables nginx buffering)
|
|
478
|
+
|
|
479
|
+
### Error Handling in Streams
|
|
480
|
+
|
|
481
|
+
Errors are formatted as SSE error events:
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
// Jaypie errors and unhandled errors are written as:
|
|
485
|
+
// event: error
|
|
486
|
+
// data: {"errors":[{"status":500,"title":"Internal Error"}]}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### TypeScript Types
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
import type {
|
|
493
|
+
ExpressStreamHandlerOptions,
|
|
494
|
+
ExpressStreamHandlerLocals,
|
|
495
|
+
JaypieStreamHandlerSetup,
|
|
496
|
+
JaypieStreamHandlerTeardown,
|
|
497
|
+
JaypieStreamHandlerValidate,
|
|
498
|
+
} from "jaypie";
|
|
499
|
+
```
|
|
@@ -361,4 +361,137 @@ Deploy using AWS CDK or other deployment tool. The Lambda handler will be refere
|
|
|
361
361
|
- Use double quotes, trailing commas, semicolons
|
|
362
362
|
- Alphabetize imports and properties
|
|
363
363
|
- Define constants for hard-coded values at file top
|
|
364
|
-
- Never throw vanilla Error; use errors from `@jaypie/errors`
|
|
364
|
+
- Never throw vanilla Error; use errors from `@jaypie/errors`
|
|
365
|
+
|
|
366
|
+
## Streaming Lambda Functions
|
|
367
|
+
|
|
368
|
+
Use `lambdaStreamHandler` for AWS Lambda Response Streaming. This enables real-time streaming responses for LLM interactions, large file processing, and SSE endpoints.
|
|
369
|
+
|
|
370
|
+
### Lambda Streaming Setup
|
|
371
|
+
|
|
372
|
+
Create a streaming Lambda handler with `awslambda.streamifyResponse`:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// src/streamWorker.ts
|
|
376
|
+
import { log } from "@jaypie/core";
|
|
377
|
+
import { lambdaStreamHandler, createLambdaStream, Llm } from "jaypie";
|
|
378
|
+
import type { StreamHandlerContext } from "@jaypie/lambda";
|
|
379
|
+
|
|
380
|
+
export interface StreamWorkerEvent {
|
|
381
|
+
prompt?: string;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const streamWorker = lambdaStreamHandler(
|
|
385
|
+
async (event: StreamWorkerEvent, context: StreamHandlerContext) => {
|
|
386
|
+
log.trace("streamWorker: start");
|
|
387
|
+
|
|
388
|
+
const llm = new Llm("anthropic");
|
|
389
|
+
const stream = llm.stream(event.prompt || "Hello");
|
|
390
|
+
|
|
391
|
+
// createLambdaStream pipes LLM chunks as SSE events
|
|
392
|
+
await createLambdaStream(stream, context.responseStream);
|
|
393
|
+
|
|
394
|
+
log.trace("streamWorker: complete");
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "streamWorker",
|
|
398
|
+
contentType: "text/event-stream",
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// Wrap with AWS streamifyResponse
|
|
403
|
+
declare const awslambda: { streamifyResponse: <T>(handler: T) => T };
|
|
404
|
+
export const handler = awslambda.streamifyResponse(streamWorker);
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Manual Stream Writing
|
|
408
|
+
|
|
409
|
+
Write directly to the response stream for custom SSE events:
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
import { lambdaStreamHandler } from "jaypie";
|
|
413
|
+
import type { StreamHandlerContext } from "@jaypie/lambda";
|
|
414
|
+
|
|
415
|
+
const manualStreamHandler = lambdaStreamHandler(
|
|
416
|
+
async (event: unknown, context: StreamHandlerContext) => {
|
|
417
|
+
const { responseStream } = context;
|
|
418
|
+
|
|
419
|
+
// Write SSE events directly
|
|
420
|
+
responseStream.write("event: start\ndata: {\"status\": \"processing\"}\n\n");
|
|
421
|
+
|
|
422
|
+
// Process data in chunks
|
|
423
|
+
for (const item of items) {
|
|
424
|
+
const result = await process(item);
|
|
425
|
+
responseStream.write(`event: data\ndata: ${JSON.stringify(result)}\n\n`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
responseStream.write("event: done\ndata: {\"status\": \"complete\"}\n\n");
|
|
429
|
+
// Handler automatically calls responseStream.end()
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "manualStream",
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Stream Handler Options
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import type { LambdaStreamHandlerOptions } from "@jaypie/lambda";
|
|
441
|
+
|
|
442
|
+
const options: LambdaStreamHandlerOptions = {
|
|
443
|
+
name: "myStreamHandler", // Handler name for logging
|
|
444
|
+
contentType: "text/event-stream", // Response content type (default)
|
|
445
|
+
chaos: "low", // Chaos testing level
|
|
446
|
+
secrets: ["API_KEY"], // AWS secrets to load into process.env
|
|
447
|
+
setup: [], // Setup function(s)
|
|
448
|
+
teardown: [], // Teardown function(s)
|
|
449
|
+
validate: [], // Validation function(s)
|
|
450
|
+
throw: false, // Re-throw errors instead of SSE error
|
|
451
|
+
unavailable: false, // Return 503 if true
|
|
452
|
+
};
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Stream Handler Types
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
import type {
|
|
459
|
+
LambdaStreamHandlerOptions,
|
|
460
|
+
StreamHandlerContext,
|
|
461
|
+
ResponseStream,
|
|
462
|
+
AwsStreamingHandler,
|
|
463
|
+
} from "@jaypie/lambda";
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### CDK Configuration for Streaming
|
|
467
|
+
|
|
468
|
+
Enable Lambda Response Streaming via Function URL in CDK:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { JaypieLambda } from "@jaypie/constructs";
|
|
472
|
+
import { FunctionUrlAuthType, InvokeMode } from "aws-cdk-lib/aws-lambda";
|
|
473
|
+
|
|
474
|
+
const streamingLambda = new JaypieLambda(this, "StreamingFunction", {
|
|
475
|
+
code: "dist",
|
|
476
|
+
handler: "streamWorker.handler",
|
|
477
|
+
timeout: Duration.minutes(5),
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Add Function URL with streaming enabled
|
|
481
|
+
streamingLambda.addFunctionUrl({
|
|
482
|
+
authType: FunctionUrlAuthType.NONE, // or AWS_IAM for auth
|
|
483
|
+
invokeMode: InvokeMode.RESPONSE_STREAM,
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Error Handling in Streams
|
|
488
|
+
|
|
489
|
+
Errors are formatted as SSE error events:
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
// Jaypie errors written as:
|
|
493
|
+
// event: error
|
|
494
|
+
// data: {"errors":[{"status":500,"title":"Internal Error"}]}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Set `throw: true` to re-throw errors instead of writing to stream.
|
|
@@ -12,7 +12,7 @@ Streamline API calls with multi-model capabilities
|
|
|
12
12
|
```
|
|
13
13
|
export interface LlmProvider {
|
|
14
14
|
operate(
|
|
15
|
-
input: string | LlmHistory | LlmInputMessage,
|
|
15
|
+
input: string | LlmHistory | LlmInputMessage | LlmOperateInput,
|
|
16
16
|
options?: LlmOperateOptions,
|
|
17
17
|
): Promise<LlmOperateResponse>;
|
|
18
18
|
send(
|
|
@@ -21,11 +21,29 @@ export interface LlmProvider {
|
|
|
21
21
|
): Promise<string | JsonObject>;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// Simplified input for files and images
|
|
25
|
+
type LlmOperateInput = LlmOperateInputContent[];
|
|
26
|
+
type LlmOperateInputContent = string | LlmOperateInputFile | LlmOperateInputImage;
|
|
27
|
+
|
|
28
|
+
interface LlmOperateInputFile {
|
|
29
|
+
file: string; // Path or filename
|
|
30
|
+
bucket?: string; // S3 bucket (uses CDK_ENV_BUCKET if omitted)
|
|
31
|
+
pages?: number[]; // Extract specific PDF pages (omit = all)
|
|
32
|
+
data?: string; // Base64 data (skips file loading)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface LlmOperateInputImage {
|
|
36
|
+
image: string; // Path or filename
|
|
37
|
+
bucket?: string; // S3 bucket (uses CDK_ENV_BUCKET if omitted)
|
|
38
|
+
data?: string; // Base64 data (skips file loading)
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
export interface LlmOperateOptions {
|
|
25
42
|
data?: NaturalMap;
|
|
26
43
|
explain?: boolean;
|
|
27
44
|
format?: JsonObject | NaturalSchema | z.ZodType;
|
|
28
45
|
history?: LlmHistory;
|
|
46
|
+
hooks?: LlmOperateHooks;
|
|
29
47
|
instructions?: string;
|
|
30
48
|
model?: string;
|
|
31
49
|
placeholders?: {
|
|
@@ -35,26 +53,44 @@ export interface LlmOperateOptions {
|
|
|
35
53
|
};
|
|
36
54
|
providerOptions?: JsonObject;
|
|
37
55
|
system?: string;
|
|
38
|
-
tools?: LlmTool[];
|
|
56
|
+
tools?: LlmTool[] | Toolkit;
|
|
39
57
|
turns?: boolean | number;
|
|
40
58
|
user?: string;
|
|
41
59
|
}
|
|
42
60
|
|
|
61
|
+
export interface LlmOperateHooks {
|
|
62
|
+
afterEachModelResponse?: (context: HookContext) => unknown | Promise<unknown>;
|
|
63
|
+
afterEachTool?: (context: ToolHookContext) => unknown | Promise<unknown>;
|
|
64
|
+
beforeEachModelRequest?: (context: HookContext) => unknown | Promise<unknown>;
|
|
65
|
+
beforeEachTool?: (context: ToolHookContext) => unknown | Promise<unknown>;
|
|
66
|
+
onRetryableModelError?: (context: ErrorHookContext) => unknown | Promise<unknown>;
|
|
67
|
+
onToolError?: (context: ToolErrorContext) => unknown | Promise<unknown>;
|
|
68
|
+
onUnrecoverableModelError?: (context: ErrorHookContext) => unknown | Promise<unknown>;
|
|
69
|
+
}
|
|
70
|
+
|
|
43
71
|
export interface LlmOperateResponse {
|
|
44
72
|
content?: string | JsonObject;
|
|
45
73
|
error?: LlmError;
|
|
46
74
|
history: LlmHistory;
|
|
75
|
+
model?: string;
|
|
47
76
|
output: LlmOutput;
|
|
77
|
+
provider?: string;
|
|
78
|
+
reasoning: string[];
|
|
48
79
|
responses: JsonReturn[];
|
|
49
80
|
status: LlmResponseStatus;
|
|
50
81
|
usage: LlmUsage;
|
|
51
82
|
}
|
|
52
83
|
|
|
53
|
-
|
|
84
|
+
// LlmUsage is an array of usage items (one per model call in multi-turn)
|
|
85
|
+
type LlmUsage = LlmUsageItem[];
|
|
86
|
+
|
|
87
|
+
interface LlmUsageItem {
|
|
54
88
|
input: number;
|
|
55
89
|
output: number;
|
|
56
90
|
reasoning: number;
|
|
57
91
|
total: number;
|
|
92
|
+
model?: string;
|
|
93
|
+
provider?: string;
|
|
58
94
|
}
|
|
59
95
|
```
|
|
60
96
|
|
|
@@ -68,6 +104,63 @@ const llm = new Llm();
|
|
|
68
104
|
const result = await llm.operate("Give me advice on Yahtzee");
|
|
69
105
|
```
|
|
70
106
|
|
|
107
|
+
## Providers and Models
|
|
108
|
+
|
|
109
|
+
Available providers: `anthropic`, `gemini`, `openai`, `openrouter`
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { Llm, PROVIDER } from "jaypie";
|
|
113
|
+
|
|
114
|
+
// Using provider name (uses provider's default model)
|
|
115
|
+
const llm = new Llm("anthropic");
|
|
116
|
+
|
|
117
|
+
// Using model name directly (provider auto-detected)
|
|
118
|
+
const llm2 = new Llm("claude-sonnet-4-0");
|
|
119
|
+
const llm3 = new Llm("gpt-4.1");
|
|
120
|
+
const llm4 = new Llm("gemini-2.5-flash");
|
|
121
|
+
|
|
122
|
+
// Using provider with specific model
|
|
123
|
+
const llm5 = new Llm("openai", { model: "gpt-4.1" });
|
|
124
|
+
|
|
125
|
+
// Using constants
|
|
126
|
+
const llm6 = new Llm(PROVIDER.OPENAI.NAME, {
|
|
127
|
+
model: PROVIDER.OPENAI.MODEL.LARGE
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Model Aliases
|
|
132
|
+
|
|
133
|
+
Each provider has standard aliases: `DEFAULT`, `SMALL`, `LARGE`, `TINY`
|
|
134
|
+
|
|
135
|
+
| Provider | DEFAULT | LARGE | SMALL | TINY |
|
|
136
|
+
|----------|---------|-------|-------|------|
|
|
137
|
+
| anthropic | claude-opus-4-1 | claude-opus-4-1 | claude-sonnet-4-0 | claude-3-5-haiku-latest |
|
|
138
|
+
| gemini | gemini-3-pro-preview | gemini-3-pro-preview | gemini-3-flash-preview | gemini-2.0-flash-lite |
|
|
139
|
+
| openai | gpt-4.1 | gpt-4.1 | gpt-4.1-mini | gpt-4.1-nano |
|
|
140
|
+
| openrouter | z-ai/glm-4.7 | z-ai/glm-4.7 | z-ai/glm-4.7 | z-ai/glm-4.7 |
|
|
141
|
+
|
|
142
|
+
### Provider Constants
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { PROVIDER } from "jaypie";
|
|
146
|
+
|
|
147
|
+
// Anthropic models
|
|
148
|
+
PROVIDER.ANTHROPIC.MODEL.CLAUDE_OPUS_4 // claude-opus-4-1
|
|
149
|
+
PROVIDER.ANTHROPIC.MODEL.CLAUDE_SONNET_4 // claude-sonnet-4-0
|
|
150
|
+
PROVIDER.ANTHROPIC.MODEL.CLAUDE_3_HAIKU // claude-3-5-haiku-latest
|
|
151
|
+
|
|
152
|
+
// Gemini models
|
|
153
|
+
PROVIDER.GEMINI.MODEL.GEMINI_3_PRO_PREVIEW // gemini-3-pro-preview
|
|
154
|
+
PROVIDER.GEMINI.MODEL.GEMINI_2_5_FLASH // gemini-2.5-flash
|
|
155
|
+
PROVIDER.GEMINI.MODEL.GEMINI_2_0_FLASH // gemini-2.0-flash
|
|
156
|
+
|
|
157
|
+
// OpenAI models
|
|
158
|
+
PROVIDER.OPENAI.MODEL.GPT_4_1 // gpt-4.1
|
|
159
|
+
PROVIDER.OPENAI.MODEL.GPT_4_O // gpt-4o
|
|
160
|
+
PROVIDER.OPENAI.MODEL.O3 // o3
|
|
161
|
+
PROVIDER.OPENAI.MODEL.O4_MINI // o4-mini
|
|
162
|
+
```
|
|
163
|
+
|
|
71
164
|
## "Operating" an Llm
|
|
72
165
|
|
|
73
166
|
operate takes an optional second object of options
|
|
@@ -106,6 +199,249 @@ error will include any errors.
|
|
|
106
199
|
output is just the output components of full responses.
|
|
107
200
|
responses are the complete responses.
|
|
108
201
|
|
|
202
|
+
## Files and Images
|
|
203
|
+
|
|
204
|
+
Use `LlmOperateInput` array syntax to send files and images with automatic loading and provider translation:
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
import { Llm } from "jaypie";
|
|
208
|
+
|
|
209
|
+
const llm = new Llm("openai");
|
|
210
|
+
|
|
211
|
+
// Image from local filesystem
|
|
212
|
+
const imageResult = await llm.operate([
|
|
213
|
+
"Extract text from this image",
|
|
214
|
+
{ image: "/path/to/photo.png" }
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
// PDF from local filesystem
|
|
218
|
+
const pdfResult = await llm.operate([
|
|
219
|
+
"Summarize this document",
|
|
220
|
+
{ file: "/path/to/document.pdf" }
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
// From S3 bucket (uses CDK_ENV_BUCKET if bucket omitted)
|
|
224
|
+
const s3Result = await llm.operate([
|
|
225
|
+
"Analyze this file",
|
|
226
|
+
{ file: "documents/report.pdf", bucket: "my-bucket" }
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
// Extract specific PDF pages
|
|
230
|
+
const pagesResult = await llm.operate([
|
|
231
|
+
"Read pages 1-3",
|
|
232
|
+
{ file: "large-doc.pdf", pages: [1, 2, 3] }
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
// With pre-loaded base64 data (skips file loading)
|
|
236
|
+
const base64Result = await llm.operate([
|
|
237
|
+
"Describe this image",
|
|
238
|
+
{ image: "photo.jpg", data: base64String }
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
// Multiple files and text
|
|
242
|
+
const multiResult = await llm.operate([
|
|
243
|
+
"Compare these documents",
|
|
244
|
+
{ file: "doc1.pdf" },
|
|
245
|
+
{ file: "doc2.pdf" },
|
|
246
|
+
"Focus on the methodology section"
|
|
247
|
+
]);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### File Resolution Order
|
|
251
|
+
|
|
252
|
+
1. If `data` is present → uses base64 directly
|
|
253
|
+
2. If `bucket` is present → loads from S3
|
|
254
|
+
3. If `CDK_ENV_BUCKET` env var exists → loads from that S3 bucket
|
|
255
|
+
4. Otherwise → loads from local filesystem (relative to process.cwd())
|
|
256
|
+
|
|
257
|
+
### Supported Image Extensions
|
|
258
|
+
|
|
259
|
+
Files with these extensions are treated as images: `png`, `jpg`, `jpeg`, `gif`, `webp`, `svg`, `bmp`, `ico`, `tiff`, `avif`
|
|
260
|
+
|
|
261
|
+
## Streaming
|
|
262
|
+
|
|
263
|
+
Use `Llm.stream()` for real-time streaming responses:
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
import { Llm } from "jaypie";
|
|
267
|
+
|
|
268
|
+
const llm = new Llm("anthropic");
|
|
269
|
+
|
|
270
|
+
// Basic streaming
|
|
271
|
+
for await (const chunk of llm.stream("Tell me a story")) {
|
|
272
|
+
if (chunk.type === "text") {
|
|
273
|
+
process.stdout.write(chunk.content);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Streaming with tools
|
|
278
|
+
for await (const chunk of llm.stream("Roll 3d6", { tools: [roll] })) {
|
|
279
|
+
switch (chunk.type) {
|
|
280
|
+
case "text":
|
|
281
|
+
console.log("Text:", chunk.content);
|
|
282
|
+
break;
|
|
283
|
+
case "tool_call":
|
|
284
|
+
console.log("Calling tool:", chunk.toolCall.name);
|
|
285
|
+
break;
|
|
286
|
+
case "tool_result":
|
|
287
|
+
console.log("Tool result:", chunk.toolResult.result);
|
|
288
|
+
break;
|
|
289
|
+
case "done":
|
|
290
|
+
console.log("Usage:", chunk.usage);
|
|
291
|
+
break;
|
|
292
|
+
case "error":
|
|
293
|
+
console.error("Error:", chunk.error);
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Static method
|
|
299
|
+
for await (const chunk of Llm.stream("Hello", { llm: "openai" })) {
|
|
300
|
+
// ...
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Stream Chunk Types
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
type LlmStreamChunk =
|
|
308
|
+
| LlmStreamChunkText // { type: "text", content: string }
|
|
309
|
+
| LlmStreamChunkToolCall // { type: "tool_call", toolCall: { id, name, arguments } }
|
|
310
|
+
| LlmStreamChunkToolResult // { type: "tool_result", toolResult: { id, name, result } }
|
|
311
|
+
| LlmStreamChunkDone // { type: "done", usage: LlmUsage }
|
|
312
|
+
| LlmStreamChunkError; // { type: "error", error: { status, title, detail? } }
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Streaming to Express
|
|
316
|
+
|
|
317
|
+
Use `createExpressStream` to pipe LLM streams to Express responses:
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
import { expressStreamHandler, Llm, createExpressStream } from "jaypie";
|
|
321
|
+
|
|
322
|
+
const chatRoute = expressStreamHandler(async (req, res) => {
|
|
323
|
+
const llm = new Llm("anthropic");
|
|
324
|
+
const stream = llm.stream(req.body.prompt);
|
|
325
|
+
await createExpressStream(stream, res);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
app.post("/chat", chatRoute);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Streaming to Lambda
|
|
332
|
+
|
|
333
|
+
Use `createLambdaStream` with Lambda Response Streaming:
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
import { lambdaStreamHandler, Llm, createLambdaStream } from "jaypie";
|
|
337
|
+
|
|
338
|
+
const handler = awslambda.streamifyResponse(
|
|
339
|
+
lambdaStreamHandler(async (event, context) => {
|
|
340
|
+
const llm = new Llm("openai");
|
|
341
|
+
const stream = llm.stream(event.prompt);
|
|
342
|
+
await createLambdaStream(stream, context.responseStream);
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### JaypieStream Wrapper
|
|
348
|
+
|
|
349
|
+
Use `JaypieStream` or `createJaypieStream` for fluent piping:
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
import { createJaypieStream, Llm } from "jaypie";
|
|
353
|
+
|
|
354
|
+
const llm = new Llm("gemini");
|
|
355
|
+
const stream = createJaypieStream(llm.stream("Hello"));
|
|
356
|
+
|
|
357
|
+
// Pipe to Express
|
|
358
|
+
await stream.toExpress(res);
|
|
359
|
+
|
|
360
|
+
// Or pipe to Lambda
|
|
361
|
+
await stream.toLambda(responseStream);
|
|
362
|
+
|
|
363
|
+
// Or iterate manually
|
|
364
|
+
for await (const chunk of stream) {
|
|
365
|
+
console.log(chunk);
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Hooks
|
|
370
|
+
|
|
371
|
+
Use hooks to intercept and observe the LLM lifecycle:
|
|
372
|
+
|
|
373
|
+
```javascript
|
|
374
|
+
const result = await llm.operate("Process this", {
|
|
375
|
+
hooks: {
|
|
376
|
+
beforeEachModelRequest: ({ input, options, providerRequest }) => {
|
|
377
|
+
console.log("About to call model with:", providerRequest);
|
|
378
|
+
},
|
|
379
|
+
afterEachModelResponse: ({ content, usage, providerResponse }) => {
|
|
380
|
+
console.log("Model responded:", content);
|
|
381
|
+
console.log("Tokens used:", usage);
|
|
382
|
+
},
|
|
383
|
+
beforeEachTool: ({ toolName, args }) => {
|
|
384
|
+
console.log(`Calling tool ${toolName} with:`, args);
|
|
385
|
+
},
|
|
386
|
+
afterEachTool: ({ toolName, result }) => {
|
|
387
|
+
console.log(`Tool ${toolName} returned:`, result);
|
|
388
|
+
},
|
|
389
|
+
onToolError: ({ toolName, error }) => {
|
|
390
|
+
console.error(`Tool ${toolName} failed:`, error);
|
|
391
|
+
},
|
|
392
|
+
onRetryableModelError: ({ error }) => {
|
|
393
|
+
console.warn("Retrying after error:", error);
|
|
394
|
+
},
|
|
395
|
+
onUnrecoverableModelError: ({ error }) => {
|
|
396
|
+
console.error("Fatal error:", error);
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Toolkit
|
|
403
|
+
|
|
404
|
+
Group tools with `Toolkit` for additional features:
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
import { Llm, Toolkit } from "jaypie";
|
|
408
|
+
|
|
409
|
+
const toolkit = new Toolkit([roll, weather, time], {
|
|
410
|
+
explain: true, // Add __Explanation param to tools
|
|
411
|
+
log: true, // Log tool calls (default)
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Extend toolkit with more tools
|
|
415
|
+
toolkit.extend([anotherTool], { replace: true });
|
|
416
|
+
|
|
417
|
+
const result = await llm.operate("Roll dice and check weather", {
|
|
418
|
+
tools: toolkit,
|
|
419
|
+
});
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Zod Schema Support
|
|
423
|
+
|
|
424
|
+
Tool parameters can be defined using Zod schemas instead of JSON Schema:
|
|
425
|
+
|
|
426
|
+
```javascript
|
|
427
|
+
import { z } from "zod/v4";
|
|
428
|
+
import { Llm, Toolkit } from "jaypie";
|
|
429
|
+
|
|
430
|
+
const weatherTool = {
|
|
431
|
+
name: "get_weather",
|
|
432
|
+
description: "Get weather for a city",
|
|
433
|
+
parameters: z.object({
|
|
434
|
+
city: z.string().describe("City name"),
|
|
435
|
+
unit: z.enum(["celsius", "fahrenheit"]),
|
|
436
|
+
}),
|
|
437
|
+
type: "function",
|
|
438
|
+
call: async ({ city, unit }) => ({ city, temp: 72, unit }),
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const toolkit = new Toolkit([weatherTool]);
|
|
442
|
+
// Zod schemas are automatically converted to JSON Schema
|
|
443
|
+
```
|
|
444
|
+
|
|
109
445
|
## Footnotes
|
|
110
446
|
|
|
111
447
|
Llm.operate(input, options)
|