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’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> {<br> "role": "ai",<br> "html": "<div style="margin-top: 10px;"><div style="display: flex; align-items: center; padding: 8px; background-color: white;">\n <span style="margin-right: 8px;"><span class="aiCompletedIcon"></span></span>\n <span style="font-weight: 400;">Thinking</span>\n </div><div style="padding: 8px; background-color: white; font-size: 14px; line-height: 1.26;">\n 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 </div><div style="display: flex; align-items: center; padding: 8px; background-color: white;">\n <span style="margin-right: 8px;"><span class="aiCompletedIcon"></span></span>\n <span style="font-weight: 400;">Generate a component</span>\n </div><div style="padding: 8px; background-color: white; font-size: 14px; line-height: 1.26;">\n 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 </div></div>"<br> },<br> {<br> "role": "ai",<br> "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> },<br> {<br> "role": "user",<br> "text": "change its color to blue"<br> }<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> {<br> "role": "user",<br> "text": "Add a button with red color"<br> },<br> {<br> "role": "ai",<br> "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> },<br> {<br> "role": "user",<br> "text": "change its color to blue"<br> }<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"><?php<br> </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> </span><span style="color: #FF8000">// Sub agent responses contain $message['html'] instead of $message['text']<br> </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> }<br></span><span style="color: #0000BB">?></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> requestBodyLimits={{<br> maxMessages: 3,<br> }}</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