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

Commit 462e2b6

Browse files
committed
DEV: Move AI translation feature into an AI Feature (part 1)
1 parent 35d62a6 commit 462e2b6

File tree

11 files changed

+146
-46
lines changed

11 files changed

+146
-46
lines changed

config/locales/client.en.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ en:
211211
custom_prompt: "Custom prompt"
212212
image_caption: "Caption images"
213213

214+
translation:
215+
name: "Translation"
216+
description: "Translates content into supported languages"
217+
locale_detection: "Locale detection"
218+
214219

215220
modals:
216221
select_option: "Select an option..."

config/locales/server.en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,9 @@ en:
372372
image_captioner:
373373
name: "Image captions"
374374
description: "Default persona powering the Helper's image caption feature"
375+
locale_detection:
376+
name: "Locale detection"
377+
description: "Powers the translation feature by detecting the locale of a given text"
375378

376379
topic_not_found: "Summary unavailable, topic not found!"
377380
summarizing: "Summarizing topic"

config/settings.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,28 +451,39 @@ discourse_ai:
451451
default: false
452452
client: true
453453
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
454+
area: "ai-features/translation"
454455
ai_translation_model:
455456
default: ""
456457
type: enum
457458
allow_any: false
458459
enum: "DiscourseAi::Configuration::LlmEnumerator"
459460
validator: "DiscourseAi::Configuration::LlmValidator"
461+
area: "ai-features/translation"
460462
ai_translation_backfill_rate:
461463
default: 0
464+
min: 0
465+
max: 1000
462466
client: false
463467
hidden: true
468+
area: "ai-features/translation"
464469
ai_translation_backfill_limit_to_public_content:
465470
default: true
466471
client: false
467-
hidden: true
472+
area: "ai-features/translation"
468473
ai_translation_backfill_max_age_days:
469474
default: 5
470475
client: false
471-
hidden: true
476+
area: "ai-features/translation"
472477
ai_translation_verbose_logs:
473478
default: false
474479
client: false
475480
hidden: true
481+
area: "ai-features/translation"
482+
ai_translation_locale_detection_persona:
483+
default: "-27"
484+
type: enum
485+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
486+
area: "ai-features/translation"
476487

477488
inferred_concepts_enabled:
478489
default: false

lib/configuration/feature.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,25 @@ def ai_helper_features
118118
]
119119
end
120120

121+
def translation_features
122+
feature_cache[:translation] ||= [
123+
new(
124+
"locale_detection",
125+
"ai_translation_locale_detection_persona",
126+
DiscourseAi::Configuration::Module::TRANSLATION_ID,
127+
DiscourseAi::Configuration::Module::TRANSLATION,
128+
),
129+
]
130+
end
131+
121132
def all
122133
[
123134
summarization_features,
124135
search_features,
125136
discord_features,
126137
inference_features,
127138
ai_helper_features,
139+
translation_features,
128140
].flatten
129141
end
130142

lib/configuration/module.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ class Module
88
DISCORD = "discord"
99
INFERENCE = "inference"
1010
AI_HELPER = "ai_helper"
11+
TRANSLATION = "translation"
1112

12-
NAMES = [SUMMARIZATION, SEARCH, DISCORD, INFERENCE, AI_HELPER]
13+
NAMES = [SUMMARIZATION, SEARCH, DISCORD, INFERENCE, AI_HELPER, TRANSLATION]
1314

1415
SUMMARIZATION_ID = 1
1516
SEARCH_ID = 2
1617
DISCORD_ID = 3
1718
INFERENCE_ID = 4
1819
AI_HELPER_ID = 5
20+
TRANSLATION_ID = 6
1921

2022
class << self
2123
def all
@@ -50,6 +52,12 @@ def all
5052
"ai_helper_enabled",
5153
features: DiscourseAi::Configuration::Feature.ai_helper_features,
5254
),
55+
new(
56+
TRANSLATION_ID,
57+
TRANSLATION,
58+
"ai_translation_enabled",
59+
features: DiscourseAi::Configuration::Feature.translation_features,
60+
),
5361
]
5462
end
5563

lib/personas/locale_detection.rb

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Personas
5+
class LocaleDetection < Persona
6+
def self.default_enabled
7+
false
8+
end
9+
10+
def system_prompt
11+
<<~PROMPT.strip
12+
You will be given a piece of text, and your task is to detect the locale (language) of the text and return it in a specific JSON format.
13+
14+
To complete this task, follow these steps:
15+
16+
1. Carefully read and analyze the provided text.
17+
2. Determine the language of the text based on its characteristics, such as vocabulary, grammar, and sentence structure.
18+
3. Do not use links or programing code in the text to detect the locale
19+
4. Identify the appropriate language code for the detected language.
20+
21+
Here is a list of common language codes for reference:
22+
- English: en
23+
- Spanish: es
24+
- French: fr
25+
- German: de
26+
- Italian: it
27+
- Brazilian Portuguese: pt-BR
28+
- Russian: ru
29+
- Simplified Chinese: zh-CN
30+
- Japanese: ja
31+
- Korean: ko
32+
33+
If the language is not in this list, use the appropriate IETF language tag code.
34+
35+
5. Format your response as a JSON object with a single key "locale" and the value as the language code.
36+
37+
Your output should be in the following format:
38+
<output>
39+
{"locale": "xx"}
40+
</output>
41+
42+
Where "xx" is replaced by the appropriate language code.
43+
44+
Important: Base your analysis solely on the provided text. Do not use any external information or make assumptions about the text's origin or context beyond what is explicitly provided.
45+
PROMPT
46+
end
47+
48+
def response_format
49+
{
50+
type: "json_schema",
51+
json_schema: {
52+
name: "reply",
53+
schema: {
54+
type: "object",
55+
properties: {
56+
locale: {
57+
type: "string",
58+
},
59+
},
60+
required: ["locale"],
61+
additionalProperties: false,
62+
},
63+
strict: true,
64+
},
65+
}
66+
end
67+
68+
def temperature
69+
0
70+
end
71+
end
72+
end
73+
end

lib/personas/persona.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def system_personas
6464
Tutor => -24,
6565
Translator => -25,
6666
ImageCaptioner => -26,
67+
LocaleDetection => -27,
6768
}
6869
end
6970

lib/translation/language_detector.rb

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,36 @@ module DiscourseAi
44
module Translation
55
class LanguageDetector
66
DETECTION_CHAR_LIMIT = 1000
7-
PROMPT_TEXT = <<~TEXT
8-
You will be given a piece of text, and your task is to detect the locale (language) of the text and return it in a specific JSON format.
9-
10-
To complete this task, follow these steps:
11-
12-
1. Carefully read and analyze the provided text.
13-
2. Determine the language of the text based on its characteristics, such as vocabulary, grammar, and sentence structure.
14-
3. Do not use links or programing code in the text to detect the locale
15-
4. Identify the appropriate language code for the detected language.
16-
17-
Here is a list of common language codes for reference:
18-
- English: en
19-
- Spanish: es
20-
- French: fr
21-
- German: de
22-
- Italian: it
23-
- Brazilian Portuguese: pt-BR
24-
- Russian: ru
25-
- Simplified Chinese: zh-CN
26-
- Japanese: ja
27-
- Korean: ko
28-
29-
If the language is not in this list, use the appropriate IETF language tag code.
30-
31-
5. Format your response as a JSON object with a single key "locale" and the value as the language code.
32-
33-
Your output should be in the following format:
34-
<output>
35-
{"locale": "xx"}
36-
</output>
37-
38-
Where "xx" is replaced by the appropriate language code.
39-
40-
Important: Base your analysis solely on the provided text. Do not use any external information or make assumptions about the text's origin or context beyond what is explicitly provided.
41-
TEXT
427

438
def initialize(text)
449
@text = text
4510
end
4611

4712
def detect
13+
return nil if !SiteSetting.ai_translation_enabled
14+
if (
15+
ai_persona = AiPersona.find_by(id: SiteSetting.ai_translation_locale_detection_persona)
16+
).blank?
17+
return nil
18+
end
19+
20+
persona_klass = ai_persona.class_instance
21+
22+
llm_model = preferred_llm_model(ai_persona, persona_klass)
23+
return nil if llm_model.blank?
24+
4825
prompt =
4926
DiscourseAi::Completions::Prompt.new(
50-
PROMPT_TEXT,
27+
ai_persona.system_prompt,
5128
messages: [{ type: :user, content: @text, id: "user" }],
5229
)
53-
30+
response_format = persona_klass.new.response_format
5431
structured_output =
55-
DiscourseAi::Completions::Llm.proxy(SiteSetting.ai_translation_model).generate(
32+
DiscourseAi::Completions::Llm.proxy(llm_model).generate(
5633
prompt,
57-
user: Discourse.system_user,
34+
user: ai_persona.user || Discourse.system_user,
5835
feature_name: "translation",
59-
response_format: response_format,
36+
response_format:,
6037
)
6138

6239
structured_output&.read_buffered_property(:locale)
@@ -81,6 +58,16 @@ def response_format
8158
},
8259
}
8360
end
61+
62+
private
63+
64+
def preferred_llm_model(ai_persona, persona_klass)
65+
if ai_persona.force_default_llm
66+
persona_klass.default_llm_id
67+
else
68+
SiteSetting.ai_translation_model.presence || persona_klass.default_llm_id
69+
end
70+
end
8471
end
8572
end
8673
end

spec/lib/translation/language_detector_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
it "creates the correct prompt" do
1515
allow(DiscourseAi::Completions::Prompt).to receive(:new).with(
16-
DiscourseAi::Translation::LanguageDetector::PROMPT_TEXT,
16+
AiPersona.find_by(id: SiteSetting.ai_translation_locale_detection_persona).system_prompt,
1717
messages: [{ type: :user, content: "meow", id: "user" }],
1818
).and_call_original
1919

spec/requests/admin/ai_features_controller_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
get "/admin/plugins/discourse-ai/ai-features.json"
2020

2121
expect(response.status).to eq(200)
22-
expect(response.parsed_body["ai_features"].count).to eq(5)
22+
expect(response.parsed_body["ai_features"].count).to eq(6)
2323
end
2424
end
2525

0 commit comments

Comments
 (0)