addToChatHistory( $state, $event->toolCallMessage ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Vendor (NeuronAI) property name. $result = yield from $this->executeTools( $event->toolCallMessage, $state ); if ( $this->only_quick_replies_after_text( $event ) && $this->all_succeeded( $result ) ) { // The next model step would have added the results; it does not // run, and a tool call must be followed by its result. $this->addToChatHistory( $state, $result ); // The non-streaming chat returns the last message in the history: // the question, not the empty tool result. // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Vendor (NeuronAI) property name. $this->addToChatHistory( $state, new AssistantMessage( (string) $event->toolCallMessage->getContent() ) ); return new StopEvent(); } // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Vendor (NeuronAI) property name. $event->inferenceEvent->setMessages( $result ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Vendor (NeuronAI) property name. return $event->inferenceEvent; } /** * Whether every tool returned ok. A refused call (e.g. one option) showed * no chips, so the model must be asked again. * * @param ToolResultMessage $result Tool results. * @return bool */ private function all_succeeded( ToolResultMessage $result ): bool { foreach ( $result->getTools() as $tool ) { if ( true !== ( json_decode( (string) $tool->getResult(), true )['ok'] ?? null ) ) { return false; } } return true; } /** * Whether every tool called was `suggest-replies` after some text. * * Any text counts, finished or not: the model often loses the last bit of * its text (the "?", a closing "**") when it switches to the tool call, * so the text cannot be trusted to end in a question mark. With no text at * all the question is still to be written, so the model is asked again. * * @param ToolCallEvent $event Tool call. * @return bool */ private function only_quick_replies_after_text( ToolCallEvent $event ): bool { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Vendor (NeuronAI) property name. $tools = $event->toolCallMessage->getTools(); foreach ( $tools as $tool ) { if ( ! $tool instanceof Suggest_Replies_Tool ) { return false; } } // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Vendor (NeuronAI) property name. return [] !== $tools && '' !== trim( (string) $event->toolCallMessage->getContent() ); } }