FEATURE: allow suppression of notifications from report generation (#533)
* FEATURE: allow suppression of notifications from report generation Previously we needed to do this by hand, unfortunately this uses up too many tokens and is very hard to discover. New option means that we can trivially disable notifications without needing any prompt engineering. * URI.parse is safer, use it
This commit is contained in:
parent
dfc13fc631
commit
d7ed8180af
|
@ -62,6 +62,9 @@ en:
|
|||
allow_secure_categories:
|
||||
label: "Allow secure categories"
|
||||
description: "Allow the report to be generated for topics in secure categories"
|
||||
suppress_notifications:
|
||||
label: "Suppress Notifications"
|
||||
description: "Suppress notifications the report may generate by transforming to content. This will remap mentions and internal links."
|
||||
debug_mode:
|
||||
label: "Debug Mode"
|
||||
description: "Enable debug mode to see the raw input and output of the LLM"
|
||||
|
|
|
@ -40,6 +40,7 @@ if defined?(DiscourseAutomation)
|
|||
field :top_p, component: :text, required: true, default_value: 0.1
|
||||
field :temperature, component: :text, required: true, default_value: 0.2
|
||||
|
||||
field :suppress_notifications, component: :boolean
|
||||
field :debug_mode, component: :boolean
|
||||
|
||||
script do |context, fields, automation|
|
||||
|
@ -70,6 +71,7 @@ if defined?(DiscourseAutomation)
|
|||
temperature = 0.2
|
||||
temperature = fields.dig("temperature", "value").to_f if fields.dig("temperature", "value")
|
||||
|
||||
suppress_notifications = !!fields.dig("suppress_notifications", "value")
|
||||
DiscourseAi::Automation::ReportRunner.run!(
|
||||
sender_username: sender,
|
||||
receivers: receivers,
|
||||
|
@ -90,6 +92,7 @@ if defined?(DiscourseAutomation)
|
|||
exclude_tags: exclude_tags,
|
||||
temperature: temperature,
|
||||
top_p: top_p,
|
||||
suppress_notifications: suppress_notifications,
|
||||
)
|
||||
rescue => e
|
||||
Discourse.warn_exception e, message: "Error running LLM report!"
|
||||
|
|
|
@ -52,7 +52,8 @@ module DiscourseAi
|
|||
exclude_category_ids: nil,
|
||||
exclude_tags: nil,
|
||||
top_p: 0.1,
|
||||
temperature: 0.2
|
||||
temperature: 0.2,
|
||||
suppress_notifications: false
|
||||
)
|
||||
@sender = User.find_by(username: sender_username)
|
||||
@receivers = User.where(username: receivers)
|
||||
|
@ -84,6 +85,7 @@ module DiscourseAi
|
|||
|
||||
@top_p = nil if top_p <= 0
|
||||
@temperature = nil if temperature <= 0
|
||||
@suppress_notifications = suppress_notifications
|
||||
|
||||
if !@topic_id && !@receivers.present? && !@email_receivers.present?
|
||||
raise ArgumentError, "Must specify topic_id or receivers"
|
||||
|
@ -160,6 +162,8 @@ Follow the provided writing composition instructions carefully and precisely ste
|
|||
|
||||
receiver_usernames = @receivers.map(&:username).join(",")
|
||||
|
||||
result = suppress_notifications(result) if @suppress_notifications
|
||||
|
||||
if @topic_id
|
||||
PostCreator.create!(@sender, raw: result, topic_id: @topic_id, skip_validations: true)
|
||||
# no debug mode for topics, it is too noisy
|
||||
|
@ -220,6 +224,44 @@ Follow the provided writing composition instructions carefully and precisely ste
|
|||
"anthropic:#{model}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def suppress_notifications(raw)
|
||||
cooked = PrettyText.cook(raw, sanitize: false)
|
||||
parsed = Nokogiri::HTML5.fragment(cooked)
|
||||
|
||||
parsed
|
||||
.css("a")
|
||||
.each do |a|
|
||||
href = a["href"]
|
||||
if href.present? && (href.start_with?("#{Discourse.base_url}") || href.start_with?("/"))
|
||||
begin
|
||||
uri = URI.parse(href)
|
||||
if uri.query.present?
|
||||
params = CGI.parse(uri.query)
|
||||
params["silent"] = "true"
|
||||
uri.query = URI.encode_www_form(params)
|
||||
else
|
||||
uri.query = "silent=true"
|
||||
end
|
||||
a["href"] = uri.to_s
|
||||
rescue URI::InvalidURIError
|
||||
# skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
parsed
|
||||
.css("span.mention")
|
||||
.each do |mention|
|
||||
mention.replace(
|
||||
"<a href='/u/#{mention.text.sub("@", "")}' class='mention'>#{mention.text}</a>",
|
||||
)
|
||||
end
|
||||
|
||||
parsed.to_html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -81,6 +81,55 @@ module DiscourseAi
|
|||
expect(debugging).not_to include(post_in_category.raw)
|
||||
end
|
||||
|
||||
it "can suppress notifications by remapping content" do
|
||||
markdown = <<~MD
|
||||
@sam is a person
|
||||
[test1](/test) is an internal link
|
||||
[test2](/test?1=2) is an internal link
|
||||
[test3](https://example.com) is an external link
|
||||
[test4](#{Discourse.base_url}) is an internal link
|
||||
<a href='/test'>test5</a> is an internal link
|
||||
[test6](/test?test=test#anchor) is an internal link with fragment
|
||||
[test7](//[[test) is a link with an invalid URL
|
||||
MD
|
||||
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([markdown]) do
|
||||
ReportRunner.run!(
|
||||
sender_username: user.username,
|
||||
receivers: [receiver.username],
|
||||
title: "test report",
|
||||
model: "gpt-4",
|
||||
category_ids: nil,
|
||||
tags: nil,
|
||||
allow_secure_categories: false,
|
||||
debug_mode: false,
|
||||
sample_size: 100,
|
||||
instructions: "make a magic report",
|
||||
days: 7,
|
||||
offset: 0,
|
||||
priority_group_id: nil,
|
||||
tokens_per_post: 150,
|
||||
suppress_notifications: true,
|
||||
)
|
||||
end
|
||||
|
||||
report = Topic.where(title: "test report").first
|
||||
|
||||
# note, magic surprise & is correct HTML 5 representation
|
||||
expected = <<~HTML
|
||||
<p><a href="/u/sam" class="mention">@sam</a> is a person<br>
|
||||
<a href="/test?silent=true">test1</a> is an internal link<br>
|
||||
<a href="/test?1=2&silent=true">test2</a> is an internal link<br>
|
||||
<a href="https://example.com" rel="noopener nofollow ugc">test3</a> is an external link<br>
|
||||
<a href="http://test.localhost?silent=true">test4</a> is an internal link<br>
|
||||
<a href="/test?silent=true">test5</a> is an internal link<br>
|
||||
<a href="/test?test=test&silent=true#anchor">test6</a> is an internal link with fragment<br>
|
||||
<a href="//%5B%5Btest?silent=true" rel="noopener nofollow ugc">test7</a> is a link with an invalid URL</p>
|
||||
HTML
|
||||
|
||||
expect(report.ordered_posts.first.raw.strip).to eq(expected.strip)
|
||||
end
|
||||
|
||||
it "can exclude tags" do
|
||||
freeze_time
|
||||
|
||||
|
|
Loading…
Reference in New Issue