Switch to the text summarization endpoint with bart-large-cnn model
Also renames OPEN_AI variables to CF_AI
This commit is contained in:
parent
c57d15d69b
commit
a85cb7891e
10
README.md
10
README.md
@ -1,6 +1,6 @@
|
|||||||
# Miniflux AI Summarizer
|
# Miniflux AI Summarizer
|
||||||
|
|
||||||
This Cloudflare Workers tool automatically adds AI-generated summaries to articles in your Miniflux RSS reader. The summaries are generated using the OpenAI API and appended to articles in a user-friendly format.
|
This Cloudflare Workers tool automatically adds AI-generated summaries to articles in your Miniflux RSS reader. The summaries are generated using the Cloudflare AI API and appended to articles in a user-friendly format.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ This Cloudflare Workers tool automatically adds AI-generated summaries to articl
|
|||||||
|
|
||||||
- [Rust](https://www.rust-lang.org/tools/install) installed
|
- [Rust](https://www.rust-lang.org/tools/install) installed
|
||||||
- A Miniflux instance with API access
|
- A Miniflux instance with API access
|
||||||
- An OpenAI account with access to the model endpoint
|
- A Cloudflare AI account with access to the model endpoint
|
||||||
- A Cloudflare account
|
- A Cloudflare account
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
@ -74,14 +74,14 @@ npx wrangler secret put SECRET_NAME
|
|||||||
- `MINIFLUX_URL`: Your Miniflux instance URL.
|
- `MINIFLUX_URL`: Your Miniflux instance URL.
|
||||||
- `MINIFLUX_USERNAME`: Your Miniflux username.
|
- `MINIFLUX_USERNAME`: Your Miniflux username.
|
||||||
- `MINIFLUX_PASSWORD`: Your Miniflux password.
|
- `MINIFLUX_PASSWORD`: Your Miniflux password.
|
||||||
- `OPENAI_URL`: The endpoint for the OpenAI API.
|
- `CF_AI_URL`: The endpoint for the Cloudflare AI API (should be in the form of `https://api.cloudflare.com/client/v4/accounts/{account_id}/ai`).
|
||||||
- `OPENAI_TOKEN`: Your OpenAI API token.
|
- `CF_AI_TOKEN`: Your Cloudflare AI API token.
|
||||||
|
|
||||||
#### Environment Variables
|
#### Environment Variables
|
||||||
|
|
||||||
These environment variables can be set in the `wrangler.toml` file under the `[vars]` section:
|
These environment variables can be set in the `wrangler.toml` file under the `[vars]` section:
|
||||||
|
|
||||||
- `OPENAI_MODEL`: The model ID to use for generating summaries. We recommend using the `@cf/qwen/qwen1.5-14b-chat-awq` model for best results.
|
- `CF_AI_MODEL`: The model ID to use for generating summaries. We recommend using the `@cf/qwen/qwen1.5-14b-chat-awq` model for best results.
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
|
81
src/lib.rs
81
src/lib.rs
@ -61,9 +61,9 @@ async fn update_entry(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ChatCompletionRequest {
|
struct SummarizeRequest {
|
||||||
model: String,
|
input_text: String,
|
||||||
messages: Vec<Message>,
|
max_length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -73,30 +73,25 @@ struct Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ChatCompletionChoice {
|
struct SummarizeResponse {
|
||||||
message: Message,
|
summary: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
async fn request_ai_summarization(
|
||||||
struct ChatCompletionResponse {
|
|
||||||
choices: Vec<ChatCompletionChoice>,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn request_openai_chat_completion(
|
|
||||||
base_url: &str,
|
base_url: &str,
|
||||||
api_key: &str,
|
api_key: &str,
|
||||||
model: &str,
|
model: &str,
|
||||||
messages: Vec<Message>,
|
input: String,
|
||||||
) -> std::result::Result<String, Box<dyn std::error::Error>> {
|
) -> std::result::Result<String, Box<dyn std::error::Error>> {
|
||||||
console_log!("request_openai_chat_completion");
|
console_log!("request_ai_summarization");
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let request_body = ChatCompletionRequest {
|
let request_body = SummarizeRequest {
|
||||||
model: model.to_string(),
|
input_text: input,
|
||||||
messages,
|
max_length: 512,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = client
|
let response = client
|
||||||
.post(format!("{}/v1/chat/completions", base_url))
|
.post(format!("{}/run/{}", base_url, model))
|
||||||
.header(AUTHORIZATION, format!("Bearer {}", api_key))
|
.header(AUTHORIZATION, format!("Bearer {}", api_key))
|
||||||
.header(CONTENT_TYPE, "application/json")
|
.header(CONTENT_TYPE, "application/json")
|
||||||
.json(&request_body)
|
.json(&request_body)
|
||||||
@ -104,12 +99,12 @@ async fn request_openai_chat_completion(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if response.status().is_success() {
|
if response.status().is_success() {
|
||||||
console_log!("request_openai_chat_completion success");
|
console_log!("request_ai_summarization success");
|
||||||
let completion_response: ChatCompletionResponse = response.json().await?;
|
let summarize_response: SummarizeResponse = response.json().await?;
|
||||||
Ok(completion_response.choices[0].message.content.clone())
|
Ok(summarize_response.summary)
|
||||||
} else {
|
} else {
|
||||||
let error_message = response.text().await?;
|
let error_message = response.text().await?;
|
||||||
console_log!("request_openai_chat_completion error: {}", error_message);
|
console_log!("request_ai_summarization error: {}", error_message);
|
||||||
Err(format!("Error: {:?}", error_message).into())
|
Err(format!("Error: {:?}", error_message).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +118,7 @@ struct Miniflux {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct OpenAi {
|
struct CloudflareAi {
|
||||||
url: String,
|
url: String,
|
||||||
token: String,
|
token: String,
|
||||||
model: String,
|
model: String,
|
||||||
@ -132,7 +127,7 @@ struct OpenAi {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Config {
|
struct Config {
|
||||||
miniflux: Miniflux,
|
miniflux: Miniflux,
|
||||||
openai: OpenAi,
|
cloudflare_ai: CloudflareAi,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn generate_and_update_entry(
|
async fn generate_and_update_entry(
|
||||||
@ -146,23 +141,17 @@ async fn generate_and_update_entry(
|
|||||||
console_log!("skipping entry due to empty content or short content length");
|
console_log!("skipping entry due to empty content or short content length");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let messages = vec![
|
let input = format!(
|
||||||
Message {
|
"Title: {}\nURL: {}\nContent: {}",
|
||||||
role: "system".to_string(),
|
&entry.title, &entry.url, &entry.content
|
||||||
content: "You are an experienced and knowledgeable internet blogger that writes short and easy-to-read summaries for articles from various RSS feeds. Please summarize the content of the article in 250 words or less. Format your output in CommonMark compliant markdown. Do not give any extra comments, headers, or prefix. Only return the actual summary text. Similar to the blurbs on the back of books, highlight any aspects of the articles that may be of interest and grab the attention to any readers perusing.".to_string(),
|
);
|
||||||
},
|
|
||||||
Message {
|
|
||||||
role: "user".to_string(),
|
|
||||||
content: format!("The following is the article:\n---\nTitle: {}\nURL: {}\nContent: {}", &entry.title, &entry.url, &entry.content),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Generate summary
|
// Generate summary
|
||||||
if let Ok(summary) = request_openai_chat_completion(
|
if let Ok(summary) = request_ai_summarization(
|
||||||
&config.openai.url,
|
&config.cloudflare_ai.url,
|
||||||
&config.openai.token,
|
&config.cloudflare_ai.token,
|
||||||
&config.openai.model,
|
&config.cloudflare_ai.model,
|
||||||
messages,
|
input,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@ -205,10 +194,10 @@ fn handle_options() -> Result<Response> {
|
|||||||
pub async fn scheduled(_event: ScheduledEvent, env: Env, _ctx: ScheduleContext) {
|
pub async fn scheduled(_event: ScheduledEvent, env: Env, _ctx: ScheduleContext) {
|
||||||
console_log!("scheduled");
|
console_log!("scheduled");
|
||||||
let config = &Config {
|
let config = &Config {
|
||||||
openai: OpenAi {
|
cloudflare_ai: CloudflareAi {
|
||||||
url: env.secret("OPENAI_URL").unwrap().to_string(),
|
url: env.secret("CF_AI_URL").unwrap().to_string(),
|
||||||
token: env.secret("OPENAI_TOKEN").unwrap().to_string(),
|
token: env.secret("CF_AI_TOKEN").unwrap().to_string(),
|
||||||
model: env.var("OPENAI_MODEL").unwrap().to_string(),
|
model: env.var("CF_AI_MODEL").unwrap().to_string(),
|
||||||
},
|
},
|
||||||
miniflux: Miniflux {
|
miniflux: Miniflux {
|
||||||
url: env.secret("MINIFLUX_URL").unwrap().to_string(),
|
url: env.secret("MINIFLUX_URL").unwrap().to_string(),
|
||||||
@ -277,10 +266,10 @@ async fn fetch(mut req: Request, env: Env, _ctx: Context) -> Result<Response> {
|
|||||||
console_log!("is post");
|
console_log!("is post");
|
||||||
|
|
||||||
let config = &Config {
|
let config = &Config {
|
||||||
openai: OpenAi {
|
cloudflare_ai: CloudflareAi {
|
||||||
url: env.secret("OPENAI_URL").unwrap().to_string(),
|
url: env.secret("CF_AI_URL").unwrap().to_string(),
|
||||||
token: env.secret("OPENAI_TOKEN").unwrap().to_string(),
|
token: env.secret("CF_AI_TOKEN").unwrap().to_string(),
|
||||||
model: env.var("OPENAI_MODEL").unwrap().to_string(),
|
model: env.var("CF_AI_MODEL").unwrap().to_string(),
|
||||||
},
|
},
|
||||||
miniflux: Miniflux {
|
miniflux: Miniflux {
|
||||||
url: env.secret("MINIFLUX_URL").unwrap().to_string(),
|
url: env.secret("MINIFLUX_URL").unwrap().to_string(),
|
||||||
|
@ -13,4 +13,4 @@ command = "cargo install -q worker-build && worker-build --release"
|
|||||||
crons = ["*/5 * * * *"]
|
crons = ["*/5 * * * *"]
|
||||||
|
|
||||||
[vars]
|
[vars]
|
||||||
OPENAI_MODEL = "@cf/qwen/qwen1.5-14b-chat-awq"
|
CF_AI_MODEL = "@cf/facebook/bart-large-cnn"
|
||||||
|
Loading…
Reference in New Issue
Block a user