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

705 refactor handlechattogpt to remove mutation #743

Merged
merged 33 commits into from
Jan 23, 2024

Conversation

heatherlogan-scottlogic
Copy link
Contributor

@heatherlogan-scottlogic heatherlogan-scottlogic commented Jan 5, 2024

Description

Refactor of functions inside chatController.ts and openAI.ts to reduce mutation.
ChatResponse, chatHistroy, sentEmails, should not be mutated across different functions - instead changes should be returned by the using functions either as

Notes

openai.ts

  • chatGptCallFunction()

    • Before - mutates sentEmails, declares then mutates reply: ChatCompletionMessageParam
    • Changed - Remove init declaration for reply and construct at the end.
      Split up function calls into handleSendEmailFunction and handleAskQuestionFunction.
      Create a copy of sentEmails and updates this.
      Returns chat completion (with tool call ids), wonLevel and updated sentEmails obj
  • performToolCalls()

    • Before - mutates chatResponse (sets wonLevel and defenceReport), calls pushCompletionToHistory()
    • Changed - Removed chatResponse from input as unused. Return wonLevel and defenceReport instead of setting chatResponse parameters. Clones history and updates tht If no toolCalls then returns original chatResponse.
  • getFinalReplyAfterToolCalls()

    • Before - Created blank chatRespond and mutated, calls pushCompletionToHistory and performToolCalls (which previously mutated wonLevel/sentEmails/chatHistory)
    • Changed - Removed blank chatResponse at start (as not needed in chatGptChatCompletion).
      Clones to create sentEmails and updatedChatHistory.
      Get reply from chatGptChatCompletion, call performToolCalls if required.
      Return reply(completion), wonLevel, defenceReport, chatHistory, sentEmails.
  • pushCompletionToHistory()

    • Before - mutates chatHisttory
    • Changed - Clones chatHistory and returns updated version
  • chatGptChatCompletion()

    • Before - mutates chatHistory and chatResponse (sets openAIErrorMessage)
    • Changed - eemove chatResponse input parameter.
      Clone chatHistory to apply changes to that depending on system role.
      Returns the completion, updated chat history and error message (possibly null)
  • chatGptSendMessage()

    • Before - mutates chatHistory and chatResponse (sets openAIErrorMessage)
    • Changed - Remove initial blank declaration of ChatResponse that will be mutated
      Clones chatHistory and edits that.
      Calls getFinalReplyAfterAllToolCalls which returns new chatResponse, chatHistory, sentEmails

chatController.ts

  • handleLowLevelChat()

    • Before - mutates chatResponse
    • Changed - instead returns chatResponse, chatHistory, sentEmails that have been copied/updated by funcs. in openai.ts
  • handleHigherLevelChat()

    • Before - mutates chatReponse (lots), mutates req.session.levelState[currentLevel].chatHistory in several places, calls chatGptSendMessage()
    • Changed - Clones the chatReponse and chatHistory objects and applies updates to these, returns chatResponse, chatHistory, sentEmails
  • handleChatError()

    • Before - mutates chatResponse, res
    • Changed - Creates updatedChatResponse with blocked/error info and res.sends that. (concern – should we be doing res.send() in chatController?
  • handleErrorGettingReply()

    • Before - handleErrorGettingReply() calls handleChatError(), mutates req (adds error to history)
    • Changed - Change to addErrorToChatHistory to prevent req being mutated in this and handleChatError. Instead returns updated chatHistory, then we can call handleChatError after in the outer function.
  • handleChatToGPT()

    • Before - mutates chatResponse
    • Changed - creates an initial blank chat repsonse obj to use in error handing. then creates an updated obj with updated values from the handleXlevelChat function. updates the state in res.levelState for chatHistory and sentEmails with the final updated values (defences in levelState shouldn't be altered by handleChatToGPT(). sends the updated response

Concerns

Checklist

Have you done the following?

  • Linked the relevant Issue
  • Added tests
  • Ensured the workflow steps are passing

@heatherlogan-scottlogic heatherlogan-scottlogic linked an issue Jan 5, 2024 that may be closed by this pull request
Copy link
Member

@chriswilty chriswilty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thorough work, good job 👍

One or two concerns about possible changed behaviour, but i think we can streamline the return values a bit, as discussed on Teams.

@@ -19,14 +18,19 @@ function handleChatError(
statusCode = 500
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, this function is only ever called with blocked=true. Do you foresee cases where blocked would be false?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm true i think at one point we did use distinguish between blocked messages and error messages for styling, but i don't think we need this defence information here now.

Copy link
Member

@chriswilty chriswilty Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pmarsh-scottlogic Now that blocked is always true, Heather has removed code that sets some values on chatResponse.defenceReport. If the UI needs the defenceReport for this kind of error, then we should set those values, otherwise we might as well set chatResponse.defenceReport to null to save on payload size.

Can you trace that in the UI code and see if we need the defence report or not?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose you mean isError is always true, when we've sent the response via handleChatError.

Anyway, luckily not much tracing to do, all you need to do is look at processChatResponse in ChatBox.tsx. Yes, when isError is true, the defenceReport is irrelevant. But the defenceReport is relevant if isError is false. And in the frontend, we don't know ahead of time whether sending a message will cause an error or not, therefore we still need isError and defenceReport in ChatHttpResponse.

Copy link
Contributor

@pmarsh-scottlogic pmarsh-scottlogic Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just checking the removed code now. Ah yes, there's no place in the backend code where handleChatError would be called when the message is also blocked. This is a good semantic improvement!

If blocked, we simply return the chatResponse as usual, with the full defenceReport, and the frontend knows to show the "block message" rather than the bot reply. Same case if blocked and there's an error (empty reply from bot, or presence of OpenAIErrorMessage): It will return the chatResponse without at error code, which is good - as far as the user is concerned, if the message was blocked by a defence then the bot never provided a reply (at least not one that they were shown)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we could send null / undefined for defenceReport when we have a chat error? If so, then defenceReport in ChatHttpResponse would need to be optional, which is maybe not great for type-safety. In that case, maybe we could split the response into two types: ChatHttpResponse and ChatHttpErrorResponse.

That might be one to think about for future improvements though!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the defenceReport in the response is necessarily not null/undefined! It just might be "empty", that is it might have a null blockedReason, or an empty triggeredDefences list. Which means the response is bigger than it needs to be when defences are not active, but It's fine for now

backend/src/controller/handleError.ts Show resolved Hide resolved
backend/src/openai.ts Outdated Show resolved Hide resolved
backend/src/openai.ts Outdated Show resolved Hide resolved
backend/src/openai.ts Outdated Show resolved Hide resolved
backend/src/openai.ts Outdated Show resolved Hide resolved
backend/src/openai.ts Outdated Show resolved Hide resolved
backend/src/openai.ts Outdated Show resolved Hide resolved
backend/test/integration/openai.test.ts Show resolved Hide resolved
@chriswilty
Copy link
Member

chriswilty commented Jan 11, 2024

One thing that goes hand-in-hand with immutability is avoiding let declarations wherever possible. Sometimes it doesn't seem possible, if we have conditional statements that might or might not overwrite that value, but such cases are often a sign that the function needs to be split. In any case, this is a strong start, and the rest we can tackle as we go.

@heatherlogan-scottlogic heatherlogan-scottlogic force-pushed the 705-refactor-handlechattogpt-to-remove-mutation branch from 45d5eca to df7ae2c Compare January 11, 2024 10:42
@pmarsh-scottlogic
Copy link
Contributor

pmarsh-scottlogic commented Jan 12, 2024

Really appreciate the thorough PR description ⭐

Copy link
Contributor

@pmarsh-scottlogic pmarsh-scottlogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cracking job, love to see it. Must've been a nightmare to keep all the moving parts straight in your head!

Some comments from me, all minor

backend/src/controller/chatController.ts Outdated Show resolved Hide resolved
backend/src/controller/chatController.ts Outdated Show resolved Hide resolved
backend/src/controller/chatController.ts Show resolved Hide resolved
backend/src/controller/chatController.ts Show resolved Hide resolved
backend/src/controller/chatController.ts Outdated Show resolved Hide resolved
backend/src/openai.ts Outdated Show resolved Hide resolved
backend/src/openai.ts Show resolved Hide resolved
backend/src/openai.ts Outdated Show resolved Hide resolved
backend/src/openai.ts Show resolved Hide resolved
backend/src/openai.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@pmarsh-scottlogic pmarsh-scottlogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a few more notes, which I'll take on

backend/src/models/chat.ts Outdated Show resolved Hide resolved
backend/src/openai.ts Show resolved Hide resolved
backend/src/openai.ts Show resolved Hide resolved
pmarsh-scottlogic and others added 4 commits January 15, 2024 09:46
* Renamed method to be clear what defences are being checked

* Moved detection of output defences

* Using await rather than then

* Clearer use of the input defence report

* WIP: openai file doesn't know about the defence report

* WIP: Using new pushMessageToHistory method

* Fixed chat history

* Simpler combining of defence reports

* Consistent blocking rules

* Not mutating chatResponse in the performToolCalls method

* Better loop

* Not mutating chatResponse in the chatGptChatCompletion method

* Simplified return

* Method to add the user messages to chat history

* Better output defence report

* Moved combineChatDefenceReports to chat controller

* No longer exporting getFilterList and detectFilterList

* Fixed test build errors

* detectTriggeredOutputDefences unit tests

* Fixed chat controller tests

* Removed output filtering integration tests

This code is now covered by the unit tests

* Moved utils method to new file

* Fixed remaining tests

* pushMessageToHistory unit tests

* WIP: Now using the updated chat response

* WIP: Fixed chat utils tests

* WIP: Fixed remaining tests

* Fix for response not being set properly

* No longer adding transformed messae twice

* Nicer chat while loop

* Only sending back sent emails, not total emails

* Fixed tests

* Using flatMap

* const updatedChatHistory in low level chat

* Constructing chat response at the end of high level chat

Like what is done in low level chat

* Removed wrong comment

* Fixed tests

* Better function name

* Better promise name

* Not setting sent emails if the message was blocked

* refactor chathistory code to reduce mutation

* change test names and add comment

* adds history check to first test

* added second history check

* removed some comments

* correct some tests in integration/chatController.test

* adds unit test for chatController to make sure history is updated properly

* fixes defence trigger tests that were broken by mocks

* refactors reused mocking code

* added unit test to check history update in sandbox

* update first test to include existing history

* makes second test use existing history

* adds comment that points out some weirdness

* polishes off those tests

* fixes weirdness about combining the empty defence report

* fixes problem of not getting updated chat history

* respond to chris - makes chatHistoryWithNewUsermessages more concise

* respond to chris - adds back useful comment

* simplify transformed message ternary expression

* refactors transformMessage and only calls combineTransformedMessage once

---------

Co-authored-by: Peter Marsh <[email protected]>
@pmarsh-scottlogic
Copy link
Contributor

pmarsh-scottlogic commented Jan 18, 2024

Review notes for myself

  • combineChatDefenceReports added to chatController. Did it exist before and is it doing the same thing?
  • createNewUserMessages added to chatController. Did it exist before and is it doing the same thing?

What happened before when user sends a message on a low level, say level 1?

  • take original history and user message
  • add user message to history (type depends on whether it was transformed)
  • getFinalReply
    • mutate history to set/unset system role
    • get reply
    • while tool calls
      • mutate history to add reply with type function call
      • mutate chatResponse with new wonLevel value and new defenceReport value
      • if email function call
        • generate sent emails and mutate the emails session variable to add new emails
      • mutate history to add function call completion
  • if problems [no reply content or openAiErrorMessage]
    • don't mutate chatResponse further
    • add error message to history
    • send error response
  • if no problems
    • add bot response to history (type depends on whether it was blocked by output defence)
    • mutate chatreponse reply, wonLevel and openAIErrorMessage
    • update sentEmails in response and send httpResponse

What happened before when user sends a message on a high level, say sandbox?

  • take original history and user message
  • transform user message according to defences
  • if transformed, mutate history to add original message [user type] and continue onwards with transformed message
  • detect input defences, and mutate chatResponse accordingly.
  • mutate chat history to add (possibly transformed) message. type depends on whether it was transformed or not
  • getFinalReply
    • mutate history to set/unset system role
    • get reply
    • while tool calls
      • mutate history to add reply with type function call
      • mutate chatResponse with new wonLevel value and new defenceReport value
      • if email function call
        • generate sent emails and mutate the emails session variable to add new emails
      • mutate history to add function call completion
  • if problems [no reply content or openAiErrorMessage]
    • don't mutate chatResponse further
    • mutate chat history to add error message
    • send error response
  • if no problems
    • run output defences
    • add bot response to history (type depends on whether it was blocked by output defence)
    • mutate chatreponse reply, wonLevel and openAIErrorMessage
    • if blocked by input defences
      • undo all mutations to chatHistory (so that it is as it was before any of above processing).
      • mutate chatHistory to add user message as info message rather than completion.
    • else (not blocked, or blocked by output defences)
      • update chatResponse's wonLevel, reply, defenceReport,isBlocked, defenceReport.blockedReason, openAIErrorMessage based on output of chatGptSendMessage(...)
      • update chatReponse's triggeredDefences (combine input and output report)
    • if blocked by either input or output defence
      • mutate history to add block message
      • updateEmails and send response
    • else
      • update emails and send response

What happens now when user sends a message on a low level, say level 1?

  • add user message to history
  • get finalReply
    • while tool calls
      • update history according system role
      • update history to add completion as function call type.
      • if email function call
      • send email and get reply. see if email won level. Update sent emails
      • update history to add functioncall reply as completion
      • update wonLevel to true according to latest function call
  • continue with updated sentEmails, completion, wonLevel, openAIErrorMessage. If problem [no reply content or openAiErrorMessage] then revert chatHistory
  • mutate emails to add freshly sent ones
  • if problems [no reply content or openAiErrorMessage]
    • mutate chat history to add error message then return error response
  • else no problems
    • add bot message to history
    • mutate state according to updated history and emails
    • send response

What happens now when user sends a message on a high level, say sandbox?

  • update history to include message and transformed message if there is one
  • detect input defences
  • get finalReply
    • while tool calls
      • update history according system role
      • update history to add completion as function call type.
      • if email function call
      • send email and get reply. see if email won level. Update sent emails
      • update history to add functioncall reply as completion
      • update wonLevel to true according to latest function call
  • detect output defences
  • if blocked restore original history and add user message as infoMessage, not completion
  • if blocked
    • update history to add blocked message
    • mutate state according to updated history and emails
    • send response
  • if problems [no reply content or openAiErrorMessage]
    • mutate chat history to add error message then return error response
  • else not blocked and no problems
    • update history to add bot reply
    • mutate state according to updated history and emails
    • send response

) {
applyOutputFilterDefence(reply.content, defences, chatResponse);
if (!chatResponse.completion?.content || chatResponse.openAIErrorMessage) {
return { chatResponse, chatHistory, sentEmails };
Copy link
Contributor

@pmarsh-scottlogic pmarsh-scottlogic Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a regression. Though is it problematic?
Before when we'd encounter empty chat response content or an openAIErrorMessage, we'd return early, but we wouldn't revert the chatHistory. Now we return the original chatHistory, therefore not including any messages added during getFinalReplyAfterAllToolCalls (namely, the function call messages: response from sendEmail and queryDocuments)

Copy link
Contributor

@pmarsh-scottlogic pmarsh-scottlogic Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reason about this let's ask: Why is it important to include the function call in the chat history? Does it change the behaviour of the program?

Yes. If we have sent an email, then if it's in the history the bot will have the context that an email has sent, which might alter a future response. More consequentially, if we have queried the QA LLM, it might have come back with an answer containing information from the documents. If that is kept in the history, then that knowledge will now available to the bot through memory, rather than having to query again.

Now let's consider what the user expects. Suppose I'd sent a message and it encountered an openAI error, or was blocked (whether by input defence or output defence).

  • If an email was sent and I know about it (because it's shown in the UI), then I expect the bot to know that it has sent an email, therefore we want that function call message in the chat history.
  • The converse is also true: If an email is sent but it is not shown on the ui, then we don't want the function call in the history.
  • Now, if the bot has queried the documents, I won't know about it because in the chat all I've seen is a blocked or error message. Therefore probably I don't want the function call in the history.

So, bearing the above in mind, what do we have now, and do we like it?

  • what happens if we send an email and the message is blocked. Do we see the email in the ui? Is the function call in the history?
    • We don't see the email in the UI, nor do we see the function call in the history ✅
  • what happens if we send an email and we get an openAI error. Do we see the email in the ui? Is the function call in the history?
  • what happens if we query the document and we get blocked. Is the function call in the history?
    • Nope, the function call is not in the chat history ✅
  • what happens if we query the document and we get an openAI error. Is the function call in the history?
    • Nope, the function call is not in the chat history ✅

So things work as I'd expect them to, apart from error when sending an email, but worst case is the user would be slightly confused: "Oh, it thinks it didn't send an email, but I think it did. Silly bot.". Therefore I don't think we should worry about this

Copy link
Member

@chriswilty chriswilty Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think it would be safer to revert to the previous behaviour. I can't decide whether it's a good or a bad thing, maybe we need to simulate it and see what we get in the UI?

Edit - our comments crossed here, so I didn't see all your marvellous detailed explanations above!

Copy link
Contributor

@pmarsh-scottlogic pmarsh-scottlogic Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. after testing I'd argue neither of the behaviour is great.

Dev branch: error thrown when sending an email

image

[
  {
    completion: {
      role: 'system',
      content: 'Your role is to assist the user with work-related tasks.\n' +
        '  Your name is ScottBrewBot and you are employed by the drinks company Sc
        '  You can retrieve information from a document store about the company an
        `  If the user asks a question that you don't know and is not in the docum
        '  You are able to send emails.\n' +
        '  Before sending an email, show the user a draft of the email (including 
        '  Do not send the email until the user has explicitly confirmed that the 
    },
    chatMessageType: 8
  },
  {
    completion: {
      role: 'user',
      content: 'email [email protected] with subject "hello" and body "howdy, partne
    },
    chatMessageType: 3
  },
  {
    completion: { role: 'assistant', content: null, tool_calls: [Array] },
    chatMessageType: 9
  },
  {
    completion: {
      role: 'tool',
      content: 'Email not sent as not confirmed with user',
      tool_call_id: 'call_t3qF6WIa377SuYBk6ro54AVn'
    },
    chatMessageType: 9
  },
  {
    completion: {
      role: 'assistant',
      content: "Sure! Here's a draft of the email:\n" +
        '\n' +
        'To: [email protected]\n' +
        'Subject: hello\n' +
        '\n' +
        '\n' +
        'Please review the draft and let me know if it looks good to you.'
    },
    chatMessageType: 0
  },
  { completion: { role: 'user', content: 'yes' }, chatMessageType: 3 },
  {
    completion: { role: 'assistant', content: null, tool_calls: [Array] },
    chatMessageType: 9
  },
  {
    completion: {
      role: 'tool',
      content: 'Email sent to [email protected] with subject hello and body howdy, partner!',    
      tool_call_id: 'call_iIfHyb1r0AS54O1Sm1ayDKal'
    },
    chatMessageType: 9
  },
  {
    completion: null,
    chatMessageType: 10,
    infoMessage: 'Failed to get ChatGPT reply.'
  },
  {
    completion: { role: 'user', content: 'did you send that email?' },
    chatMessageType: 3
  },
  {
    completion: {
      role: 'assistant',
      content: 'Yes, I have sent the email to [email protected] with the subject "hello" and the 
body "howdy, partner!".'
    },
    chatMessageType: 0
  }
]

This branch: error thrown when sending an email

image

chatHistory:  [
  {
    completion: {
      role: 'system',
      content: 'Your role is to assist the user with work-related tasks.\n' +
        '  Your name is ScottBrewBot and you are employed by the drinks company ScottBrew Ltd. \n' +
        '  You can retrieve information from a document store about the company and product.\n' +
        `  If the user asks a question that you don't know and is not in the documents, you can say "I don't know".\n` +
        '  You are able to send emails.\n' +
        '  Before sending an email, show the user a draft of the email (including recipient, subject, and body), and ask them to confirm it is correct.\n' +
        '  Do not send the email until the user has explicitly confirmed that the draft is correct.'
    },
    chatMessageType: 8
  },
  {
    completion: {
      role: 'user',
      content: 'email [email protected] with subject "hey" and body "how are you?"'
    },
    chatMessageType: 3
  },
  {
    completion: { role: 'assistant', content: null, tool_calls: [Array] },
    chatMessageType: 9
  },
  {
    completion: {
      role: 'tool',
      content: 'Email not sent as not confirmed with user',
      tool_call_id: 'call_Fy0YX5ruo99Z0CAqchFRaUhE'
    },
    chatMessageType: 9
  },
  {
    completion: {
      role: 'assistant',
      content: "Sure, I can help you draft an email. Here's a draft of the email:\n" +
        '\n' +
        'Recipient: [email protected]\n' +
        'Subject: hey\n' +
        'Body: how are you?\n' +
        '\n' +
        'Please review the draft and let me know if it looks good to you.'
    },
    chatMessageType: 0
  },
  { completion: { role: 'user', content: 'yes' }, chatMessageType: 3 },
  {
    completion: null,
    chatMessageType: 10,
    infoMessage: 'Failed to get ChatGPT reply.'
  }
]

TL;DR

We ask for an email to be sent. We confirm yes, but gpt fails to give us a reply

Copy link
Contributor

@pmarsh-scottlogic pmarsh-scottlogic Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my vote is that if we encounter an error while confirming an email, we don't send the email. That means it doesn't show up in the ui, and also the function call doesn't appear in the history. The processing would so much simpler that way. We can remove a bunch of the trickly logic that facilitates this.

It would, however, not allow the user to win if they encounter an error while confirming a winning email, which undoes the work done for #467

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep that seems to make sense to me. The user just needs to try again.

The obvious alternative is #467, where we send the email, it shows up in the UI and chat history, and we win the level. That is a tad confusing for the user cos they've seen an error message. So I agree, best to just reset everything for that attempt and ask the user to try again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely. I've made ticket #797 capture that

Copy link
Member

@chriswilty chriswilty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like you @pmarsh-scottlogic, I have a couple of concerns, plus a handful of very minor things.

@@ -19,14 +18,19 @@ function handleChatError(
statusCode = 500
Copy link
Member

@chriswilty chriswilty Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pmarsh-scottlogic Now that blocked is always true, Heather has removed code that sets some values on chatResponse.defenceReport. If the UI needs the defenceReport for this kind of error, then we should set those values, otherwise we might as well set chatResponse.defenceReport to null to save on payload size.

Can you trace that in the UI code and see if we need the defence report or not?

backend/src/controller/chatController.ts Outdated Show resolved Hide resolved
backend/src/controller/chatController.ts Outdated Show resolved Hide resolved
backend/src/controller/chatController.ts Outdated Show resolved Hide resolved
backend/src/controller/chatController.ts Outdated Show resolved Hide resolved
backend/src/openai.ts Show resolved Hide resolved
backend/src/openai.ts Show resolved Hide resolved
backend/src/openai.ts Show resolved Hide resolved
backend/src/openai.ts Outdated Show resolved Hide resolved
) {
applyOutputFilterDefence(reply.content, defences, chatResponse);
if (!chatResponse.completion?.content || chatResponse.openAIErrorMessage) {
return { chatResponse, chatHistory, sentEmails };
Copy link
Member

@chriswilty chriswilty Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think it would be safer to revert to the previous behaviour. I can't decide whether it's a good or a bad thing, maybe we need to simulate it and see what we get in the UI?

Edit - our comments crossed here, so I didn't see all your marvellous detailed explanations above!

@chriswilty
Copy link
Member

@pmarsh-scottlogic I'm very happy with this now, good work. The linter rule is small beans, remove it if you wish - doesn't stop me approving.

Copy link
Member

@chriswilty chriswilty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌 Awesome work!

@pmarsh-scottlogic pmarsh-scottlogic merged commit b10a251 into dev Jan 23, 2024
2 checks passed
@pmarsh-scottlogic pmarsh-scottlogic deleted the 705-refactor-handlechattogpt-to-remove-mutation branch January 23, 2024 14:00
chriswilty pushed a commit that referenced this pull request Apr 8, 2024
* refactor chatGptCallFunction

* pass history/email vars through openAI functions

* remove chatResponse from tool call functions

* start refactor on chatGptSendMessage

* begin to remove mutations from controller functions

* merge dev

* dont return defences from chatGptSendMessage

* update tests

* rebase frontend changes & tidy up

* fix alerted defences showing

* fix user message added to history when transformed

* set chatHistory to gptreply.chatHistory in getFinalReplyAfterAllToolCalls (to be safe)

* Address some PR comments

* remove blocked and defence report from handleChatError as it is not used

* save chat history to session on error

* address PR comments

* remove defenceReport from ChatResponse returned by openai

* removes defenceReport from LevelHandlerResponse interface

* 708 move logic for detecting output defence bot filtering (#740)

* Renamed method to be clear what defences are being checked

* Moved detection of output defences

* Using await rather than then

* Clearer use of the input defence report

* WIP: openai file doesn't know about the defence report

* WIP: Using new pushMessageToHistory method

* Fixed chat history

* Simpler combining of defence reports

* Consistent blocking rules

* Not mutating chatResponse in the performToolCalls method

* Better loop

* Not mutating chatResponse in the chatGptChatCompletion method

* Simplified return

* Method to add the user messages to chat history

* Better output defence report

* Moved combineChatDefenceReports to chat controller

* No longer exporting getFilterList and detectFilterList

* Fixed test build errors

* detectTriggeredOutputDefences unit tests

* Fixed chat controller tests

* Removed output filtering integration tests

This code is now covered by the unit tests

* Moved utils method to new file

* Fixed remaining tests

* pushMessageToHistory unit tests

* WIP: Now using the updated chat response

* WIP: Fixed chat utils tests

* WIP: Fixed remaining tests

* Fix for response not being set properly

* No longer adding transformed messae twice

* Nicer chat while loop

* Only sending back sent emails, not total emails

* Fixed tests

* Using flatMap

* const updatedChatHistory in low level chat

* Constructing chat response at the end of high level chat

Like what is done in low level chat

* Removed wrong comment

* Fixed tests

* Better function name

* Better promise name

* Not setting sent emails if the message was blocked

* refactor chathistory code to reduce mutation

* change test names and add comment

* adds history check to first test

* added second history check

* removed some comments

* correct some tests in integration/chatController.test

* adds unit test for chatController to make sure history is updated properly

* fixes defence trigger tests that were broken by mocks

* refactors reused mocking code

* added unit test to check history update in sandbox

* update first test to include existing history

* makes second test use existing history

* adds comment that points out some weirdness

* polishes off those tests

* fixes weirdness about combining the empty defence report

* fixes problem of not getting updated chat history

* respond to chris - makes chatHistoryWithNewUsermessages more concise

* respond to chris - adds back useful comment

* simplify transformed message ternary expression

* refactors transformMessage and only calls combineTransformedMessage once

---------

Co-authored-by: Peter Marsh <[email protected]>

* adds imports to test files to fix linting

* improve comment

* update name from high or low level chat to chat with or without defence detection

* removes stale comments

* moves sentEmails out of LevelHandlerResponse and uses the property in chatResponse instead

* changed an if to an else if

* unspread that spread

* return combined report without declaring const first

* makes chatResponse decleration more concise with buttery spreads

* updates comment

* remove linter rule about uninitialised let statements. changed the let statement in the chatController

---------

Co-authored-by: Peter Marsh <[email protected]>
Co-authored-by: George Sproston <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Refactor handleChatToGPT() to remove mutation
4 participants