Compare commits

..
Author SHA1 Message Date
dependabot[bot] 415c1156e0 Build(deps-dev): Bump rack from 3.1.16 to 3.1.18
Bumps [rack](https://github.com/rack/rack) from 3.1.16 to 3.1.18.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/v3.1.16...v3.1.18)

---
updated-dependencies:
- dependency-name: rack
  dependency-version: 3.1.18
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-27 22:27:24 +00:00
Jarek Radosz c312b3184a DEV: Mention the core move in readme (#1514) 2025-07-22 11:08:32 +01:00
Jarek Radosz f231aad8b5 DEV: Disable the plugin by default (#1511)
…and preserve the current setting on existing sites
2025-07-22 11:05:52 +01:00
Alan Guo Xiang Tan cc77e73cfd PERF: Create index ON ai_topics_embeddings(topic_id, model_id) (#1513)
This index helps to speed up queries that joins the `topics` table
against the `ai_topics_embeddings` table on the `topic_id` column. There
are a number of queries which filters on `ai_topics_embeddings.model_id`
so we are including that in the index as well.
2025-07-22 16:46:56 +08:00
Natalie Tay 1d88ca44f0 DEV: Use core's locale normalizer (#1509)
We've moved the class to core here discourse/discourse#33702 so following up a deletion in this repo.
2025-07-22 10:09:03 +08:00
Roman Rizzi 6059b6e111 FIX: Customizable max_output_tokens for AI triage. (#1510)
We enforced a hard limit of 700 tokens in this script, which is not enough when using thinking models, which can quickly use all of them.

A temporary solution could be bumping the limit, but there is no guarantee we won't hit it again, and it's hard to find one value that fits all scenarios. Another alternative could be removing it and relying on the LLM config's `max_output_token`, but if you want different rules and want to assign different limits, you are forced to duplicate the config each time.

Considering all this, we are adding a dedicated field for this in the triage script, giving you an easy way to tweak it to your needs. If empty, no limit is applied.
2025-07-21 15:36:39 -03:00
Natalie Tay 5d80a34589 FIX: Add a max token limit based on the text to be translated (#1507)
We're seeing that some LLMs are using 65000+ tokens for raw text that is only 10-1000 characters long.

This PR adds a max_token to be passed to the LLM API for each translation based on the length of the text.
2025-07-17 17:47:15 +08:00
Natalie Tay 8630bc145e DEV: Set a min and max for translations backfill (#1508)
Since we use the value for days.ago and are seeing PG::DatetimeFieldOverflow: ERROR:  timestamp out of range: "5473790-07-13 08:43:28.497823 BC, set limits to the site setting.
2025-07-17 17:43:05 +08:00
281 changed files with 1107 additions and 946 deletions
+1
View File
@@ -1,3 +1,4 @@
< 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 -1
View File
@@ -34,7 +34,7 @@ GEM
racc
prettier_print (1.2.1)
racc (1.8.1)
rack (3.1.16)
rack (3.1.18)
rainbow (3.1.1)
regexp_parser (2.10.0)
rubocop (1.74.0)
+5 -2
View File
@@ -1,15 +1,18 @@
# **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
### Evals
The directory `evals` contains AI evals for the Discourse AI plugin.
The directory `evals` contains AI evals for the Discourse AI plugin.
You may create a local config by copying `config/eval-llms.yml` to `config/eval-llms.local.yml` and modifying the values.
To run them use:
To run them use:
cd evals
./run --help
+1 -1
View File
@@ -40,7 +40,7 @@ module Jobs
return if locales.blank?
locales.each do |locale|
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, detected_locale)
next if LocaleNormalizer.is_same?(locale, detected_locale)
regionless_locale = locale.split("_").first
next if post.post_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
+1 -1
View File
@@ -38,7 +38,7 @@ module Jobs
return if locales.blank?
locales.each do |locale|
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, detected_locale)
next if LocaleNormalizer.is_same?(locale, detected_locale)
regionless_locale = locale.split("_").first
next if topic.topic_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
+1 -1
View File
@@ -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 DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, category.locale)
next if LocaleNormalizer.is_same?(locale, category.locale)
begin
DiscourseAi::Translation::CategoryLocalizer.localize(category, locale)
+12
View File
@@ -12,6 +12,7 @@ class AiPersona < ActiveRecord::Base
validates :system_prompt, presence: true, length: { maximum: 10_000_000 }
validate :system_persona_unchangeable, on: :update, if: :system
validate :chat_preconditions
validate :allowed_seeded_model, if: :default_llm_id
validate :well_formated_examples
validates :max_context_posts, numericality: { greater_than: 0 }, allow_nil: true
# leaves some room for growth but sets a maximum to avoid memory issues
@@ -364,6 +365,17 @@ class AiPersona < ActiveRecord::Base
end
end
def allowed_seeded_model
return if default_llm_id.blank?
return if default_llm.nil?
return if !default_llm.seeded?
return if SiteSetting.ai_bot_allowed_seeded_models_map.include?(default_llm.id.to_s)
errors.add(:default_llm, I18n.t("discourse_ai.llm.configuration.invalid_seeded_model"))
end
def well_formated_examples
return if examples.blank?
+1 -1
View File
@@ -99,7 +99,7 @@ class LlmModel < ActiveRecord::Base
end
def identifier
"#{id}"
"custom:#{id}"
end
def toggle_companion_user
@@ -1,46 +0,0 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { ajax } from "discourse/lib/ajax";
import { i18n } from "discourse-i18n";
import SiteSettingComponent from "admin/components/site-setting";
import SiteSetting from "admin/models/site-setting";
export default class AiDefaultLlmSelector extends Component {
@tracked defaultLlmSetting = null;
constructor() {
super(...arguments);
this.#loadDefaultLlmSetting();
}
async #loadDefaultLlmSetting() {
const { site_settings } = await ajax("/admin/config/site_settings.json", {
data: {
plugin: "discourse-ai",
category: "discourse_ai",
},
});
const defaultLlmSetting = site_settings.find(
(setting) => setting.setting === "ai_default_llm_model"
);
this.defaultLlmSetting = SiteSetting.create(defaultLlmSetting);
}
<template>
<div class="ai-configure-default-llm">
<div class="ai-configure-default-llm__header">
<h3>{{i18n "discourse_ai.default_llm.title"}}</h3>
<p>{{i18n "discourse_ai.default_llm.description"}}</p>
</div>
{{#if this.defaultLlmSetting}}
<SiteSettingComponent
@setting={{this.defaultLlmSetting}}
class="ai-configure-default-llm__setting"
/>
{{/if}}
</div>
</template>
}
@@ -9,7 +9,6 @@ import DPageSubheader from "discourse/components/d-page-subheader";
import DSelect from "discourse/components/d-select";
import FilterInput from "discourse/components/filter-input";
import { i18n } from "discourse-i18n";
import AiDefaultLlmSelector from "./ai-default-llm-selector";
import AiFeaturesList from "./ai-features-list";
const ALL = "all";
@@ -203,8 +202,6 @@ export default class AiFeatures extends Component {
/>
</div>
<AiDefaultLlmSelector />
{{#if this.filteredFeatures.length}}
<AiFeaturesList @modules={{this.filteredFeatures}} />
{{else}}
@@ -9,7 +9,6 @@ import I18n, { i18n } from "discourse-i18n";
import AdminSectionLandingItem from "admin/components/admin-section-landing-item";
import AdminSectionLandingWrapper from "admin/components/admin-section-landing-wrapper";
import DTooltip from "float-kit/components/d-tooltip";
import AiDefaultLlmSelector from "./ai-default-llm-selector";
import AiLlmEditor from "./ai-llm-editor";
function isPreseeded(llm) {
@@ -138,9 +137,6 @@ export default class AiLlmsListEditor extends Component {
}}
@learnMoreUrl="https://meta.discourse.org/t/discourse-ai-large-language-model-llm-settings-page/319903"
/>
<AiDefaultLlmSelector />
{{#if this.hasLlmElements}}
<section class="ai-llms-list-editor__configured">
<DPageSubheader
@@ -90,7 +90,7 @@ export default class AiSpam extends Component {
this.isEnabled = model.is_enabled;
if (model.llm_id) {
this.selectedLLM = model.llm_id;
this.selectedLLM = "custom:" + model.llm_id;
} else {
if (this.availableLLMs.length) {
this.selectedLLM = this.availableLLMs[0].id;
@@ -127,7 +127,7 @@ export default class AiSpam extends Component {
}
get llmId() {
return this.selectedLLM;
return this.selectedLLM.toString().split(":")[1];
}
@action
@@ -158,51 +158,3 @@
.ai-expanded-list__toggle-button {
padding: 0;
}
.ai-configure-default-llm {
display: flex;
align-items: center;
gap: var(--space-2);
background: var(--primary-very-low);
padding: 1rem;
margin-block: 1rem;
border-radius: var(--d-border-radius);
&__header {
flex: 3;
}
&__setting {
flex: 2;
display: flex;
gap: var(--space-2);
align-items: center;
justify-content: center;
.setting-controls__undo {
display: none;
}
}
h3 {
color: var(--primary);
font-size: var(--font-0);
margin-bottom: 0;
}
p {
margin-top: 0;
font-size: var(--font-down-1);
color: var(--primary-high-or-secondary-low);
}
.select-kit,
.btn {
font-size: var(--font-down-1);
}
.setting-label,
.desc {
display: none;
}
}
+4 -5
View File
@@ -180,14 +180,13 @@ 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"
default_llm:
title: "Default LLM model"
description: "The default LLM model to use for all AI features. This will be used if no LLM is specified in the feature configuration or persona. If no default LLM is specified, the last created LLM will be used."
features:
short_title: "Features"
description: "These are the AI features available to visitors on your site. These can be configured to use specific personas and LLMs, and can be access controlled by groups."
@@ -570,7 +569,7 @@ en:
max_usages_required: "Must be set if max tokens is not set"
usage:
ai_bot: "AI bot"
ai_helper: "Helper (%{persona})"
ai_helper: "Helper"
ai_helper_image_caption: "Image caption"
ai_persona: "Persona (%{persona})"
ai_summarization: "Summarize"
+1 -2
View File
@@ -25,7 +25,6 @@ en:
description: "Periodic report based on a large language model"
site_settings:
discourse_ai_enabled: "Enable the discourse AI plugin."
ai_default_llm_model: "The default LLM model to use for all AI features"
ai_artifact_security: "The AI artifact system generates IFRAMEs with runnable code. Strict mode forces an extra click to run code. Lax mode runs code immediately. Hybrid mode allows user to supply data-ai-artifact-autorun to show right away. Disabled mode disables the artifact system."
ai_toxicity_enabled: "Enable the toxicity module."
ai_toxicity_inference_service_api_endpoint: "URL where the API is running for the toxicity module"
@@ -583,7 +582,7 @@ en:
llm:
configuration:
create_llm: "You need to setup an LLM before enabling this feature"
disable_modules_first: "You must disable these modules first: %{settings}"
disable_module_first: "You have to disable %{setting} first."
set_llm_first: "Set %{setting} first"
model_unreachable: "We couldn't get a response from this model. Check your settings first."
invalid_seeded_model: "You can't use this model with this feature"
+11 -13
View File
@@ -1,6 +1,6 @@
discourse_ai:
discourse_ai_enabled:
default: true
default: false
client: true
ai_artifact_security:
client: true
@@ -11,12 +11,6 @@ discourse_ai:
- "lax"
- "hybrid"
- "strict"
ai_default_llm_model:
default: ""
type: enum
allow_any: false
enum: "DiscourseAi::Configuration::LlmEnumerator"
validator: "DiscourseAi::Configuration::LlmValidator"
ai_sentiment_enabled:
default: false
@@ -109,7 +103,7 @@ discourse_ai:
default: false
client: true
area: "ai-features/ai_helper"
ai_helper_model: # Deprecated. TODO(keegan): Remove 2025-09-01
ai_helper_model:
default: ""
allow_any: false
type: enum
@@ -155,7 +149,7 @@ discourse_ai:
- "context_menu"
- "image_caption"
area: "ai-features/ai_helper"
ai_helper_image_caption_model: # Deprecated. TODO(keegan): Remove 2025-09-01
ai_helper_image_caption_model:
default: ""
type: enum
enum: "DiscourseAi::Configuration::LlmVisionEnumerator"
@@ -266,12 +260,12 @@ discourse_ai:
client: true
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
area: "ai-features/embeddings"
ai_embeddings_semantic_search_hyde_model: # Deprecated. TODO(keegan): Remove 2025-09-01
hidden: true
ai_embeddings_semantic_search_hyde_model:
default: ""
type: enum
allow_any: false
enum: "DiscourseAi::Configuration::LlmEnumerator"
validator: "DiscourseAi::Configuration::LlmValidator"
area: "ai-features/embeddings"
ai_embeddings_semantic_search_hyde_model_allowed_seeded_models:
default: ""
@@ -322,11 +316,12 @@ discourse_ai:
client: true
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
area: "ai-features/summarization"
ai_summarization_model: # Deprecated. TODO(keegan): Remove 2025-09-01
ai_summarization_model:
default: ""
allow_any: false
type: enum
enum: "DiscourseAi::Configuration::LlmEnumerator"
validator: "DiscourseAi::Configuration::LlmValidator"
hidden: true
ai_summarization_persona:
default: "-11"
@@ -488,11 +483,12 @@ discourse_ai:
client: true
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
area: "ai-features/translation"
ai_translation_model: # Deprecated. TODO(keegan): Remove 2025-09-01
ai_translation_model:
default: ""
type: enum
allow_any: false
enum: "DiscourseAi::Configuration::LlmEnumerator"
validator: "DiscourseAi::Configuration::LlmValidator"
area: "ai-features/translation"
ai_translation_locale_detector_persona:
default: "-27"
@@ -531,6 +527,8 @@ 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,16 +0,0 @@
# frozen_string_literal: true
class SeedAiDefaultLlmModel < ActiveRecord::Migration[7.2]
def up
return if DB.query_single("SELECT 1 FROM llm_models LIMIT 1").empty?
last_model_id = DB.query_single("SELECT id FROM llm_models ORDER BY id DESC LIMIT 1").first
if last_model_id.present?
execute "UPDATE site_settings SET value = '#{last_model_id}' WHERE name = 'ai_default_llm_model' AND (value IS NULL OR value = '');"
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,33 +0,0 @@
# frozen_string_literal: true
class CopyAiSummarizationModelToPersonaDefault < ActiveRecord::Migration[7.2]
def up
ai_summarization_model =
DB.query_single("SELECT value FROM site_settings WHERE name = 'ai_summarization_model'").first
if ai_summarization_model.present? && ai_summarization_model.start_with?("custom:")
# Extract the model ID from the setting value (e.g., "custom:-5" -> "-5")
model_id = ai_summarization_model.split(":").last
persona_settings = %w[ai_summarization_persona ai_summary_gists_persona]
default_persona_ids = [-11, -12]
persona_ids_query =
persona_settings
.map { |setting| "SELECT value FROM site_settings WHERE name = '#{setting}'" }
.join(" UNION ")
persona_ids = DB.query_single(persona_ids_query).compact
all_persona_ids = (default_persona_ids + persona_ids).map(&:to_i).uniq.join(",")
# Update the summarization personas with the extracted model ID
execute(<<~SQL)
UPDATE ai_personas
SET default_llm_id = #{model_id}
WHERE id IN (#{all_persona_ids}) AND default_llm_id IS NULL
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,43 +0,0 @@
# frozen_string_literal: true
class CopyAiHelperModelToPersonaDefault < ActiveRecord::Migration[7.2]
def up
ai_helper_model =
DB.query_single("SELECT value FROM site_settings WHERE name = 'ai_helper_model'").first
if ai_helper_model.present? && ai_helper_model.start_with?("custom:")
# Extract the model ID from the setting value (e.g., "custom:1" -> "1")
model_id = ai_helper_model.split(":").last
# Get persona IDs from site settings
persona_settings = %w[
ai_helper_proofreader_persona
ai_helper_title_suggestions_persona
ai_helper_explain_persona
ai_helper_smart_dates_persona
ai_helper_translator_persona
ai_helper_markdown_tables_persona
ai_helper_custom_prompt_persona
]
default_persona_ids = [-18, -19, -20, -22, -23, -24, -25]
persona_ids_query =
persona_settings
.map { |setting| "SELECT value FROM site_settings WHERE name = '#{setting}'" }
.join(" UNION ")
persona_ids = DB.query_single(persona_ids_query).compact
all_persona_ids = (default_persona_ids + persona_ids).map(&:to_i).uniq.join(",")
# Update the helper personas with the extracted model ID
execute(<<~SQL)
UPDATE ai_personas
SET default_llm_id = #{model_id}
WHERE id IN (#{all_persona_ids}) AND default_llm_id IS NULL
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,35 +0,0 @@
# frozen_string_literal: true
class CopyHydeModelToPersona < ActiveRecord::Migration[7.2]
def up
hyde_model =
DB.query_single(
"SELECT value FROM site_settings WHERE name = 'ai_embeddings_semantic_search_hyde_model'",
).first
if hyde_model.present? && hyde_model.start_with?("custom:")
# Extract the model ID from the setting value (e.g., "custom:1" -> "1")
model_id = hyde_model.split(":").last
persona_settings = %w[ai_embeddings_semantic_search_hyde_persona]
default_persona_ids = [-32]
persona_ids_query =
persona_settings
.map { |setting| "SELECT value FROM site_settings WHERE name = '#{setting}'" }
.join(" UNION ")
persona_ids = DB.query_single(persona_ids_query).compact
all_persona_ids = (default_persona_ids + persona_ids).map(&:to_i).uniq.join(",")
# Update the hyde persona with the extracted model ID
execute(<<~SQL)
UPDATE ai_personas
SET default_llm_id = #{model_id}
WHERE id IN (#{all_persona_ids}) AND default_llm_id IS NULL
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,39 +0,0 @@
# frozen_string_literal: true
class CopyTranslationModelToPersona < ActiveRecord::Migration[7.2]
def up
ai_translation_model =
DB.query_single("SELECT value FROM site_settings WHERE name = 'ai_translation_model'").first
if ai_translation_model.present? && ai_translation_model.start_with?("custom:")
# Extract the model ID from the setting value (e.g., "custom:-5" -> "-5")
model_id = ai_translation_model.split(":").last
persona_settings = %w[
ai_translation_locale_detector_persona
ai_translation_post_raw_translator_persona
ai_translation_topic_title_translator_persona
ai_translation_short_text_translator_persona
]
default_persona_ids = [-27, -28, -29, -30]
persona_ids_query =
persona_settings
.map { |setting| "SELECT value FROM site_settings WHERE name = '#{setting}'" }
.join(" UNION ")
persona_ids = DB.query_single(persona_ids_query).compact
all_persona_ids = (default_persona_ids + persona_ids).map(&:to_i).uniq.join(",")
# Update the translation personas (IDs -27, -28, -29, -30) with the extracted model ID
execute(<<~SQL)
UPDATE ai_personas
SET default_llm_id = #{model_id}
WHERE id IN (#{all_persona_ids}) AND default_llm_id IS NULL
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,37 +0,0 @@
# frozen_string_literal: true
class CopyAiImageCaptionModelToPersonaDefault < ActiveRecord::Migration[7.2]
def up
ai_helper_image_caption_model =
DB.query_single(
"SELECT value FROM site_settings WHERE name = 'ai_helper_image_caption_model'",
).first
if ai_helper_image_caption_model.present? &&
ai_helper_image_caption_model.start_with?("custom:")
# Extract the model ID from the setting value (e.g., "custom:1" -> "1")
model_id = ai_helper_image_caption_model.split(":").last
persona_settings = %w[ai_helper_post_illustrator_persona ai_helper_image_caption_persona]
default_persona_ids = [-21, -26]
persona_ids_query =
persona_settings
.map { |setting| "SELECT value FROM site_settings WHERE name = '#{setting}'" }
.join(" UNION ")
persona_ids = DB.query_single(persona_ids_query).compact
all_persona_ids = (default_persona_ids + persona_ids).map(&:to_i).uniq.join(",")
# Update the helper personas with the extracted model ID
execute(<<~SQL)
UPDATE ai_personas
SET default_llm_id = #{model_id}
WHERE id IN (#{all_persona_ids}) AND default_llm_id IS NULL
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -0,0 +1,22 @@
# 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
@@ -0,0 +1,23 @@
# frozen_string_literal: true
class EnableAiIfAlreadyInstalled < ActiveRecord::Migration[7.2]
def up
installed_at = DB.query_single(<<~SQL)&.first
SELECT created_at FROM schema_migration_details WHERE version='20230224165056'
SQL
if installed_at && installed_at < 1.hour.ago
# The plugin was installed before we changed it to be disabled-by-default
# Therefore, if there is no existing database value, enable the plugin
execute <<~SQL
INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
VALUES('discourse_ai_enabled', 5, 't', NOW(), NOW())
ON CONFLICT (name) DO NOTHING
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -0,0 +1,11 @@
# 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
+5
View File
@@ -24,6 +24,7 @@ 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
@@ -85,6 +86,9 @@ 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")
@@ -122,6 +126,7 @@ if defined?(DiscourseAutomation)
stop_sequences: stop_sequences,
automation: self.automation,
temperature: temperature,
max_output_tokens: max_output_tokens,
action: context["action"],
)
rescue => e
+11 -2
View File
@@ -312,9 +312,18 @@ module DiscourseAi
# Priorities are:
# 1. Persona's default LLM
# 2. SiteSetting.ai_default_llm_id (or newest LLM if not set)
# 2. Hidden `ai_helper_model` setting, or `ai_helper_image_caption_model` for image_caption.
# 3. Newest LLM config
def self.find_ai_helper_model(helper_mode, persona_klass)
model_id = persona_klass.default_llm_id || SiteSetting.ai_default_llm_model
model_id = persona_klass.default_llm_id
if !model_id
if helper_mode == IMAGE_CAPTION
model_id = SiteSetting.ai_helper_image_caption_model&.split(":")&.last
else
model_id = SiteSetting.ai_helper_model&.split(":")&.last
end
end
if model_id.present?
LlmModel.find_by(id: model_id)
+1 -5
View File
@@ -12,14 +12,10 @@ module DiscourseAi
return nil if content.blank?
suggested_title = call_llm(content)
return nil if suggested_title.blank?
cleanup(suggested_title)
end
def call_llm(thread_content)
return nil if SiteSetting.ai_default_llm_model.blank?
chat = "<input>\n#{thread_content}\n</input>"
prompt =
@@ -34,7 +30,7 @@ module DiscourseAi
messages: [{ type: :user, content: chat, id: "User" }],
)
DiscourseAi::Completions::Llm.proxy(SiteSetting.ai_default_llm_model).generate(
DiscourseAi::Completions::Llm.proxy(SiteSetting.ai_helper_model).generate(
prompt,
user: Discourse.system_user,
stop_sequences: ["</input>"],
+1 -7
View File
@@ -57,12 +57,6 @@ module DiscourseAi
end
def diffusion_prompt(text, user)
llm_model =
AiPersona.find_by(id: SiteSetting.ai_helper_post_illustrator_persona)&.default_llm_id ||
SiteSetting.ai_default_llm_model
return nil if llm_model.blank?
prompt =
DiscourseAi::Completions::Prompt.new(
<<~TEXT.strip,
@@ -72,7 +66,7 @@ module DiscourseAi
messages: [{ type: :user, content: text, id: user.username }],
)
DiscourseAi::Completions::Llm.proxy(llm_model).generate(
DiscourseAi::Completions::Llm.proxy(SiteSetting.ai_helper_model).generate(
prompt,
user: user,
feature_name: "illustrate_post",
+1 -1
View File
@@ -37,7 +37,7 @@ module DiscourseAi
value_h["id"] > 0 ||
SiteSetting.ai_automation_allowed_seeded_models_map.include?(value_h["id"].to_s)
end
.each { |value_h| value_h["id"] = "#{value_h["id"]}" }
.each { |value_h| value_h["id"] = "custom:#{value_h["id"]}" }
values
end

Some files were not shown because too many files have changed in this diff Show More