2024-05-13 12:46:42 -03:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class LlmModel < ActiveRecord::Base
|
2024-06-18 14:32:14 -03:00
|
|
|
FIRST_BOT_USER_ID = -1200
|
2024-06-24 19:26:30 -03:00
|
|
|
BEDROCK_PROVIDER_NAME = "aws_bedrock"
|
2024-06-18 14:32:14 -03:00
|
|
|
|
2025-01-14 15:54:09 +11:00
|
|
|
has_many :llm_quotas, dependent: :destroy
|
2024-06-18 14:32:14 -03:00
|
|
|
belongs_to :user
|
|
|
|
|
2024-08-06 14:35:35 -03:00
|
|
|
validates :display_name, presence: true, length: { maximum: 100 }
|
|
|
|
validates :tokenizer, presence: true, inclusion: DiscourseAi::Completions::Llm.tokenizer_names
|
|
|
|
validates :provider, presence: true, inclusion: DiscourseAi::Completions::Llm.provider_names
|
|
|
|
validates :url, presence: true, unless: -> { provider == BEDROCK_PROVIDER_NAME }
|
|
|
|
validates_presence_of :name, :api_key
|
|
|
|
validates :max_prompt_tokens, numericality: { greater_than: 0 }
|
2024-08-07 16:08:56 -03:00
|
|
|
validate :required_provider_params
|
2024-12-17 08:00:05 +09:00
|
|
|
scope :in_use,
|
|
|
|
-> do
|
|
|
|
model_ids = DiscourseAi::Configuration::LlmEnumerator.global_usage.keys
|
|
|
|
where(id: model_ids)
|
|
|
|
end
|
2024-08-06 14:35:35 -03:00
|
|
|
|
2024-06-24 19:26:30 -03:00
|
|
|
def self.provider_params
|
|
|
|
{
|
|
|
|
aws_bedrock: {
|
2024-08-21 11:41:55 -03:00
|
|
|
access_key_id: :text,
|
|
|
|
region: :text,
|
2024-10-25 06:24:53 +11:00
|
|
|
disable_native_tools: :checkbox,
|
2025-02-25 17:32:12 +11:00
|
|
|
enable_reasoning: :checkbox,
|
|
|
|
reasoning_tokens: :number,
|
2024-10-25 06:24:53 +11:00
|
|
|
},
|
|
|
|
anthropic: {
|
|
|
|
disable_native_tools: :checkbox,
|
2025-02-25 17:32:12 +11:00
|
|
|
enable_reasoning: :checkbox,
|
|
|
|
reasoning_tokens: :number,
|
2024-06-24 19:26:30 -03:00
|
|
|
},
|
|
|
|
open_ai: {
|
2024-08-21 11:41:55 -03:00
|
|
|
organization: :text,
|
2024-11-19 09:22:39 +11:00
|
|
|
disable_native_tools: :checkbox,
|
2025-01-13 17:01:01 +11:00
|
|
|
disable_streaming: :checkbox,
|
2025-02-01 14:08:34 +11:00
|
|
|
reasoning_effort: {
|
|
|
|
type: :enum,
|
|
|
|
values: %w[default low medium high],
|
|
|
|
default: "default",
|
|
|
|
},
|
2024-11-19 09:22:39 +11:00
|
|
|
},
|
2024-11-19 17:28:09 +11:00
|
|
|
mistral: {
|
|
|
|
disable_native_tools: :checkbox,
|
|
|
|
},
|
2024-11-19 09:22:39 +11:00
|
|
|
google: {
|
|
|
|
disable_native_tools: :checkbox,
|
|
|
|
},
|
|
|
|
azure: {
|
|
|
|
disable_native_tools: :checkbox,
|
2024-08-21 11:41:55 -03:00
|
|
|
},
|
|
|
|
hugging_face: {
|
|
|
|
disable_system_prompt: :checkbox,
|
|
|
|
},
|
|
|
|
vllm: {
|
|
|
|
disable_system_prompt: :checkbox,
|
|
|
|
},
|
|
|
|
ollama: {
|
|
|
|
disable_system_prompt: :checkbox,
|
2024-10-11 07:25:53 +11:00
|
|
|
enable_native_tool: :checkbox,
|
2025-01-13 17:01:01 +11:00
|
|
|
disable_streaming: :checkbox,
|
2024-06-24 19:26:30 -03:00
|
|
|
},
|
2024-12-10 05:59:19 +11:00
|
|
|
open_router: {
|
|
|
|
disable_native_tools: :checkbox,
|
|
|
|
provider_order: :text,
|
|
|
|
provider_quantizations: :text,
|
2025-01-13 17:01:01 +11:00
|
|
|
disable_streaming: :checkbox,
|
2024-12-10 05:59:19 +11:00
|
|
|
},
|
2024-06-24 19:26:30 -03:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2024-06-27 17:27:40 +10:00
|
|
|
def to_llm
|
FEATURE: PDF support for rag pipeline (#1118)
This PR introduces several enhancements and refactorings to the AI Persona and RAG (Retrieval-Augmented Generation) functionalities within the discourse-ai plugin. Here's a breakdown of the changes:
**1. LLM Model Association for RAG and Personas:**
- **New Database Columns:** Adds `rag_llm_model_id` to both `ai_personas` and `ai_tools` tables. This allows specifying a dedicated LLM for RAG indexing, separate from the persona's primary LLM. Adds `default_llm_id` and `question_consolidator_llm_id` to `ai_personas`.
- **Migration:** Includes a migration (`20250210032345_migrate_persona_to_llm_model_id.rb`) to populate the new `default_llm_id` and `question_consolidator_llm_id` columns in `ai_personas` based on the existing `default_llm` and `question_consolidator_llm` string columns, and a post migration to remove the latter.
- **Model Changes:** The `AiPersona` and `AiTool` models now `belong_to` an `LlmModel` via `rag_llm_model_id`. The `LlmModel.proxy` method now accepts an `LlmModel` instance instead of just an identifier. `AiPersona` now has `default_llm_id` and `question_consolidator_llm_id` attributes.
- **UI Updates:** The AI Persona and AI Tool editors in the admin panel now allow selecting an LLM for RAG indexing (if PDF/image support is enabled). The RAG options component displays an LLM selector.
- **Serialization:** The serializers (`AiCustomToolSerializer`, `AiCustomToolListSerializer`, `LocalizedAiPersonaSerializer`) have been updated to include the new `rag_llm_model_id`, `default_llm_id` and `question_consolidator_llm_id` attributes.
**2. PDF and Image Support for RAG:**
- **Site Setting:** Introduces a new hidden site setting, `ai_rag_pdf_images_enabled`, to control whether PDF and image files can be indexed for RAG. This defaults to `false`.
- **File Upload Validation:** The `RagDocumentFragmentsController` now checks the `ai_rag_pdf_images_enabled` setting and allows PDF, PNG, JPG, and JPEG files if enabled. Error handling is included for cases where PDF/image indexing is attempted with the setting disabled.
- **PDF Processing:** Adds a new utility class, `DiscourseAi::Utils::PdfToImages`, which uses ImageMagick (`magick`) to convert PDF pages into individual PNG images. A maximum PDF size and conversion timeout are enforced.
- **Image Processing:** A new utility class, `DiscourseAi::Utils::ImageToText`, is included to handle OCR for the images and PDFs.
- **RAG Digestion Job:** The `DigestRagUpload` job now handles PDF and image uploads. It uses `PdfToImages` and `ImageToText` to extract text and create document fragments.
- **UI Updates:** The RAG uploader component now accepts PDF and image file types if `ai_rag_pdf_images_enabled` is true. The UI text is adjusted to indicate supported file types.
**3. Refactoring and Improvements:**
- **LLM Enumeration:** The `DiscourseAi::Configuration::LlmEnumerator` now provides a `values_for_serialization` method, which returns a simplified array of LLM data (id, name, vision_enabled) suitable for use in serializers. This avoids exposing unnecessary details to the frontend.
- **AI Helper:** The `AiHelper::Assistant` now takes optional `helper_llm` and `image_caption_llm` parameters in its constructor, allowing for greater flexibility.
- **Bot and Persona Updates:** Several updates were made across the codebase, changing the string based association to a LLM to the new model based.
- **Audit Logs:** The `DiscourseAi::Completions::Endpoints::Base` now formats raw request payloads as pretty JSON for easier auditing.
- **Eval Script:** An evaluation script is included.
**4. Testing:**
- The PR introduces a new eval system for LLMs, this allows us to test how functionality works across various LLM providers. This lives in `/evals`
2025-02-14 12:15:07 +11:00
|
|
|
DiscourseAi::Completions::Llm.proxy(self)
|
2024-12-12 09:17:25 +11:00
|
|
|
end
|
|
|
|
|
|
|
|
def identifier
|
|
|
|
"custom:#{id}"
|
2024-06-27 17:27:40 +10:00
|
|
|
end
|
|
|
|
|
2024-06-18 14:32:14 -03:00
|
|
|
def toggle_companion_user
|
|
|
|
return if name == "fake" && Rails.env.production?
|
|
|
|
|
|
|
|
enable_check = SiteSetting.ai_bot_enabled && enabled_chat_bot
|
|
|
|
|
|
|
|
if enable_check
|
|
|
|
if !user
|
|
|
|
next_id = DB.query_single(<<~SQL).first
|
|
|
|
SELECT min(id) - 1 FROM users
|
|
|
|
SQL
|
|
|
|
|
|
|
|
new_user =
|
|
|
|
User.new(
|
|
|
|
id: [FIRST_BOT_USER_ID, next_id].min,
|
2024-06-21 17:32:15 +10:00
|
|
|
email: "no_email_#{SecureRandom.hex}",
|
2024-06-18 14:32:14 -03:00
|
|
|
name: name.titleize,
|
|
|
|
username: UserNameSuggester.suggest(name),
|
|
|
|
active: true,
|
|
|
|
approved: true,
|
|
|
|
admin: true,
|
|
|
|
moderator: true,
|
|
|
|
trust_level: TrustLevel[4],
|
|
|
|
)
|
|
|
|
new_user.save!(validate: false)
|
|
|
|
self.update!(user: new_user)
|
|
|
|
else
|
2024-06-19 15:49:36 +10:00
|
|
|
user.active = true
|
|
|
|
user.save!(validate: false)
|
2024-06-18 14:32:14 -03:00
|
|
|
end
|
|
|
|
elsif user
|
|
|
|
# will include deleted
|
|
|
|
has_posts = DB.query_single("SELECT 1 FROM posts WHERE user_id = #{user.id} LIMIT 1").present?
|
|
|
|
|
|
|
|
if has_posts
|
|
|
|
user.update!(active: false) if user.active
|
|
|
|
else
|
|
|
|
user.destroy!
|
|
|
|
self.update!(user: nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-13 12:46:42 -03:00
|
|
|
def tokenizer_class
|
|
|
|
tokenizer.constantize
|
|
|
|
end
|
2024-06-24 19:26:30 -03:00
|
|
|
|
|
|
|
def lookup_custom_param(key)
|
|
|
|
provider_params&.dig(key)
|
|
|
|
end
|
2024-08-07 16:08:56 -03:00
|
|
|
|
2024-08-28 15:57:58 -03:00
|
|
|
def seeded?
|
2024-11-05 23:19:13 +09:00
|
|
|
id.present? && id < 0
|
|
|
|
end
|
|
|
|
|
|
|
|
def api_key
|
|
|
|
if seeded?
|
|
|
|
env_key = "DISCOURSE_AI_SEEDED_LLM_API_KEY_#{id.abs}"
|
|
|
|
ENV[env_key] || self[:api_key]
|
|
|
|
else
|
|
|
|
self[:api_key]
|
|
|
|
end
|
2024-08-28 15:57:58 -03:00
|
|
|
end
|
|
|
|
|
2024-08-07 16:08:56 -03:00
|
|
|
private
|
|
|
|
|
|
|
|
def required_provider_params
|
|
|
|
return if provider != BEDROCK_PROVIDER_NAME
|
|
|
|
|
|
|
|
%w[access_key_id region].each do |field|
|
|
|
|
if lookup_custom_param(field).blank?
|
|
|
|
errors.add(:base, I18n.t("discourse_ai.llm_models.missing_provider_param", param: field))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2024-05-13 12:46:42 -03:00
|
|
|
end
|
2024-05-21 13:35:50 -03:00
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: llm_models
|
|
|
|
#
|
|
|
|
# id :bigint not null, primary key
|
|
|
|
# display_name :string
|
|
|
|
# name :string not null
|
|
|
|
# provider :string not null
|
|
|
|
# tokenizer :string not null
|
|
|
|
# max_prompt_tokens :integer not null
|
|
|
|
# created_at :datetime not null
|
|
|
|
# updated_at :datetime not null
|
|
|
|
# url :string
|
|
|
|
# api_key :string
|
2024-06-18 14:32:14 -03:00
|
|
|
# user_id :integer
|
|
|
|
# enabled_chat_bot :boolean default(FALSE), not null
|
2024-07-24 16:29:47 -03:00
|
|
|
# provider_params :jsonb
|
|
|
|
# vision_enabled :boolean default(FALSE), not null
|
2024-05-21 13:35:50 -03:00
|
|
|
#
|