Canvas AI: Make chat history length configurable
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3555239. --> Reported by: [akhil babu](https://www.drupal.org/user/3632866) Related to !687 >>> <h3 id="overview">Overview</h3> <p>Copied from #4 in <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3554026" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3554026</a></span></p> <p>Currently, only the last two messages are sent as history to the orchestrator, which affects the overall user experience.</p> <p>See the attached video for reference. When asked to create a CTA for a tech journal website, the orchestrator asks whether a code component should be created or an existing component should be added (meaning the page builder should be used). When asked to create a code component, the component agent fails. So I asked the agent to "add an existing component then." The agent then responds with:<br> "Please let me know what type of existing component or section you would like to add to your page, and any specific details or placements you have in mind."<br> This happens because it doesn&rsquo;t have access to the complete chat history.</p> <p><del>Another issue is that intermediate status messages from sub-agents are also included in the history. For example:</del></p> <p><del>When you ask the agent to "Create a button code component with a red background," the component agent is invoked. Once the component is created, it outputs:</del><br> <del>"I have created a new 'Red Button' component with customizable button text and a red background. Let me know if you need any further adjustments!"</del></p> <p><del>When the orchestrator receives this message, it also outputs something like:</del><br> <del>"A new 'Red Button' component has been created with customizable button text and a red background. You can now use and adjust the button text as needed."</del></p> <p><del>Now, when the user asks to change the color to blue, the message history sent to the orchestrator looks like this:<br> This includes the outputs of both the component agent and the orchestrator, but not the initial user message</del></p> <pre>[<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; "role": "ai",<br>&nbsp;&nbsp;&nbsp; "html": "&lt;div style="margin-top: 10px;"&gt;&lt;div style="display: flex; align-items: center; padding: 8px; background-color: white;"&gt;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;span style="margin-right: 8px;"&gt;&lt;span class="aiCompletedIcon"&gt;&lt;/span&gt;&lt;/span&gt;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;span style="font-weight: 400;"&gt;Thinking&lt;/span&gt;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/div&gt;&lt;div style="padding: 8px; background-color: white; font-size: 14px; line-height: 1.26;"&gt;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; I have created a new "Red Button" component with a customizable button text and a red background. Let me know if you need any further adjustments!\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/div&gt;&lt;div style="display: flex; align-items: center; padding: 8px; background-color: white;"&gt;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;span style="margin-right: 8px;"&gt;&lt;span class="aiCompletedIcon"&gt;&lt;/span&gt;&lt;/span&gt;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;span style="font-weight: 400;"&gt;Generate a component&lt;/span&gt;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/div&gt;&lt;div style="padding: 8px; background-color: white; font-size: 14px; line-height: 1.26;"&gt;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A new "Red Button" component has been created with a customizable button text and a red background. You can now use and adjust the button text as needed.\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/div&gt;&lt;/div&gt;"<br>&nbsp; },<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; "role": "ai",<br>&nbsp;&nbsp;&nbsp; "text": "A new "Red Button" component has been created with a customizable button text and a red background. You can now use and adjust the button text as needed."<br>&nbsp; },<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; "role": "user",<br>&nbsp;&nbsp;&nbsp; "text": "change its color to blue"<br>&nbsp; }<br>]</pre><p><del>I think the sub-agent output can be ignored in the history since the orchestrator already returns a similar message. Instead, the history should look like this:</del></p> <pre>[<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; "role": "user",<br>&nbsp;&nbsp;&nbsp; "text": "Add a button with red color"<br>&nbsp; },<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; "role": "ai",<br>&nbsp;&nbsp;&nbsp; "text": "A new 'Red Button' component has been created with a customizable button text and a red background. You can now use and adjust the button text as needed."<br>&nbsp; },<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; "role": "user",<br>&nbsp;&nbsp;&nbsp; "text": "change its color to blue"<br>&nbsp; }<br>]</pre><p>--------------------------------------------------------</p> <p>The above observation is partially incorrect. Even though the front end sends sub agent responses to backend, the code only considers user inputs and the orchestrator responses for chat history </p> <div class="codeblock"> <pre><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br>&nbsp; </span><span style="color: #007700">if (!empty(</span><span style="color: #0000BB">$message</span><span style="color: #007700">[</span><span style="color: #DD0000">'text'</span><span style="color: #007700">])) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #FF8000">// Sub agent responses contain $message['html'] instead of $message['text']<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: #0000BB">$messages</span><span style="color: #007700">[] = new </span><span style="color: #0000BB">ChatMessage</span><span style="color: #007700">(</span><span style="color: #0000BB">$message</span><span style="color: #007700">[</span><span style="color: #DD0000">'role'</span><span style="color: #007700">] === </span><span style="color: #DD0000">'user' </span><span style="color: #007700">? </span><span style="color: #DD0000">'user' </span><span style="color: #007700">: </span><span style="color: #DD0000">'assistant'</span><span style="color: #007700">, </span><span style="color: #0000BB">$message</span><span style="color: #007700">[</span><span style="color: #DD0000">'text'</span><span style="color: #007700">]);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br></span><span style="color: #0000BB">?&gt;</span></span></pre></div> <p>The real issue is thatCurrently only the last 3 messages are sent to backend due to this setting in <code>web/modules/contrib/canvas/ui/src/components/aiExtension/AiWizard.tsx</code></p> <pre>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestBodyLimits={{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; maxMessages: 3,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }}</pre><p>We should remove this setting and allow all messages to be sent to the backend. For that, as per the deepchat documentation, the maxMessages value should be set 0 or less.</p> <p><a href="https://deepchat.dev/docs/connect/#requestBodyLimits">https://deepchat.dev/docs/connect/#requestBodyLimits</a></p> <blockquote><p>maxMessages is the maximum number of messages counting from the most recent one. If this is set to a number higher than 0 such as 1 - the outgoing request will only include the new user message, if it is 2 - it will also include the message before the latest one (from AI or the user) and so on... If the number is 0 or below - the request will include all messages in the chat. If it is undefined, the request will only include the input text/files.</p></blockquote> <h3 id="proposed-resolution">Proposed resolution</h3> <p>The AIWizard.tsx file had this comment<br> <code>@todo Revisit once https://www.drupal.org/node/3528730 is in.</code></p> <p>That issue suggests using the short-term memory plugin from the AI module to control chat history. The "last_n" plugin provided by AI core could be used, but it simply returns a fixed number of messages from the complete history. In this issue, custom logic is used for the same purpose, as it is more efficient.</p> <h4>Set request body limit in Deep Chat</h4> <ul> <li>Set <code>requestBodyLimits</code> to <code>-1</code> so the full conversation history is sent to the backend with each request</li> </ul> <h4>Make chat history length configurable</h4> <ul> <li>Created <code>src/CanvasAiChatHelper.php</code> with a <code>getFilteredChatHistory</code> method</li> <li>This method takes the entire chat history sent from the UI to the backend controller and returns only the number of messages equal to <code>chat_history_max_messages</code></li> <li>Fixes a bug in the previous logic where, if an image message was encountered, a <code>break</code> was triggered in the loop after fetching the image file. This prevented all subsequent messages from being included in the history. The <code>break</code> has been removed.</li> <li>Removed the logic where the text accompanying an uploaded image was removed (See: <a href="https://git.drupalcode.org/project/canvas/-/merge_requests/687#note_819457">https://git.drupalcode.org/project/canvas/-/merge_requests/687#note_819457</a>)</li> <li>Added a <code>chat_history_max_messages</code> setting with default <code>10</code></li> <li><code>0</code> means no history, <code>-1</code> means full history, positive values limit to last N messages</li> <li>Kept the default history length as 10. Added an update hook to set the history limit as 3 for existing users (the current limit is 3)</li> </ul> <h4>Test coverage</h4> <ul> <li>Added kernel tests for the service.</li> </ul> <h3 id="ui-changes">User interface changes</h3> > Related issue: [Issue #3554026](https://www.drupal.org/node/3554026)
issue