Skip to content

Commit 6440b3a

Browse files
committed
Define prompts within translator instead of relying on prompts from discourse-ai
1 parent f318223 commit 6440b3a

File tree

6 files changed

+199
-39
lines changed

6 files changed

+199
-39
lines changed
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+
class LanguageDetector
5+
PROMPT_TEXT = <<~TEXT
6+
I want you to act as a language expert, determining the locale for a set of text.
7+
The locale is a language identifier, such as "en" for English, "de" for German, etc,
8+
and can also include a region identifier, such as "en-GB" for British English, or "zh-Hans" for Simplified Chinese.
9+
I will provide you with text, and you will determine the locale of the text.
10+
Include your locale between <language></language> XML tags.
11+
TEXT
12+
13+
def initialize(text)
14+
@text = text
15+
end
16+
17+
def detect
18+
prompt =
19+
DiscourseAi::Completions::Prompt.new(
20+
PROMPT_TEXT,
21+
messages: [{ type: :user, content: @text, id: "user" }],
22+
)
23+
24+
response =
25+
DiscourseAi::Completions::Llm.proxy(SiteSetting.ai_helper_model).generate(
26+
prompt,
27+
user: Discourse.system_user,
28+
feature_name: "translator-language-detect",
29+
)
30+
31+
(Nokogiri::HTML5.fragment(response).at("language")&.text || response)
32+
end
33+
end
34+
end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
class Translator
5+
def initialize(text, target_language)
6+
@text = text
7+
@target_language = target_language
8+
end
9+
10+
def translate
11+
prompt =
12+
DiscourseAi::Completions::Prompt.new(
13+
build_prompt(@target_language),
14+
messages: [{ type: :user, content: @text, id: "user" }],
15+
)
16+
17+
llm_translation =
18+
DiscourseAi::Completions::Llm.proxy(SiteSetting.ai_helper_model).generate(
19+
prompt,
20+
user: Discourse.system_user,
21+
feature_name: "translator-translate",
22+
)
23+
24+
(Nokogiri::HTML5.fragment(llm_translation).at("translation")&.text || llm_translation)
25+
end
26+
27+
private
28+
29+
def build_prompt(target_language)
30+
<<~TEXT
31+
You are a highly skilled translator with expertise in many languages.
32+
Your task is to identify the language of the text I provide and accurately translate it into this language locale "#{target_language}" while preserving the meaning, tone, and nuance of the original text.
33+
The text may also contain html tags, which should be preserved in the translation.
34+
Please maintain proper grammar, spelling, and punctuation in the translated version.
35+
Wrap the translated text in a <translation> tag.
36+
TEXT
37+
end
38+
end
39+
end

app/services/discourse_translator/discourse_ai.rb

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,11 @@ def self.detect(topic_or_post)
1414
return unless required_settings_enabled
1515

1616
topic_or_post.custom_fields[DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD] ||= begin
17-
# we don't need that much text to determine the locale
18-
text = get_text(topic_or_post).truncate(MAX_DETECT_LOCALE_TEXT_LENGTH)
19-
20-
get_ai_helper_output(
21-
text,
22-
CompletionPrompt.find_by(id: CompletionPrompt::DETECT_TEXT_LOCALE),
23-
)
17+
::DiscourseAi::LanguageDetector.new(text_for_detection(topic_or_post)).detect
2418
end
19+
rescue => e
20+
e.message
21+
Rails.logger.warn("Failed to detect language: #{e}")
2522
end
2623

2724
def self.translate(topic_or_post)
@@ -30,27 +27,14 @@ def self.translate(topic_or_post)
3027
detected_lang = detect(topic_or_post)
3128
translated_text =
3229
from_custom_fields(topic_or_post) do
33-
get_ai_helper_output(
34-
get_text(topic_or_post),
35-
CompletionPrompt.find_by(id: CompletionPrompt::TRANSLATE),
36-
)
30+
::DiscourseAi::Translator.new(text_for_translation(topic_or_post), I18n.locale).translate
3731
end
3832

3933
[detected_lang, translated_text]
4034
end
4135

4236
private
4337

44-
def self.get_ai_helper_output(text, prompt)
45-
::DiscourseAi::AiHelper::Assistant.new.generate_and_send_prompt(
46-
prompt,
47-
text,
48-
Discourse.system_user,
49-
)[
50-
:suggestions
51-
].first
52-
end
53-
5438
def self.required_settings_enabled
5539
SiteSetting.translator_enabled && SiteSetting.translator == "DiscourseAi" &&
5640
SiteSetting.discourse_ai_enabled && SiteSetting.ai_helper_enabled
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
require "rails_helper"
4+
5+
describe DiscourseAi::LanguageDetector do
6+
before do
7+
Fabricate(:fake_model).tap do |fake_llm|
8+
SiteSetting.public_send("ai_helper_model=", "custom:#{fake_llm.id}")
9+
end
10+
SiteSetting.ai_helper_enabled = true
11+
end
12+
13+
describe ".detect" do
14+
it "creates the correct prompt" do
15+
allow(DiscourseAi::Completions::Prompt).to receive(:new).with(
16+
DiscourseAi::LanguageDetector::PROMPT_TEXT,
17+
messages: [{ type: :user, content: "meow", id: "user" }],
18+
).and_call_original
19+
20+
described_class.new("meow").detect
21+
end
22+
23+
it "sends the language detection prompt to the ai helper model" do
24+
mock_prompt = instance_double(DiscourseAi::Completions::Prompt)
25+
mock_llm = instance_double(DiscourseAi::Completions::Llm)
26+
27+
allow(DiscourseAi::Completions::Prompt).to receive(:new).and_return(mock_prompt)
28+
allow(DiscourseAi::Completions::Llm).to receive(:proxy).with(
29+
SiteSetting.ai_helper_model,
30+
).and_return(mock_llm)
31+
allow(mock_llm).to receive(:generate).with(
32+
mock_prompt,
33+
user: Discourse.system_user,
34+
feature_name: "translator-language-detect",
35+
)
36+
37+
described_class.new("meow").detect
38+
end
39+
40+
it "returns the language from the llm's response in the language tag" do
41+
DiscourseAi::Completions::Llm.with_prepared_responses(["<language>de</language>"]) do
42+
expect(described_class.new("meow").detect).to eq "de"
43+
end
44+
end
45+
end
46+
end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
require "rails_helper"
4+
5+
describe DiscourseAi::Translator do
6+
before do
7+
Fabricate(:fake_model).tap do |fake_llm|
8+
SiteSetting.public_send("ai_helper_model=", "custom:#{fake_llm.id}")
9+
end
10+
SiteSetting.ai_helper_enabled = true
11+
end
12+
13+
describe ".translate" do
14+
let(:text_to_translate) { "cats are great" }
15+
let(:target_language) { "de" }
16+
17+
it "creates the correct prompt" do
18+
allow(DiscourseAi::Completions::Prompt).to receive(:new).with(
19+
<<~TEXT,
20+
You are a highly skilled translator with expertise in many languages.
21+
Your task is to identify the language of the text I provide and accurately translate it into this language locale "de" while preserving the meaning, tone, and nuance of the original text.
22+
The text may also contain html tags, which should be preserved in the translation.
23+
Please maintain proper grammar, spelling, and punctuation in the translated version.
24+
Wrap the translated text in a <translation> tag.
25+
TEXT
26+
messages: [{ type: :user, content: text_to_translate, id: "user" }],
27+
).and_call_original
28+
29+
described_class.new(text_to_translate, target_language).translate
30+
end
31+
32+
it "sends the translation prompt to the selected ai helper model" do
33+
mock_prompt = instance_double(DiscourseAi::Completions::Prompt)
34+
mock_llm = instance_double(DiscourseAi::Completions::Llm)
35+
36+
allow(DiscourseAi::Completions::Prompt).to receive(:new).and_return(mock_prompt)
37+
allow(DiscourseAi::Completions::Llm).to receive(:proxy).with(
38+
SiteSetting.ai_helper_model,
39+
).and_return(mock_llm)
40+
allow(mock_llm).to receive(:generate).with(
41+
mock_prompt,
42+
user: Discourse.system_user,
43+
feature_name: "translator-translate",
44+
)
45+
46+
described_class.new(text_to_translate, target_language).translate
47+
end
48+
49+
it "returns the translation from the llm's response in the translation tag" do
50+
DiscourseAi::Completions::Llm.with_prepared_responses(
51+
["<translation>hur dur hur dur!</translation>"],
52+
) do
53+
expect(
54+
described_class.new(text_to_translate, target_language).translate,
55+
).to eq "hur dur hur dur!"
56+
end
57+
end
58+
59+
it "returns the raw response if the translation tag is not present" do
60+
DiscourseAi::Completions::Llm.with_prepared_responses(["raw response."]) do
61+
expect(
62+
described_class.new(text_to_translate, target_language).translate,
63+
).to eq "raw response."
64+
end
65+
end
66+
end
67+
end

spec/services/discourse_ai_spec.rb

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,22 @@
1414
SiteSetting.translator = "DiscourseAi"
1515
end
1616

17+
describe ".language_supported?" do
18+
it "returns true for any language" do
19+
expect(described_class.language_supported?("any-language")).to eq(true)
20+
end
21+
end
22+
1723
describe ".detect" do
1824
it "stores the detected language in a custom field" do
1925
locale = "de"
20-
DiscourseAi::Completions::Llm.with_prepared_responses(["<output>de</output>"]) do
26+
DiscourseAi::Completions::Llm.with_prepared_responses(["<language>de</language>"]) do
2127
DiscourseTranslator::DiscourseAi.detect(post)
2228
post.save_custom_fields
2329
end
2430

2531
expect(post.custom_fields[DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD]).to eq locale
2632
end
27-
28-
it "truncates to MAX LENGTH" do
29-
truncated_text =
30-
post.cooked.truncate(DiscourseTranslator::DiscourseAi::MAX_DETECT_LOCALE_TEXT_LENGTH)
31-
expect_any_instance_of(::DiscourseAi::AiHelper::Assistant).to receive(
32-
:generate_and_send_prompt,
33-
).with(
34-
CompletionPrompt.find_by(id: CompletionPrompt::DETECT_TEXT_LOCALE),
35-
truncated_text,
36-
Discourse.system_user,
37-
).and_call_original
38-
39-
DiscourseAi::Completions::Llm.with_prepared_responses(["<output>de</output>"]) do
40-
DiscourseTranslator::DiscourseAi.detect(post)
41-
end
42-
end
4333
end
4434

4535
describe ".translate" do
@@ -50,7 +40,7 @@
5040

5141
it "translates the post and returns [locale, translated_text]" do
5242
DiscourseAi::Completions::Llm.with_prepared_responses(
53-
["<output>some translated text</output>", "<output>translated</output>"],
43+
["<translation>some translated text</translation>"],
5444
) do
5545
locale, translated_text = DiscourseTranslator::DiscourseAi.translate(post)
5646
expect(locale).to eq "de"

0 commit comments

Comments
 (0)