Skip to content

Commit

Permalink
Merge branch 'main' into feat/hubspot-backend-creation
Browse files Browse the repository at this point in the history
  • Loading branch information
codeincontext authored Aug 29, 2024
2 parents 4a6e42f + ac54789 commit 44c3860
Show file tree
Hide file tree
Showing 47 changed files with 2,258 additions and 913 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"dopplerhq",
"EASS",
"EHRC",
"estree",
"estruyf",
"firstname",
"fkey",
Expand Down Expand Up @@ -107,6 +108,7 @@
"Sedar",
"slidedeck",
"sslmode",
"SUBJ",
"superjson",
"tailwindcss",
"tanstack",
Expand Down
44 changes: 22 additions & 22 deletions apps/nextjs/src/ai-apps/lesson-planner/state/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,36 @@ import {
* which isn't very useful
*/

const enum LessonPlannerAppActions {
CreateSession = "CREATE_SESSION",
Begin = "BEGIN",
enum LessonPlannerAppActions {
AddKeyLearningPoint = "ADD_KEY_LEARNING_POINT",
AddKeyword = "ADD_KEYWORD",
AddMisconception = "ADD_MISCONCEPTION",
BackToEditSubjectAndKS = "BACK_TO_EDIT_SUBJ_KS",
Begin = "BEGIN",
CreateSession = "CREATE_SESSION",
EncounteredNonRecoverableError = "ENCOUNTERED_NON_RECOVERABLE_ERROR",
ExtendedQuiz = "EXTENDED_QUIZ",
GeneratedLessonPlan = "GENERATED_LESSON_PLAN",
RegeneratedKeyLearningPoints = "REGENERATED_KEY_LEARNING_POINTS",
RegeneratedKeywords = "REGENERATED_KEYWORDS",
RegeneratedMisconceptions = "REGENERATED_MISCONCEPTIONS",
RemoveKeyLearningPoint = "REMOVE_KEY_LEARNING_POINT",
RemoveKeyword = "REMOVE_KEYWORD",
RemoveMisconception = "REMOVE_MISCONCEPTION",
RequestReset = "REQUEST_RESET",
RestoreExitQuizFromLocalStorage = "RESTORE_EXIT_QUIZ_FROM_LOCAL_STORAGE",
RestoreSession = "RESTORE_SESSION",
RestoreStarterQuizFromLocalStorage = "RESTORE_STARTER_QUIZ_FROM_LOCAL_STORAGE",
SetKeyStage = "SET_KEY_STAGE",
SetLessonTitle = "SET_LESSON_TITLE",
SetSubject = "SET_SUBJECT",
SetTopic = "SET_TOPIC",
SetLessonTitle = "SET_LESSON_TITLE",
TweakedKeyLearningPoint = "TWEAK_KEY_LEARNING_POINT",
TweakedKeyLearningPoints = "TWEAK_KEY_LEARNING_POINTS",
TweakedKeyword = "TWEAK_KEYWORD",
TweakedMisconception = "TWEAK_MISCONCEPTION",
TweakedKeyLearningPoints = "TWEAK_KEY_LEARNING_POINTS",
AddKeyword = "ADD_KEYWORD",
RemoveKeyword = "REMOVE_KEYWORD",
RemoveMisconception = "REMOVE_MISCONCEPTION",
AddMisconception = "ADD_MISCONCEPTION",
TweakedKeyLearningPoint = "TWEAK_KEY_LEARNING_POINT",
AddKeyLearningPoint = "ADD_KEY_LEARNING_POINT",
RemoveKeyLearningPoint = "REMOVE_KEY_LEARNING_POINT",
UpdateGenerationRateLimit = "UPDATE_GENERATION_RATE_LIMIT",
UpdatePartialLessonPlan = "UPDATE_PARTIAL_LESSON_PLAN",
GeneratedLessonPlan = "GENERATED_LESSON_PLAN",
RegeneratedKeyLearningPoints = "REGENERATED_KEY_LEARNING_POINTS",
RegeneratedMisconceptions = "REGENERATED_MISCONCEPTIONS",
RegeneratedKeywords = "REGENERATED_KEYWORDS",
ExtendedQuiz = "EXTENDED_QUIZ",
RestoreStarterQuizFromLocalStorage = "RESTORE_STARTER_QUIZ_FROM_LOCAL_STORAGE",
RestoreExitQuizFromLocalStorage = "RESTORE_EXIT_QUIZ_FROM_LOCAL_STORAGE",
RestoreSession = "RESTORE_SESSION",
RequestReset = "REQUEST_RESET",
EncounteredNonRecoverableError = "ENCOUNTERED_NON_RECOVERABLE_ERROR",
}

export type LessonPlannerAppAction =
Expand Down
96 changes: 90 additions & 6 deletions packages/aila/src/core/Aila.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Aila } from ".";
import { MockLLMService } from "../../tests/mocks/MockLLMService";
import { setupPolly } from "../../tests/mocks/setupPolly";
import { MockCategoriser } from "../features/categorisation/categorisers/MockCategoriser";
import { AilaAuthenticationError } from "./AilaError";
import { MockLLMService } from "./llm/MockLLMService";

describe("Aila", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -252,10 +253,9 @@ describe("Aila", () => {
value: newTitle,
},
};
const llmService = new MockLLMService(
`${JSON.stringify(mockedResponse)}␞\n`,
);

const chatLlmService = new MockLLMService([
JSON.stringify(mockedResponse),
]);
const ailaInstance = new Aila({
lessonPlan: {
title: "Roman Britain",
Expand All @@ -266,7 +266,6 @@ describe("Aila", () => {
chat: {
id: "123",
userId: "user123",
llmService,
},
options: {
usePersistence: false,
Expand All @@ -275,6 +274,9 @@ describe("Aila", () => {
useModeration: false,
},
plugins: [],
services: {
chatLlmService,
},
});

await ailaInstance.generateSync({
Expand All @@ -285,4 +287,86 @@ describe("Aila", () => {
expect(ailaInstance.lesson.plan.title).toBe(newTitle);
}, 20000);
});

describe("categorisation", () => {
it("should use the provided MockCategoriser", async () => {
const mockedLessonPlan = {
title: "Mocked Lesson Plan",
subject: "Mocked Subject",
keyStage: "key-stage-3",
};

const mockCategoriser = new MockCategoriser({ mockedLessonPlan });

const ailaInstance = new Aila({
lessonPlan: {},
chat: { id: "123", userId: "user123" },
options: {
usePersistence: false,
useRag: false,
useAnalytics: false,
useModeration: false,
},
services: {
chatLlmService: new MockLLMService(),
chatCategoriser: mockCategoriser,
},
plugins: [],
});

await ailaInstance.initialise();

expect(ailaInstance.lesson.plan.title).toBe("Mocked Lesson Plan");
expect(ailaInstance.lesson.plan.subject).toBe("Mocked Subject");
expect(ailaInstance.lesson.plan.keyStage).toBe("key-stage-3");
});
});

describe("categorisation and LLM service", () => {
it("should use both MockCategoriser and MockLLMService", async () => {
const mockedLessonPlan = {
title: "Mocked Lesson Plan",
subject: "Mocked Subject",
keyStage: "key-stage-3",
};

const mockCategoriser = new MockCategoriser({ mockedLessonPlan });

const mockLLMResponse = [
'{"type":"patch","reasoning":"Update title","value":{"op":"replace","path":"/title","value":"Updated Mocked Lesson Plan"}}␞\n',
'{"type":"patch","reasoning":"Update subject","value":{"op":"replace","path":"/subject","value":"Updated Mocked Subject"}}␞\n',
];
const mockLLMService = new MockLLMService(mockLLMResponse);

const ailaInstance = new Aila({
lessonPlan: {},
chat: { id: "123", userId: "user123" },
options: {
usePersistence: false,
useRag: false,
useAnalytics: false,
useModeration: false,
},
services: {
chatCategoriser: mockCategoriser,
chatLlmService: mockLLMService,
},
plugins: [],
});

await ailaInstance.initialise();

// Check if MockCategoriser was used
expect(ailaInstance.lesson.plan.title).toBe("Mocked Lesson Plan");
expect(ailaInstance.lesson.plan.subject).toBe("Mocked Subject");
expect(ailaInstance.lesson.plan.keyStage).toBe("key-stage-3");

// Use MockLLMService to generate a response
await ailaInstance.generateSync({ input: "Test input" });

// Check if MockLLMService updates were applied
expect(ailaInstance.lesson.plan.title).toBe("Updated Mocked Lesson Plan");
expect(ailaInstance.lesson.plan.subject).toBe("Updated Mocked Subject");
});
});
});
71 changes: 32 additions & 39 deletions packages/aila/src/core/Aila.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import {
DEFAULT_TEMPERATURE,
DEFAULT_RAG_LESSON_PLANS,
} from "../constants";
import { AilaCategorisation } from "../features/categorisation";
import {
AilaAnalyticsFeature,
AilaErrorReportingFeature,
AilaModerationFeature,
AilaPersistenceFeature,
AilaThreatDetectionFeature,
} from "../features/types";
import { fetchCategorisedInput } from "../utils/lessonPlan/fetchCategorisedInput";
import { AilaAuthenticationError, AilaGenerationError } from "./AilaError";
import { AilaFeatureFactory } from "./AilaFeatureFactory";
import {
Expand All @@ -23,6 +23,8 @@ import {
} from "./AilaServices";
import { AilaChat, Message } from "./chat";
import { AilaLesson } from "./lesson";
import { LLMService } from "./llm/LLMService";
import { OpenAIService } from "./llm/OpenAIService";
import { AilaPlugin } from "./plugins/types";
import {
AilaGenerateLessonPlanOptions,
Expand All @@ -37,25 +39,46 @@ export class Aila implements AilaServices {
private _errorReporter?: AilaErrorReportingFeature;
private _isShutdown: boolean = false;
private _lesson: AilaLessonService;
private _chatLlmService: LLMService;
private _moderation?: AilaModerationFeature;
private _options: AilaOptionsWithDefaultFallbackValues;
private _persistence: AilaPersistenceFeature[] = [];
private _threatDetection?: AilaThreatDetectionFeature;
private _prisma: PrismaClientWithAccelerate;
private _plugins: AilaPlugin[];
private _userId!: string | undefined;
private _chatId!: string;

constructor(options: AilaInitializationOptions) {
this._userId = options.chat.userId;
this._chatId = options.chat.id;
this._options = this.initialiseOptions(options.options);

this._chatLlmService =
options.services?.chatLlmService ??
new OpenAIService({ userId: this._userId, chatId: this._chatId });
this._chat = new AilaChat({
...options.chat,
aila: this,
promptBuilder: options.promptBuilder,
llmService: this._chatLlmService,
});

this._lesson = new AilaLesson({ lessonPlan: options.lessonPlan ?? {} });
this._prisma = options.prisma ?? globalPrisma;

this._lesson = new AilaLesson({
aila: this,
lessonPlan: options.lessonPlan ?? {},
categoriser:
options.services?.chatCategoriser ??
new AilaCategorisation({
aila: this,
prisma: this._prisma,
chatId: this._chatId,
userId: this._userId,
}),
});

this._analytics = AilaFeatureFactory.createAnalytics(this, this._options);
this._moderation = AilaFeatureFactory.createModeration(this, this._options);
this._persistence = AilaFeatureFactory.createPersistence(
Expand All @@ -81,7 +104,7 @@ export class Aila implements AilaServices {
// Initialization methods
public async initialise() {
this.checkUserIdPresentIfPersisting();
await this.setUpInitialLessonPlan();
await this._lesson.setUpInitialLessonPlan(this._chat.messages);
}

private initialiseOptions(options?: AilaOptions) {
Expand Down Expand Up @@ -128,11 +151,11 @@ export class Aila implements AilaServices {
}

public get chatId() {
return this._chat.id;
return this._chatId;
}

public get userId() {
return this._chat.userId;
return this._userId;
}

public get messages() {
Expand All @@ -159,6 +182,10 @@ export class Aila implements AilaServices {
return this._plugins;
}

public get chatLlmService() {
return this._chatLlmService;
}

// Check methods
public checkUserIdPresentIfPersisting() {
if (!this._chat.userId && this._options.usePersistence) {
Expand All @@ -168,40 +195,6 @@ export class Aila implements AilaServices {
}
}

// Setup methods

// #TODO this is in the wrong place and should be
// moved to be hook into the initialisation of the lesson
// or chat
public async setUpInitialLessonPlan() {
const shouldRequestInitialState = Boolean(
!this.lesson.plan.subject &&
!this.lesson.plan.keyStage &&
!this.lesson.plan.title,
);

if (shouldRequestInitialState) {
const { title, subject, keyStage, topic } = this.lesson.plan;
const input = this.chat.messages.map((i) => i.content).join("\n\n");
const categorisationInput = [title, subject, keyStage, topic, input]
.filter((i) => i)
.join(" ");

const result = await fetchCategorisedInput({
input: categorisationInput,
prisma: this._prisma,
chatMeta: {
userId: this._chat.userId,
chatId: this._chat.id,
},
});

if (result) {
this.lesson.initialise(result);
}
}
}

// Generation methods
public async generateSync(opts: AilaGenerateLessonPlanOptions) {
const stream = await this.generate(opts);
Expand Down
1 change: 1 addition & 0 deletions packages/aila/src/core/AilaServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface AilaLessonService {
readonly hasSetInitialState: boolean;
applyPatches(patches: string): void;
initialise(plan: LooseLessonPlan): void;
setUpInitialLessonPlan(messages: Message[]): Promise<void>;
}

export interface AilaChatService {
Expand Down
3 changes: 2 additions & 1 deletion packages/aila/src/core/chat/AilaChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export class AilaChat implements AilaChatService {
systemPrompt,
status: "PENDING",
});
await this._generation.setupPromptId();
this._chunks = [];
}

Expand All @@ -230,7 +231,7 @@ export class AilaChat implements AilaChatService {
if (status === "SUCCESS") {
const responseText = this.accumulatedText();
invariant(responseText, "Response text not set");
this._generation.complete({ status, responseText });
await this._generation.complete({ status, responseText });
}
this._generation.persist(status);
}
Expand Down
Loading

0 comments on commit 44c3860

Please sign in to comment.