FEATURE: Use different personas to power AI helper features.
You can now edit each AI helper prompt individually through personas, limit access to specific groups, set different LLMs, etc.
This commit is contained in:
@@ -25,25 +25,24 @@ module DiscourseAi
|
||||
input = get_text_param!
|
||||
force_default_locale = params[:force_default_locale] || false
|
||||
|
||||
prompt = CompletionPrompt.find_by(id: params[:mode])
|
||||
raise Discourse::InvalidParameters.new(:mode) if params[:mode].blank?
|
||||
|
||||
raise Discourse::InvalidParameters.new(:mode) if !prompt || !prompt.enabled?
|
||||
|
||||
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
|
||||
if params[:mode] == DiscourseAi::AiHelper::Assistant::CUSTOM_PROMPT
|
||||
raise Discourse::InvalidParameters.new(:custom_prompt) if params[:custom_prompt].blank?
|
||||
|
||||
prompt.custom_instruction = params[:custom_prompt]
|
||||
end
|
||||
|
||||
return suggest_thumbnails(input) if prompt.id == CompletionPrompt::ILLUSTRATE_POST
|
||||
if params[:mode] == DiscourseAi::AiHelper::Assistant::ILLUSTRATE_POST
|
||||
return suggest_thumbnails(input)
|
||||
end
|
||||
|
||||
hijack do
|
||||
render json:
|
||||
DiscourseAi::AiHelper::Assistant.new.generate_and_send_prompt(
|
||||
prompt,
|
||||
params[:mode],
|
||||
input,
|
||||
current_user,
|
||||
force_default_locale: force_default_locale,
|
||||
custom_prompt: params[:custom_prompt],
|
||||
),
|
||||
status: 200
|
||||
end
|
||||
@@ -60,13 +59,10 @@ module DiscourseAi
|
||||
input = get_text_param!
|
||||
end
|
||||
|
||||
prompt = CompletionPrompt.enabled_by_name("generate_titles")
|
||||
raise Discourse::InvalidParameters.new(:mode) if !prompt
|
||||
|
||||
hijack do
|
||||
render json:
|
||||
DiscourseAi::AiHelper::Assistant.new.generate_and_send_prompt(
|
||||
prompt,
|
||||
DiscourseAi::AiHelper::Assistant::GENERATE_TITLES,
|
||||
input,
|
||||
current_user,
|
||||
),
|
||||
@@ -115,12 +111,12 @@ module DiscourseAi
|
||||
location = params[:location]
|
||||
raise Discourse::InvalidParameters.new(:location) if !location
|
||||
|
||||
prompt = CompletionPrompt.find_by(id: params[:mode])
|
||||
raise Discourse::InvalidParameters.new(:mode) if params[:mode].blank?
|
||||
if params[:mode] == DiscourseAi::AiHelper::Assistant::ILLUSTRATE_POST
|
||||
return suggest_thumbnails(input)
|
||||
end
|
||||
|
||||
raise Discourse::InvalidParameters.new(:mode) if !prompt || !prompt.enabled?
|
||||
return suggest_thumbnails(input) if prompt.id == CompletionPrompt::ILLUSTRATE_POST
|
||||
|
||||
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
|
||||
if params[:mode] == DiscourseAi::AiHelper::Assistant::CUSTOM_PROMPT
|
||||
raise Discourse::InvalidParameters.new(:custom_prompt) if params[:custom_prompt].blank?
|
||||
end
|
||||
|
||||
@@ -133,7 +129,7 @@ module DiscourseAi
|
||||
:stream_composer_helper,
|
||||
user_id: current_user.id,
|
||||
text: text,
|
||||
prompt: prompt.name,
|
||||
prompt: params[:mode],
|
||||
custom_prompt: params[:custom_prompt],
|
||||
force_default_locale: params[:force_default_locale] || false,
|
||||
client_id: params[:client_id],
|
||||
@@ -149,7 +145,7 @@ module DiscourseAi
|
||||
post_id: post.id,
|
||||
user_id: current_user.id,
|
||||
text: text,
|
||||
prompt: prompt.name,
|
||||
prompt: params[:mode],
|
||||
custom_prompt: params[:custom_prompt],
|
||||
client_id: params[:client_id],
|
||||
)
|
||||
|
||||
@@ -10,19 +10,16 @@ module Jobs
|
||||
return unless args[:text]
|
||||
return unless args[:client_id]
|
||||
|
||||
prompt = CompletionPrompt.enabled_by_name(args[:prompt])
|
||||
|
||||
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
|
||||
prompt.custom_instruction = args[:custom_prompt]
|
||||
end
|
||||
helper_mode = args[:prompt]
|
||||
|
||||
DiscourseAi::AiHelper::Assistant.new.stream_prompt(
|
||||
prompt,
|
||||
helper_mode,
|
||||
args[:text],
|
||||
user,
|
||||
"/discourse-ai/ai-helper/stream_composer_suggestion",
|
||||
force_default_locale: args[:force_default_locale],
|
||||
client_id: args[:client_id],
|
||||
custom_prompt: args[:custom_prompt],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,16 +14,12 @@ module Jobs
|
||||
|
||||
return unless user.guardian.can_see?(post)
|
||||
|
||||
prompt = CompletionPrompt.enabled_by_name(args[:prompt])
|
||||
helper_mode = args[:prompt]
|
||||
|
||||
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
|
||||
prompt.custom_instruction = args[:custom_prompt]
|
||||
end
|
||||
|
||||
if prompt.name == "explain"
|
||||
input = <<~TEXT
|
||||
<term>#{args[:text]}</term>
|
||||
<context>#{post.raw}</context>
|
||||
if helper_mode == DiscourseAi::AiHelper::Assistant::EXPLAIN
|
||||
input = <<~TEXT.strip
|
||||
<term>#{args[:text]}</term>
|
||||
<context>#{post.raw}</context>
|
||||
<topic>#{topic.title}</topic>
|
||||
#{reply_to ? "<replyTo>#{reply_to.raw}</replyTo>" : nil}
|
||||
TEXT
|
||||
@@ -32,10 +28,11 @@ module Jobs
|
||||
end
|
||||
|
||||
DiscourseAi::AiHelper::Assistant.new.stream_prompt(
|
||||
prompt,
|
||||
helper_mode,
|
||||
input,
|
||||
user,
|
||||
"/discourse-ai/ai-helper/stream_suggestion/#{post.id}",
|
||||
custom_prompt: args[:custom_prompt],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ module Jobs
|
||||
return if !SiteSetting.discourse_ai_enabled
|
||||
return if !SiteSetting.ai_translation_enabled
|
||||
limit = SiteSetting.ai_translation_backfill_rate
|
||||
|
||||
return if limit == 0
|
||||
|
||||
topics = Topic.where(locale: nil, deleted_at: nil).where("topics.user_id > 0")
|
||||
|
||||
@@ -73,7 +73,7 @@ export default class AiComposerHelperMenu extends Component {
|
||||
}
|
||||
|
||||
prompts.forEach((p) => {
|
||||
this.prompts[p.id] = p;
|
||||
this.prompts[p.name] = p;
|
||||
});
|
||||
|
||||
this.promptTypes = prompts.reduce((memo, p) => {
|
||||
@@ -116,7 +116,7 @@ export default class AiComposerHelperMenu extends Component {
|
||||
if (option.name === "illustrate_post") {
|
||||
return this.modal.show(ThumbnailSuggestion, {
|
||||
model: {
|
||||
mode: option.id,
|
||||
mode: option.name,
|
||||
selectedText: this.args.data.selectedText,
|
||||
thumbnails: this.thumbnailSuggestions,
|
||||
},
|
||||
@@ -128,7 +128,7 @@ export default class AiComposerHelperMenu extends Component {
|
||||
|
||||
return this.modal.show(ModalDiffModal, {
|
||||
model: {
|
||||
mode: option.id,
|
||||
mode: option.name,
|
||||
selectedText: this.args.data.selectedText,
|
||||
revert: this.undoAiAction,
|
||||
toolbarEvent: this.args.data.toolbarEvent,
|
||||
|
||||
@@ -29,13 +29,12 @@ export default class AiHelperOptionsList extends Component {
|
||||
@submit={{@performAction}}
|
||||
/>
|
||||
{{else}}
|
||||
<li data-name={{option.translated_name}} data-value={{option.id}}>
|
||||
<li data-name={{option.translated_name}} data-value={{option.name}}>
|
||||
<DButton
|
||||
@icon={{option.icon}}
|
||||
@translatedLabel={{option.translated_name}}
|
||||
@action={{fn @performAction option}}
|
||||
data-name={{option.name}}
|
||||
data-value={{option.id}}
|
||||
class="ai-helper-options__button"
|
||||
>
|
||||
{{#if (and (eq option.name "proofread") this.showShortcut)}}
|
||||
|
||||
@@ -202,7 +202,7 @@ export default class AiPostHelperMenu extends Component {
|
||||
this._activeAiRequest = ajax("/discourse-ai/ai-helper/suggest", {
|
||||
method: "POST",
|
||||
data: {
|
||||
mode: option.id,
|
||||
mode: option.name,
|
||||
text: this.args.data.quoteState.buffer,
|
||||
custom_prompt: this.customPromptValue,
|
||||
},
|
||||
@@ -238,7 +238,7 @@ export default class AiPostHelperMenu extends Component {
|
||||
method: "POST",
|
||||
data: {
|
||||
location: "post",
|
||||
mode: option.id,
|
||||
mode: option.name,
|
||||
text: this.args.data.selectedText,
|
||||
post_id: this.args.data.quoteState.postId,
|
||||
custom_prompt: this.customPromptValue,
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ export default class AiEditSuggestionButton extends Component {
|
||||
this._activeAIRequest = ajax("/discourse-ai/ai-helper/suggest", {
|
||||
method: "POST",
|
||||
data: {
|
||||
mode: this.mode.id,
|
||||
mode: this.mode.name,
|
||||
text: this.args.outletArgs.initialValue,
|
||||
custom_prompt: "",
|
||||
},
|
||||
|
||||
@@ -44,7 +44,7 @@ function initializeAiHelperTrigger(api) {
|
||||
|
||||
const mode = currentUser?.ai_helper_prompts.find(
|
||||
(p) => p.name === "proofread"
|
||||
).id;
|
||||
).name;
|
||||
|
||||
modal.show(ModalDiffModal, {
|
||||
model: {
|
||||
|
||||
@@ -339,6 +339,33 @@ en:
|
||||
concept_deduplicator:
|
||||
name: "Concept Deduplicator"
|
||||
description: "AI Bot specialized in deduplicating concepts"
|
||||
custom_prompt:
|
||||
name: "Custom Prompt"
|
||||
description: "Default persona powering the AI helper's custom prompt feature"
|
||||
smart_dates:
|
||||
name: "Smart Dates"
|
||||
description: "Default persona powering the AI helper's smart dates feature"
|
||||
markdown_table_generator:
|
||||
name: "Markdown Table Generator"
|
||||
description: "Default persona powering the AI helper's generate Markdown table feature"
|
||||
post_illustrator:
|
||||
name: "Post Illustrator"
|
||||
description: "Generates StableDiffusion prompts to power the AI helper's illustrate post feature"
|
||||
proofreader:
|
||||
name: "Proofreader"
|
||||
description: "Default persona powering the AI helper's proofread text feature"
|
||||
titles_generator:
|
||||
name: "Titles Generator"
|
||||
description: "Default persona powering the AI helper's suggest topic titles feature"
|
||||
tutor:
|
||||
name: "Tutor"
|
||||
description: "Default persona powering the AI helper's explain feature"
|
||||
translator:
|
||||
name: "Translator"
|
||||
description: "Default persona powering the AI helper's translator feature"
|
||||
image_captioner:
|
||||
name: "Image Captions"
|
||||
description: "Default persona powering the AI helper's image caption feature"
|
||||
topic_not_found: "Summary unavailable, topic not found!"
|
||||
summarizing: "Summarizing topic"
|
||||
searching: "Searching for: '%{query}'"
|
||||
|
||||
+40
-2
@@ -104,13 +104,14 @@ discourse_ai:
|
||||
allow_any: false
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::LlmEnumerator"
|
||||
validator: "DiscourseAi::Configuration::LlmValidator"
|
||||
ai_helper_custom_prompts_allowed_groups:
|
||||
hidden: true
|
||||
ai_helper_custom_prompts_allowed_groups: # Deprecated. TODO(roman): Remove 2025-09-01
|
||||
type: group_list
|
||||
list_type: compact
|
||||
default: "3" # 3: @staff
|
||||
allow_any: false
|
||||
refresh: true
|
||||
hidden: true
|
||||
post_ai_helper_allowed_groups:
|
||||
type: group_list
|
||||
list_type: compact
|
||||
@@ -143,6 +144,7 @@ discourse_ai:
|
||||
default: ""
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::LlmVisionEnumerator"
|
||||
hidden: true
|
||||
ai_auto_image_caption_allowed_groups:
|
||||
client: true
|
||||
type: group_list
|
||||
@@ -160,6 +162,42 @@ discourse_ai:
|
||||
hidden: true
|
||||
type: list
|
||||
list_type: compact
|
||||
ai_helper_proofreader_persona:
|
||||
default: "-22"
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||
ai_helper_tittle_suggestions_persona:
|
||||
default: "-23"
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||
ai_helper_explain_persona:
|
||||
default: "-24"
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||
ai_helper_post_illustrator_persona:
|
||||
default: "-21"
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||
ai_helper_smart_dates_persona:
|
||||
default: "-19"
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||
ai_helper_translator_persona:
|
||||
default: "-25"
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||
ai_helper_markdown_tables_persona:
|
||||
default: "-20"
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||
ai_helper_custom_prompt_persona:
|
||||
default: "-18"
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||
ai_helper_image_caption_persona:
|
||||
default: "-26"
|
||||
type: enum
|
||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||
|
||||
ai_embeddings_enabled:
|
||||
default: false
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
CompletionPrompt.seed do |cp|
|
||||
cp.id = -301
|
||||
cp.name = "translate"
|
||||
cp.prompt_type = CompletionPrompt.prompt_types[:text]
|
||||
cp.stop_sequences = ["\n</output>", "</output>"]
|
||||
cp.temperature = 0.2
|
||||
cp.messages = {
|
||||
insts: <<~TEXT,
|
||||
I want you to act as an %LANGUAGE% translator, spelling corrector and improver. I will write to you
|
||||
in any language and you will detect the language, translate it and answer in the corrected and
|
||||
improved version of my text, in %LANGUAGE%. I want you to replace my simplified A0-level words and
|
||||
sentences with more beautiful and elegant, upper level %LANGUAGE% words and sentences.
|
||||
Keep the meaning same, but make them more literary. I want you to only reply the correction,
|
||||
the improvements and nothing else, do not write explanations.
|
||||
You will find the text between <input></input> XML tags.
|
||||
Include your translation between <output></output> XML tags.
|
||||
TEXT
|
||||
examples: [["<input>Hello</input>", "<output>...%LANGUAGE% translation...</output>"]],
|
||||
}
|
||||
end
|
||||
|
||||
CompletionPrompt.seed do |cp|
|
||||
cp.id = -303
|
||||
cp.name = "proofread"
|
||||
cp.prompt_type = CompletionPrompt.prompt_types[:diff]
|
||||
cp.temperature = 0
|
||||
cp.stop_sequences = ["\n</output>"]
|
||||
cp.messages = {
|
||||
insts: <<~TEXT,
|
||||
You are a markdown proofreader. You correct egregious typos and phrasing issues but keep the user's original voice.
|
||||
You do not touch code blocks. I will provide you with text to proofread. If nothing needs fixing, then you will echo the text back.
|
||||
You will find the text between <input></input> XML tags.
|
||||
You will ALWAYS return the corrected text between <output></output> XML tags.
|
||||
TEXT
|
||||
examples: [
|
||||
[
|
||||
"<input></input>",
|
||||
"<output></output>",
|
||||
],
|
||||
[<<~TEXT, "The rain in Spain, stays mainly in the Plane."],
|
||||
<input>
|
||||
The rain in spain stays mainly in the plane.
|
||||
</input>
|
||||
TEXT
|
||||
[
|
||||
"<input>The rain in Spain, stays mainly in the Plane.</input>",
|
||||
"<output>The rain in Spain, stays mainly in the Plane.</output>",
|
||||
],
|
||||
[<<~TEXT, <<~TEXT],
|
||||
<input>
|
||||
Hello,
|
||||
|
||||
Sometimes the logo isn't changing automatically when color scheme changes.
|
||||
|
||||

|
||||
</input>
|
||||
TEXT
|
||||
<output>
|
||||
Hello,
|
||||
Sometimes the logo does not change automatically when the color scheme changes.
|
||||

|
||||
</output>
|
||||
TEXT
|
||||
[<<~TEXT, <<~TEXT],
|
||||
<input>
|
||||
Any ideas what is wrong with this peace of cod?
|
||||
> This quot contains a typo
|
||||
```ruby
|
||||
# this has speling mistakes
|
||||
testin.atypo = 11
|
||||
baad = "bad"
|
||||
```
|
||||
</input>
|
||||
TEXT
|
||||
<output>
|
||||
Any ideas what is wrong with this piece of code?
|
||||
> This quot contains a typo
|
||||
```ruby
|
||||
# This has spelling mistakes
|
||||
testing.a_typo = 11
|
||||
bad = "bad"
|
||||
```
|
||||
</output>
|
||||
TEXT
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
CompletionPrompt.seed do |cp|
|
||||
cp.id = -304
|
||||
cp.name = "markdown_table"
|
||||
cp.prompt_type = CompletionPrompt.prompt_types[:diff]
|
||||
cp.temperature = 0.5
|
||||
cp.stop_sequences = ["\n</output>"]
|
||||
cp.messages = {
|
||||
insts: <<~TEXT,
|
||||
You are a markdown table formatter, I will provide you text inside <input></input> XML tags and you will format it into a markdown table
|
||||
TEXT
|
||||
examples: [
|
||||
["<input>sam,joe,jane\nage: 22| 10|11</input>", <<~TEXT],
|
||||
<output>
|
||||
| | sam | joe | jane |
|
||||
|---|---|---|---|
|
||||
| age | 22 | 10 | 11 |
|
||||
</output>
|
||||
TEXT
|
||||
[<<~TEXT, <<~TEXT],
|
||||
<input>
|
||||
sam: speed 100, age 22
|
||||
jane: age 10
|
||||
fred: height 22
|
||||
</input>
|
||||
TEXT
|
||||
<output>
|
||||
| | speed | age | height |
|
||||
|---|---|---|---|
|
||||
| sam | 100 | 22 | - |
|
||||
| jane | - | 10 | - |
|
||||
| fred | - | - | 22 |
|
||||
</output>
|
||||
TEXT
|
||||
[<<~TEXT, <<~TEXT],
|
||||
<input>
|
||||
chrome 22ms (first load 10ms)
|
||||
firefox 10ms (first load: 9ms)
|
||||
</input>
|
||||
TEXT
|
||||
<output>
|
||||
| Browser | Load Time (ms) | First Load Time (ms) |
|
||||
|---|---|---|
|
||||
| Chrome | 22 | 10 |
|
||||
| Firefox | 10 | 9 |
|
||||
</output>
|
||||
TEXT
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
CompletionPrompt.seed do |cp|
|
||||
cp.id = -305
|
||||
cp.name = "custom_prompt"
|
||||
cp.prompt_type = CompletionPrompt.prompt_types[:diff]
|
||||
cp.messages = { insts: <<~TEXT }
|
||||
You are a helpful assistant. I will give you instructions inside <input></input> XML tags.
|
||||
You will look at them and reply with a result.
|
||||
TEXT
|
||||
end
|
||||
|
||||
CompletionPrompt.seed do |cp|
|
||||
cp.id = -306
|
||||
cp.name = "explain"
|
||||
cp.prompt_type = CompletionPrompt.prompt_types[:text]
|
||||
cp.messages = { insts: <<~TEXT }
|
||||
You are a tutor explaining a term to a student in a specific context.
|
||||
|
||||
I will provide everything you need to know inside <input> tags, which consists of the term I want you
|
||||
to explain inside <term> tags, the context of where it was used inside <context> tags, the title of
|
||||
the topic where it was used inside <topic> tags, and optionally, the previous post in the conversation
|
||||
in <replyTo> tags.
|
||||
|
||||
Using all this information, write a paragraph with a brief explanation
|
||||
of what the term means. Format the response using Markdown. Reply only with the explanation and
|
||||
nothing more.
|
||||
TEXT
|
||||
end
|
||||
|
||||
CompletionPrompt.seed do |cp|
|
||||
cp.id = -307
|
||||
cp.name = "generate_titles"
|
||||
cp.prompt_type = CompletionPrompt.prompt_types[:list]
|
||||
cp.messages = {
|
||||
insts: <<~TEXT,
|
||||
I want you to act as a title generator for written pieces. I will provide you with a text,
|
||||
and you will generate five titles. Please keep the title concise and under 20 words,
|
||||
and ensure that the meaning is maintained. Replies will utilize the language type of the topic.
|
||||
I want you to only reply the list of options and nothing else, do not write explanations.
|
||||
Never ever use colons in the title. Always use sentence case, using a capital letter at
|
||||
the start of the title, never start the title with a lower case letter. Proper nouns in the title
|
||||
can have a capital letter, and acronyms like LLM can use capital letters. Format some titles
|
||||
as questions, some as statements. Make sure to use question marks if the title is a question.
|
||||
Each title you generate must be separated by *
|
||||
You will find the text between <input></input> XML tags.
|
||||
TEXT
|
||||
examples: [
|
||||
[
|
||||
"<input>In the labyrinth of time, a solitary horse, etched in gold by the setting sun, embarked on an infinite journey.</input>",
|
||||
"<item>The solitary horse</item><item>The horse etched in gold</item><item>A horse's infinite journey</item><item>A horse lost in time</item><item>A horse's last ride</item>",
|
||||
],
|
||||
],
|
||||
post_insts: "Wrap each title between <item></item> XML tags.",
|
||||
}
|
||||
end
|
||||
|
||||
CompletionPrompt.seed do |cp|
|
||||
cp.id = -308
|
||||
cp.name = "illustrate_post"
|
||||
cp.prompt_type = CompletionPrompt.prompt_types[:list]
|
||||
cp.messages = {}
|
||||
end
|
||||
|
||||
CompletionPrompt.seed do |cp|
|
||||
cp.id = -309
|
||||
cp.name = "detect_text_locale"
|
||||
cp.prompt_type = CompletionPrompt.prompt_types[:text]
|
||||
cp.messages = {
|
||||
insts: <<~TEXT,
|
||||
I want you to act as a language expert, determining the locale for a set of text.
|
||||
The locale is a language identifier, such as "en" for English, "de" for German, etc,
|
||||
and can also include a region identifier, such as "en-GB" for British English, or "zh-Hans" for Simplified Chinese.
|
||||
I will provide you with text, and you will determine the locale of the text.
|
||||
You will find the text between <input></input> XML tags.
|
||||
Include your locale between <output></output> XML tags.
|
||||
TEXT
|
||||
examples: [["<input>Hello my favourite colour is red</input>", "<output>en-GB</output>"]],
|
||||
}
|
||||
end
|
||||
|
||||
CompletionPrompt.seed do |cp|
|
||||
cp.id = -310
|
||||
cp.name = "replace_dates"
|
||||
cp.prompt_type = CompletionPrompt.prompt_types[:diff]
|
||||
cp.temperature = 0
|
||||
cp.stop_sequences = ["\n</output>"]
|
||||
cp.messages = {
|
||||
insts: <<~TEXT,
|
||||
You are a date and time formatter for Discourse posts. Convert natural language time references into date placeholders.
|
||||
Do not modify any markdown, code blocks, or existing date formats.
|
||||
|
||||
Here's the temporal context:
|
||||
{{temporal_context}}
|
||||
|
||||
Available date placeholder formats:
|
||||
- Simple day without time: {{date:1}} for tomorrow, {{date:7}} for a week from today
|
||||
- Specific time: {{datetime:2pm+1}} for 2 PM tomorrow
|
||||
- Time range: {{datetime:2pm+1:4pm+1}} for tomorrow 2 PM to 4 PM
|
||||
|
||||
You will find the text between <input></input> XML tags.
|
||||
Return the text with dates converted between <output></output> XML tags.
|
||||
TEXT
|
||||
examples: [
|
||||
[
|
||||
"<input>The meeting is at 2pm tomorrow</input>",
|
||||
"<output>The meeting is at {{datetime:2pm+1}}</output>",
|
||||
],
|
||||
["<input>Due in 3 days</input>", "<output>Due {{date:3}}</output>"],
|
||||
[
|
||||
"<input>Meeting next Tuesday at 2pm</input>",
|
||||
"<output>Meeting {{next_week:tuesday-2pm}}</output>",
|
||||
],
|
||||
[
|
||||
"<input>Meeting from 2pm to 4pm tomorrow</input>",
|
||||
"<output>Meeting {{datetime:2pm+1:4pm+1}}</output>",
|
||||
],
|
||||
[
|
||||
"<input>Meeting notes for tomorrow:
|
||||
* Action items in `config.rb`
|
||||
* Review PR #1234
|
||||
* Deadline is 5pm
|
||||
* Check [this link](https://example.com)</input>",
|
||||
"<output>Meeting notes for {{date:1}}:
|
||||
* Action items in `config.rb`
|
||||
* Review PR #1234
|
||||
* Deadline is {{datetime:5pm+1}}
|
||||
* Check [this link](https://example.com)</output>",
|
||||
],
|
||||
],
|
||||
}
|
||||
end
|
||||
@@ -3,10 +3,13 @@
|
||||
summarization_personas = [DiscourseAi::Personas::Summarizer, DiscourseAi::Personas::ShortSummarizer]
|
||||
|
||||
def from_setting(setting_name)
|
||||
DB.query_single(
|
||||
"SELECT value FROM site_settings WHERE name = :setting_name",
|
||||
setting_name: setting_name,
|
||||
)
|
||||
DB
|
||||
.query_single(
|
||||
"SELECT value FROM site_settings WHERE name = :setting_name",
|
||||
setting_name: setting_name,
|
||||
)
|
||||
&.first
|
||||
&.split("|")
|
||||
end
|
||||
|
||||
DiscourseAi::Personas::Persona.system_personas.each do |persona_class, id|
|
||||
@@ -28,7 +31,11 @@ DiscourseAi::Personas::Persona.system_personas.each do |persona_class, id|
|
||||
default_groups = [Group::AUTO_GROUPS[:everyone]]
|
||||
end
|
||||
|
||||
persona.allowed_group_ids = from_setting(setting_name).first&.split("|") || default_groups
|
||||
persona.allowed_group_ids = from_setting(setting_name) || default_groups
|
||||
elsif persona_class == DiscourseAi::Personas::CustomPrompt
|
||||
setting_name = "ai_helper_custom_prompts_allowed_groups"
|
||||
default_groups = [Group::AUTO_GROUPS[:staff]]
|
||||
persona.allowed_group_ids = from_setting(setting_name) || default_groups
|
||||
else
|
||||
persona.allowed_group_ids = [Group::AUTO_GROUPS[:trust_level_0]]
|
||||
end
|
||||
|
||||
+2
-3
@@ -188,8 +188,7 @@ class DiscourseAi::Evals::Eval
|
||||
end
|
||||
|
||||
def helper(llm, input:, name:, locale: nil)
|
||||
completion_prompt = CompletionPrompt.find_by(name: name)
|
||||
helper = DiscourseAi::AiHelper::Assistant.new(helper_llm: llm.llm_proxy)
|
||||
helper = DiscourseAi::AiHelper::Assistant.new(helper_llm: llm.llm_model)
|
||||
user = Discourse.system_user
|
||||
if locale
|
||||
user = User.new
|
||||
@@ -202,7 +201,7 @@ class DiscourseAi::Evals::Eval
|
||||
end
|
||||
result =
|
||||
helper.generate_and_send_prompt(
|
||||
completion_prompt,
|
||||
name,
|
||||
input,
|
||||
current_user = user,
|
||||
_force_default_locale = false,
|
||||
|
||||
+248
-138
File diff suppressed because it is too large
Load Diff
@@ -41,8 +41,6 @@ module DiscourseAi
|
||||
"The number of completions you requested exceed the number of canned responses"
|
||||
end
|
||||
|
||||
response = as_structured_output(response) if model_params[:response_format].present?
|
||||
|
||||
raise response if response.is_a?(StandardError)
|
||||
|
||||
@completions += 1
|
||||
@@ -57,8 +55,9 @@ module DiscourseAi
|
||||
yield(response, cancel_fn)
|
||||
elsif is_thinking?(response)
|
||||
yield(response, cancel_fn)
|
||||
elsif is_structured_output?(response)
|
||||
yield(response, cancel_fn)
|
||||
elsif model_params[:response_format].present?
|
||||
structured_output = as_structured_output(response)
|
||||
yield(structured_output, cancel_fn)
|
||||
else
|
||||
response.each_char do |char|
|
||||
break if cancelled
|
||||
@@ -69,6 +68,7 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
response = response.first if response.is_a?(Array) && response.length == 1
|
||||
response = as_structured_output(response) if model_params[:response_format].present?
|
||||
|
||||
response
|
||||
end
|
||||
@@ -87,10 +87,6 @@ module DiscourseAi
|
||||
response.is_a?(DiscourseAi::Completions::ToolCall)
|
||||
end
|
||||
|
||||
def is_structured_output?(response)
|
||||
response.is_a?(DiscourseAi::Completions::StructuredOutput)
|
||||
end
|
||||
|
||||
def as_structured_output(response)
|
||||
schema_properties = model_params[:response_format].dig(:json_schema, :schema, :properties)
|
||||
return response if schema_properties.blank?
|
||||
|
||||
@@ -10,7 +10,7 @@ module DiscourseAi
|
||||
def valid_value?(val)
|
||||
return true if val == "f"
|
||||
|
||||
if @opts[:name] == :ai_summarization_enabled
|
||||
if @opts[:name] == :ai_summarization_enabled || @opts[:name] == :ai_helper_enabled
|
||||
has_llms = LlmModel.count > 0
|
||||
@no_llms_configured = !has_llms
|
||||
has_llms
|
||||
|
||||
@@ -18,7 +18,10 @@ module DiscourseAi
|
||||
:feature_name,
|
||||
:resource_url,
|
||||
:cancel_manager,
|
||||
:inferred_concepts
|
||||
:inferred_concepts,
|
||||
:format_dates,
|
||||
:temporal_context,
|
||||
:user_language
|
||||
|
||||
def initialize(
|
||||
post: nil,
|
||||
@@ -37,13 +40,15 @@ module DiscourseAi
|
||||
feature_name: "bot",
|
||||
resource_url: nil,
|
||||
cancel_manager: nil,
|
||||
inferred_concepts: []
|
||||
inferred_concepts: [],
|
||||
format_dates: false
|
||||
)
|
||||
@participants = participants
|
||||
@user = user
|
||||
@skip_tool_details = skip_tool_details
|
||||
@messages = messages
|
||||
@custom_instructions = custom_instructions
|
||||
@format_dates = format_dates
|
||||
|
||||
@message_id = message_id
|
||||
@channel_id = channel_id
|
||||
@@ -78,6 +83,8 @@ module DiscourseAi
|
||||
participants
|
||||
resource_url
|
||||
inferred_concepts
|
||||
user_language
|
||||
temporal_context
|
||||
]
|
||||
|
||||
def lookup_template_param(key)
|
||||
@@ -125,6 +132,8 @@ module DiscourseAi
|
||||
feature_name: @feature_name,
|
||||
resource_url: @resource_url,
|
||||
inferred_concepts: @inferred_concepts,
|
||||
user_language: @user_language,
|
||||
temporal_context: @temporal_context,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Personas
|
||||
class CustomPrompt < Persona
|
||||
def self.default_enabled
|
||||
false
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
<<~PROMPT.strip
|
||||
You are a helpful assistant. I will give you instructions inside <input></input> XML tags.
|
||||
You will look at them and reply with a result.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "output", "type" => "string" }]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Personas
|
||||
class ImageCaptioner < Persona
|
||||
def self.default_enabled
|
||||
false
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
"You are a bot specializing in image captioning."
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "output", "type" => "string" }]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Personas
|
||||
class MarkdownTableGenerator < Persona
|
||||
def self.default_enabled
|
||||
false
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
<<~PROMPT.strip
|
||||
You are a markdown table formatter, I will provide you text inside <input></input> XML tags and you will format it into a markdown table
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "output", "type" => "string" }]
|
||||
end
|
||||
|
||||
def temperature
|
||||
0.5
|
||||
end
|
||||
|
||||
def examples
|
||||
[
|
||||
["<input>sam,joe,jane\nage: 22| 10|11</input>", { output: <<~TEXT }.to_json],
|
||||
| | sam | joe | jane |
|
||||
|---|---|---|---|
|
||||
| age | 22 | 10 | 11 |
|
||||
TEXT
|
||||
[<<~TEXT, { output: <<~TEXT }.to_json],
|
||||
<input>
|
||||
sam: speed 100, age 22
|
||||
jane: age 10
|
||||
fred: height 22
|
||||
</input>
|
||||
TEXT
|
||||
| | speed | age | height |
|
||||
|---|---|---|---|
|
||||
| sam | 100 | 22 | - |
|
||||
| jane | - | 10 | - |
|
||||
| fred | - | - | 22 |
|
||||
TEXT
|
||||
[<<~TEXT, { output: <<~TEXT }.to_json],
|
||||
<input>
|
||||
chrome 22ms (first load 10ms)
|
||||
firefox 10ms (first load: 9ms)
|
||||
</input>
|
||||
TEXT
|
||||
| Browser | Load Time (ms) | First Load Time (ms) |
|
||||
|---|---|---|
|
||||
| Chrome | 22 | 10 |
|
||||
| Firefox | 10 | 9 |
|
||||
TEXT
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+18
-4
@@ -55,6 +55,15 @@ module DiscourseAi
|
||||
ConceptFinder => -15,
|
||||
ConceptMatcher => -16,
|
||||
ConceptDeduplicator => -17,
|
||||
CustomPrompt => -18,
|
||||
SmartDates => -19,
|
||||
MarkdownTableGenerator => -20,
|
||||
PostIllustrator => -21,
|
||||
Proofreader => -22,
|
||||
TitlesGenerator => -23,
|
||||
Tutor => -24,
|
||||
Translator => -25,
|
||||
ImageCaptioner => -26,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -260,10 +269,15 @@ module DiscourseAi
|
||||
protected
|
||||
|
||||
def replace_placeholders(content, context)
|
||||
content.gsub(/\{(\w+)\}/) do |match|
|
||||
found = context.lookup_template_param(match[1..-2])
|
||||
found.nil? ? match : found.to_s
|
||||
end
|
||||
replaced =
|
||||
content.gsub(/\{(\w+)\}/) do |match|
|
||||
found = context.lookup_template_param(match[1..-2])
|
||||
found.nil? ? match : found.to_s
|
||||
end
|
||||
|
||||
return replaced if !context.format_dates
|
||||
|
||||
::DiscourseAi::AiHelper::DateFormatter.process_date_placeholders(replaced, context.user)
|
||||
end
|
||||
|
||||
def tool_instance(tool_call, bot_user:, llm:, context:, existing_tools:)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Personas
|
||||
class PostIllustrator < Persona
|
||||
def self.default_enabled
|
||||
false
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
<<~PROMPT.strip
|
||||
Provide me a StableDiffusion prompt to generate an image that illustrates the following post in 40 words or less, be creative.
|
||||
You'll find the post between <input></input> XML tags.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "output", "type" => "string" }]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Personas
|
||||
class Proofreader < Persona
|
||||
def self.default_enabled
|
||||
false
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
<<~PROMPT.strip
|
||||
You are a markdown proofreader. You correct egregious typos and phrasing issues but keep the user's original voice.
|
||||
You do not touch code blocks. I will provide you with text to proofread. If nothing needs fixing, then you will echo the text back.
|
||||
You will find the text between <input></input> XML tags.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "output", "type" => "string" }]
|
||||
end
|
||||
|
||||
def examples
|
||||
[
|
||||
[
|
||||
"<input></input>",
|
||||
{ output: "" }.to_json,
|
||||
],
|
||||
[
|
||||
"<input>The rain in spain stays mainly in the plane.</input>",
|
||||
{ output: "The rain in Spain, stays mainly in the Plane." }.to_json,
|
||||
],
|
||||
[
|
||||
"<input>The rain in Spain, stays mainly in the Plane.</input>",
|
||||
{ output: "The rain in Spain, stays mainly in the Plane." }.to_json,
|
||||
],
|
||||
[<<~TEXT, { output: <<~TEXT }.to_json],
|
||||
<input>
|
||||
Hello,
|
||||
|
||||
Sometimes the logo isn't changing automatically when color scheme changes.
|
||||
|
||||

|
||||
</input>
|
||||
TEXT
|
||||
Hello,
|
||||
Sometimes the logo does not change automatically when the color scheme changes.
|
||||

|
||||
TEXT
|
||||
[<<~TEXT, { output: <<~TEXT }.to_json],
|
||||
<input>
|
||||
Any ideas what is wrong with this peace of cod?
|
||||
> This quot contains a typo
|
||||
```ruby
|
||||
# this has speling mistakes
|
||||
testin.atypo = 11
|
||||
baad = "bad"
|
||||
```
|
||||
</input>
|
||||
TEXT
|
||||
Any ideas what is wrong with this piece of code?
|
||||
> This quot contains a typo
|
||||
```ruby
|
||||
# This has spelling mistakes
|
||||
testing.a_typo = 11
|
||||
bad = "bad"
|
||||
```
|
||||
TEXT
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Personas
|
||||
class SmartDates < Persona
|
||||
def self.default_enabled
|
||||
false
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
<<~PROMPT.strip
|
||||
You are a date and time formatter for Discourse posts. Convert natural language time references into date placeholders.
|
||||
Do not modify any markdown, code blocks, or existing date formats.
|
||||
|
||||
Here's the temporal context:
|
||||
{temporal_context}
|
||||
|
||||
Available date placeholder formats:
|
||||
- Simple day without time: {{date:1}} for tomorrow, {{date:7}} for a week from today
|
||||
- Specific time: {{datetime:2pm+1}} for 2 PM tomorrow
|
||||
- Time range: {{datetime:2pm+1:4pm+1}} for tomorrow 2 PM to 4 PM
|
||||
|
||||
You will find the text between <input></input> XML tags.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "output", "type" => "string" }]
|
||||
end
|
||||
|
||||
def examples
|
||||
[
|
||||
[
|
||||
"<input>The meeting is at 2pm tomorrow</input>",
|
||||
{ output: "The meeting is at {{datetime:2pm+1}}" }.to_json,
|
||||
],
|
||||
["<input>Due in 3 days</input>", { output: "Due {{date:3}}" }.to_json],
|
||||
[
|
||||
"<input>Meeting next Tuesday at 2pm</input>",
|
||||
{ output: "Meeting {{next_week:tuesday-2pm}}" }.to_json,
|
||||
],
|
||||
[
|
||||
"<input>Meeting from 2pm to 4pm tomorrow</input>",
|
||||
{ output: "Meeting {{datetime:2pm+1:4pm+1}}" }.to_json,
|
||||
],
|
||||
[<<~TEXT, { output: <<~TEXT }.to_json],
|
||||
<input>Meeting notes for tomorrow:
|
||||
* Action items in `config.rb`
|
||||
* Review PR #1234
|
||||
* Deadline is 5pm
|
||||
* Check [this link](https://example.com)</input>
|
||||
TEXT
|
||||
Meeting notes for {{date:1}}:
|
||||
* Action items in `config.rb`
|
||||
* Review PR #1234
|
||||
* Deadline is {{datetime:5pm+1}}
|
||||
* Check [this link](https://example.com)
|
||||
TEXT
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Personas
|
||||
class TitlesGenerator < Persona
|
||||
def self.default_enabled
|
||||
false
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
<<~PROMPT.strip
|
||||
I want you to act as a title generator for written pieces. I will provide you with a text,
|
||||
and you will generate five titles. Please keep the title concise and under 20 words,
|
||||
and ensure that the meaning is maintained. Replies will utilize the language type of the topic.
|
||||
I want you to only reply the list of options and nothing else, do not write explanations.
|
||||
Never ever use colons in the title. Always use sentence case, using a capital letter at
|
||||
the start of the title, never start the title with a lower case letter. Proper nouns in the title
|
||||
can have a capital letter, and acronyms like LLM can use capital letters. Format some titles
|
||||
as questions, some as statements. Make sure to use question marks if the title is a question.
|
||||
You will find the text between <input></input> XML tags.
|
||||
Wrap each title between <item></item> XML tags.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "output", "type" => "string" }]
|
||||
end
|
||||
|
||||
def examples
|
||||
[
|
||||
[
|
||||
"<input>In the labyrinth of time, a solitary horse, etched in gold by the setting sun, embarked on an infinite journey.</input>",
|
||||
"<item>The solitary horse</item><item>The horse etched in gold</item><item>A horse's infinite journey</item><item>A horse lost in time</item><item>A horse's last ride</item>",
|
||||
],
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Personas
|
||||
class Translator < Persona
|
||||
def self.default_enabled
|
||||
false
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
<<~PROMPT.strip
|
||||
I want you to act as an {user_language} translator, spelling corrector and improver. I will write to you
|
||||
in any language and you will detect the language, translate it and answer in the corrected and
|
||||
improved version of my text, in {user_language}. I want you to replace my simplified A0-level words and
|
||||
sentences with more beautiful and elegant, upper level {user_language} words and sentences.
|
||||
Keep the meaning same, but make them more literary. I want you to only reply the correction,
|
||||
the improvements and nothing else, do not write explanations.
|
||||
You will find the text between <input></input> XML tags.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "output", "type" => "string" }]
|
||||
end
|
||||
|
||||
def temperature
|
||||
0.2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Personas
|
||||
class Tutor < Persona
|
||||
def self.default_enabled
|
||||
false
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
<<~PROMPT.strip
|
||||
You are a tutor explaining a term to a student in a specific context.
|
||||
|
||||
I will provide everything you need to know inside <input> tags, which consists of the term I want you
|
||||
to explain inside <term> tags, the context of where it was used inside <context> tags, the title of
|
||||
the topic where it was used inside <topic> tags, and optionally, the previous post in the conversation
|
||||
in <replyTo> tags.
|
||||
|
||||
Using all this information, write a paragraph with a brief explanation
|
||||
of what the term means. Format the response using Markdown. Reply only with the explanation and
|
||||
nothing more.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "output", "type" => "string" }]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -15,13 +15,12 @@ RSpec.describe Jobs::StreamComposerHelper do
|
||||
end
|
||||
|
||||
describe "validates params" do
|
||||
let(:mode) { CompletionPrompt::PROOFREAD }
|
||||
let(:prompt) { CompletionPrompt.find_by(id: mode) }
|
||||
let(:mode) { DiscourseAi::AiHelper::Assistant::PROOFREAD }
|
||||
|
||||
it "does nothing if there is no user" do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/stream_suggestion") do
|
||||
job.execute(user_id: nil, text: input, prompt: prompt.name, force_default_locale: false)
|
||||
job.execute(user_id: nil, text: input, prompt: mode, force_default_locale: false)
|
||||
end
|
||||
|
||||
expect(messages).to be_empty
|
||||
@@ -33,7 +32,7 @@ RSpec.describe Jobs::StreamComposerHelper do
|
||||
job.execute(
|
||||
user_id: user.id,
|
||||
text: nil,
|
||||
prompt: prompt.name,
|
||||
prompt: mode,
|
||||
force_default_locale: false,
|
||||
client_id: "123",
|
||||
)
|
||||
@@ -44,12 +43,10 @@ RSpec.describe Jobs::StreamComposerHelper do
|
||||
end
|
||||
|
||||
context "when all params are provided" do
|
||||
let(:mode) { CompletionPrompt::PROOFREAD }
|
||||
let(:prompt) { CompletionPrompt.find_by(id: mode) }
|
||||
let(:mode) { DiscourseAi::AiHelper::Assistant::PROOFREAD }
|
||||
|
||||
it "publishes updates with a partial result" do
|
||||
proofread_result = "I like to eat pie for breakfast because it is delicious."
|
||||
partial_result = "I"
|
||||
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([proofread_result]) do
|
||||
messages =
|
||||
@@ -57,7 +54,7 @@ RSpec.describe Jobs::StreamComposerHelper do
|
||||
job.execute(
|
||||
user_id: user.id,
|
||||
text: input,
|
||||
prompt: prompt.name,
|
||||
prompt: mode,
|
||||
force_default_locale: true,
|
||||
client_id: "123",
|
||||
)
|
||||
@@ -65,7 +62,7 @@ RSpec.describe Jobs::StreamComposerHelper do
|
||||
|
||||
partial_result_update = messages.first.data
|
||||
expect(partial_result_update[:done]).to eq(false)
|
||||
expect(partial_result_update[:result]).to eq(partial_result)
|
||||
expect(partial_result_update[:result]).to eq(proofread_result)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -78,7 +75,7 @@ RSpec.describe Jobs::StreamComposerHelper do
|
||||
job.execute(
|
||||
user_id: user.id,
|
||||
text: input,
|
||||
prompt: prompt.name,
|
||||
prompt: mode,
|
||||
force_default_locale: true,
|
||||
client_id: "123",
|
||||
)
|
||||
|
||||
@@ -23,8 +23,7 @@ RSpec.describe Jobs::StreamPostHelper do
|
||||
end
|
||||
|
||||
describe "validates params" do
|
||||
let(:mode) { CompletionPrompt::EXPLAIN }
|
||||
let(:prompt) { CompletionPrompt.find_by(id: mode) }
|
||||
let(:mode) { DiscourseAi::AiHelper::Assistant::EXPLAIN }
|
||||
|
||||
it "does nothing if there is no post" do
|
||||
messages =
|
||||
@@ -55,24 +54,21 @@ RSpec.describe Jobs::StreamPostHelper do
|
||||
end
|
||||
|
||||
context "when the prompt is explain" do
|
||||
let(:mode) { CompletionPrompt::EXPLAIN }
|
||||
let(:prompt) { CompletionPrompt.find_by(id: mode) }
|
||||
let(:mode) { DiscourseAi::AiHelper::Assistant::EXPLAIN }
|
||||
|
||||
it "publishes updates with a partial result" do
|
||||
explanation =
|
||||
"In this context, \"pie\" refers to a baked dessert typically consisting of a pastry crust and filling."
|
||||
|
||||
partial_explanation = "I"
|
||||
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([explanation]) do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/stream_suggestion/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, text: "pie", prompt: prompt.name)
|
||||
job.execute(post_id: post.id, user_id: user.id, text: "pie", prompt: mode)
|
||||
end
|
||||
|
||||
partial_result_update = messages.first.data
|
||||
expect(partial_result_update[:done]).to eq(false)
|
||||
expect(partial_result_update[:result]).to eq(partial_explanation)
|
||||
expect(partial_result_update[:result]).to eq(explanation)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,7 +79,7 @@ RSpec.describe Jobs::StreamPostHelper do
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([explanation]) do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/stream_suggestion/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, text: "pie", prompt: prompt.name)
|
||||
job.execute(post_id: post.id, user_id: user.id, text: "pie", prompt: mode)
|
||||
end
|
||||
|
||||
final_update = messages.last.data
|
||||
@@ -94,23 +90,21 @@ RSpec.describe Jobs::StreamPostHelper do
|
||||
end
|
||||
|
||||
context "when the prompt is translate" do
|
||||
let(:mode) { CompletionPrompt::TRANSLATE }
|
||||
let(:prompt) { CompletionPrompt.find_by(id: mode) }
|
||||
let(:mode) { DiscourseAi::AiHelper::Assistant::TRANSLATE }
|
||||
|
||||
it "publishes updates with a partial result" do
|
||||
sentence = "I like to eat pie."
|
||||
translation = "Me gusta comer pastel."
|
||||
partial_translation = "M"
|
||||
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([translation]) do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/stream_suggestion/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, text: sentence, prompt: prompt.name)
|
||||
job.execute(post_id: post.id, user_id: user.id, text: sentence, prompt: mode)
|
||||
end
|
||||
|
||||
partial_result_update = messages.first.data
|
||||
expect(partial_result_update[:done]).to eq(false)
|
||||
expect(partial_result_update[:result]).to eq(partial_translation)
|
||||
expect(partial_result_update[:result]).to eq(translation)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -121,7 +115,7 @@ RSpec.describe Jobs::StreamPostHelper do
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([translation]) do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/stream_suggestion/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, text: sentence, prompt: prompt.name)
|
||||
job.execute(post_id: post.id, user_id: user.id, text: sentence, prompt: mode)
|
||||
end
|
||||
|
||||
final_update = messages.last.data
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user