Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop sending full defences on low levels #844

Merged
merged 43 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
7bedff7
defences undefined for levels 1 and 2
pmarsh-scottlogic Feb 23, 2024
7e86ae4
fixes tests
pmarsh-scottlogic Feb 23, 2024
b0a4823
propoerly undefines defences on level 1 and 2
pmarsh-scottlogic Feb 23, 2024
4e45d90
moves variable declarations to destructureing
pmarsh-scottlogic Feb 29, 2024
d92314c
missed one
pmarsh-scottlogic Feb 29, 2024
74d8fb4
undo error swallow
pmarsh-scottlogic Feb 29, 2024
1237cca
merge dev
pmarsh-scottlogic Feb 29, 2024
dc66ffb
remove bad type guards
pmarsh-scottlogic Feb 29, 2024
c442d04
improve validation for configureDefence
pmarsh-scottlogic Feb 29, 2024
aa8f672
last bit of validation
pmarsh-scottlogic Feb 29, 2024
aa0c576
update tests for handleConfigureDefence
pmarsh-scottlogic Feb 29, 2024
d6b6455
new folder for defence controller unit tests and wrote first handleD…
pmarsh-scottlogic Feb 29, 2024
294de45
adds test for defence activation
pmarsh-scottlogic Feb 29, 2024
4785161
adds tests for defence deactivation
pmarsh-scottlogic Feb 29, 2024
66cdd2e
moves tests all back into one file again
pmarsh-scottlogic Feb 29, 2024
e74a26f
adds tests for handleResetSingleDefence
pmarsh-scottlogic Feb 29, 2024
663b22e
improve validation on handleGetDefenceStatus
pmarsh-scottlogic Feb 29, 2024
8429bfd
removes extraneous stuff from test objects
pmarsh-scottlogic Feb 29, 2024
ebba951
adds tests to make sure defences aren't chngeable for level 1
pmarsh-scottlogic Feb 29, 2024
107e453
add loop to test levels 1 and 2 with same code
pmarsh-scottlogic Feb 29, 2024
373e0b7
adds remaining tests for defence controller integration
pmarsh-scottlogic Feb 29, 2024
8293a4a
handleResetSingleDefence takes level parameter now
pmarsh-scottlogic Feb 29, 2024
ae5e746
adds level to frontend call to reset defence config item. Types all t…
pmarsh-scottlogic Feb 29, 2024
a336617
some renamings and fill in gaps in tests
pmarsh-scottlogic Feb 29, 2024
23771d9
adds another missing test
pmarsh-scottlogic Feb 29, 2024
2aab5f7
tweaks
pmarsh-scottlogic Feb 29, 2024
a04ca87
merge dev
pmarsh-scottlogic Mar 5, 2024
24b7c04
fix test suite
pmarsh-scottlogic Mar 5, 2024
2fc5a6f
replace defence search with defences.some and replace cuurrentDefence…
pmarsh-scottlogic Mar 5, 2024
e907b04
change defenceId for generic falsy check
pmarsh-scottlogic Mar 5, 2024
f56383f
improve guards for level and config
pmarsh-scottlogic Mar 5, 2024
65b5644
improve guards for level and config
pmarsh-scottlogic Mar 5, 2024
b45cce9
correct test for configuring a config item
pmarsh-scottlogic Mar 5, 2024
d670f00
updates error message when not allowed to activate or modify defences
pmarsh-scottlogic Mar 5, 2024
095ad15
rescturecure defence controller integration tests
pmarsh-scottlogic Mar 5, 2024
0d02968
change other tests to use test.each rather than foreach
pmarsh-scottlogic Mar 5, 2024
9f9d628
uses isValidLevel
pmarsh-scottlogic Mar 5, 2024
dec9904
improve validateFilterConfig
pmarsh-scottlogic Mar 5, 2024
e98c251
just pass qa defence rather than all defences
pmarsh-scottlogic Mar 7, 2024
b0e17d0
fix tests
pmarsh-scottlogic Mar 7, 2024
360c30c
uses type intersection to define QaLlmDefence
pmarsh-scottlogic Mar 12, 2024
600dc65
Merge branch 'dev' into stop-sending-full-defences-on-low-leevels
pmarsh-scottlogic Mar 12, 2024
8ec48c7
only gedDefencesFrDTOs if it is defined
pmarsh-scottlogic Mar 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions backend/src/controller/chatController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
ChatInfoMessage,
chatInfoMessageTypes,
} from '@src/models/chatMessage';
import { Defence } from '@src/models/defence';
import { DEFENCE_ID, Defence, QaLlmDefence } from '@src/models/defence';
import { EmailInfo } from '@src/models/email';
import { LEVEL_NAMES } from '@src/models/level';
import { chatGptSendMessage } from '@src/openai';
Expand Down Expand Up @@ -95,8 +95,7 @@ async function handleChatWithoutDefenceDetection(
chatResponse: ChatHttpResponse,
currentLevel: LEVEL_NAMES,
chatModel: ChatModel,
chatHistory: ChatMessage[],
defences: Defence[]
chatHistory: ChatMessage[]
): Promise<LevelHandlerResponse> {
console.log(`User message: '${message}'`);

Expand All @@ -107,7 +106,6 @@ async function handleChatWithoutDefenceDetection(

const openAiReply = await chatGptSendMessage(
updatedChatHistory,
defences,
chatModel,
currentLevel
);
Expand Down Expand Up @@ -150,11 +148,15 @@ async function handleChatWithDefenceDetection(
}'`
);

const qaLlmDefence = defences.find(
(defence) => defence.id === DEFENCE_ID.QA_LLM
) as QaLlmDefence | undefined;

const openAiReplyPromise = chatGptSendMessage(
chatHistoryWithNewUserMessages,
defences,
chatModel,
currentLevel
currentLevel,
qaLlmDefence
);

// run input defence detection and chatGPT concurrently
Expand Down Expand Up @@ -258,8 +260,7 @@ async function handleChatToGPT(req: OpenAiChatRequest, res: Response) {
initChatResponse,
currentLevel,
chatModel,
currentChatHistory,
defences
currentChatHistory
);
} else {
levelResult = await handleChatWithDefenceDetection(
Expand Down
19 changes: 16 additions & 3 deletions backend/src/models/defence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,24 @@ type DefenceConfigItem = {
value: string;
};

type Defence = {
id: DEFENCE_ID;
type QaLlmDefence = {
id: DEFENCE_ID.QA_LLM;
config: DefenceConfigItem[];
isActive: boolean;
};
pmarsh-scottlogic marked this conversation as resolved.
Show resolved Hide resolved

type Defence =
| {
id: DEFENCE_ID;
config: DefenceConfigItem[];
isActive: boolean;
}
| QaLlmDefence;
pmarsh-scottlogic marked this conversation as resolved.
Show resolved Hide resolved

export { DEFENCE_ID };
export type { Defence, DefenceConfigItem, DEFENCE_CONFIG_ITEM_ID };
export type {
Defence,
DefenceConfigItem,
DEFENCE_CONFIG_ITEM_ID,
QaLlmDefence,
};
40 changes: 20 additions & 20 deletions backend/src/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
ChatCompletionMessageToolCall,
} from 'openai/resources/chat/completions';

import { isDefenceActive, getQAPromptFromConfig } from './defence';
import { getQAPromptFromConfig } from './defence';
import { sendEmail } from './email';
import { queryDocuments } from './langchain';
import {
Expand All @@ -17,7 +17,7 @@ import {
ToolCallResponse,
} from './models/chat';
import { ChatMessage } from './models/chatMessage';
import { DEFENCE_ID, Defence } from './models/defence';
import { QaLlmDefence } from './models/defence';
import { EmailResponse } from './models/email';
import { LEVEL_NAMES } from './models/level';
import {
Expand Down Expand Up @@ -141,14 +141,14 @@ function isChatGptFunction(functionName: string) {
async function handleAskQuestionFunction(
functionCallArgs: string | undefined,
currentLevel: LEVEL_NAMES,
defences: Defence[]
qaLlmDefence?: QaLlmDefence
) {
if (functionCallArgs) {
const params = JSON.parse(functionCallArgs) as FunctionAskQuestionParams;
console.debug(`Asking question: ${params.question}`);
// if asking a question, call the queryDocuments
const configQAPrompt = isDefenceActive(DEFENCE_ID.QA_LLM, defences)
? getQAPromptFromConfig(defences)
const configQAPrompt = qaLlmDefence?.isActive
? getQAPromptFromConfig([qaLlmDefence])
pmarsh-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
: '';
return await queryDocuments(params.question, configQAPrompt, currentLevel);
} else {
Expand Down Expand Up @@ -191,11 +191,11 @@ function handleSendEmailFunction(
}

async function chatGptCallFunction(
defences: Defence[],
toolCallId: string,
functionCall: ChatCompletionMessageToolCall.Function,
// default to sandbox
currentLevel: LEVEL_NAMES = LEVEL_NAMES.SANDBOX
currentLevel: LEVEL_NAMES = LEVEL_NAMES.SANDBOX,
qaLlmDefence?: QaLlmDefence
): Promise<FunctionCallResponse> {
const functionName = functionCall.name;
let functionReply = '';
Expand All @@ -220,7 +220,7 @@ async function chatGptCallFunction(
functionReply = await handleAskQuestionFunction(
functionCall.arguments,
currentLevel,
defences
qaLlmDefence
);
}
} else {
Expand Down Expand Up @@ -326,18 +326,18 @@ function getChatCompletionsInContextWindow(
async function performToolCalls(
toolCalls: ChatCompletionMessageToolCall[],
chatHistory: ChatMessage[],
defences: Defence[],
currentLevel: LEVEL_NAMES
currentLevel: LEVEL_NAMES,
qaLlmDefence?: QaLlmDefence
): Promise<ToolCallResponse> {
for (const toolCall of toolCalls) {
// only tool type supported by openai is function
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (toolCall.type === 'function') {
const functionCallReply = await chatGptCallFunction(
defences,
toolCall.id,
toolCall.function,
currentLevel
currentLevel,
qaLlmDefence
);

// We assume only one function call in toolCalls, and so we return after getting function reply.
Expand All @@ -358,9 +358,9 @@ async function performToolCalls(

async function getFinalReplyAfterAllToolCalls(
chatHistory: ChatMessage[],
defences: Defence[],
chatModel: ChatModel,
currentLevel: LEVEL_NAMES
currentLevel: LEVEL_NAMES,
qaLlmDefence?: QaLlmDefence
) {
let updatedChatHistory = [...chatHistory];
const sentEmails = [];
Expand All @@ -385,8 +385,8 @@ async function getFinalReplyAfterAllToolCalls(
const toolCallReply = await performToolCalls(
gptReply.completion.tool_calls,
updatedChatHistory,
defences,
currentLevel
currentLevel,
qaLlmDefence
);

updatedChatHistory = toolCallReply.chatHistory;
Expand All @@ -408,17 +408,17 @@ async function getFinalReplyAfterAllToolCalls(

async function chatGptSendMessage(
chatHistory: ChatMessage[],
defences: Defence[],
chatModel: ChatModel,
currentLevel: LEVEL_NAMES = LEVEL_NAMES.SANDBOX
currentLevel: LEVEL_NAMES = LEVEL_NAMES.SANDBOX,
qaLlmDefence?: QaLlmDefence
) {
// this method just calls getFinalReplyAfterAllToolCalls then reformats the output. Does it need to exist?

const finalToolCallResponse = await getFinalReplyAfterAllToolCalls(
chatHistory,
defences,
chatModel,
currentLevel
currentLevel,
qaLlmDefence
);

const chatResponse: ChatResponse = {
Expand Down
9 changes: 1 addition & 8 deletions backend/test/integration/openai.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { expect, jest, test, describe } from '@jest/globals';

import { defaultDefences } from '@src/defaultDefences';
import { CHAT_MODELS, ChatModel } from '@src/models/chat';
import { ChatMessage } from '@src/models/chatMessage';
import { Defence } from '@src/models/defence';
import { chatGptSendMessage } from '@src/openai';

const mockCreateChatCompletion =
Expand Down Expand Up @@ -55,7 +53,6 @@ describe('OpenAI Integration Tests', () => {
},
},
];
const defences: Defence[] = defaultDefences;
const chatModel: ChatModel = {
id: CHAT_MODELS.GPT_4,
configuration: {
Expand All @@ -68,11 +65,7 @@ describe('OpenAI Integration Tests', () => {

mockCreateChatCompletion.mockResolvedValueOnce(chatResponseAssistant('Hi'));

const reply = await chatGptSendMessage(
chatHistoryWithMessage,
defences,
chatModel
);
const reply = await chatGptSendMessage(chatHistoryWithMessage, chatModel);

expect(reply).toBeDefined();
expect(reply.chatResponse.completion).toBeDefined();
Expand Down
31 changes: 23 additions & 8 deletions backend/test/unit/controller/chatController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
MessageTransformation,
} from '@src/models/chat';
import { ChatMessage } from '@src/models/chatMessage';
import { DEFENCE_ID, Defence } from '@src/models/defence';
import { DEFENCE_ID, Defence, QaLlmDefence } from '@src/models/defence';
import { EmailInfo } from '@src/models/email';
import { LEVEL_NAMES, LevelState } from '@src/models/level';
import { chatGptSendMessage } from '@src/openai';
Expand Down Expand Up @@ -497,7 +497,6 @@ describe('handleChatToGPT unit tests', () => {

expect(mockChatGptSendMessage).toHaveBeenCalledWith(
[...existingHistory, newUserChatMessage],
[],
mockChatModel,
LEVEL_NAMES.LEVEL_1
);
Expand Down Expand Up @@ -558,10 +557,18 @@ describe('handleChatToGPT unit tests', () => {
},
} as ChatMessage;

const qaLllmDefence = {
id: DEFENCE_ID.QA_LLM,
isActive: true,
config: [{ id: 'PROMPT', value: 'query them documents!' }],
} as QaLlmDefence;

const req = openAiChatRequestMock(
'send an email to [email protected] saying hi',
LEVEL_NAMES.SANDBOX,
existingHistory
existingHistory,
[],
[qaLllmDefence]
);
const res = responseMock();

Expand Down Expand Up @@ -590,9 +597,9 @@ describe('handleChatToGPT unit tests', () => {

expect(mockChatGptSendMessage).toHaveBeenCalledWith(
[...existingHistory, newUserChatMessage],
[],
mockChatModel,
LEVEL_NAMES.SANDBOX
LEVEL_NAMES.SANDBOX,
qaLllmDefence
);

expect(res.send).toHaveBeenCalledWith({
Expand Down Expand Up @@ -655,10 +662,18 @@ describe('handleChatToGPT unit tests', () => {
},
} as ChatMessage;

const qaLllmDefence = {
id: DEFENCE_ID.QA_LLM,
isActive: true,
config: [{ id: 'PROMPT', value: 'query them documents!' }],
} as QaLlmDefence;

const req = openAiChatRequestMock(
'hello bot',
LEVEL_NAMES.SANDBOX,
existingHistory
existingHistory,
[],
[qaLllmDefence]
);
const res = responseMock();

Expand Down Expand Up @@ -690,9 +705,9 @@ describe('handleChatToGPT unit tests', () => {

expect(mockChatGptSendMessage).toHaveBeenCalledWith(
[...existingHistory, ...newTransformationChatMessages],
[],
mockChatModel,
LEVEL_NAMES.SANDBOX
LEVEL_NAMES.SANDBOX,
qaLllmDefence
);

expect(res.send).toHaveBeenCalledWith({
Expand Down
Loading