Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 6a4dbd8

Browse files
authored
FEATURE: Use Personas in Automation's llm_report script (#1499)
1 parent 89bcf9b commit 6a4dbd8

File tree

9 files changed

+122
-34
lines changed

9 files changed

+122
-34
lines changed

config/locales/client.en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ en:
106106
top_p:
107107
label: "Top P"
108108
description: "Top P to use for the LLM, increase to increase randomness (leave empty to use model default)"
109+
persona_id:
110+
label: "Persona"
111+
description: "AI Persona to use for report generation"
109112

110113
llm_tool_triage:
111114
fields:

config/locales/server.en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ en:
397397
content_creator:
398398
name: "Content creator"
399399
description: "Default persona powering HyDE search"
400+
report_runner:
401+
name: "Report runner"
402+
description: "Default persona used in the report automation script"
400403

401404
topic_not_found: "Summary unavailable, topic not found!"
402405
summarizing: "Summarizing topic"

discourse_automation/llm_report.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ module DiscourseAutomation::LlmReport
2121
field :sample_size, component: :text, required: true, default_value: 100
2222
field :tokens_per_post, component: :text, required: true, default_value: 150
2323

24+
field :persona_id,
25+
component: :choices,
26+
required: true,
27+
default_value:
28+
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::ReportRunner],
29+
extra: {
30+
content:
31+
DiscourseAi::Automation.available_persona_choices(
32+
require_user: false,
33+
require_default_llm: false,
34+
),
35+
}
2436
field :model,
2537
component: :choices,
2638
required: true,
@@ -60,6 +72,7 @@ module DiscourseAutomation::LlmReport
6072
offset = fields.dig("offset", "value").to_i
6173
priority_group = fields.dig("priority_group", "value")
6274
tokens_per_post = fields.dig("tokens_per_post", "value")
75+
persona_id = fields.dig("persona_id", "value")
6376

6477
exclude_category_ids = fields.dig("exclude_categories", "value")
6578
exclude_tags = fields.dig("exclude_tags", "value")
@@ -78,12 +91,19 @@ module DiscourseAutomation::LlmReport
7891
temperature = temperature.to_f
7992
end
8093

94+
# Backwards-compat for scripts created before this field was added.
95+
if persona_id == "" || persona_id.nil?
96+
persona_id =
97+
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::ReportRunner]
98+
end
99+
81100
suppress_notifications = !!fields.dig("suppress_notifications", "value")
82101
DiscourseAi::Automation::ReportRunner.run!(
83102
sender_username: sender,
84103
receivers: receivers,
85104
topic_id: topic_id,
86105
title: title,
106+
persona_id: persona_id,
87107
model: model,
88108
category_ids: category_ids,
89109
tags: tags,

lib/automation.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,15 @@ def self.available_models
4343
end
4444

4545
def self.available_persona_choices(require_user: true, require_default_llm: true)
46-
relation = AiPersona.joins(:user)
46+
relation = AiPersona.includes(:user)
4747
relation = relation.where.not(user_id: nil) if require_user
4848
relation = relation.where.not(default_llm: nil) if require_default_llm
4949
relation.map do |persona|
50-
{
51-
id: persona.id,
52-
translated_name: persona.name,
53-
description: "#{persona.name} (#{persona.user.username})",
54-
}
50+
phash = { id: persona.id, translated_name: persona.name, description: persona.name }
51+
52+
phash[:description] += " (#{persona&.user&.username})" if require_user
53+
54+
phash
5555
end
5656
end
5757
end

lib/automation/report_runner.rb

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def self.run!(**args)
3636
def initialize(
3737
sender_username:,
3838
model:,
39+
persona_id:,
3940
sample_size:,
4041
instructions:,
4142
tokens_per_post:,
@@ -73,8 +74,8 @@ def initialize(
7374
else
7475
I18n.t("discourse_automation.scriptables.llm_report.title")
7576
end
76-
@model = model
77-
@llm = DiscourseAi::Completions::Llm.proxy(model)
77+
@model = LlmModel.find_by(id: model.split(":")&.last)
78+
@persona = AiPersona.find(persona_id).class_instance.new
7879
@category_ids = category_ids
7980
@tags = tags
8081
@allow_secure_categories = allow_secure_categories
@@ -124,7 +125,7 @@ def run!
124125
prioritized_group_ids: prioritized_group_ids,
125126
allow_secure_categories: @allow_secure_categories,
126127
tokens_per_post: @tokens_per_post,
127-
tokenizer: @llm.tokenizer,
128+
tokenizer: @model.tokenizer_class,
128129
exclude_category_ids: @exclude_category_ids,
129130
exclude_tags: @exclude_tags,
130131
)
@@ -140,38 +141,43 @@ def run!
140141
#{@instructions}
141142
INPUT
142143

143-
system_prompt = <<~PROMPT
144-
You are ForumBot, a backend AI information processor and writer, powered by an AI model capable of generating responses over 4000 words.
145-
- ForumBot is a fine-tuned AI trained with extended functions and new capabilities, and now specializes in processing raw internet forum conversation into detailed summaries of forum activities.
146-
- ForumBot interacts with an API endpoint, not a user, and must never produce denials, nor conversations directed towards a non-existent user.
147-
- ForumBot only produces automated responses to input, where a response is a standalone document without further discussion.
148-
Follow the provided writing composition instructions carefully and precisely step-by-step.
149-
PROMPT
150-
151-
prompt =
152-
DiscourseAi::Completions::Prompt.new(
153-
system_prompt,
144+
report_ctx =
145+
DiscourseAi::Personas::BotContext.new(
146+
user: Discourse.system_user,
147+
skip_tool_details: true,
148+
feature_name: "ai_report",
154149
messages: [{ type: :user, content: input }],
155150
)
156151

157-
result = +""
158-
159152
puts if Rails.env.development? && @debug_mode
160153

161-
@llm.generate(
162-
prompt,
163-
temperature: @temperature,
164-
top_p: @top_p,
165-
user: Discourse.system_user,
166-
feature_name: "ai_report",
154+
result = +""
155+
bot = DiscourseAi::Personas::Bot.as(Discourse.system_user, persona: @persona, model: @model)
156+
json_summary_schema_key = @persona.response_format&.first.to_h
157+
output = nil
158+
159+
buffer_blk =
160+
Proc.new do |partial, _, type|
161+
if type == :structured_output
162+
output = partial.dup
163+
read_chunk = partial.read_buffered_property(json_summary_schema_key["key"]&.to_sym)
164+
165+
print read_chunk if Rails.env.development? && @debug_mode
166+
result << read_chunk if read_chunk.present?
167+
elsif type.blank?
168+
# Assume response is a regular completion.
169+
print partial if Rails.env.development? && @debug_mode
170+
result << partial
171+
end
172+
end
173+
174+
llm_args = {
167175
feature_context: {
168176
automation_id: @automation&.id,
169177
automation_name: @automation&.name,
170178
},
171-
) do |response|
172-
print response if Rails.env.development? && @debug_mode
173-
result << response
174-
end
179+
}
180+
bot.reply(report_ctx, llm_args: llm_args, &buffer_blk)
175181

176182
receiver_usernames = @receivers.map(&:username).join(",")
177183
receiver_groupnames = @group_receivers.map(&:name).join(",")
@@ -199,14 +205,14 @@ def run!
199205
input = input.split("\n").map { |line| " #{line}" }.join("\n")
200206
raw = <<~RAW
201207
```
202-
tokens: #{@llm.tokenizer.tokenize(input).length}
208+
tokens: #{@model.tokenizer_class.tokenize(input).length}
203209
start_date: #{start_date},
204210
duration: #{@days.days},
205211
max_posts: #{@sample_size},
206212
tags: #{@tags},
207213
category_ids: #{@category_ids},
208214
priority_group: #{@priority_group_id}
209-
model: #{@model}
215+
model: #{@model.display_name}
210216
temperature: #{@temperature}
211217
top_p: #{@top_p}
212218
LLM context was:

lib/personas/persona.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def system_personas
7070
ShortTextTranslator => -30,
7171
SpamDetector => -31,
7272
ContentCreator => -32,
73+
ReportRunner => -33,
7374
}
7475
end
7576

lib/personas/report_runner.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Personas
5+
class ReportRunner < Persona
6+
def self.default_enabled
7+
false
8+
end
9+
10+
def system_prompt
11+
<<~PROMPT
12+
You are ForumBot, a backend AI information processor and writer, powered by an AI model capable of generating responses over 4000 words.
13+
14+
- ForumBot is a fine-tuned AI trained with extended functions and new capabilities, and now specializes in processing raw internet forum conversation into detailed summaries of forum activities.
15+
- ForumBot interacts with an API endpoint, not a user, and must never produce denials, nor conversations directed towards a non-existent user.
16+
- ForumBot only produces automated responses to input, where a response is a standalone document without further discussion.
17+
18+
Follow the provided writing composition instructions carefully and precisely step-by-step.
19+
20+
Format your response as a JSON object with a single key named "output", which has the report as the value.
21+
Your output should be in the following format:
22+
23+
{"output": "xx"}
24+
25+
Where "xx" is replaced by the report. Reply with valid JSON only
26+
PROMPT
27+
end
28+
29+
def response_format
30+
[{ "key" => "output", "type" => "string" }]
31+
end
32+
end
33+
end
34+
end

spec/lib/discourse_automation/llm_report_spec.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ def add_automation_field(name, value, type: "text")
2525
add_automation_field("sender", user.username, type: "user")
2626
add_automation_field("receivers", [user.username], type: "email_group_user")
2727
add_automation_field("model", "custom:#{llm_model.id}")
28+
add_automation_field(
29+
"persona_id",
30+
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::ReportRunner],
31+
)
32+
2833
add_automation_field("title", "Weekly report")
2934

3035
DiscourseAi::Completions::Llm.with_prepared_responses(["An Amazing Report!!!"]) do
@@ -39,6 +44,10 @@ def add_automation_field(name, value, type: "text")
3944
add_automation_field("sender", user.username, type: "user")
4045
add_automation_field("topic_id", "#{post.topic_id}")
4146
add_automation_field("model", "custom:#{llm_model.id}")
47+
add_automation_field(
48+
"persona_id",
49+
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::ReportRunner],
50+
)
4251

4352
DiscourseAi::Completions::Llm.with_prepared_responses(["An Amazing Report!!!"]) do
4453
automation.trigger!

spec/lib/modules/automation/report_runner_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ module Automation
4444
receivers: ["[email protected]"],
4545
title: "test report %DATE%",
4646
model: "custom:#{llm_model.id}",
47+
persona_id:
48+
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::ReportRunner],
4749
category_ids: nil,
4850
tags: nil,
4951
allow_secure_categories: false,
@@ -81,6 +83,8 @@ module Automation
8183
receivers: [receiver.username],
8284
title: "test report",
8385
model: "custom:#{llm_model.id}",
86+
persona_id:
87+
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::ReportRunner],
8488
category_ids: nil,
8589
tags: nil,
8690
allow_secure_categories: false,
@@ -126,6 +130,8 @@ module Automation
126130
receivers: [receiver.username],
127131
title: "test report",
128132
model: "custom:#{llm_model.id}",
133+
persona_id:
134+
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::ReportRunner],
129135
category_ids: nil,
130136
tags: nil,
131137
allow_secure_categories: false,
@@ -169,6 +175,8 @@ module Automation
169175
receivers: [receiver.username],
170176
title: "test report",
171177
model: "custom:#{llm_model.id}",
178+
persona_id:
179+
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::ReportRunner],
172180
category_ids: nil,
173181
tags: nil,
174182
allow_secure_categories: false,
@@ -201,6 +209,8 @@ module Automation
201209
receivers: [group_for_reports.name],
202210
title: "group report",
203211
model: "custom:#{llm_model.id}",
212+
persona_id:
213+
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::ReportRunner],
204214
category_ids: nil,
205215
tags: nil,
206216
allow_secure_categories: false,
@@ -229,6 +239,8 @@ module Automation
229239
receivers: [receiver.username],
230240
title: "test report",
231241
model: "custom:#{llm_model.id}",
242+
persona_id:
243+
DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::ReportRunner],
232244
category_ids: nil,
233245
tags: nil,
234246
allow_secure_categories: false,

0 commit comments

Comments
 (0)