Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da55eb31d0 |
@@ -1,4 +1,3 @@
|
||||
< 3.5.0.beta8-dev: 5d80a34589d63e381d80273828072badc18955b1
|
||||
< 3.5.0.beta7-dev: cd14b0c0bee0cf63c59b64b6f7213e31a37f11a7
|
||||
< 3.5.0.beta6-dev: 3e74eea1e5e3143888d67a8d8a11206df214dc24
|
||||
< 3.5.0.beta3-dev: 09a68414804a1447f52e5d60691ba59742cda9ec
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# **Discourse AI** Plugin
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This plugin has now been bundled into Discourse core. See: https://meta.discourse.org/t/bundling-more-popular-plugins-with-discourse-core/373574
|
||||
|
||||
**Plugin Summary**
|
||||
|
||||
For more information, please see: https://meta.discourse.org/t/discourse-ai/259214?u=falco
|
||||
|
||||
@@ -40,7 +40,7 @@ module Jobs
|
||||
return if locales.blank?
|
||||
|
||||
locales.each do |locale|
|
||||
next if LocaleNormalizer.is_same?(locale, detected_locale)
|
||||
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, detected_locale)
|
||||
regionless_locale = locale.split("_").first
|
||||
next if post.post_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ module Jobs
|
||||
return if locales.blank?
|
||||
|
||||
locales.each do |locale|
|
||||
next if LocaleNormalizer.is_same?(locale, detected_locale)
|
||||
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, detected_locale)
|
||||
regionless_locale = locale.split("_").first
|
||||
next if topic.topic_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ module Jobs
|
||||
missing_locales = locales - existing_locales - [category.locale]
|
||||
missing_locales.each do |locale|
|
||||
break if remaining_limit <= 0
|
||||
next if LocaleNormalizer.is_same?(locale, category.locale)
|
||||
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, category.locale)
|
||||
|
||||
begin
|
||||
DiscourseAi::Translation::CategoryLocalizer.localize(category, locale)
|
||||
|
||||
@@ -84,7 +84,7 @@ class EmbeddingDefinition < ActiveRecord::Base
|
||||
dimensions: 2000,
|
||||
max_sequence_length: 8191,
|
||||
pg_function: "<=>",
|
||||
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiCl100kTokenizer",
|
||||
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiTokenizer",
|
||||
url: "https://api.openai.com/v1/embeddings",
|
||||
provider: OPEN_AI,
|
||||
matryoshka_dimensions: true,
|
||||
@@ -98,7 +98,7 @@ class EmbeddingDefinition < ActiveRecord::Base
|
||||
dimensions: 1536,
|
||||
max_sequence_length: 8191,
|
||||
pg_function: "<=>",
|
||||
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiCl100kTokenizer",
|
||||
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiTokenizer",
|
||||
url: "https://api.openai.com/v1/embeddings",
|
||||
provider: OPEN_AI,
|
||||
matryoshka_dimensions: true,
|
||||
@@ -112,7 +112,7 @@ class EmbeddingDefinition < ActiveRecord::Base
|
||||
dimensions: 1536,
|
||||
max_sequence_length: 8191,
|
||||
pg_function: "<=>",
|
||||
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiCl100kTokenizer",
|
||||
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiTokenizer",
|
||||
url: "https://api.openai.com/v1/embeddings",
|
||||
provider: OPEN_AI,
|
||||
provider_params: {
|
||||
|
||||
@@ -66,9 +66,12 @@ class ExpandableList extends Component {
|
||||
|
||||
export default class AiFeaturesList extends Component {
|
||||
get sortedModules() {
|
||||
const modules = this.args.modules?.toArray() || this.args.modules;
|
||||
return [...(modules || [])].toSorted((a, b) =>
|
||||
(a.module_name || "").localeCompare(b.module_name || "")
|
||||
if (!this.args.modules || !this.args.modules.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.args.modules.toSorted((a, b) =>
|
||||
a.module_name.localeCompare(b.module_name)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -110,9 +110,6 @@ de:
|
||||
top_p:
|
||||
label: "Top P"
|
||||
description: "Top P für die LLM, erhöhen, um die Zufälligkeit zu erhöhen (leer lassen, um das Standardmodell zu verwenden)"
|
||||
persona_id:
|
||||
label: "Persona"
|
||||
description: "KI Persona zur Berichtserstellung"
|
||||
llm_tool_triage:
|
||||
fields:
|
||||
model:
|
||||
@@ -182,9 +179,6 @@ de:
|
||||
temperature:
|
||||
label: "Temperatur"
|
||||
description: "Temperatur, die für das LLM verwendet werden soll. Erhöhen, um die Zufälligkeit zu erhöhen (leer lassen, um das Standardmodell zu verwenden)"
|
||||
max_output_tokens:
|
||||
label: "Maximale Ausgabetoken"
|
||||
description: "Wenn angegeben, wird eine Obergrenze für die maximale Anzahl von Token festgelegt, die das Modell erzeugen kann. Beachtet die Obergrenze für die Anzahl der ausgegebenen Token des LLM"
|
||||
discourse_ai:
|
||||
title: "KI"
|
||||
features:
|
||||
@@ -239,9 +233,7 @@ de:
|
||||
inference:
|
||||
name: "Abgeleitete Konzepte"
|
||||
description: "Ordnet Themen und Beiträge in Interessensgebiete / Labels."
|
||||
generate_concepts: "Konzepterkennung"
|
||||
match_concepts: "Passende Konzepte"
|
||||
deduplicate_concepts: "Deduplizierung von Konzepten"
|
||||
ai_helper:
|
||||
name: "Helfer"
|
||||
description: "Unterstützt Benutzer bei der Interaktion in der Community, z. B. beim Erstellen von Themen, Verfassen von Beiträgen und Lesen von Inhalten"
|
||||
@@ -781,7 +773,6 @@ de:
|
||||
shortcut_title: "Starte eine PN mit einem KI-Bot"
|
||||
share: "KI-Unterhaltung kopieren"
|
||||
conversation_shared: "Unterhaltung kopiert"
|
||||
embed_copied: "Einbettung in die Zwischenablage kopiert"
|
||||
debug_ai: "Rohdaten der KI-Anfrage und -Antwort anzeigen"
|
||||
sidebar_empty: "Der Verlauf der Bot-Konversation wird hier angezeigt."
|
||||
debug_ai_modal:
|
||||
@@ -842,7 +833,6 @@ de:
|
||||
last_7_days: "Letzte 7 Tage"
|
||||
last_30_days: "Letzte 30 Tage"
|
||||
upload_files: "Dateien hochladen"
|
||||
uploads_in_progress: "Absenden nicht möglich, solange Uploads nicht abgeschlossen sind"
|
||||
sentiments:
|
||||
dashboard:
|
||||
title: "Stimmung"
|
||||
|
||||
@@ -180,9 +180,6 @@ en:
|
||||
temperature:
|
||||
label: "Temperature"
|
||||
description: "Temperature to use for the LLM. Increase to increase randomness (leave empty to use model default)"
|
||||
max_output_tokens:
|
||||
label: "Max output tokens"
|
||||
description: "When specified, sets an upper bound to the maximum number of tokens the model can generate. Respects LLM's max output tokens limit"
|
||||
|
||||
discourse_ai:
|
||||
title: "AI"
|
||||
|
||||
@@ -109,9 +109,6 @@ he:
|
||||
top_p:
|
||||
label: "ה־P המובילים"
|
||||
description: "ה־P המוביל לשימוש למודל השפה הגדול (LLM), הגדלה תגדיל את האקראיות (להשאיר ריק כדי להשתמש בברירת המחדל של הדגם)"
|
||||
persona_id:
|
||||
label: "דמות"
|
||||
description: "דמות בינה מלאכותית לשימוש להפקת דוחות"
|
||||
llm_tool_triage:
|
||||
fields:
|
||||
model:
|
||||
@@ -170,8 +167,6 @@ he:
|
||||
temperature:
|
||||
label: "טמפרטורה"
|
||||
description: "טמפרטורה לשימוש למודל השפה הגדול (LLM), הגדלה תגדיל את האקראיות (להשאיר ריק כדי להשתמש בברירת המחדל של הדגם)"
|
||||
max_output_tokens:
|
||||
label: "כמות אסימוני פלט מרבית"
|
||||
discourse_ai:
|
||||
title: "בינה מלאכותית"
|
||||
features:
|
||||
@@ -778,7 +773,6 @@ he:
|
||||
last_7_days: "7 הימים האחרונים"
|
||||
last_30_days: "30 הימים האחרונים"
|
||||
upload_files: "העלאת קבצים"
|
||||
uploads_in_progress: "לא ניתן להגיש תוך כדי שמתבצעות העלאות"
|
||||
sentiments:
|
||||
dashboard:
|
||||
title: "רגש"
|
||||
|
||||
@@ -110,8 +110,6 @@ lt:
|
||||
top_p:
|
||||
label: "Viršutinė P"
|
||||
description: "Viršutinė P, naudojama LLM, padidinkite, kad padidėtų atsitiktinumas (palikite tuščią, jei norite naudoti numatytuosius modelio parametrus)"
|
||||
persona_id:
|
||||
label: "Persona"
|
||||
llm_tool_triage:
|
||||
fields:
|
||||
model:
|
||||
|
||||
@@ -88,8 +88,6 @@ pl_PL:
|
||||
temperature:
|
||||
label: "Temperatura"
|
||||
description: "Temperatura używana dla LLM. Zwiększ, aby zwiększyć losowość (pozostaw puste, aby użyć domyślnego modelu)."
|
||||
persona_id:
|
||||
label: "Osobowość"
|
||||
llm_tool_triage:
|
||||
fields:
|
||||
model:
|
||||
|
||||
@@ -94,8 +94,6 @@ zh_CN:
|
||||
top_p:
|
||||
label: "Top P"
|
||||
description: "用于 LLM 的 Top P 值,增加该值会提高生成内容的随机性(留空则使用模型默认值)"
|
||||
persona_id:
|
||||
label: "角色模型"
|
||||
llm_tool_triage:
|
||||
fields:
|
||||
model:
|
||||
|
||||
@@ -116,7 +116,7 @@ de:
|
||||
ai_translation_model: "Das Modell, das für die Übersetzung verwendet werden soll. Dieses Modell muss die Übersetzung unterstützen. Personas können diese Einstellung überschreiben."
|
||||
ai_translation_backfill_limit_to_public_content: "Wenn diese Option aktiviert ist, werden nur Inhalte in öffentlichen Kategorien übersetzt. Wenn sie deaktiviert ist, werden auch Inhalte in Gruppen-PMs und privaten Kategorien zur Übersetzung gesendet."
|
||||
ai_translation_max_post_length: "Die maximale Länge eines zu übersetzenden Beitrags. Längere Beiträge werden nicht übersetzt."
|
||||
ai_translation_backfill_max_age_days: "Das maximale Alter eines Beitrags oder Themas, das übersetzt werden soll. Beiträge und Themen, die älter sind, werden nicht übersetzt. 0 deaktiviert die rückwirkende Übersetzung, aber nicht die Übersetzung von neuen Beiträgen."
|
||||
ai_translation_backfill_max_age_days: "Das maximale Alter eines zu übersetzenden Beitrags und Themas. Ältere Beiträge und Themen werden nicht übersetzt."
|
||||
reviewables:
|
||||
reasons:
|
||||
flagged_by_toxicity: Das KI-Plug-in meldete dies nach der Klassifizierung als toxisch.
|
||||
@@ -221,7 +221,6 @@ de:
|
||||
one: "Du kannst nur %{count} Schlüssel im Artefakt haben."
|
||||
other: "Du kannst nur %{count} Schlüssel im Artefakt haben."
|
||||
link: "In neuem Tab anzeigen"
|
||||
copy_embed: "Einbettung kopieren"
|
||||
view_source: "Quelle anzeigen"
|
||||
view_changes: "Änderungen anzeigen"
|
||||
change_description: "Beschreibung ändern"
|
||||
@@ -384,9 +383,6 @@ de:
|
||||
content_creator:
|
||||
name: "Inhaltsersteller"
|
||||
description: "Standard-Persona für die HyDE-Suche"
|
||||
report_runner:
|
||||
name: "Berichtsausführer"
|
||||
description: "Standard-Persona, die im Automatisierungsskript für Berichte verwendet wird."
|
||||
topic_not_found: "Zusammenfassung nicht verfügbar, Thema nicht gefunden!"
|
||||
summarizing: "Thema zusammenfassen"
|
||||
searching: "Suche nach: „%{query}“"
|
||||
@@ -594,7 +590,6 @@ de:
|
||||
bedrock_invalid_url: "Bitte fülle alle Felder aus, um dieses Modell zu verwenden."
|
||||
ai_staff_action_logger:
|
||||
updated: "aktualisiert"
|
||||
set: "gesetzt"
|
||||
removed: "entfernt"
|
||||
errors:
|
||||
quota_exceeded: "Du hast das Kontingent für dieses Modell überschritten. Bitte versuche es erneut in %{relative_time}."
|
||||
|
||||
@@ -302,9 +302,6 @@ he:
|
||||
name: "בקשה מותאמת אישית"
|
||||
smart_dates:
|
||||
name: "תאריכים חכמים"
|
||||
report_runner:
|
||||
name: "אצן דוחות"
|
||||
description: "דמות ברירת המחדל שסקריפט אוטומציית הדוחות משתמש בה"
|
||||
topic_not_found: "תקציר לא זמין, לא נמצא נושא!"
|
||||
summarizing: "הנושא מסוכם"
|
||||
searching: "חיפוש אחר: ‚%{query}’"
|
||||
|
||||
@@ -527,8 +527,6 @@ discourse_ai:
|
||||
area: "ai-features/translation"
|
||||
ai_translation_backfill_max_age_days:
|
||||
default: 5
|
||||
min: 0
|
||||
max: 20000
|
||||
client: false
|
||||
area: "ai-features/translation"
|
||||
ai_translation_verbose_logs:
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
class UpdateOpenAiEmbeddingsTokenizer < ActiveRecord::Migration[7.2]
|
||||
def up
|
||||
execute <<~SQL
|
||||
UPDATE embedding_definitions
|
||||
SET tokenizer_class = 'DiscourseAi::Tokenizer::OpenAiCl100kTokenizer'
|
||||
WHERE url LIKE '%https://api.openai.com/%' AND tokenizer_class <> 'DiscourseAi::Tokenizer::OpenAiCl100kTokenizer'
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
||||
@@ -1,22 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
class SetTranslationBackfillMaxAge < ActiveRecord::Migration[7.2]
|
||||
def up
|
||||
execute <<~SQL
|
||||
UPDATE site_settings
|
||||
SET value = '20000'
|
||||
WHERE name = 'ai_translation_backfill_max_age_days'
|
||||
AND value::integer > 20000;
|
||||
SQL
|
||||
|
||||
execute <<~SQL
|
||||
UPDATE site_settings
|
||||
SET value = '0'
|
||||
WHERE name = 'ai_translation_backfill_max_age_days'
|
||||
AND value::integer < 0;
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexToAiTopicsEmbeddings < ActiveRecord::Migration[7.2]
|
||||
def up
|
||||
add_index :ai_topics_embeddings, %i[topic_id model_id]
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
||||
@@ -24,7 +24,6 @@ if defined?(DiscourseAutomation)
|
||||
field :max_post_tokens, component: :text
|
||||
field :stop_sequences, component: :text_list, required: false
|
||||
field :temperature, component: :text
|
||||
field :max_output_tokens, component: :text
|
||||
|
||||
# Actions
|
||||
field :category, component: :category
|
||||
@@ -86,9 +85,6 @@ if defined?(DiscourseAutomation)
|
||||
temperature = temperature.to_f
|
||||
end
|
||||
|
||||
max_output_tokens = fields.dig("max_output_tokens", "value").to_i
|
||||
max_output_tokens = nil if max_output_tokens <= 0
|
||||
|
||||
max_post_tokens = nil if max_post_tokens <= 0
|
||||
|
||||
stop_sequences = fields.dig("stop_sequences", "value")
|
||||
@@ -126,7 +122,6 @@ if defined?(DiscourseAutomation)
|
||||
stop_sequences: stop_sequences,
|
||||
automation: self.automation,
|
||||
temperature: temperature,
|
||||
max_output_tokens: max_output_tokens,
|
||||
action: context["action"],
|
||||
)
|
||||
rescue => e
|
||||
|
||||
@@ -21,7 +21,6 @@ module DiscourseAi
|
||||
temperature: nil,
|
||||
whisper: nil,
|
||||
reply_persona_id: nil,
|
||||
max_output_tokens: nil,
|
||||
action: nil
|
||||
)
|
||||
if category_id.blank? && tags.blank? && canned_reply.blank? && hide_topic.blank? &&
|
||||
@@ -60,8 +59,8 @@ module DiscourseAi
|
||||
result =
|
||||
llm.generate(
|
||||
prompt,
|
||||
max_tokens: max_output_tokens,
|
||||
temperature: temperature,
|
||||
max_tokens: 700, # ~500 words
|
||||
user: Discourse.system_user,
|
||||
stop_sequences: stop_sequences,
|
||||
feature_name: "llm_triage",
|
||||
|
||||
@@ -154,10 +154,12 @@ module DiscourseAi
|
||||
result = +""
|
||||
bot = DiscourseAi::Personas::Bot.as(Discourse.system_user, persona: @persona, model: @model)
|
||||
json_summary_schema_key = @persona.response_format&.first.to_h
|
||||
output = nil
|
||||
|
||||
buffer_blk =
|
||||
Proc.new do |partial, _, type|
|
||||
if type == :structured_output
|
||||
output = partial.dup
|
||||
read_chunk = partial.read_buffered_property(json_summary_schema_key["key"]&.to_sym)
|
||||
|
||||
print read_chunk if Rails.env.development? && @debug_mode
|
||||
|
||||
@@ -34,22 +34,43 @@ module DiscourseAi
|
||||
|
||||
5. Avoid using `und` and prefer `en` over `en-US` or `en-GB` unless the text specifically indicates a regional variant.
|
||||
|
||||
Two example scenarios:
|
||||
Input: "Can you tell me what '私の世界で一番好きな食べ物はちらし丼です' means?"
|
||||
Output: "en"
|
||||
6. Format your response as a JSON object with a single key "locale" and the value as the language code.
|
||||
|
||||
Input: [quote]\nNon smettere mai di credere nella bellezza dei tuoi sogni. Anche quando tutto sembra perduto, c'è sempre una luce che aspetta di essere trovata.\nOgni passo, anche il più piccolo, ti avvicina a ciò che desideri. La forza che cerchi è già dentro di te.\n[/quote]\n¿Cuál es el mensaje principal de esta cita?
|
||||
Output: "es"
|
||||
Your output should be in the following format:
|
||||
<output>
|
||||
{"locale": "xx"}
|
||||
</output>
|
||||
|
||||
Where "xx" is replaced by the appropriate language code.
|
||||
|
||||
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.
|
||||
|
||||
Your response must be a language code, and nothing else.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "locale", "type" => "string" }]
|
||||
end
|
||||
|
||||
def temperature
|
||||
0
|
||||
end
|
||||
|
||||
def examples
|
||||
spanish = <<~MARKDOWN
|
||||
[quote]
|
||||
Non smettere mai di credere nella bellezza dei tuoi sogni. Anche quando tutto sembra perduto, c'è sempre una luce che aspetta di essere trovata.
|
||||
|
||||
Ogni passo, anche il più piccolo, ti avvicina a ciò che desideri. La forza che cerchi è già dentro di te.
|
||||
[/quote]
|
||||
|
||||
¿Cuál es el mensaje principal de esta cita?
|
||||
MARKDOWN
|
||||
|
||||
[
|
||||
["Can you tell me what '私の世界で一番好きな食べ物はちらし丼です' means?", { locale: "en" }.to_json],
|
||||
[spanish, { locale: "es" }.to_json],
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,74 +8,62 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
examples = [
|
||||
{
|
||||
input: {
|
||||
content:
|
||||
"**Heathrow fechado**: Suspensão de voos deve continuar nos próximos dias, afirma gerente do aeroporto de Londres\n\n[details=Do site da BBC]\n\nA British Airways estimou que 85% de seus voos planejados seriam realizados no sábado, mas com atrasos em todos os voos. Às 7h GMT, a maioria das partidas havia ocorrido conforme o esperado, mas, das chegadas, nove dos primeiros 20 voos programados para aterrissar foram cancelados.\n\n[/details]",
|
||||
target_locale: "en",
|
||||
}.to_json,
|
||||
output:
|
||||
"**Heathrow Closed**: Flight Suspension Expected to Continue for the Coming Days, Says London Airport Manager\n\n[details=From the BBC website]\n\nBritish Airways estimated that 85% of its scheduled flights would operate on Saturday, but all flights were delayed. By 7:00 a.m. GMT, most departures had proceeded as expected, but of the arrivals, nine of the first 20 flights scheduled to land were canceled.\n\n[/details]",
|
||||
},
|
||||
{
|
||||
input: {
|
||||
content:
|
||||
"[quote] What does the new update include? [/quote]\n\nNew Update for Minecraft Adds Underwater Temples",
|
||||
target_locale: "es",
|
||||
}.to_json,
|
||||
output:
|
||||
"[quote]¿Qué incluye la nueva actualización?[/quote]\n\nNueva actualización para Minecraft añade templos submarinos",
|
||||
},
|
||||
{
|
||||
input: {
|
||||
content:
|
||||
"There has been an error in my update\n\n```ruby\napi_key = \"a quick brown fox\"\nfetch(\"https://api.example.com/data\", headers: { 'Authorization' => api_key })\n```\n\nPlease help me fix it.",
|
||||
target_locale: "ja",
|
||||
}.to_json,
|
||||
output:
|
||||
"アップデートでエラーが発生しました\n\n```ruby\napi_key = \"a quick brown fox\"\nfetch(\"https://api.example.com/data\", headers: { 'Authorization' => api_key })\n```\n\n修正にご協力ください。\"",
|
||||
},
|
||||
]
|
||||
|
||||
<<~PROMPT.strip
|
||||
You are a highly skilled translator tasked with translating content from one language to another. Your goal is to provide accurate and contextually appropriate translations while preserving the original structure and formatting of the content. Follow these instructions strictly:
|
||||
|
||||
1. Preserve Markdown elements, HTML elements, or newlines. Text must be translated without altering the original formatting.
|
||||
2. Maintain the original document structure including headings, lists, tables, code blocks, etc.
|
||||
3. Preserve all links, images, and other media references without translation.
|
||||
4. For technical and brand terminology:
|
||||
4. For technical terminology:
|
||||
- Provide the accepted target language term if it exists.
|
||||
- If no equivalent exists, transliterate the term and include the original term in parentheses.
|
||||
5. For ambiguous terms or phrases, choose the most contextually appropriate translation.
|
||||
6. Ensure the translation only contains the original language and the target language.
|
||||
|
||||
Follow these instructions on what NOT to do:
|
||||
7. Do not translate code snippets or programming language names, but ensure that any comments within the code are translated. Code can be represented in ``` or in single ` backticks or in <code> HTML tags.
|
||||
7. Do not translate code snippets or programming language names, but ensure that any comments within the code are translated.
|
||||
8. Do not add any content besides the translation.
|
||||
9. Do not add unnecessary newlines.
|
||||
|
||||
Here are three examples of correct translations:
|
||||
|
||||
Input: #{examples[0][:input]}
|
||||
Output: #{examples[0][:output]}
|
||||
|
||||
Input: #{examples[1][:input]}
|
||||
Output: #{examples[1][:output]}
|
||||
|
||||
Input: #{examples[2][:input]}
|
||||
Output: #{examples[2][:output]}
|
||||
|
||||
The text to translate will be provided in JSON format with the following structure:
|
||||
{"content": "Text to translate", "target_locale": "Target language code"}
|
||||
|
||||
You are being consumed via an API that expects only the translated text. Only return the translated text in the correct language. Do not add questions or explanations.
|
||||
Output your translation in the following JSON format:
|
||||
{"translation": "Your translated text here"}
|
||||
|
||||
You are being consumed via an API. Only return the translated text in the specified JSON format. Do not include any additional information or explanations.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "translation", "type" => "string" }]
|
||||
end
|
||||
|
||||
def temperature
|
||||
0.3
|
||||
end
|
||||
|
||||
def examples
|
||||
[
|
||||
[
|
||||
{
|
||||
content:
|
||||
"**Heathrow fechado**: paralisação de voos deve continuar nos próximos dias, diz gestora do aeroporto de *Londres*",
|
||||
target_locale: "en",
|
||||
}.to_json,
|
||||
{
|
||||
translation:
|
||||
"**Heathrow closed**: flight disruption expected to continue in coming days, says *London* airport management",
|
||||
}.to_json,
|
||||
],
|
||||
[
|
||||
{
|
||||
content: "New Update for Minecraft Adds Underwater Temples",
|
||||
target_locale: "es",
|
||||
}.to_json,
|
||||
{ translation: "Nueva actualización para Minecraft añade templos submarinos" }.to_json,
|
||||
],
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,16 +10,25 @@ module DiscourseAi
|
||||
def system_prompt
|
||||
<<~PROMPT
|
||||
You are ForumBot, a backend AI information processor and writer, powered by an AI model capable of generating responses over 4000 words.
|
||||
|
||||
|
||||
- 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.
|
||||
- ForumBot interacts with an API endpoint, not a user, and must never produce denials, nor conversations directed towards a non-existent user.
|
||||
- ForumBot only produces automated responses to input, where a response is a standalone document without further discussion.
|
||||
|
||||
|
||||
Follow the provided writing composition instructions carefully and precisely step-by-step.
|
||||
|
||||
Format your response as valid Markdown, ensuring it is well-structured and easy to read.
|
||||
Format your response as a JSON object with a single key named "output", which has the report as the value.
|
||||
Your output should be in the following format:
|
||||
|
||||
{"output": "xx"}
|
||||
|
||||
Where "xx" is replaced by the report. Reply with valid JSON only
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "output", "type" => "string" }]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,16 +8,6 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
examples = [
|
||||
{ input: { content: "Japan", target_locale: "es" }.to_json, output: "Japón" },
|
||||
{ input: { content: "Cats and Dogs", target_locale: "zh_CN" }.to_json, output: "猫和狗" },
|
||||
{
|
||||
input: { content: "Q&A", target_locale: "pt" }.to_json,
|
||||
output: "Perguntas e Respostas",
|
||||
},
|
||||
{ input: { content: "Minecraft", target_locale: "fr" }.to_json, output: "Minecraft" },
|
||||
]
|
||||
|
||||
<<~PROMPT.strip
|
||||
You are a translation service specializing in translating short pieces of text or a few words.
|
||||
These words may be things like a name, description, or title. Adhere to the following guidelines:
|
||||
@@ -27,30 +17,41 @@ module DiscourseAi
|
||||
3. Translation maintains the original meaning
|
||||
4. Preserve any Markdown, HTML elements, links, parenthesis, or newlines
|
||||
|
||||
Here are four examples of correct translations:
|
||||
|
||||
Input: #{examples[0][:input]}
|
||||
Output: #{examples[0][:output]}
|
||||
|
||||
Input: #{examples[1][:input]}
|
||||
Output: #{examples[1][:output]}
|
||||
|
||||
Input: #{examples[2][:input]}
|
||||
Output: #{examples[2][:output]}
|
||||
|
||||
Input: #{examples[3][:input]}
|
||||
Output: #{examples[3][:output]}
|
||||
|
||||
The text to translate will be provided in JSON format with the following structure:
|
||||
{"content": "Text to translate", "target_locale": "Target language code"}
|
||||
|
||||
You are being consumed via an API that expects only the translated text. Only return the translated text in the correct language. Do not add questions or explanations.
|
||||
Provide your translation in the following JSON format:
|
||||
{"translation": "target_locale translation here"}
|
||||
|
||||
Translate the text now and provide your answer in the specified JSON format.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "translation", "type" => "string" }]
|
||||
end
|
||||
|
||||
def temperature
|
||||
0.3
|
||||
end
|
||||
|
||||
def examples
|
||||
[
|
||||
[{ content: "Japan", target_locale: "es" }.to_json, { translation: "Japón" }.to_json],
|
||||
[
|
||||
{ content: "Cats and Dogs", target_locale: "zh_CN" }.to_json,
|
||||
{ translation: "猫和狗" }.to_json,
|
||||
],
|
||||
[
|
||||
{ content: "Q&A", target_locale: "pt" }.to_json,
|
||||
{ translation: "Perguntas e Respostas" }.to_json,
|
||||
],
|
||||
[
|
||||
{ content: "Minecraft", target_locale: "fr" }.to_json,
|
||||
{ translation: "Minecraft" }.to_json,
|
||||
],
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,32 +8,6 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def system_prompt
|
||||
examples = [
|
||||
{
|
||||
input: {
|
||||
content: "New Update for Minecraft Adds Underwater Temples",
|
||||
target_locale: "es",
|
||||
}.to_json,
|
||||
output: "Nueva actualización para Minecraft añade templos submarinos",
|
||||
},
|
||||
{
|
||||
input: {
|
||||
content: "Toyota announces revolutionary battery technology",
|
||||
target_locale: "fr",
|
||||
}.to_json,
|
||||
output: "Toyota annonce une technologie de batteries révolutionnaire",
|
||||
},
|
||||
{
|
||||
input: {
|
||||
content:
|
||||
"Heathrow fechado: paralisação de voos deve continuar nos próximos dias, diz gestora do aeroporto de Londres",
|
||||
target_locale: "en",
|
||||
}.to_json,
|
||||
output:
|
||||
"Heathrow closed: flight disruption expected to continue in coming days, says London airport management",
|
||||
},
|
||||
]
|
||||
|
||||
<<~PROMPT.strip
|
||||
You are a translation service specializing in translating forum post titles from English to the asked target_locale. Your task is to provide accurate and contextually appropriate translations while adhering to the following guidelines:
|
||||
|
||||
@@ -50,27 +24,53 @@ module DiscourseAi
|
||||
4. Adjust the translation if necessary to keep the length similar to the original title.
|
||||
5. Review your translation for accuracy and naturalness in the target_locale.
|
||||
|
||||
Here are three examples of correct translations:
|
||||
|
||||
Input: #{examples[0][:input]}
|
||||
Output: #{examples[0][:output]}
|
||||
|
||||
Input: #{examples[1][:input]}
|
||||
Output: #{examples[1][:output]}
|
||||
|
||||
Input: #{examples[2][:input]}
|
||||
Output: #{examples[2][:output]}
|
||||
|
||||
The text to translate will be provided in JSON format with the following structure:
|
||||
{"content": "Title to translate", "target_locale": "Target language code"}
|
||||
|
||||
Remember to keep proper nouns like "Minecraft" and "Toyota" in their original form. You are being consumed via an API that expects only the translated title. Only return the translated title in the correct language. Do not add questions or explanations.
|
||||
Provide your translation in the following JSON format:
|
||||
{"translation": "Your target_locale translation here"}
|
||||
|
||||
Remember to keep proper nouns like "Minecraft" and "Toyota" in their original form. Translate the title now and provide your answer in the specified JSON format.
|
||||
PROMPT
|
||||
end
|
||||
|
||||
def response_format
|
||||
[{ "key" => "translation", "type" => "string" }]
|
||||
end
|
||||
|
||||
def temperature
|
||||
0.3
|
||||
end
|
||||
|
||||
def examples
|
||||
[
|
||||
[
|
||||
{
|
||||
content: "New Update for Minecraft Adds Underwater Temples",
|
||||
target_locale: "es",
|
||||
}.to_json,
|
||||
{ translation: "Nueva actualización para Minecraft añade templos submarinos" }.to_json,
|
||||
],
|
||||
[
|
||||
{
|
||||
content: "Toyota announces revolutionary battery technology",
|
||||
target_locale: "fr",
|
||||
}.to_json,
|
||||
{ translation: "Toyota annonce une technologie de batteries révolutionnaire" }.to_json,
|
||||
],
|
||||
[
|
||||
{
|
||||
content:
|
||||
"Heathrow fechado: paralisação de voos deve continuar nos próximos dias, diz gestora do aeroporto de Londres",
|
||||
target_locale: "en",
|
||||
}.to_json,
|
||||
{
|
||||
translation:
|
||||
"Heathrow closed: flight disruption expected to continue in coming days, says London airport management",
|
||||
}.to_json,
|
||||
],
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,22 +46,13 @@ module DiscourseAi
|
||||
topic: @topic,
|
||||
post: @post,
|
||||
)
|
||||
max_tokens = get_max_tokens(text)
|
||||
llm_args = { max_tokens: }
|
||||
|
||||
result = +""
|
||||
bot.reply(context, llm_args:) { |partial| result << partial }
|
||||
result
|
||||
end
|
||||
|
||||
def get_max_tokens(text)
|
||||
if text.length < 100
|
||||
500
|
||||
elsif text.length < 500
|
||||
1000
|
||||
else
|
||||
text.length * 2
|
||||
structured_output = nil
|
||||
bot.reply(context) do |partial, _, type|
|
||||
structured_output = partial if type == :structured_output
|
||||
end
|
||||
|
||||
structured_output&.read_buffered_property(:translation)
|
||||
end
|
||||
|
||||
def persona_setting
|
||||
|
||||
@@ -42,12 +42,11 @@ module DiscourseAi
|
||||
post: @post,
|
||||
)
|
||||
|
||||
result = +""
|
||||
bot.reply(context) do |partial|
|
||||
next if partial.strip.blank?
|
||||
result << partial
|
||||
structured_output = nil
|
||||
bot.reply(context) do |partial, _, type|
|
||||
structured_output = partial if type == :structured_output
|
||||
end
|
||||
result
|
||||
structured_output&.read_buffered_property(:locale) || []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Translation
|
||||
class LocaleNormalizer
|
||||
# Normalizes locale string, matching the list of I18n.locales where possible
|
||||
# @param locale [String,Symbol] the locale to normalize
|
||||
# @return [String] the normalized locale
|
||||
def self.normalize_to_i18n(locale)
|
||||
return nil if locale.blank?
|
||||
locale = locale.to_s.gsub("-", "_")
|
||||
|
||||
i18n_pairs.each { |downcased, value| return value if locale.downcase == downcased }
|
||||
|
||||
locale
|
||||
end
|
||||
|
||||
def self.is_same?(locale1, locale2)
|
||||
return true if locale1 == locale2
|
||||
locale1 = locale1.gsub("-", "_").downcase
|
||||
locale2 = locale2.gsub("-", "_").downcase
|
||||
locale1.split("_").first == locale2.split("_").first
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.i18n_pairs
|
||||
# they should look like this for the input to match against:
|
||||
# {
|
||||
# "lowercased" => "actual",
|
||||
# "en" => "en",
|
||||
# "zh_cn" => "zh_CN",
|
||||
# "zh" => "zh_CN",
|
||||
# }
|
||||
@locale_map ||=
|
||||
I18n
|
||||
.available_locales
|
||||
.reduce({}) do |output, sym|
|
||||
locale = sym.to_s
|
||||
output[locale.downcase] = locale
|
||||
if locale.include?("_")
|
||||
short = locale.split("_").first
|
||||
output[short] = locale if output[short].blank?
|
||||
end
|
||||
output
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user