@llm-newsletter-kit/core 2.0.6 → 2.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/README.md +83 -0
- package/dist/index.cjs +92 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +109 -39
- package/dist/index.js +92 -3
- package/dist/index.js.map +1 -1
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -149,6 +149,88 @@ const newsletterId = await generator.generate();
|
|
|
149
149
|
|
|
150
150
|
👉 **See the reference implementation: https://github.com/heripo-lab/heripo-research-radar**
|
|
151
151
|
|
|
152
|
+
## Customizing LLM Prompts (PromptProvider)
|
|
153
|
+
|
|
154
|
+
The kit ships with built-in prompts designed to cover a wide range of domains. However, these general-purpose prompts may not be flexible enough for specialized requirements. For example:
|
|
155
|
+
|
|
156
|
+
- Your domain uses unique terminology or jargon that the default prompts don't account for
|
|
157
|
+
- You need a specific newsletter tone or structure (e.g., academic style, casual briefing)
|
|
158
|
+
- Tag classification requires domain-specific taxonomy rules
|
|
159
|
+
- Importance scoring needs custom criteria tailored to your industry
|
|
160
|
+
|
|
161
|
+
`PromptProvider` lets you **replace** any built-in prompt — system prompt, user prompt, or both — on a per-stage basis while keeping the rest of the pipeline intact.
|
|
162
|
+
|
|
163
|
+
### How It Works
|
|
164
|
+
|
|
165
|
+
PromptProvider is organized by pipeline stage. Every field is optional — omitted prompts fall back to the built-in defaults.
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
PromptProvider
|
|
169
|
+
├── analysis
|
|
170
|
+
│ ├── classifyTags — Tag classification (system / user)
|
|
171
|
+
│ ├── analyzeImages — Image analysis (system / user)
|
|
172
|
+
│ └── determineImportance — Importance scoring (system / user)
|
|
173
|
+
└── contentGenerate
|
|
174
|
+
└── generateNewsletter — Final newsletter generation (system / user)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Each prompt slot is a `PromptBuilder<TContext>` — an object with optional `system` and `user` functions. The function receives a typed context object containing all the data the default prompt would use (articles, tags, expert fields, dates, etc.), so you have full control over prompt construction.
|
|
178
|
+
|
|
179
|
+
> **Dependency note:** Analysis prompts (classifyTags → analyzeImages → determineImportance) produce data that flows into the content generation prompt. Changing an analysis prompt may indirectly affect the final newsletter output.
|
|
180
|
+
|
|
181
|
+
### Example
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import type {
|
|
185
|
+
GenerateNewsletterConfig,
|
|
186
|
+
PromptProvider,
|
|
187
|
+
} from '@llm-newsletter-kit/core';
|
|
188
|
+
|
|
189
|
+
const promptProvider: PromptProvider = {
|
|
190
|
+
analysis: {
|
|
191
|
+
// Override only the system prompt for tag classification
|
|
192
|
+
classifyTags: {
|
|
193
|
+
system: (ctx) =>
|
|
194
|
+
`You are a legal-domain specialist. Classify articles using ` +
|
|
195
|
+
`legal taxonomy standards. Available tags: ${ctx.existTags.join(', ')}. ` +
|
|
196
|
+
`Output language: ${ctx.outputLanguage}.`,
|
|
197
|
+
// user prompt falls back to the built-in default
|
|
198
|
+
},
|
|
199
|
+
// Override importance scoring with domain-specific criteria
|
|
200
|
+
determineImportance: {
|
|
201
|
+
system: (ctx) =>
|
|
202
|
+
`Score article importance for ${ctx.expertFields.join(', ')} professionals. ` +
|
|
203
|
+
`Regulatory changes and court rulings score 8+. ` +
|
|
204
|
+
`Commentary and opinion pieces score 3-5.`,
|
|
205
|
+
user: (ctx) =>
|
|
206
|
+
`Article: ${ctx.targetArticle.title}\n` +
|
|
207
|
+
`Content: ${ctx.targetArticle.detailContent}\n` +
|
|
208
|
+
`Score this article 1-10.`,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
contentGenerate: {
|
|
212
|
+
// Override the newsletter generation prompt entirely
|
|
213
|
+
generateNewsletter: {
|
|
214
|
+
system: (ctx) =>
|
|
215
|
+
`You produce a weekly legal digest for "${ctx.newsletterBrandName}". ` +
|
|
216
|
+
`Write in ${ctx.outputLanguage}. Use formal academic tone.`,
|
|
217
|
+
user: (ctx) =>
|
|
218
|
+
`Publication date: ${ctx.dateService.getPublicationDisplayDateString()}\n\n` +
|
|
219
|
+
ctx.targetArticles
|
|
220
|
+
.map((a) => `- [${a.title}](${a.url}) (score: ${a.importanceScore})`)
|
|
221
|
+
.join('\n'),
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const config: GenerateNewsletterConfig<string> = {
|
|
227
|
+
// ... other config (contentOptions, dateService, taskService, providers, etc.)
|
|
228
|
+
promptProvider, // Inject custom prompts
|
|
229
|
+
};
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
For full context types (`ClassifyTagsPromptContext`, `GenerateNewsletterPromptContext`, etc.), see `src/generate-newsletter/models/prompt-provider.ts`.
|
|
233
|
+
|
|
152
234
|
## Public API Overview
|
|
153
235
|
|
|
154
236
|
Entry point: `src/index.ts`
|
|
@@ -163,6 +245,7 @@ Entry point: `src/index.ts`
|
|
|
163
245
|
- AnalysisProvider { classifyTagOptions.model, analyzeImagesOptions.model, determineScoreOptions(model, minimumImportanceScoreRules), fetchUnscoredArticles, fetchTags, update }
|
|
164
246
|
- ContentGenerateProvider { model and generation options, issueOrder, publicationCriteria, subscribePageUrl, newsletterBrandName, fetchArticleCandidates, htmlTemplate, saveNewsletter }
|
|
165
247
|
- GenerateNewsletterOptions { logger, llm, chain, previewNewsletter(emailService/emailMessage/fetchNewsletterForPreview) }
|
|
248
|
+
- PromptProvider { analysis?(classifyTags, analyzeImages, determineImportance), contentGenerate?(generateNewsletter) }
|
|
166
249
|
- Domain models: DateService, EmailService, Newsletter, etc.
|
|
167
250
|
|
|
168
251
|
For detailed field descriptions, see `src/generate-newsletter/models/interfaces.ts` and type definitions under `src/models/*`.
|
package/dist/index.cjs
CHANGED
|
@@ -193,8 +193,10 @@ class AnalyzeImages extends LLMQuery {
|
|
|
193
193
|
.string()
|
|
194
194
|
.describe('A high-level overview of the general theme, mood, and context conveyed by the images (without specific names, titles, or exact figures from text within images)'),
|
|
195
195
|
});
|
|
196
|
+
promptBuilder;
|
|
196
197
|
constructor(config) {
|
|
197
198
|
super(config);
|
|
199
|
+
this.promptBuilder = config.promptBuilder;
|
|
198
200
|
}
|
|
199
201
|
async execute() {
|
|
200
202
|
if (!this.targetArticle.hasAttachedImage ||
|
|
@@ -218,7 +220,17 @@ class AnalyzeImages extends LLMQuery {
|
|
|
218
220
|
});
|
|
219
221
|
return { result: output.imageContext, usage };
|
|
220
222
|
}
|
|
223
|
+
get promptContext() {
|
|
224
|
+
return {
|
|
225
|
+
expertFields: this.expertFields,
|
|
226
|
+
outputLanguage: this.options.content.outputLanguage,
|
|
227
|
+
targetArticle: this.targetArticle,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
221
230
|
get systemPrompt() {
|
|
231
|
+
if (this.promptBuilder?.system) {
|
|
232
|
+
return this.promptBuilder.system(this.promptContext);
|
|
233
|
+
}
|
|
222
234
|
return `# Image Analysis Expert System
|
|
223
235
|
|
|
224
236
|
## Identity & Expertise
|
|
@@ -289,6 +301,12 @@ You are a specialized image analysis expert in: ${this.expertFields.join(', ')}
|
|
|
289
301
|
}));
|
|
290
302
|
}
|
|
291
303
|
get textMessage() {
|
|
304
|
+
if (this.promptBuilder?.user) {
|
|
305
|
+
return {
|
|
306
|
+
type: 'text',
|
|
307
|
+
text: this.promptBuilder.user(this.promptContext),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
292
310
|
return {
|
|
293
311
|
type: 'text',
|
|
294
312
|
text: `## Analysis Task
|
|
@@ -326,9 +344,11 @@ class ClassifyTags extends LLMQuery {
|
|
|
326
344
|
tag2: zod.z.string(),
|
|
327
345
|
tag3: zod.z.string(),
|
|
328
346
|
});
|
|
347
|
+
promptBuilder;
|
|
329
348
|
existTags = [];
|
|
330
349
|
constructor(config) {
|
|
331
350
|
super(config);
|
|
351
|
+
this.promptBuilder = config.promptBuilder;
|
|
332
352
|
}
|
|
333
353
|
async execute({ existTags }) {
|
|
334
354
|
this.existTags = existTags;
|
|
@@ -341,7 +361,18 @@ class ClassifyTags extends LLMQuery {
|
|
|
341
361
|
});
|
|
342
362
|
return { result: output, usage };
|
|
343
363
|
}
|
|
364
|
+
get promptContext() {
|
|
365
|
+
return {
|
|
366
|
+
expertFields: this.expertFields,
|
|
367
|
+
outputLanguage: this.options.content.outputLanguage,
|
|
368
|
+
targetArticle: this.targetArticle,
|
|
369
|
+
existTags: this.existTags,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
344
372
|
get systemPrompt() {
|
|
373
|
+
if (this.promptBuilder?.system) {
|
|
374
|
+
return this.promptBuilder.system(this.promptContext);
|
|
375
|
+
}
|
|
345
376
|
return `You are an AI specializing in analyzing and categorizing articles for professionals in ${this.expertFields.join(', ')}.
|
|
346
377
|
|
|
347
378
|
## Core Responsibility
|
|
@@ -355,7 +386,7 @@ All classifications must be written in ${this.options.content.outputLanguage}.
|
|
|
355
386
|
2. **New Tag Criteria**: Create new classifications only when:
|
|
356
387
|
- Best existing match scores below 80% compatibility
|
|
357
388
|
- New tag demonstrates versatility across 10+ similar articles
|
|
358
|
-
3. **Naming Standards**:
|
|
389
|
+
3. **Naming Standards**:
|
|
359
390
|
- Length: 3-15 characters
|
|
360
391
|
- Style: Clear, intuitive ${this.options.content.outputLanguage} terms
|
|
361
392
|
- Balance industry precision with general reader comprehension
|
|
@@ -369,6 +400,9 @@ Prioritize in order:
|
|
|
369
400
|
- Long-term scalability and maintainability`;
|
|
370
401
|
}
|
|
371
402
|
get userPrompt() {
|
|
403
|
+
if (this.promptBuilder?.user) {
|
|
404
|
+
return this.promptBuilder.user(this.promptContext);
|
|
405
|
+
}
|
|
372
406
|
return `**Task**: Classify this article with 3 optimal detailed tags.
|
|
373
407
|
|
|
374
408
|
**Article Information**
|
|
@@ -406,10 +440,12 @@ class DetermineArticleImportance extends LLMQuery {
|
|
|
406
440
|
.describe('Article importance score (1-10, 10 is most important)'),
|
|
407
441
|
});
|
|
408
442
|
dateService;
|
|
443
|
+
promptBuilder;
|
|
409
444
|
constructor(config) {
|
|
410
445
|
super(config);
|
|
411
446
|
this.minimumImportanceScoreRules = config.minimumImportanceScoreRules ?? [];
|
|
412
447
|
this.dateService = config.dateService;
|
|
448
|
+
this.promptBuilder = config.promptBuilder;
|
|
413
449
|
}
|
|
414
450
|
async execute() {
|
|
415
451
|
const { output, usage } = await generateObjectByLLM({
|
|
@@ -428,7 +464,18 @@ class DetermineArticleImportance extends LLMQuery {
|
|
|
428
464
|
get hasHigherMinimumScore() {
|
|
429
465
|
return this.minPoint > 1;
|
|
430
466
|
}
|
|
467
|
+
get promptContext() {
|
|
468
|
+
return {
|
|
469
|
+
expertFields: this.expertFields,
|
|
470
|
+
targetArticle: this.targetArticle,
|
|
471
|
+
dateService: this.dateService,
|
|
472
|
+
minimumImportanceScoreRules: this.minimumImportanceScoreRules,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
431
475
|
get systemPrompt() {
|
|
476
|
+
if (this.promptBuilder?.system) {
|
|
477
|
+
return this.promptBuilder.system(this.promptContext);
|
|
478
|
+
}
|
|
432
479
|
return `You are an expert in importance evaluation in the field of ${this.expertFields.join(', ')}.
|
|
433
480
|
|
|
434
481
|
Role:
|
|
@@ -465,6 +512,9 @@ Important Notes:
|
|
|
465
512
|
- Be sensitive to core keywords, events, policies considered important in the field.`;
|
|
466
513
|
}
|
|
467
514
|
get userPrompt() {
|
|
515
|
+
if (this.promptBuilder?.user) {
|
|
516
|
+
return this.promptBuilder.user(this.promptContext);
|
|
517
|
+
}
|
|
468
518
|
return `Please rate the importance of this article from ${this.minPoint} to 10.
|
|
469
519
|
|
|
470
520
|
**Newsletter Publication Date:** ${this.dateService.getPublicationISODateString()}${this.targetArticle.publishedDate
|
|
@@ -509,9 +559,11 @@ class PrivateChain extends BaseChain {
|
|
|
509
559
|
|
|
510
560
|
class ArticleInsightsChain extends PrivateChain {
|
|
511
561
|
dateService;
|
|
562
|
+
promptProvider;
|
|
512
563
|
constructor(config) {
|
|
513
564
|
super(config);
|
|
514
565
|
this.dateService = config.dateService;
|
|
566
|
+
this.promptProvider = config.promptProvider;
|
|
515
567
|
}
|
|
516
568
|
/* istanbul ignore next - pipeline arrow functions are exercised via higher-level tests */
|
|
517
569
|
get chain() {
|
|
@@ -607,7 +659,10 @@ class ArticleInsightsChain extends PrivateChain {
|
|
|
607
659
|
},
|
|
608
660
|
});
|
|
609
661
|
try {
|
|
610
|
-
const classifyTags = new ClassifyTags(
|
|
662
|
+
const classifyTags = new ClassifyTags({
|
|
663
|
+
...this.getLlmQueryConfig(this.provider.classifyTagOptions.model, article),
|
|
664
|
+
promptBuilder: this.promptProvider?.classifyTags,
|
|
665
|
+
});
|
|
611
666
|
const { result: generatedTags } = await classifyTags.execute({
|
|
612
667
|
existTags,
|
|
613
668
|
});
|
|
@@ -682,7 +737,10 @@ class ArticleInsightsChain extends PrivateChain {
|
|
|
682
737
|
},
|
|
683
738
|
});
|
|
684
739
|
try {
|
|
685
|
-
const analyzeImages = new AnalyzeImages(
|
|
740
|
+
const analyzeImages = new AnalyzeImages({
|
|
741
|
+
...this.getLlmQueryConfig(this.provider.analyzeImagesOptions.model, article),
|
|
742
|
+
promptBuilder: this.promptProvider?.analyzeImages,
|
|
743
|
+
});
|
|
686
744
|
const { result: imageContextByLlm } = await analyzeImages.execute();
|
|
687
745
|
if (imageContextByLlm) {
|
|
688
746
|
articlesWithImageContext.push({
|
|
@@ -781,6 +839,7 @@ class ArticleInsightsChain extends PrivateChain {
|
|
|
781
839
|
...this.getLlmQueryConfig(this.provider.determineScoreOptions.model, article),
|
|
782
840
|
minimumImportanceScoreRules: this.provider.determineScoreOptions.minimumImportanceScoreRules,
|
|
783
841
|
dateService: this.dateService,
|
|
842
|
+
promptBuilder: this.promptProvider?.determineImportance,
|
|
784
843
|
});
|
|
785
844
|
const { result: importanceScore } = await determineArticleImportance.execute();
|
|
786
845
|
const processedArticle = {
|
|
@@ -845,9 +904,11 @@ class ArticleInsightsChain extends PrivateChain {
|
|
|
845
904
|
|
|
846
905
|
class AnalysisChain extends Chain {
|
|
847
906
|
dateService;
|
|
907
|
+
promptProvider;
|
|
848
908
|
constructor(config) {
|
|
849
909
|
super(config);
|
|
850
910
|
this.dateService = config.dateService;
|
|
911
|
+
this.promptProvider = config.promptProvider;
|
|
851
912
|
}
|
|
852
913
|
get chain() {
|
|
853
914
|
return runnables.RunnablePassthrough.assign({
|
|
@@ -903,6 +964,7 @@ class AnalysisChain extends Chain {
|
|
|
903
964
|
options: this.options,
|
|
904
965
|
loggingExecutor: new LoggingExecutor(this.logger, this.taskId),
|
|
905
966
|
dateService: this.dateService,
|
|
967
|
+
promptProvider: this.promptProvider,
|
|
906
968
|
});
|
|
907
969
|
return await articleInsightsChain.generateInsights();
|
|
908
970
|
});
|
|
@@ -936,6 +998,7 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
|
|
|
936
998
|
dateService;
|
|
937
999
|
subscribePageUrl;
|
|
938
1000
|
newsletterBrandName;
|
|
1001
|
+
promptBuilder;
|
|
939
1002
|
schema = zod.z.object({
|
|
940
1003
|
title: zod.z.string().max(70).min(20).describe('Title of the newsletter email'),
|
|
941
1004
|
content: zod.z.string().describe('Email content in markdown format'),
|
|
@@ -961,6 +1024,7 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
|
|
|
961
1024
|
this.dateService = config.dateService;
|
|
962
1025
|
this.subscribePageUrl = config.subscribePageUrl;
|
|
963
1026
|
this.newsletterBrandName = config.newsletterBrandName;
|
|
1027
|
+
this.promptBuilder = config.promptBuilder;
|
|
964
1028
|
}
|
|
965
1029
|
async execute() {
|
|
966
1030
|
const { output, usage, finishReason } = await generateObjectByLLM({
|
|
@@ -995,7 +1059,22 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
|
|
|
995
1059
|
}
|
|
996
1060
|
return { result: esToolkit.pick(output, ['title', 'content']), usage };
|
|
997
1061
|
}
|
|
1062
|
+
get promptContext() {
|
|
1063
|
+
return {
|
|
1064
|
+
expertFields: this.expertFields,
|
|
1065
|
+
outputLanguage: this.options.content.outputLanguage,
|
|
1066
|
+
dateService: this.dateService,
|
|
1067
|
+
targetArticles: this.targetArticles,
|
|
1068
|
+
freeFormIntro: this.options.content.freeFormIntro,
|
|
1069
|
+
titleContext: this.options.content.titleContext,
|
|
1070
|
+
subscribePageUrl: this.subscribePageUrl,
|
|
1071
|
+
newsletterBrandName: this.newsletterBrandName,
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
998
1074
|
get systemPrompt() {
|
|
1075
|
+
if (this.promptBuilder?.system) {
|
|
1076
|
+
return this.promptBuilder.system(this.promptContext);
|
|
1077
|
+
}
|
|
999
1078
|
return `You are a newsletter production expert for "${this.newsletterBrandName}" who analyzes and delivers trends in the fields of ${this.expertFields.join(', ')}. Your goal is to provide in-depth analysis that helps industry professionals easily understand complex information and make informed decisions.
|
|
1000
1079
|
|
|
1001
1080
|
Important rule for displaying date ranges: When displaying date ranges, you must use a hyphen (-) instead of a tilde (~). For example, use 'June 1-2, 2025' instead of 'June 1~2, 2025'. The tilde (~) can be rendered as strikethrough in markdown.
|
|
@@ -1127,6 +1206,9 @@ Output Format & Requirements:
|
|
|
1127
1206
|
${this.subscribePageUrl ? `- Add \`${this.subscribePageUrl}\`(Subscribe to ${this.newsletterBrandName}) page access link button at appropriate attention-worthy spot for natural recommendation to others.` : ''}`;
|
|
1128
1207
|
}
|
|
1129
1208
|
get userPrompt() {
|
|
1209
|
+
if (this.promptBuilder?.user) {
|
|
1210
|
+
return this.promptBuilder.user(this.promptContext);
|
|
1211
|
+
}
|
|
1130
1212
|
return `Below is the complete list of newly collected ${this.expertFields.join(', ')} related news:
|
|
1131
1213
|
|
|
1132
1214
|
${this.targetArticles
|
|
@@ -1203,9 +1285,11 @@ class ContentGenerateChain extends Chain {
|
|
|
1203
1285
|
minimumArticleCountForIssue;
|
|
1204
1286
|
priorityArticleScoreThreshold;
|
|
1205
1287
|
htmlTemplate;
|
|
1288
|
+
promptProvider;
|
|
1206
1289
|
constructor(config) {
|
|
1207
1290
|
super(config);
|
|
1208
1291
|
this.dateService = config.dateService;
|
|
1292
|
+
this.promptProvider = config.promptProvider;
|
|
1209
1293
|
this.minimumArticleCountForIssue =
|
|
1210
1294
|
config.provider.publicationCriteria?.minimumArticleCountForIssue ?? 5;
|
|
1211
1295
|
this.priorityArticleScoreThreshold =
|
|
@@ -1288,6 +1372,7 @@ class ContentGenerateChain extends Chain {
|
|
|
1288
1372
|
subscribePageUrl: this.provider.subscribePageUrl,
|
|
1289
1373
|
newsletterBrandName: this.provider.newsletterBrandName,
|
|
1290
1374
|
dateService: this.dateService,
|
|
1375
|
+
promptBuilder: this.promptProvider?.generateNewsletter,
|
|
1291
1376
|
});
|
|
1292
1377
|
const { result } = await generateNewsletter.execute();
|
|
1293
1378
|
return result;
|
|
@@ -1777,6 +1862,7 @@ class GenerateNewsletter {
|
|
|
1777
1862
|
contentGenerateProvider;
|
|
1778
1863
|
logger;
|
|
1779
1864
|
options;
|
|
1865
|
+
promptProvider;
|
|
1780
1866
|
previewNewsletterOptions;
|
|
1781
1867
|
/** Independent internal field **/
|
|
1782
1868
|
taskId = null;
|
|
@@ -1820,6 +1906,7 @@ class GenerateNewsletter {
|
|
|
1820
1906
|
...config.options?.chain,
|
|
1821
1907
|
},
|
|
1822
1908
|
};
|
|
1909
|
+
this.promptProvider = config.promptProvider;
|
|
1823
1910
|
// Default logger (no-op)
|
|
1824
1911
|
this.logger = config.options?.logger ?? {
|
|
1825
1912
|
info: (_msg) => { },
|
|
@@ -1849,6 +1936,7 @@ class GenerateNewsletter {
|
|
|
1849
1936
|
options: this.options,
|
|
1850
1937
|
loggingExecutor,
|
|
1851
1938
|
dateService: this.dateService,
|
|
1939
|
+
promptProvider: this.promptProvider?.analysis,
|
|
1852
1940
|
});
|
|
1853
1941
|
const contentGenerateChain = new ContentGenerateChain({
|
|
1854
1942
|
logger: this.logger,
|
|
@@ -1857,6 +1945,7 @@ class GenerateNewsletter {
|
|
|
1857
1945
|
options: this.options,
|
|
1858
1946
|
loggingExecutor,
|
|
1859
1947
|
dateService: this.dateService,
|
|
1948
|
+
promptProvider: this.promptProvider?.contentGenerate,
|
|
1860
1949
|
});
|
|
1861
1950
|
const taskChain = runnables.RunnableSequence.from([
|
|
1862
1951
|
crawlingChain.chain,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -289,44 +289,6 @@ type ChainOptions = {
|
|
|
289
289
|
stopAfterAttempt?: number;
|
|
290
290
|
};
|
|
291
291
|
|
|
292
|
-
/**
|
|
293
|
-
* Token markers in template.
|
|
294
|
-
*/
|
|
295
|
-
type HtmlTemplateMarkers = {
|
|
296
|
-
/**
|
|
297
|
-
* Title token
|
|
298
|
-
*
|
|
299
|
-
* When the title is set to "NEWSLETTER_TITLE", it replaces the "{{NEWSLETTER_TITLE}}" pattern in the template string.
|
|
300
|
-
*
|
|
301
|
-
* @default "NEWSLETTER_TITLE"
|
|
302
|
-
*/
|
|
303
|
-
title?: string;
|
|
304
|
-
/**
|
|
305
|
-
* Content HTML token
|
|
306
|
-
*
|
|
307
|
-
* When content is set to "NEWSLETTER_CONTENT", it replaces the "{{NEWSLETTER_CONTENT}}" pattern in the template string.
|
|
308
|
-
*
|
|
309
|
-
* @default "NEWSLETTER_CONTENT"
|
|
310
|
-
*/
|
|
311
|
-
content?: string;
|
|
312
|
-
};
|
|
313
|
-
/**
|
|
314
|
-
* String template and marker set
|
|
315
|
-
*/
|
|
316
|
-
type HtmlTemplate = {
|
|
317
|
-
/**
|
|
318
|
-
* Original template string
|
|
319
|
-
*/
|
|
320
|
-
html: string;
|
|
321
|
-
/**
|
|
322
|
-
* Uses default markers when not specified
|
|
323
|
-
*/
|
|
324
|
-
markers?: HtmlTemplateMarkers;
|
|
325
|
-
};
|
|
326
|
-
type RequiredHtmlTemplate = Pick<HtmlTemplate, 'html'> & {
|
|
327
|
-
markers: Required<HtmlTemplateMarkers>;
|
|
328
|
-
};
|
|
329
|
-
|
|
330
292
|
/**
|
|
331
293
|
* Type representing an email address.
|
|
332
294
|
* @example "user@example.com"
|
|
@@ -488,6 +450,108 @@ interface DateService {
|
|
|
488
450
|
getPublicationDisplayDateString: () => string;
|
|
489
451
|
}
|
|
490
452
|
|
|
453
|
+
/**
|
|
454
|
+
* A pair of optional prompt builder functions.
|
|
455
|
+
* The returned string **replaces** the built-in default prompt entirely.
|
|
456
|
+
* If omitted, the default prompt is used.
|
|
457
|
+
*/
|
|
458
|
+
type PromptBuilder<TContext> = {
|
|
459
|
+
system?: (context: TContext) => string;
|
|
460
|
+
user?: (context: TContext) => string;
|
|
461
|
+
};
|
|
462
|
+
type ClassifyTagsPromptContext = {
|
|
463
|
+
expertFields: string[];
|
|
464
|
+
outputLanguage: string;
|
|
465
|
+
targetArticle: UnscoredArticle;
|
|
466
|
+
existTags: string[];
|
|
467
|
+
};
|
|
468
|
+
type AnalyzeImagesPromptContext = {
|
|
469
|
+
expertFields: string[];
|
|
470
|
+
outputLanguage: string;
|
|
471
|
+
targetArticle: UnscoredArticle;
|
|
472
|
+
};
|
|
473
|
+
type DetermineImportancePromptContext = {
|
|
474
|
+
expertFields: string[];
|
|
475
|
+
targetArticle: UnscoredArticle;
|
|
476
|
+
dateService: DateService;
|
|
477
|
+
minimumImportanceScoreRules: MinimumImportanceScoreRule[];
|
|
478
|
+
};
|
|
479
|
+
type GenerateNewsletterPromptContext = {
|
|
480
|
+
expertFields: string[];
|
|
481
|
+
outputLanguage: string;
|
|
482
|
+
dateService: DateService;
|
|
483
|
+
targetArticles: ArticleForGenerateContent[];
|
|
484
|
+
freeFormIntro?: boolean;
|
|
485
|
+
titleContext?: string;
|
|
486
|
+
subscribePageUrl?: UrlString;
|
|
487
|
+
newsletterBrandName: string;
|
|
488
|
+
};
|
|
489
|
+
/**
|
|
490
|
+
* Optional provider for customizing LLM prompts.
|
|
491
|
+
* Grouped by pipeline stage. All fields are optional —
|
|
492
|
+
* omitted prompts fall back to built-in defaults.
|
|
493
|
+
*
|
|
494
|
+
* **Dependency note:** Analysis prompts (❶❷❸) produce data that flows
|
|
495
|
+
* into the content generation prompt (❹). Changing an analysis prompt
|
|
496
|
+
* may indirectly affect the final newsletter output.
|
|
497
|
+
*/
|
|
498
|
+
interface PromptProvider {
|
|
499
|
+
/**
|
|
500
|
+
* Analysis stage prompts (❶ classifyTags → ❷ analyzeImages → ❸ determineImportance).
|
|
501
|
+
* Results feed into the content generation stage.
|
|
502
|
+
*/
|
|
503
|
+
analysis?: {
|
|
504
|
+
classifyTags?: PromptBuilder<ClassifyTagsPromptContext>;
|
|
505
|
+
analyzeImages?: PromptBuilder<AnalyzeImagesPromptContext>;
|
|
506
|
+
determineImportance?: PromptBuilder<DetermineImportancePromptContext>;
|
|
507
|
+
};
|
|
508
|
+
/**
|
|
509
|
+
* Content generation stage prompt (❹ generateNewsletter).
|
|
510
|
+
* Produces the final newsletter title and body.
|
|
511
|
+
*/
|
|
512
|
+
contentGenerate?: {
|
|
513
|
+
generateNewsletter?: PromptBuilder<GenerateNewsletterPromptContext>;
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Token markers in template.
|
|
519
|
+
*/
|
|
520
|
+
type HtmlTemplateMarkers = {
|
|
521
|
+
/**
|
|
522
|
+
* Title token
|
|
523
|
+
*
|
|
524
|
+
* When the title is set to "NEWSLETTER_TITLE", it replaces the "{{NEWSLETTER_TITLE}}" pattern in the template string.
|
|
525
|
+
*
|
|
526
|
+
* @default "NEWSLETTER_TITLE"
|
|
527
|
+
*/
|
|
528
|
+
title?: string;
|
|
529
|
+
/**
|
|
530
|
+
* Content HTML token
|
|
531
|
+
*
|
|
532
|
+
* When content is set to "NEWSLETTER_CONTENT", it replaces the "{{NEWSLETTER_CONTENT}}" pattern in the template string.
|
|
533
|
+
*
|
|
534
|
+
* @default "NEWSLETTER_CONTENT"
|
|
535
|
+
*/
|
|
536
|
+
content?: string;
|
|
537
|
+
};
|
|
538
|
+
/**
|
|
539
|
+
* String template and marker set
|
|
540
|
+
*/
|
|
541
|
+
type HtmlTemplate = {
|
|
542
|
+
/**
|
|
543
|
+
* Original template string
|
|
544
|
+
*/
|
|
545
|
+
html: string;
|
|
546
|
+
/**
|
|
547
|
+
* Uses default markers when not specified
|
|
548
|
+
*/
|
|
549
|
+
markers?: HtmlTemplateMarkers;
|
|
550
|
+
};
|
|
551
|
+
type RequiredHtmlTemplate = Pick<HtmlTemplate, 'html'> & {
|
|
552
|
+
markers: Required<HtmlTemplateMarkers>;
|
|
553
|
+
};
|
|
554
|
+
|
|
491
555
|
/**
|
|
492
556
|
* Publishable final Newsletter entity.
|
|
493
557
|
*/
|
|
@@ -807,6 +871,11 @@ type GenerateNewsletterConfig<TaskId> = {
|
|
|
807
871
|
* Optional behavior/settings.
|
|
808
872
|
*/
|
|
809
873
|
options?: GenerateNewsletterOptions;
|
|
874
|
+
/**
|
|
875
|
+
* Optional provider for customizing LLM prompts.
|
|
876
|
+
* When omitted, built-in default prompts are used.
|
|
877
|
+
*/
|
|
878
|
+
promptProvider?: PromptProvider;
|
|
810
879
|
};
|
|
811
880
|
|
|
812
881
|
/**
|
|
@@ -822,6 +891,7 @@ declare class GenerateNewsletter<TaskId> {
|
|
|
822
891
|
private readonly contentGenerateProvider;
|
|
823
892
|
private readonly logger;
|
|
824
893
|
private readonly options;
|
|
894
|
+
private readonly promptProvider?;
|
|
825
895
|
private readonly previewNewsletterOptions?;
|
|
826
896
|
/** Independent internal field **/
|
|
827
897
|
private taskId;
|
|
@@ -858,4 +928,4 @@ declare class GenerateNewsletter<TaskId> {
|
|
|
858
928
|
}
|
|
859
929
|
|
|
860
930
|
export { DateType, GenerateNewsletter };
|
|
861
|
-
export type { AnalysisProvider, AppLogger, ArticleForGenerateContent, ArticleForUpdateByAnalysis, ContentGenerateProvider, CrawlingProvider, CrawlingTarget, CrawlingTargetGroup, DateService, EmailAddress, EmailAttachment, EmailMessage, EmailService, GenerateNewsletterConfig, GenerateNewsletterOptions, HtmlString, HtmlTemplate, HtmlTemplateMarkers, IsoDateString, LogLevel, LogMessage, MarkdownString, MinimumImportanceScoreRule, Newsletter, ParsedTarget, ParsedTargetDetail, ParsedTargetListItem, RequiredHtmlTemplate, TaskService, UniqueIdentifier, UnscoredArticle, UrlString };
|
|
931
|
+
export type { AnalysisProvider, AnalyzeImagesPromptContext, AppLogger, ArticleForGenerateContent, ArticleForUpdateByAnalysis, ClassifyTagsPromptContext, ContentGenerateProvider, CrawlingProvider, CrawlingTarget, CrawlingTargetGroup, DateService, DetermineImportancePromptContext, EmailAddress, EmailAttachment, EmailMessage, EmailService, GenerateNewsletterConfig, GenerateNewsletterOptions, GenerateNewsletterPromptContext, HtmlString, HtmlTemplate, HtmlTemplateMarkers, IsoDateString, LogLevel, LogMessage, MarkdownString, MinimumImportanceScoreRule, Newsletter, ParsedTarget, ParsedTargetDetail, ParsedTargetListItem, PromptBuilder, PromptProvider, RequiredHtmlTemplate, TaskService, UniqueIdentifier, UnscoredArticle, UrlString };
|
package/dist/index.js
CHANGED
|
@@ -191,8 +191,10 @@ class AnalyzeImages extends LLMQuery {
|
|
|
191
191
|
.string()
|
|
192
192
|
.describe('A high-level overview of the general theme, mood, and context conveyed by the images (without specific names, titles, or exact figures from text within images)'),
|
|
193
193
|
});
|
|
194
|
+
promptBuilder;
|
|
194
195
|
constructor(config) {
|
|
195
196
|
super(config);
|
|
197
|
+
this.promptBuilder = config.promptBuilder;
|
|
196
198
|
}
|
|
197
199
|
async execute() {
|
|
198
200
|
if (!this.targetArticle.hasAttachedImage ||
|
|
@@ -216,7 +218,17 @@ class AnalyzeImages extends LLMQuery {
|
|
|
216
218
|
});
|
|
217
219
|
return { result: output.imageContext, usage };
|
|
218
220
|
}
|
|
221
|
+
get promptContext() {
|
|
222
|
+
return {
|
|
223
|
+
expertFields: this.expertFields,
|
|
224
|
+
outputLanguage: this.options.content.outputLanguage,
|
|
225
|
+
targetArticle: this.targetArticle,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
219
228
|
get systemPrompt() {
|
|
229
|
+
if (this.promptBuilder?.system) {
|
|
230
|
+
return this.promptBuilder.system(this.promptContext);
|
|
231
|
+
}
|
|
220
232
|
return `# Image Analysis Expert System
|
|
221
233
|
|
|
222
234
|
## Identity & Expertise
|
|
@@ -287,6 +299,12 @@ You are a specialized image analysis expert in: ${this.expertFields.join(', ')}
|
|
|
287
299
|
}));
|
|
288
300
|
}
|
|
289
301
|
get textMessage() {
|
|
302
|
+
if (this.promptBuilder?.user) {
|
|
303
|
+
return {
|
|
304
|
+
type: 'text',
|
|
305
|
+
text: this.promptBuilder.user(this.promptContext),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
290
308
|
return {
|
|
291
309
|
type: 'text',
|
|
292
310
|
text: `## Analysis Task
|
|
@@ -324,9 +342,11 @@ class ClassifyTags extends LLMQuery {
|
|
|
324
342
|
tag2: z.string(),
|
|
325
343
|
tag3: z.string(),
|
|
326
344
|
});
|
|
345
|
+
promptBuilder;
|
|
327
346
|
existTags = [];
|
|
328
347
|
constructor(config) {
|
|
329
348
|
super(config);
|
|
349
|
+
this.promptBuilder = config.promptBuilder;
|
|
330
350
|
}
|
|
331
351
|
async execute({ existTags }) {
|
|
332
352
|
this.existTags = existTags;
|
|
@@ -339,7 +359,18 @@ class ClassifyTags extends LLMQuery {
|
|
|
339
359
|
});
|
|
340
360
|
return { result: output, usage };
|
|
341
361
|
}
|
|
362
|
+
get promptContext() {
|
|
363
|
+
return {
|
|
364
|
+
expertFields: this.expertFields,
|
|
365
|
+
outputLanguage: this.options.content.outputLanguage,
|
|
366
|
+
targetArticle: this.targetArticle,
|
|
367
|
+
existTags: this.existTags,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
342
370
|
get systemPrompt() {
|
|
371
|
+
if (this.promptBuilder?.system) {
|
|
372
|
+
return this.promptBuilder.system(this.promptContext);
|
|
373
|
+
}
|
|
343
374
|
return `You are an AI specializing in analyzing and categorizing articles for professionals in ${this.expertFields.join(', ')}.
|
|
344
375
|
|
|
345
376
|
## Core Responsibility
|
|
@@ -353,7 +384,7 @@ All classifications must be written in ${this.options.content.outputLanguage}.
|
|
|
353
384
|
2. **New Tag Criteria**: Create new classifications only when:
|
|
354
385
|
- Best existing match scores below 80% compatibility
|
|
355
386
|
- New tag demonstrates versatility across 10+ similar articles
|
|
356
|
-
3. **Naming Standards**:
|
|
387
|
+
3. **Naming Standards**:
|
|
357
388
|
- Length: 3-15 characters
|
|
358
389
|
- Style: Clear, intuitive ${this.options.content.outputLanguage} terms
|
|
359
390
|
- Balance industry precision with general reader comprehension
|
|
@@ -367,6 +398,9 @@ Prioritize in order:
|
|
|
367
398
|
- Long-term scalability and maintainability`;
|
|
368
399
|
}
|
|
369
400
|
get userPrompt() {
|
|
401
|
+
if (this.promptBuilder?.user) {
|
|
402
|
+
return this.promptBuilder.user(this.promptContext);
|
|
403
|
+
}
|
|
370
404
|
return `**Task**: Classify this article with 3 optimal detailed tags.
|
|
371
405
|
|
|
372
406
|
**Article Information**
|
|
@@ -404,10 +438,12 @@ class DetermineArticleImportance extends LLMQuery {
|
|
|
404
438
|
.describe('Article importance score (1-10, 10 is most important)'),
|
|
405
439
|
});
|
|
406
440
|
dateService;
|
|
441
|
+
promptBuilder;
|
|
407
442
|
constructor(config) {
|
|
408
443
|
super(config);
|
|
409
444
|
this.minimumImportanceScoreRules = config.minimumImportanceScoreRules ?? [];
|
|
410
445
|
this.dateService = config.dateService;
|
|
446
|
+
this.promptBuilder = config.promptBuilder;
|
|
411
447
|
}
|
|
412
448
|
async execute() {
|
|
413
449
|
const { output, usage } = await generateObjectByLLM({
|
|
@@ -426,7 +462,18 @@ class DetermineArticleImportance extends LLMQuery {
|
|
|
426
462
|
get hasHigherMinimumScore() {
|
|
427
463
|
return this.minPoint > 1;
|
|
428
464
|
}
|
|
465
|
+
get promptContext() {
|
|
466
|
+
return {
|
|
467
|
+
expertFields: this.expertFields,
|
|
468
|
+
targetArticle: this.targetArticle,
|
|
469
|
+
dateService: this.dateService,
|
|
470
|
+
minimumImportanceScoreRules: this.minimumImportanceScoreRules,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
429
473
|
get systemPrompt() {
|
|
474
|
+
if (this.promptBuilder?.system) {
|
|
475
|
+
return this.promptBuilder.system(this.promptContext);
|
|
476
|
+
}
|
|
430
477
|
return `You are an expert in importance evaluation in the field of ${this.expertFields.join(', ')}.
|
|
431
478
|
|
|
432
479
|
Role:
|
|
@@ -463,6 +510,9 @@ Important Notes:
|
|
|
463
510
|
- Be sensitive to core keywords, events, policies considered important in the field.`;
|
|
464
511
|
}
|
|
465
512
|
get userPrompt() {
|
|
513
|
+
if (this.promptBuilder?.user) {
|
|
514
|
+
return this.promptBuilder.user(this.promptContext);
|
|
515
|
+
}
|
|
466
516
|
return `Please rate the importance of this article from ${this.minPoint} to 10.
|
|
467
517
|
|
|
468
518
|
**Newsletter Publication Date:** ${this.dateService.getPublicationISODateString()}${this.targetArticle.publishedDate
|
|
@@ -507,9 +557,11 @@ class PrivateChain extends BaseChain {
|
|
|
507
557
|
|
|
508
558
|
class ArticleInsightsChain extends PrivateChain {
|
|
509
559
|
dateService;
|
|
560
|
+
promptProvider;
|
|
510
561
|
constructor(config) {
|
|
511
562
|
super(config);
|
|
512
563
|
this.dateService = config.dateService;
|
|
564
|
+
this.promptProvider = config.promptProvider;
|
|
513
565
|
}
|
|
514
566
|
/* istanbul ignore next - pipeline arrow functions are exercised via higher-level tests */
|
|
515
567
|
get chain() {
|
|
@@ -605,7 +657,10 @@ class ArticleInsightsChain extends PrivateChain {
|
|
|
605
657
|
},
|
|
606
658
|
});
|
|
607
659
|
try {
|
|
608
|
-
const classifyTags = new ClassifyTags(
|
|
660
|
+
const classifyTags = new ClassifyTags({
|
|
661
|
+
...this.getLlmQueryConfig(this.provider.classifyTagOptions.model, article),
|
|
662
|
+
promptBuilder: this.promptProvider?.classifyTags,
|
|
663
|
+
});
|
|
609
664
|
const { result: generatedTags } = await classifyTags.execute({
|
|
610
665
|
existTags,
|
|
611
666
|
});
|
|
@@ -680,7 +735,10 @@ class ArticleInsightsChain extends PrivateChain {
|
|
|
680
735
|
},
|
|
681
736
|
});
|
|
682
737
|
try {
|
|
683
|
-
const analyzeImages = new AnalyzeImages(
|
|
738
|
+
const analyzeImages = new AnalyzeImages({
|
|
739
|
+
...this.getLlmQueryConfig(this.provider.analyzeImagesOptions.model, article),
|
|
740
|
+
promptBuilder: this.promptProvider?.analyzeImages,
|
|
741
|
+
});
|
|
684
742
|
const { result: imageContextByLlm } = await analyzeImages.execute();
|
|
685
743
|
if (imageContextByLlm) {
|
|
686
744
|
articlesWithImageContext.push({
|
|
@@ -779,6 +837,7 @@ class ArticleInsightsChain extends PrivateChain {
|
|
|
779
837
|
...this.getLlmQueryConfig(this.provider.determineScoreOptions.model, article),
|
|
780
838
|
minimumImportanceScoreRules: this.provider.determineScoreOptions.minimumImportanceScoreRules,
|
|
781
839
|
dateService: this.dateService,
|
|
840
|
+
promptBuilder: this.promptProvider?.determineImportance,
|
|
782
841
|
});
|
|
783
842
|
const { result: importanceScore } = await determineArticleImportance.execute();
|
|
784
843
|
const processedArticle = {
|
|
@@ -843,9 +902,11 @@ class ArticleInsightsChain extends PrivateChain {
|
|
|
843
902
|
|
|
844
903
|
class AnalysisChain extends Chain {
|
|
845
904
|
dateService;
|
|
905
|
+
promptProvider;
|
|
846
906
|
constructor(config) {
|
|
847
907
|
super(config);
|
|
848
908
|
this.dateService = config.dateService;
|
|
909
|
+
this.promptProvider = config.promptProvider;
|
|
849
910
|
}
|
|
850
911
|
get chain() {
|
|
851
912
|
return RunnablePassthrough.assign({
|
|
@@ -901,6 +962,7 @@ class AnalysisChain extends Chain {
|
|
|
901
962
|
options: this.options,
|
|
902
963
|
loggingExecutor: new LoggingExecutor(this.logger, this.taskId),
|
|
903
964
|
dateService: this.dateService,
|
|
965
|
+
promptProvider: this.promptProvider,
|
|
904
966
|
});
|
|
905
967
|
return await articleInsightsChain.generateInsights();
|
|
906
968
|
});
|
|
@@ -934,6 +996,7 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
|
|
|
934
996
|
dateService;
|
|
935
997
|
subscribePageUrl;
|
|
936
998
|
newsletterBrandName;
|
|
999
|
+
promptBuilder;
|
|
937
1000
|
schema = z.object({
|
|
938
1001
|
title: z.string().max(70).min(20).describe('Title of the newsletter email'),
|
|
939
1002
|
content: z.string().describe('Email content in markdown format'),
|
|
@@ -959,6 +1022,7 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
|
|
|
959
1022
|
this.dateService = config.dateService;
|
|
960
1023
|
this.subscribePageUrl = config.subscribePageUrl;
|
|
961
1024
|
this.newsletterBrandName = config.newsletterBrandName;
|
|
1025
|
+
this.promptBuilder = config.promptBuilder;
|
|
962
1026
|
}
|
|
963
1027
|
async execute() {
|
|
964
1028
|
const { output, usage, finishReason } = await generateObjectByLLM({
|
|
@@ -993,7 +1057,22 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
|
|
|
993
1057
|
}
|
|
994
1058
|
return { result: pick(output, ['title', 'content']), usage };
|
|
995
1059
|
}
|
|
1060
|
+
get promptContext() {
|
|
1061
|
+
return {
|
|
1062
|
+
expertFields: this.expertFields,
|
|
1063
|
+
outputLanguage: this.options.content.outputLanguage,
|
|
1064
|
+
dateService: this.dateService,
|
|
1065
|
+
targetArticles: this.targetArticles,
|
|
1066
|
+
freeFormIntro: this.options.content.freeFormIntro,
|
|
1067
|
+
titleContext: this.options.content.titleContext,
|
|
1068
|
+
subscribePageUrl: this.subscribePageUrl,
|
|
1069
|
+
newsletterBrandName: this.newsletterBrandName,
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
996
1072
|
get systemPrompt() {
|
|
1073
|
+
if (this.promptBuilder?.system) {
|
|
1074
|
+
return this.promptBuilder.system(this.promptContext);
|
|
1075
|
+
}
|
|
997
1076
|
return `You are a newsletter production expert for "${this.newsletterBrandName}" who analyzes and delivers trends in the fields of ${this.expertFields.join(', ')}. Your goal is to provide in-depth analysis that helps industry professionals easily understand complex information and make informed decisions.
|
|
998
1077
|
|
|
999
1078
|
Important rule for displaying date ranges: When displaying date ranges, you must use a hyphen (-) instead of a tilde (~). For example, use 'June 1-2, 2025' instead of 'June 1~2, 2025'. The tilde (~) can be rendered as strikethrough in markdown.
|
|
@@ -1125,6 +1204,9 @@ Output Format & Requirements:
|
|
|
1125
1204
|
${this.subscribePageUrl ? `- Add \`${this.subscribePageUrl}\`(Subscribe to ${this.newsletterBrandName}) page access link button at appropriate attention-worthy spot for natural recommendation to others.` : ''}`;
|
|
1126
1205
|
}
|
|
1127
1206
|
get userPrompt() {
|
|
1207
|
+
if (this.promptBuilder?.user) {
|
|
1208
|
+
return this.promptBuilder.user(this.promptContext);
|
|
1209
|
+
}
|
|
1128
1210
|
return `Below is the complete list of newly collected ${this.expertFields.join(', ')} related news:
|
|
1129
1211
|
|
|
1130
1212
|
${this.targetArticles
|
|
@@ -1201,9 +1283,11 @@ class ContentGenerateChain extends Chain {
|
|
|
1201
1283
|
minimumArticleCountForIssue;
|
|
1202
1284
|
priorityArticleScoreThreshold;
|
|
1203
1285
|
htmlTemplate;
|
|
1286
|
+
promptProvider;
|
|
1204
1287
|
constructor(config) {
|
|
1205
1288
|
super(config);
|
|
1206
1289
|
this.dateService = config.dateService;
|
|
1290
|
+
this.promptProvider = config.promptProvider;
|
|
1207
1291
|
this.minimumArticleCountForIssue =
|
|
1208
1292
|
config.provider.publicationCriteria?.minimumArticleCountForIssue ?? 5;
|
|
1209
1293
|
this.priorityArticleScoreThreshold =
|
|
@@ -1286,6 +1370,7 @@ class ContentGenerateChain extends Chain {
|
|
|
1286
1370
|
subscribePageUrl: this.provider.subscribePageUrl,
|
|
1287
1371
|
newsletterBrandName: this.provider.newsletterBrandName,
|
|
1288
1372
|
dateService: this.dateService,
|
|
1373
|
+
promptBuilder: this.promptProvider?.generateNewsletter,
|
|
1289
1374
|
});
|
|
1290
1375
|
const { result } = await generateNewsletter.execute();
|
|
1291
1376
|
return result;
|
|
@@ -1775,6 +1860,7 @@ class GenerateNewsletter {
|
|
|
1775
1860
|
contentGenerateProvider;
|
|
1776
1861
|
logger;
|
|
1777
1862
|
options;
|
|
1863
|
+
promptProvider;
|
|
1778
1864
|
previewNewsletterOptions;
|
|
1779
1865
|
/** Independent internal field **/
|
|
1780
1866
|
taskId = null;
|
|
@@ -1818,6 +1904,7 @@ class GenerateNewsletter {
|
|
|
1818
1904
|
...config.options?.chain,
|
|
1819
1905
|
},
|
|
1820
1906
|
};
|
|
1907
|
+
this.promptProvider = config.promptProvider;
|
|
1821
1908
|
// Default logger (no-op)
|
|
1822
1909
|
this.logger = config.options?.logger ?? {
|
|
1823
1910
|
info: (_msg) => { },
|
|
@@ -1847,6 +1934,7 @@ class GenerateNewsletter {
|
|
|
1847
1934
|
options: this.options,
|
|
1848
1935
|
loggingExecutor,
|
|
1849
1936
|
dateService: this.dateService,
|
|
1937
|
+
promptProvider: this.promptProvider?.analysis,
|
|
1850
1938
|
});
|
|
1851
1939
|
const contentGenerateChain = new ContentGenerateChain({
|
|
1852
1940
|
logger: this.logger,
|
|
@@ -1855,6 +1943,7 @@ class GenerateNewsletter {
|
|
|
1855
1943
|
options: this.options,
|
|
1856
1944
|
loggingExecutor,
|
|
1857
1945
|
dateService: this.dateService,
|
|
1946
|
+
promptProvider: this.promptProvider?.contentGenerate,
|
|
1858
1947
|
});
|
|
1859
1948
|
const taskChain = RunnableSequence.from([
|
|
1860
1949
|
crawlingChain.chain,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@llm-newsletter-kit/core",
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "2.0
|
|
5
|
+
"version": "2.1.0",
|
|
6
6
|
"description": "An extensible framework to automate your entire newsletter workflow. Handles data collection, LLM-based content analysis, and email generation, letting you focus on your unique domain logic.",
|
|
7
7
|
"main": "dist/index.cjs",
|
|
8
8
|
"module": "dist/index.js",
|
|
@@ -49,36 +49,36 @@
|
|
|
49
49
|
"author": "kimhongyeon",
|
|
50
50
|
"license": "Apache-2.0",
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@langchain/core": "^1.
|
|
53
|
-
"ai": "^6.0.
|
|
52
|
+
"@langchain/core": "^1.2.0",
|
|
53
|
+
"ai": "^6.0.208",
|
|
54
54
|
"es-toolkit": "^1.47.1",
|
|
55
55
|
"jsdom": "^29.1.1",
|
|
56
|
-
"juice": "^12.1.
|
|
57
|
-
"safe-markdown2html": "^1.0.
|
|
56
|
+
"juice": "^12.1.1",
|
|
57
|
+
"safe-markdown2html": "^1.0.8",
|
|
58
58
|
"zod": "^4.4.3"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@ai-sdk/anthropic": "^3.0.
|
|
62
|
-
"@ai-sdk/google": "^3.0.
|
|
63
|
-
"@ai-sdk/openai": "^3.0.
|
|
64
|
-
"@ai-sdk/togetherai": "^2.0.
|
|
61
|
+
"@ai-sdk/anthropic": "^3.0.85",
|
|
62
|
+
"@ai-sdk/google": "^3.0.83",
|
|
63
|
+
"@ai-sdk/openai": "^3.0.73",
|
|
64
|
+
"@ai-sdk/togetherai": "^2.0.56",
|
|
65
65
|
"@eslint/js": "^10.0.1",
|
|
66
66
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
67
67
|
"@types/jsdom": "^28.0.3",
|
|
68
|
-
"@types/node": "^
|
|
69
|
-
"@vitest/coverage-v8": "^4.1.
|
|
70
|
-
"@vitest/expect": "^4.1.
|
|
68
|
+
"@types/node": "^26.0.0",
|
|
69
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
70
|
+
"@vitest/expect": "^4.1.9",
|
|
71
71
|
"eslint": "^10.5.0",
|
|
72
72
|
"eslint-plugin-unused-imports": "^4.4.1",
|
|
73
73
|
"prettier": "^3.8.4",
|
|
74
74
|
"rimraf": "^6.1.3",
|
|
75
|
-
"rollup": "^4.62.
|
|
75
|
+
"rollup": "^4.62.2",
|
|
76
76
|
"rollup-plugin-dts": "^6.4.1",
|
|
77
77
|
"rollup-plugin-typescript2": "^0.37.0",
|
|
78
78
|
"tsx": "^4.22.4",
|
|
79
79
|
"typescript": "^6.0.3",
|
|
80
|
-
"typescript-eslint": "^8.61.
|
|
81
|
-
"vitest": "^4.1.
|
|
80
|
+
"typescript-eslint": "^8.61.1",
|
|
81
|
+
"vitest": "^4.1.9"
|
|
82
82
|
},
|
|
83
83
|
"repository": {
|
|
84
84
|
"type": "git",
|