FEATURE: support sending AI report to an email address (#368)
Support emailing the AI report to any arbitrary email
This commit is contained in:
parent
d0f54443ae
commit
529703b5ec
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AiReportMailer < ActionMailer::Base
|
||||
include Email::BuildEmailHelper
|
||||
|
||||
def send_report(to_address, opts = {})
|
||||
build_email(to_address, **opts)
|
||||
end
|
||||
end
|
|
@ -17,7 +17,6 @@ module DiscourseAi
|
|||
- Markdown Usage: Enhance readability with **bold**, *italic*, and > quotes.
|
||||
- Linking: Use `#{Discourse.base_url}/t/-/TOPIC_ID/POST_NUMBER` for direct references.
|
||||
- User Mentions: Reference users with @USERNAME
|
||||
- Context tips: Staff are denoted with Username *. For example: jane * means that jane is a staff member. Do not render the * in the report.
|
||||
- Add many topic links: strive to link to at least 30 topics in the report. Topic Id is meaningless to end users if you need to throw in a link use [ref](...) or better still just embed it into the [sentence](...)
|
||||
- Categories and tags: use the format #TAG and #CATEGORY to denote tags and categories
|
||||
|
||||
|
@ -52,6 +51,7 @@ module DiscourseAi
|
|||
)
|
||||
@sender = User.find_by(username: sender_username)
|
||||
@receivers = User.where(username: receivers)
|
||||
@email_receivers = receivers.filter { |r| r.include? "@" }
|
||||
@title = title
|
||||
|
||||
@model = model
|
||||
|
@ -70,6 +70,14 @@ module DiscourseAi
|
|||
|
||||
def run!
|
||||
start_date = (@offset + @days).days.ago
|
||||
end_date = start_date + @days.days
|
||||
|
||||
@title =
|
||||
@title.gsub(
|
||||
"%DATE%",
|
||||
start_date.strftime("%Y-%m-%d") + " - " + end_date.strftime("%Y-%m-%d"),
|
||||
)
|
||||
|
||||
prioritized_group_ids = [@priority_group_id] if @priority_group_id.present?
|
||||
context =
|
||||
DiscourseAi::Automation::ReportContextGenerator.generate(
|
||||
|
@ -113,20 +121,24 @@ module DiscourseAi
|
|||
result << response
|
||||
end
|
||||
|
||||
post =
|
||||
PostCreator.create!(
|
||||
@sender,
|
||||
raw: result,
|
||||
title: @title,
|
||||
archetype: Archetype.private_message,
|
||||
target_usernames: @receivers.map(&:username).join(","),
|
||||
skip_validations: true,
|
||||
)
|
||||
receiver_usernames = @receivers.map(&:username).join(",")
|
||||
|
||||
if @debug_mode
|
||||
input = input.split("\n").map { |line| " #{line}" }.join("\n")
|
||||
raw = <<~RAW
|
||||
if receiver_usernames.present?
|
||||
post =
|
||||
PostCreator.create!(
|
||||
@sender,
|
||||
raw: result,
|
||||
title: @title,
|
||||
archetype: Archetype.private_message,
|
||||
target_usernames: receiver_usernames,
|
||||
skip_validations: true,
|
||||
)
|
||||
|
||||
if @debug_mode
|
||||
input = input.split("\n").map { |line| " #{line}" }.join("\n")
|
||||
raw = <<~RAW
|
||||
```
|
||||
tokens: #{@llm.tokenizer.tokenize(input).length}
|
||||
start_date: #{start_date},
|
||||
duration: #{@days.days},
|
||||
max_posts: #{@sample_size},
|
||||
|
@ -138,7 +150,17 @@ module DiscourseAi
|
|||
|
||||
#{input}
|
||||
RAW
|
||||
PostCreator.create!(@sender, raw: raw, topic_id: post.topic_id, skip_validations: true)
|
||||
PostCreator.create!(@sender, raw: raw, topic_id: post.topic_id, skip_validations: true)
|
||||
end
|
||||
end
|
||||
|
||||
if @email_receivers.present?
|
||||
@email_receivers.each do |to_address|
|
||||
Email::Sender.new(
|
||||
::AiReportMailer.send_report(to_address, subject: @title, body: result),
|
||||
:ai_report,
|
||||
).send
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,6 +78,9 @@ module DiscourseAi
|
|||
def extract_completion_from(response_raw)
|
||||
parsed = JSON.parse(response_raw, symbolize_names: true).dig(:choices, 0)
|
||||
|
||||
# half a line sent here
|
||||
return if !parsed
|
||||
|
||||
response_h = @streaming_mode ? parsed.dig(:delta) : parsed.dig(:message)
|
||||
|
||||
has_function_call = response_h.dig(:tool_calls).present?
|
||||
|
|
|
@ -14,6 +14,34 @@ module DiscourseAi
|
|||
fab!(:secure_post) { Fabricate(:post, raw: "Top secret date !!!!", topic: secure_topic) }
|
||||
|
||||
describe "#run!" do
|
||||
it "is able to generate email reports" do
|
||||
freeze_time
|
||||
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses(["magical report"]) do
|
||||
ReportRunner.run!(
|
||||
sender_username: user.username,
|
||||
receivers: ["fake@discourse.com"],
|
||||
title: "test report %DATE%",
|
||||
model: "gpt-4",
|
||||
category_ids: nil,
|
||||
tags: nil,
|
||||
allow_secure_categories: false,
|
||||
sample_size: 100,
|
||||
instructions: "make a magic report",
|
||||
days: 7,
|
||||
offset: 0,
|
||||
priority_group_id: nil,
|
||||
tokens_per_post: 150,
|
||||
debug_mode: nil,
|
||||
)
|
||||
end
|
||||
|
||||
expect(ActionMailer::Base.deliveries.length).to eq(1)
|
||||
expect(ActionMailer::Base.deliveries.first.subject).to eq(
|
||||
"test report #{7.days.ago.strftime("%Y-%m-%d")} - #{Time.zone.now.strftime("%Y-%m-%d")}",
|
||||
)
|
||||
end
|
||||
|
||||
it "generates correctly respects the params" do
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses(["magical report"]) do
|
||||
ReportRunner.run!(
|
||||
|
|
|
@ -4,6 +4,8 @@ require "rails_helper"
|
|||
describe DiscourseAi::Inference::OpenAiCompletions do
|
||||
before { SiteSetting.ai_openai_api_key = "abc-123" }
|
||||
|
||||
fab!(:user)
|
||||
|
||||
it "supports sending an organization id" do
|
||||
SiteSetting.ai_openai_organization = "org_123"
|
||||
|
||||
|
@ -302,6 +304,31 @@ describe DiscourseAi::Inference::OpenAiCompletions do
|
|||
restore_net_http
|
||||
end
|
||||
|
||||
it "supports extremely slow streaming under new interface" do
|
||||
raw_data = <<~TEXT
|
||||
data: {"choices":[{"delta":{"content":"test"}}]}
|
||||
|
||||
data: {"choices":[{"delta":{"content":"test1"}}]}
|
||||
|
||||
data: {"choices":[{"delta":{"content":"test2"}}]}
|
||||
|
||||
data: [DONE]
|
||||
TEXT
|
||||
|
||||
chunks = raw_data.split("")
|
||||
|
||||
stub_request(:post, "https://api.openai.com/v1/chat/completions").to_return(
|
||||
status: 200,
|
||||
body: chunks,
|
||||
)
|
||||
|
||||
partials = []
|
||||
llm = DiscourseAi::Completions::Llm.proxy("gpt-3.5-turbo")
|
||||
llm.completion!({ insts: "test" }, user) { |partial| partials << partial }
|
||||
|
||||
expect(partials.join).to eq("testtest1test2")
|
||||
end
|
||||
|
||||
it "support extremely slow streaming" do
|
||||
raw_data = <<~TEXT
|
||||
data: {"choices":[{"delta":{"content":"test"}}]}
|
||||
|
|
Loading…
Reference in New Issue