Skip to content

Commit

Permalink
fix(pencil): fix decorators
Browse files Browse the repository at this point in the history
  • Loading branch information
Woody Rousseau committed Mar 10, 2024
1 parent 5abaa17 commit 01102e9
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 110 deletions.
9 changes: 9 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ module.exports = {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
"@typescript-eslint/ban-types": [
"error",
{
"types": {
"Function": false,
},
"extendDefaults": true,
},
],
},
};

15 changes: 0 additions & 15 deletions CHANGELOG.md

This file was deleted.

8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"scripts": {
"prepare": "husky install",
"build": "tsc",
"build:watch": "tsc -w",
"release": "semantic-release",
"lint": "eslint \"src/**/!(*.d).ts\" --fix",
"format": "prettier \"**/*.ts\" --ignore-path ./.prettierignore --write"
Expand All @@ -38,6 +39,7 @@
"@nestjs/testing": "10.3.0",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^9.2.6",
"@semantic-release/changelog": "^6.0.3",
"@tsconfig/recommended": "^1.0.2",
"@types/jest": "28.1.6",
"@types/multer": "^1.4.11",
Expand Down Expand Up @@ -75,6 +77,12 @@
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
"@semantic-release/npm",
"@semantic-release/git"
]
Expand Down
8 changes: 8 additions & 0 deletions src/decorators/generative-ai-feedback.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Type, UsePipes } from '@nestjs/common';
import { AICheckPipe } from '../pipes';

export function AIFeedback(dtoType: Type<any>): MethodDecorator {
return function (target: any, key: any, descriptor: PropertyDescriptor) {
UsePipes(AICheckPipe(dtoType))(target, key, descriptor);
};
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
import { ValidationArguments, registerDecorator } from 'class-validator';
import { Inject } from '@nestjs/common';
import { AICheckParams } from '../interfaces/generative-ai.interface';
import { AIFeedbackEngine } from '../generative-ai.service';
import { AISpecificationsParams } from '../interfaces/generative-ai.interface';
import { AIService } from '../generative-ai.service';
import { FieldsSpecificationsStore, ValidationMessageStore } from '../utils';

export const AICheck = (
export const AISpecifications = (
specifications: string[],
checkParams?: AICheckParams,
specificationsParams?: AISpecificationsParams,
) => {
const injectFeedbackEngine = Inject(AIFeedbackEngine);

return (target: any, propertyKey: string) => {
injectFeedbackEngine(target, 'feedbackEngine');
const feedbackEngine: AIFeedbackEngine = target.feedbackEngine;

if (checkParams === undefined || !checkParams.validate) {
if (specificationsParams === undefined || !specificationsParams.validate) {
FieldsSpecificationsStore.setClassFieldsSpecifications(
target.constructor.name,
{
fieldName: propertyKey,
specifications,
},
);
return;
}

const validate = async (value, args) => {
const aiService = AIService.getInstance();
const [guidelines] = args.constraints;

const feedback =
await feedbackEngine.generateFeedbackOnInputWithGuidelines(
value,
guidelines,
);
const feedback = await aiService.generateFeedbackOnInputWithGuidelines(
value,
guidelines,
);

const isValid = feedback.includes("It's good");

Expand All @@ -58,7 +51,7 @@ export const AICheck = (
};

registerDecorator({
name: 'aICheck',
name: 'aISpecifications',
target: target.constructor,
propertyName: propertyKey,
constraints: [specifications],
Expand Down
3 changes: 2 additions & 1 deletion src/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './generative-ai-check.decorator';
export * from './generative-ai-specifications.decorator';
export * from './generative-ai-feedback.decorator';
31 changes: 5 additions & 26 deletions src/generative-ai.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { DynamicModule, Global, Module, Provider } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
import { AIFeedbackEngine } from './generative-ai.service';
import { AICheckPipe, AISummarizeDocumentPipe } from './pipes';
import { AIService } from './generative-ai.service';
import {
GenerativeAIModuleAsyncOptions,
GenerativeAIModuleOptions,
Expand All @@ -19,37 +17,18 @@ export class GenerativeAIModule {
provide: GENERATIVE_AI_MODULE_OPTIONS,
useValue: options,
},
AIFeedbackEngine,
{
provide: APP_PIPE,
useClass: AICheckPipe,
},
{
provide: APP_PIPE,
useClass: AISummarizeDocumentPipe,
},
AIService,
],
exports: [AIFeedbackEngine],
exports: [AIService],
};
}

static forRootAsync(options: GenerativeAIModuleAsyncOptions): DynamicModule {
return {
module: GenerativeAIModule,
imports: options.imports || [],
providers: [
this.createAsyncOptionsProvider(options),
AIFeedbackEngine,
{
provide: APP_PIPE,
useClass: AICheckPipe,
},
{
provide: APP_PIPE,
useClass: AISummarizeDocumentPipe,
},
],
exports: [AIFeedbackEngine],
providers: [this.createAsyncOptionsProvider(options), AIService],
exports: [AIService],
};
}

Expand Down
29 changes: 24 additions & 5 deletions src/generative-ai.service.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { ChatOpenAI } from '@langchain/openai';
import { GenerativeAIModuleOptions } from './interfaces';
import { GENERATIVE_AI_MODULE_OPTIONS } from './constants';
import { loadSummarizationChain } from 'langchain/chains';
import { Document } from '@langchain/core/documents';

@Injectable()
export class AIFeedbackEngine implements OnModuleInit {
export class AIService {
private chatModel!: ChatOpenAI;
private static instance: AIService;

constructor(
@Inject(GENERATIVE_AI_MODULE_OPTIONS)
protected readonly options: GenerativeAIModuleOptions,
) {}

async onModuleInit() {
) {
this.chatModel = new ChatOpenAI({
openAIApiKey: this.options.modelApiKey,
});
AIService.instance = this;
}

public static getInstance(): AIService {
if (!AIService.instance) {
throw new Error('AIService instance has not been initialized');
}
return AIService.instance;
}

async generateFeedbackOnInputWithGuidelines(
Expand Down Expand Up @@ -46,4 +55,14 @@ export class AIFeedbackEngine implements OnModuleInit {

return result;
}

async summarizeDocuments(docs: Document[]): Promise<string> {
const summarizeChain = loadSummarizationChain(this.chatModel, {
type: 'stuff',
});

const result = await summarizeChain.invoke({ input_documents: docs });

return result.text;
}
}
4 changes: 2 additions & 2 deletions src/interfaces/generative-ai.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class SummaryEnriched<T> {
public summary!: string;
}

export interface AICheckParams {
export interface AISpecificationsParams {
validate: boolean;
}

Expand All @@ -17,7 +17,7 @@ export interface FieldSpecifications {
specifications: string[];
}

export class AIFile {
export class SummaryEnrichedFile {
file!: Express.Multer.File;
summary!: string;
}
77 changes: 45 additions & 32 deletions src/pipes/generative-ai-check.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,54 @@
import { Injectable, PipeTransform, Type } from '@nestjs/common';
import { Inject, PipeTransform, Type, mixin } from '@nestjs/common';
import { FeedbackEnriched } from '../interfaces/generative-ai.interface';
import { AIFeedbackEngine } from '../generative-ai.service';
import { FieldsSpecificationsStore } from '../utils';

@Injectable()
export class AICheckPipe<T>
implements PipeTransform<T, Promise<FeedbackEnriched<T>>>
{
constructor(
private expectedType: Type<any>,
private readonly feedbackEngine: AIFeedbackEngine,
) {}
async transform(value: T): Promise<FeedbackEnriched<T>> {
const fieldsSpecifications =
FieldsSpecificationsStore.getClassFieldsSpecifications(
this.expectedType.name,
);

if (fieldsSpecifications !== undefined) {
const input = value[fieldsSpecifications.fieldName];
const specifications = fieldsSpecifications.specifications;

const feedback =
await this.feedbackEngine.generateFeedbackOnInputWithGuidelines(
input,
specifications,
import { AIService } from '../generative-ai.service';
import { FieldsSpecificationsStore, memoize } from '../utils';

export type IAICheckPipe = {
transform<T>(value: T): Promise<FeedbackEnriched<T>>;
};

export const AICheckPipe: (expectedType: Type<any>) => Type<IAICheckPipe> =
memoize(createAICheckPipe);

function createAICheckPipe(expectedType: Type<any>): Type<IAICheckPipe> {
class MixinAICheckPipe<T>
implements PipeTransform<T, Promise<FeedbackEnriched<T>>>
{
protected aiService: AIService;
constructor(@Inject(AIService) feedbackEngine: AIService) {
this.aiService = feedbackEngine;
}

async transform(value: T): Promise<FeedbackEnriched<T>> {
const fieldsSpecifications =
FieldsSpecificationsStore.getClassFieldsSpecifications(
expectedType.name,
);

if (fieldsSpecifications !== undefined) {
const input = value[fieldsSpecifications.fieldName];
const specifications = fieldsSpecifications.specifications;

const feedback =
await this.aiService.generateFeedbackOnInputWithGuidelines(
input,
specifications,
);

return {
data: value,
feedback,
};
}

return {
data: value,
feedback,
feedback: undefined,
};
}

return {
data: value,
feedback: undefined,
};
}

const pipe = mixin(MixinAICheckPipe);

return pipe as Type<IAICheckPipe>;
}
17 changes: 6 additions & 11 deletions src/pipes/generative-ai-summarize-document.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import { join } from 'path';
import { tmpdir } from 'os';
import { promises as fs } from 'fs';
import { PDFLoader } from 'langchain/document_loaders/fs/pdf';
import { ChatOpenAI } from '@langchain/openai';
import { loadSummarizationChain } from 'langchain/chains';
import { SummaryEnrichedFile } from '../interfaces';
import { AIService } from '../generative-ai.service';

@Injectable()
export class AISummarizeDocumentPipe implements PipeTransform {
constructor(private readonly aiService: AIService) {}
async transform(value: Express.Multer.File, metadata: ArgumentMetadata) {
if (
metadata.metatype === undefined ||
metadata.metatype.name !== 'AIFile'
metadata.metatype.name !== SummaryEnrichedFile.name
) {
return value;
}
Expand All @@ -25,17 +26,11 @@ export class AISummarizeDocumentPipe implements PipeTransform {

try {
const docs = await loader.loadAndSplit();
const llm = new ChatOpenAI();

const summarizeChain = loadSummarizationChain(llm, {
type: 'stuff',
});

const summary = await summarizeChain.invoke({ input_documents: docs });
const summary = await this.aiService.summarizeDocuments(docs);

const result = {
file: value,
summary: summary.text,
summary,
};

return result;
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './memoize';
export * from './stores';
Loading

0 comments on commit 01102e9

Please sign in to comment.