Allow tool calling in streamed chat
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3528549. -->
Reported by: [marcus_johansson](https://www.drupal.org/user/385947)
Related to !787
>>>
<p>--- AI TRACKER METADATA ---<br>
<strong>Update Summary: </strong>Streaming chat with tools - done. Making implementation.<br>
<strong>Check-in Date: </strong>MM/DD/YYYY (US format) [When we should see progress/get an update]<br>
<strong>Due Date:</strong> MM/DD/YYYY (US format) [When the issue should be fully completed]<br>
<strong>Blocked by:</strong> [#XXXXXX] (New issues on new lines)<br>
<strong>Additional Collaborators:</strong> @username1, @username2<br>
AI Tracker found here: <a href="https://www.drupalstarforge.ai/" title="AI Tracker">https://www.drupalstarforge.ai/</a><br>
--- END METADATA ---</p>
<h3 id="summary-problem-motivation">Problem/Motivation</h3>
<p>Currently we only allow you to return the following data in the streaming interface:</p>
<ul>
<li>Text</li>
<li>Role</li>
<li>Metadata</li>
</ul>
<p>This is subject to change with <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/ai/issues/3524435" title="Status: Closed (fixed)">#3524435: Add token usage to streamed chat</a></span> where token usage might be normalized as well.</p>
<p>We also need to normalize the tools usage in the StreamedChatMessageInterface that can take the tools response and put it in a getTools and setTools method. Since the tool might not be formed yet and it makes no sense to call the tool or the normalizer before that and it should not be normalized at that stage.</p>
<p>So what we should do is on the StreamedChatMessageIterator that is the abstract class, we should add a variable on the createStreamedChatMessage that adds the mixed $tool part and then adds this to a tool class.</p>
<p>On the StreamedChatMessageIteratorInterface we should force a method called normalizeTool, where each provider has to have a method for normalizing that tool. This can be added to the abstract method, to not break providers and be forced in 2.0.0</p>
<p>This should also be sent to the PostStreamingResponseEvent so anyone that needs to get the tools after it streamed out the text, can get it.</p>
<p>This is the steps of a streamed tools call, just for reference for a mental model:</p>
<pre>data: {<br> "choices": [<br> {<br> "delta": { "role": "assistant" },<br> "index": 0<br> }<br> ]<br>}<br><br>data: {<br> "choices": [<br> {<br> "delta": { "content": "Sure! Let me check..." },<br> "index": 0<br> }<br> ]<br>}<br><br>data: {<br> "choices": [<br> {<br> "delta": {<br> "tool_calls": [<br> {<br> "id": "tool_abc",<br> "type": "function",<br> "function": {<br> "name": "get_weather",<br> "arguments": "{"<br> }<br> }<br> ]<br> },<br> "index": 0<br> }<br> ]<br>}<br><br>data: {<br> "choices": [<br> {<br> "delta": {<br> "tool_calls": [<br> {<br> "function": {<br> "arguments": ""location":"Berlin"<br> }<br> }<br> ]<br> },<br> "index": 0<br> }<br> ]<br>}<br><br>data: {<br> "choices": [<br> {<br> "delta": {<br> "tool_calls": [<br> {<br> "function": {<br> "arguments": """<br> }<br> }<br> ]<br> },<br> "index": 0<br> }<br> ]<br>}<br><br>data: {<br> "choices": [<br> {<br> "delta": {},<br> "finish_reason": "tool_calls",<br> "index": 0<br> }<br> ]<br>}<br><br>data: [DONE]</pre><h4 id="summary-steps-reproduce">Steps to reproduce</h4>
<h3 id="summary-proposed-resolution">Proposed resolution</h3>
<p>In StreamedChatMessageInterface/StreamedChatMessage add getTools and setTools and add it to the constructor.<br>
In StreamedChatMessageIterator add a variable called tools<br>
In StreamedChatMessageIterator add tools to the createStreamedChatMessage method and add those to the tools<br>
In StreamedChatMessageIteratorInterface add a method that is normalizeTools that each provider has to take care of to normalize the tool output according to how its already done.<br>
In StreamedChatMessageIterator add a default method that does nothing, just to not have breaking changes. This will be removed in 2.0.0.<br>
Add the tools to the PostStreamingResponseEvent, via the constructor.<br>
In the PostStreamingResponseEvent add a getTools and setTools to add the tools.</p>
> Related issue: [Issue #3528549](https://www.drupal.org/node/3528549)
issue