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

Automatic excerpt generation using OpenAI's ChatGPT API #405

Merged
merged 40 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a303429
Add the beginnings of an OpenAI integration
dkotter Mar 3, 2023
3ffce38
Add additional settings
dkotter Mar 7, 2023
5766ce6
Add REST endpoint and initial round of code that runs on this endpoin…
dkotter Mar 7, 2023
f0777be
Store last response and use that in our debug data
dkotter Mar 8, 2023
528ca50
Don't override the excerpt panel if the excerpt setting isn't turned …
dkotter Mar 8, 2023
fc49cc6
Move tokenizer code into it's own class and make it a little more reu…
dkotter Mar 8, 2023
0322f48
Convert sentence length into text for a better prompt
dkotter Mar 8, 2023
d38a11f
Add some filters around data, allowing easy modification if needed.
dkotter Mar 8, 2023
8561ece
Test with a valid API key and make a few tweaks to get everything wor…
dkotter Mar 8, 2023
1623bb1
Modify prompt a bit to handle plurals correctly
dkotter Mar 8, 2023
4225571
Update readmes. Optimize images
dkotter Mar 8, 2023
019f74c
Fix eslint error
dkotter Mar 8, 2023
4bafa63
Fix eslint error, maybe?
dkotter Mar 8, 2023
e0f4a5e
Fix eslint error, maybe?
dkotter Mar 8, 2023
9542a02
Update some error messages
dkotter Mar 9, 2023
249e0e3
Update some more error messages
dkotter Mar 9, 2023
842e07a
Change our length setting from sentences to words and set the default…
dkotter Mar 9, 2023
fda9d9a
Merge branch 'feature/openai' of github.com:10up/classifai into featu…
zamanq Mar 10, 2023
1ce2c06
Set default temperature value to 0 as this seems to give us better ex…
dkotter Mar 10, 2023
a7d396a
Merge branch 'develop' into feature/openai
dkotter Mar 10, 2023
33d7388
Fix existing tests
dkotter Mar 10, 2023
cff6f7e
Add a couple new tests
dkotter Mar 10, 2023
adddb48
Merge branch 'develop' into feature/openai
dkotter Mar 10, 2023
6f6c868
Mock the ChatGPT request and add a test to ensure the Generate Excerpt
dkotter Mar 10, 2023
1e8558e
Merge branch 'feature/openai' of github.com:10up/classifai into featu…
zamanq Mar 12, 2023
6dcec79
Enabling Excerpt to be modified/regenerated in the Pre Publish Panel
zamanq Mar 12, 2023
17b0598
Refactoring the Pre publish panel for excerpt
zamanq Mar 13, 2023
63738f8
Change to using a component for our excerpt panel and use internal st…
dkotter Mar 13, 2023
9b5a475
Add new setting to choose which roles have access to generate excerpt…
dkotter Mar 14, 2023
33258fe
Restrict access to our generate excerpt REST endpoint based on the al…
dkotter Mar 14, 2023
0d4e4e6
Remove temperature setting and hardcode that to zero. Add a filter ar…
dkotter Mar 14, 2023
e8407a6
Remove temperature setting from our tests as that doesn't exist anymore
dkotter Mar 14, 2023
887c087
Remove unneeded code. Add a timeout value to our request to try and a…
dkotter Mar 15, 2023
41da0d8
Fix lint error
dkotter Mar 15, 2023
f7a726c
Add the timeout value to our APIRequest class so we don't duplicate code
dkotter Mar 15, 2023
2bccaa7
Namespace REST URL with openai, in case we ever add another provider …
dkotter Mar 15, 2023
4598678
Follow best practices and ensure we return a response object. Add a m…
dkotter Mar 16, 2023
ad15a15
Add role setting to our debug output. Add tests around the roles setting
dkotter Mar 16, 2023
d819bcd
Use right selector for role setting we want to disable
dkotter Mar 16, 2023
ba6ad5b
Merge pull request #408 from 10up/feature/openai-prepublish-check
dkotter Mar 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 45 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
* [Pricing](#pricing)
* [Installation](#installation)
* [Register ClassifAI account](#register-classifai-account)
* [Set Up Language Processing](#set-up-language-processing-via-ibm-watson)
* [Set Up NLU Language Processing](#set-up-language-processing-via-ibm-watson)
* [Set Up ChatGPT Language Processing](#set-up-language-processing-via-openai)
* [Set Up Image Processing](#set-up-image-processing-via-microsoft-azure)
* [Set Up Recommended Content](#set-up-recommended-content-via-microsoft-azure-personalizer)
* [WP CLI Commands](#wp-cli-commands)
Expand All @@ -24,6 +25,7 @@
## Features

* Classify your content using [IBM Watson's Natural Language Understanding API](https://www.ibm.com/watson/services/natural-language-understanding/) and [Microsoft Azure's Computer Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/)
* Automatically generate a summary of your content and store that as an excerpt using [OpenAI's ChatGPT](https://platform.openai.com/docs/guides/chat)
* Supports Watson's [Categories](https://cloud.ibm.com/docs/natural-language-understanding?topic=natural-language-understanding-about#categories), [Keywords](https://cloud.ibm.com/docs/natural-language-understanding?topic=natural-language-understanding-about#keywords), [Concepts](https://cloud.ibm.com/docs/natural-language-understanding?topic=natural-language-understanding-about#concepts) & [Entities](https://cloud.ibm.com/docs/natural-language-understanding?topic=natural-language-understanding-about#entities) and Azure's [Describe Image](https://westus.dev.cognitive.microsoft.com/docs/services/5adf991815e1060e6355ad44/operations/56f91f2e778daf14a499e1fe)
* Automatically classify content and images on save
* Automatically generate alt text and image tags for images
Expand All @@ -32,9 +34,9 @@
* BETA: Recommend content based on overall site traffic via [Azure Personalizer](https://azure.microsoft.com/en-us/services/cognitive-services/personalizer/) (note that we're gathering feedback on this feature and may significantly iterate depending on community input)
* Bulk classify content with [WP-CLI](https://wp-cli.org/)

| Language Processing - Tagging | Recommended Content |
| :-: | :-: |
| ![Screenshot of ClassifAI post tagging](assets/img/screenshot-1.png "Example of a Block Editor post with Watson Categories, Keywords, Concepts, and Entities.") | ![Screenshot of ClassifAI recommended content](assets/img/screenshot-2.png "Example of a Recommended Content Block with Azure Personalizer.") |
| Language Processing - Tagging | Recommended Content | Excerpt Generation |
| :-: | :-: | :-: |
| ![Screenshot of ClassifAI post tagging](assets/img/screenshot-1.png "Example of a Block Editor post with Watson Categories, Keywords, Concepts, and Entities.") | ![Screenshot of ClassifAI recommended content](assets/img/screenshot-2.png "Example of a Recommended Content Block with Azure Personalizer.") | ![Screenshot of ClassifAI excerpt generation](assets/img/screenshot-7.png "Example of automatic excerpt generation with OpenAI.") |

| Image Processing - Alt Text | Image Processing - Smart Cropping | Image Processing - Tagging |
| :-: | :-: | :-: |
Expand All @@ -44,14 +46,17 @@

* PHP 7.4+
* [WordPress](http://wordpress.org) 5.7+
* To utilize the Language Processing functionality, you will need an active [IBM Watson](https://cloud.ibm.com/registration) account.
* To utilize the NLU Language Processing functionality, you will need an active [IBM Watson](https://cloud.ibm.com/registration) account.
* To utilize the ChatGPT Language Processing functionality, you will need an active [OpenAI](https://platform.openai.com/signup) account.
* To utilize the Image Processing functionality, you will need an active [Microsoft Azure](https://signup.azure.com/signup) account.

## Pricing

Note that there is no cost to using ClassifAI and that both IBM Watson and Microsoft Azure have free plans for their AI services, but that above those free plans there are paid levels as well. So if you expect to process a high volume of content, then you'll want to review the pricing plans for these services to understand if you'll incur any costs. For the most part, both services' free plans are quite generous and should at least allow for testing ClassifAI to better understand its featureset and could at best allow for totally free usage.
Note that there is no cost to using ClassifAI itself. Both IBM Watson and Microsoft Azure have free plans for their AI services, but above those free plans there are paid levels as well. So if you expect to process a high volume of content, then you'll want to review the pricing plans for these services to understand if you'll incur any costs. For the most part, both services' free plans are quite generous and should at least allow for testing ClassifAI to better understand its featureset and could at best allow for totally free usage. OpenAI has a limited trial option that can be used for testing but will require a valid paid plan after that.

The service that powers ClassifAI's NLU Language Processing, IBM Watson's Natural Language Understanding ("NLU"), has a ["lite" pricing tier](https://www.ibm.com/cloud/watson-natural-language-understanding/pricing) that offers 30,000 free NLU items per month.

The service that powers ClassifAI's Language Processing, IBM Watson's Natural Language Understanding ("NLU"), has a ["lite" pricing tier](https://www.ibm.com/cloud/watson-natural-language-understanding/pricing) that offers 30,000 free NLU items per month.
The service that powers ClassifAI's ChatGPT Language Processing, OpenAI's ChatGPT, has a limited free trial and then requires a [pay per usage](https://openai.com/pricing) plan.

The service that powers ClassifAI's Image Processing, Microsoft Azure's Computer Vision, has a ["free" pricing tier](https://azure.microsoft.com/en-us/pricing/details/cognitive-services/computer-vision/) that offers 20 transactions per minute and 5,000 transactions per month.

Expand All @@ -61,7 +66,7 @@ The service that powers ClassifAI's Recommended Content, Microsoft Azure's Perso

### Manual Installation

#### 1. Download or Clone this repo, install dependencies and build.
#### 1. Download or Clone this repo, install dependencies and build

- `git clone https://github.com/10up/classifai.git && cd classifai`
- `composer install && npm install && npm run build`
Expand All @@ -70,7 +75,7 @@ The service that powers ClassifAI's Recommended Content, Microsoft Azure's Perso

### Installation via Composer

ClassifAI releases can be installed via Composer.
ClassifAI releases can be installed via Composer.

#### 1. Update composer.json

Expand Down Expand Up @@ -143,7 +148,7 @@ ClassifAI is a sophisticated solution that we want organizations of all shapes a
- Log into your account (accepting the privacy policy) and create a new [*Natural Language Understanding*](https://cloud.ibm.com/catalog/services/natural-language-understanding) Resource if you do not already have one. It may take a minute for your account to fully populate with the default resource group to use.
- Click `Manage` in the left hand menu, then `Show credentials` on the Manage page to view the credentials for this resource.

#### 2. Configure IBM Watson API Keys under ClassifAI > Language Processing
#### 2. Configure IBM Watson API Keys under ClassifAI > Language Processing > IBM Watson

**The credentials screen will show either an API key or a username/password combination.**

Expand All @@ -158,7 +163,7 @@ ClassifAI is a sophisticated solution that we want organizations of all shapes a
- Enter the `username` value into the `API Username`.
- Enter the `password` into the `API Key` field.

#### 3. Configure Post Types to classify and IBM Watson Features to enable under ClassifAI > Language Processing
#### 3. Configure Post Types to classify and IBM Watson Features to enable under ClassifAI > Language Processing > IBM Watson

- Choose which public post types to classify when saved.
- Choose whether to assign category, keyword, entity, and concept as well as the thresholds and taxonomies used for each.
Expand All @@ -171,6 +176,31 @@ IBM Watson endpoint urls with `watsonplatform.net` were deprecated on 26 May 202

For more information, see https://cloud.ibm.com/docs/watson?topic=watson-endpoint-change.

## Set Up Language Processing (via OpenAI)

#### 1. Sign up for OpenAI

* [Sign up for an OpenAI account](https://platform.openai.com/signup) or sign into your existing one.
* If creating a new account, complete the verification process (requires confirming your email and phone number).
* Log into your account and go to the [API key page](https://platform.openai.com/account/api-keys).
* Click `Create new secret key` and copy the key that is shown.

#### 2. Configure OpenAI API Keys under ClassifAI > Language Processing > OpenAI

* Enter your API Key copied from the above step into the `API Key` field.

#### 3. Enable specific Language Processing features

* Choose to add the ability to generate excerpts.
* If excerpt generation is configured, set the other options as needed.
* Save changes and ensure a success message is shown. An error will show if API authentication fails.

#### 4. Edit a content type that has excerpts enabled

* Edit (or create) an item that supports excerpts. Note: only the block editor is supported.
* Ensure this item has content saved.
* Open the Excerpt panel in the sidebar and click on `Generate Excerpt`

## Set Up Image Processing (via Microsoft Azure)

Note that [Computer Vision](https://docs.microsoft.com/en-us/azure/cognitive-services/computer-vision/home#image-requirements) can analyze and crop images that meet the following requirements:
Expand Down Expand Up @@ -229,15 +259,15 @@ For more information, see https://docs.microsoft.com/en-us/azure/cognitive-servi

### What data does ClassifAI gather?

ClassifAI connects your WordPress site directly to your account with specific service provider(s) (e.g. Microsoft Azure AI, IBM Watson), so no data is gathered by 10up. The data gathered in our [registration form](https://classifaiplugin.com/#cta) is used simply to stay in touch with users so we can provide product updates and news. More information is available in the [Privacy Policy on ClassifAIplugin.com](https://drive.google.com/open?id=1Hn4XEWmNGqeMzLqnS7Uru2Hl2vJeLc7cI7225ztThgQ).
ClassifAI connects your WordPress site directly to your account with specific service provider(s) (e.g. Microsoft Azure AI, IBM Watson, OpenAI), so no data is gathered by 10up. The data gathered in our [registration form](https://classifaiplugin.com/#cta) is used simply to stay in touch with users so we can provide product updates and news. More information is available in the [Privacy Policy on ClassifAIplugin.com](https://drive.google.com/open?id=1Hn4XEWmNGqeMzLqnS7Uru2Hl2vJeLc7cI7225ztThgQ).

### What are the Categories, Keywords, Concepts, and Entities within the Language Processing feature?
### What are the Categories, Keywords, Concepts, and Entities within the NLU Language Processing feature?

[Categories](https://cloud.ibm.com/docs/natural-language-understanding?topic=natural-language-understanding-about#categories) are five levels of hierarchies that IBM Watson can identify from your text. [Keywords](https://cloud.ibm.com/docs/natural-language-understanding?topic=natural-language-understanding-about#keywords) are specific terms from your text that IBM Watson is able to identify. [Concepts](https://cloud.ibm.com/docs/natural-language-understanding?topic=natural-language-understanding-about#concepts) are high-level concepts that are not necessarily directly referenced in your text. [Entities](https://cloud.ibm.com/docs/natural-language-understanding?topic=natural-language-understanding-about#entities) are people, companies, locations, and classifications that are made by IBM Watson from your text.

### How can I view the taxonomies that are generated from Language Processing?
### How can I view the taxonomies that are generated from the NLU Language Processing?

Whatever options you have selected in the Category, Keyword, Entity, and Concept taxonomy dropdowns in the Language Processing settings can be viewed within Classic Editor metaboxes and the Block Editor side panel. They can also be viewed in the All Posts and All Pages table list views by utilizing the Screen Options to enable those columns if they're not already appearing in your table list view.
Whatever options you have selected in the Category, Keyword, Entity, and Concept taxonomy dropdowns in the NLU Language Processing settings can be viewed within Classic Editor metaboxes and the Block Editor side panel. They can also be viewed in the All Posts and All Pages table list views by utilizing the Screen Options to enable those columns if they're not already appearing in your table list view.

## Support Level

Expand Down
Binary file modified assets/img/screenshot-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/img/screenshot-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/img/screenshot-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/img/screenshot-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/img/screenshot-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/img/screenshot-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/screenshot-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 36 additions & 2 deletions includes/Classifai/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ function get_plugin() {
* Returns the ClassifAI plugin's stored settings in the WP options.
*
* @param string $service The service to get settings from, defaults to the ServiceManager class.
*
* @param string $provider The provider service name to get settings from, defaults to the first one found.
* @return array The array of ClassifAi settings.
*/
function get_plugin_settings( $service = '' ) {
function get_plugin_settings( $service = '', $provider = '' ) {
$services = Plugin::$instance->services;
if ( empty( $services ) || empty( $services['service_manager'] ) || ! $services['service_manager'] instanceof ServicesManager ) {
return [];
Expand All @@ -43,6 +43,17 @@ function get_plugin_settings( $service = '' ) {
return [];
}

// If we want settings for a specific provider, find the proper provider service.
if ( ! empty( $provider ) ) {
foreach ( $service_manager->service_classes[ $service ]->provider_classes as $provider_class ) {
if ( $provider_class->provider_service_name === $provider ) {
return $provider_class->get_settings();
}
}

return [];
}

/** @var Provider $provider An instance or extension of the provider abstract class. */
$provider = $service_manager->service_classes[ $service ]->provider_classes[0];
return $provider->get_settings();
Expand Down Expand Up @@ -583,3 +594,26 @@ function attachment_is_pdf( $post ) {

return false;
}

/**
* Get asset info from extracted asset files.
*
* @param string $slug Asset slug as defined in build/webpack configuration.
* @param string $attribute Optional attribute to get. Can be version or dependencies.
* @return string|array
*/
function get_asset_info( $slug, $attribute = null ) {
if ( file_exists( CLASSIFAI_PLUGIN_DIR . '/dist/' . $slug . '.asset.php' ) ) {
$asset = require CLASSIFAI_PLUGIN_DIR . '/dist/' . $slug . '.asset.php';
} elseif ( file_exists( CLASSIFAI_PLUGIN_DIR . '/dist/' . $slug . '.asset.php' ) ) {
$asset = require CLASSIFAI_PLUGIN_DIR . '/dist/' . $slug . '.asset.php';
dkotter marked this conversation as resolved.
Show resolved Hide resolved
} else {
return null;
}

if ( ! empty( $attribute ) && isset( $asset[ $attribute ] ) ) {
return $asset[ $attribute ];
}

return $asset;
}
106 changes: 106 additions & 0 deletions includes/Classifai/Providers/OpenAI/APIRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

namespace Classifai\Providers\OpenAI;

/**
* The APIRequest class is a low level class to make OpenAI API
* requests.
*
* The returned response is parsed into JSON and returned as an
* associative array.
*
* Usage:
*
* $request = new Classifai\Providers\OpenAI\APIRequest();
* $request->post( $openai_url, $options );
*/
class APIRequest {

/**
* The OpenAI API key.
*
* @var string
*/
public $api_key;

/**
* OpenAI APIRequest constructor.
*
* @param string $api_key OpenAI API key.
*/
public function __construct( $api_key = '' ) {
$this->api_key = $api_key;
}

/**
* Makes an authorized POST request.
*
* @param string $url The OpenAI API URL.
* @param array $options Additional query params.
* @return array|WP_Error
*/
public function post( $url = '', $options = [] ) {
$this->add_headers( $options );
return $this->get_result( wp_remote_post( $url, $options ) ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get
}

/**
* Get results from the response.
*
* @param object $response The API response.
* @return array|WP_Error
dkotter marked this conversation as resolved.
Show resolved Hide resolved
*/
public function get_result( $response ) {
if ( ! is_wp_error( $response ) ) {
$body = wp_remote_retrieve_body( $response );
$code = wp_remote_retrieve_response_code( $response );
$json = json_decode( $body, true );

if ( json_last_error() === JSON_ERROR_NONE ) {
if ( empty( $json['error'] ) ) {
return $json;
} else {
$message = $json['error']['message'] ?? esc_html__( 'An error occured', 'classifai' );
return new \WP_Error( $code, $message );
}
} else {
return new \WP_Error( 'Invalid JSON: ' . json_last_error_msg(), $body );
}
} else {
return $response;
}
}

/**
* Add the headers.
*
* @param array $options The header options, passed by reference.
*/
public function add_headers( &$options = [] ) {
if ( empty( $options['headers'] ) ) {
$options['headers'] = [];
}

$options['headers']['Authorization'] = $this->get_auth_header();
$options['headers']['Content-Type'] = 'application/json';
}

/**
* Get the auth header.
*
* @return string
*/
public function get_auth_header() {
return 'Bearer ' . $this->get_api_key();
}

/**
* Get the OpenAI API key.
*
* @return string
*/
public function get_api_key() {
return $this->api_key;
}

}
Loading