From 4a29f8ed1c6571545e2aab44e23823212f764b4a Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Apr 2024 23:22:06 +1000 Subject: [PATCH] FEATURE: Enhance AI debugging capabilities and improve interface adjustments (#577) * FIX: various RAG edge cases - Nicer text to describe RAG, avoids the word RAG - Do not attempt to save persona when removing uploads and it is not created - Remove old code that avoided touching rag params on create * FIX: Missing pause button for persona users * Feature: allow specific users to debug ai request / response chains This can help users easily tune RAG and figure out what is going on with requests. * discourse helper so it does not explode * fix test * simplify implementation --- .../discourse_ai/ai_bot/bot_controller.rb | 15 ++ app/models/ai_api_audit_log.rb | 3 + app/models/ai_persona.rb | 26 ++++ .../discourse/admin/models/ai-persona.js | 10 +- .../components/ai-persona-editor.gjs | 4 +- .../components/modal/debug-ai-modal.gjs | 136 ++++++++++++++++++ .../components/persona-rag-uploader.gjs | 2 +- .../initializers/ai-bot-replies.js | 45 +++++- .../modules/ai-bot/common/bot-replies.scss | 20 +++ config/locales/client.en.yml | 15 +- config/locales/server.en.yml | 1 + config/routes.rb | 3 +- config/settings.yml | 5 + lib/ai_bot/bot.rb | 1 - lib/ai_bot/entry_point.rb | 22 ++- lib/ai_bot/tools/discourse_meta_search.rb | 11 +- lib/guardian_extensions.rb | 13 ++ spec/lib/modules/ai_bot/entry_point_spec.rb | 24 ++++ spec/requests/ai_bot/bot_controller_spec.rb | 52 ++++++- .../unit/models/ai-persona-test.js | 3 + 20 files changed, 385 insertions(+), 26 deletions(-) create mode 100644 assets/javascripts/discourse/components/modal/debug-ai-modal.gjs diff --git a/app/controllers/discourse_ai/ai_bot/bot_controller.rb b/app/controllers/discourse_ai/ai_bot/bot_controller.rb index cffa1fde..5da8c0c9 100644 --- a/app/controllers/discourse_ai/ai_bot/bot_controller.rb +++ b/app/controllers/discourse_ai/ai_bot/bot_controller.rb @@ -6,6 +6,21 @@ module DiscourseAi requires_plugin ::DiscourseAi::PLUGIN_NAME requires_login + def show_debug_info + post = Post.find(params[:post_id]) + guardian.ensure_can_debug_ai_bot_conversation!(post) + + posts = + Post + .where("post_number <= ?", post.post_number) + .where(topic_id: post.topic_id) + .order("post_number DESC") + + debug_info = AiApiAuditLog.where(post: posts).order(created_at: :desc).first + + render json: debug_info, status: 200 + end + def stop_streaming_response post = Post.find(params[:post_id]) guardian.ensure_can_see!(post) diff --git a/app/models/ai_api_audit_log.rb b/app/models/ai_api_audit_log.rb index 01c3440e..7001bc08 100644 --- a/app/models/ai_api_audit_log.rb +++ b/app/models/ai_api_audit_log.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true class AiApiAuditLog < ActiveRecord::Base + belongs_to :post + belongs_to :topic + module Provider OpenAI = 1 Anthropic = 2 diff --git a/app/models/ai_persona.rb b/app/models/ai_persona.rb index e88cc14f..92919fa2 100644 --- a/app/models/ai_persona.rb +++ b/app/models/ai_persona.rb @@ -76,6 +76,32 @@ class AiPersona < ActiveRecord::Base .map(&:class_instance) end + def self.persona_users(user: nil) + persona_users = + persona_cache[:persona_users] ||= AiPersona + .where(enabled: true) + .joins(:user) + .pluck( + "ai_personas.id, users.id, users.username_lower, allowed_group_ids, default_llm, mentionable", + ) + .map do |id, user_id, username, allowed_group_ids, default_llm, mentionable| + { + id: id, + user_id: user_id, + username: username, + allowed_group_ids: allowed_group_ids, + default_llm: default_llm, + mentionable: mentionable, + } + end + + if user + persona_users.select { |mentionable| user.in_any_groups?(mentionable[:allowed_group_ids]) } + else + persona_users + end + end + def self.mentionables(user: nil) all_mentionables = persona_cache[:mentionable_usernames] ||= AiPersona diff --git a/assets/javascripts/discourse/admin/models/ai-persona.js b/assets/javascripts/discourse/admin/models/ai-persona.js index 8b731dfd..4b4a705c 100644 --- a/assets/javascripts/discourse/admin/models/ai-persona.js +++ b/assets/javascripts/discourse/admin/models/ai-persona.js @@ -22,14 +22,10 @@ const CREATE_ATTRIBUTES = [ "vision_enabled", "vision_max_pixels", "rag_uploads", -]; - -// rag params are populated on save, only show it when editing -const ATTRIBUTES = CREATE_ATTRIBUTES.concat([ "rag_chunk_tokens", "rag_chunk_overlap_tokens", "rag_conversation_chunks", -]); +]; const SYSTEM_ATTRIBUTES = [ "id", @@ -129,7 +125,7 @@ export default class AiPersona extends RestModel { updateProperties() { let attrs = this.system ? this.getProperties(SYSTEM_ATTRIBUTES) - : this.getProperties(ATTRIBUTES); + : this.getProperties(CREATE_ATTRIBUTES); attrs.id = this.id; this.populateCommandOptions(attrs); @@ -143,7 +139,7 @@ export default class AiPersona extends RestModel { } workingCopy() { - let attrs = this.getProperties(ATTRIBUTES); + let attrs = this.getProperties(CREATE_ATTRIBUTES); this.populateCommandOptions(attrs); return AiPersona.create(attrs); } diff --git a/assets/javascripts/discourse/components/ai-persona-editor.gjs b/assets/javascripts/discourse/components/ai-persona-editor.gjs index d9978712..72659465 100644 --- a/assets/javascripts/discourse/components/ai-persona-editor.gjs +++ b/assets/javascripts/discourse/components/ai-persona-editor.gjs @@ -217,7 +217,9 @@ export default class PersonaEditor extends Component { @action removeUpload(upload) { this.editingModel.rag_uploads.removeObject(upload); - this.save(); + if (!this.args.model.isNew) { + this.save(); + } } async toggleField(field, sortPersonas) { diff --git a/assets/javascripts/discourse/components/modal/debug-ai-modal.gjs b/assets/javascripts/discourse/components/modal/debug-ai-modal.gjs new file mode 100644 index 00000000..c61b12fa --- /dev/null +++ b/assets/javascripts/discourse/components/modal/debug-ai-modal.gjs @@ -0,0 +1,136 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { action } from "@ember/object"; +import { next } from "@ember/runloop"; +import { htmlSafe } from "@ember/template"; +import DButton from "discourse/components/d-button"; +import DModal from "discourse/components/d-modal"; +import { ajax } from "discourse/lib/ajax"; +import { clipboardCopy, escapeExpression } from "discourse/lib/utilities"; +import i18n from "discourse-common/helpers/i18n"; +import discourseLater from "discourse-common/lib/later"; +import I18n from "discourse-i18n"; + +export default class DebugAiModal extends Component { + @tracked info = null; + @tracked justCopiedText = ""; + + constructor() { + super(...arguments); + next(() => { + this.loadApiRequestInfo(); + }); + } + + get htmlContext() { + if (!this.info) { + return ""; + } + + let parsed; + + try { + parsed = JSON.parse(this.info.raw_request_payload); + } catch (e) { + return this.info.raw_request_payload; + } + + return htmlSafe(this.jsonToHtml(parsed)); + } + + jsonToHtml(json) { + let html = ""; + return html; + } + + @action + copyRequest() { + this.copy(this.info.raw_request_payload); + } + + @action + copyResponse() { + this.copy(this.info.raw_response_payload); + } + + copy(text) { + clipboardCopy(text); + this.justCopiedText = I18n.t("discourse_ai.ai_bot.conversation_shared"); + + discourseLater(() => { + this.justCopiedText = ""; + }, 2000); + } + + loadApiRequestInfo() { + ajax( + `/discourse-ai/ai-bot/post/${this.args.model.id}/show-debug-info.json` + ).then((result) => { + this.info = result; + }); + } + + +} diff --git a/assets/javascripts/discourse/components/persona-rag-uploader.gjs b/assets/javascripts/discourse/components/persona-rag-uploader.gjs index 7a0a2b6c..286b97e0 100644 --- a/assets/javascripts/discourse/components/persona-rag-uploader.gjs +++ b/assets/javascripts/discourse/components/persona-rag-uploader.gjs @@ -34,7 +34,7 @@ export default class PersonaRagUploader extends Component.extend( this.filteredUploads = this.ragUploads || []; - if (this.ragUploads?.length) { + if (this.ragUploads?.length && this.persona?.id) { ajax( `/admin/plugins/discourse-ai/ai-personas/${this.persona.id}/files/status.json` ).then((statuses) => { diff --git a/assets/javascripts/initializers/ai-bot-replies.js b/assets/javascripts/initializers/ai-bot-replies.js index e4020e33..1a0b475f 100644 --- a/assets/javascripts/initializers/ai-bot-replies.js +++ b/assets/javascripts/initializers/ai-bot-replies.js @@ -3,6 +3,7 @@ import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { withPluginApi } from "discourse/lib/plugin-api"; import { registerWidgetShim } from "discourse/widgets/render-glimmer"; +import DebugAiModal from "../discourse/components/modal/debug-ai-modal"; import ShareModal from "../discourse/components/modal/share-modal"; import streamText from "../discourse/lib/ai-streamer"; import copyConversation from "../discourse/lib/copy-conversation"; @@ -11,6 +12,7 @@ import AiBotHeaderIcon from "../discourse/components/ai-bot-header-icon"; import { showShareConversationModal } from "../discourse/lib/ai-bot-helper"; let enabledChatBotIds = []; +let allowDebug = false; function isGPTBot(user) { return user && enabledChatBotIds.includes(user.id); } @@ -102,6 +104,44 @@ function initializePersonaDecorator(api) { const MAX_PERSONA_USER_ID = -1200; +function initializeDebugButton(api) { + const currentUser = api.getCurrentUser(); + if (!currentUser || !currentUser.ai_enabled_chat_bots || !allowDebug) { + return; + } + + let debugAiResponse = async function ({ post }) { + const modal = api.container.lookup("service:modal"); + + modal.show(DebugAiModal, { model: post }); + }; + + api.addPostMenuButton("debugAi", (post) => { + if (post.topic?.archetype !== "private_message") { + return; + } + + if ( + !currentUser.ai_enabled_chat_bots.any( + (bot) => post.username === bot.username + ) + ) { + // special handling for personas (persona bot users start at ID -1200 and go down) + if (post.user_id > MAX_PERSONA_USER_ID) { + return; + } + } + + return { + action: debugAiResponse, + icon: "info", + className: "post-action-menu__debug-ai", + title: "discourse_ai.ai_bot.debug_ai", + position: "first", + }; + }); +} + function initializeShareButton(api) { const currentUser = api.getCurrentUser(); if (!currentUser || !currentUser.ai_enabled_chat_bots) { @@ -113,6 +153,7 @@ function initializeShareButton(api) { await copyConversation(post.topic, 1, post.post_number); showFeedback("discourse_ai.ai_bot.conversation_shared"); } else { + const modal = api.container.lookup("service:modal"); modal.show(ShareModal, { model: post }); } }; @@ -142,8 +183,6 @@ function initializeShareButton(api) { position: "first", }; }); - - const modal = api.container.lookup("service:modal"); } function initializeShareTopicButton(api) { @@ -177,9 +216,11 @@ export default { if (user?.ai_enabled_chat_bots) { enabledChatBotIds = user.ai_enabled_chat_bots.map((bot) => bot.id); + allowDebug = user.can_debug_ai_bot_conversations; withPluginApi("1.6.0", attachHeaderIcon); withPluginApi("1.6.0", initializeAIBotReplies); withPluginApi("1.6.0", initializePersonaDecorator); + withPluginApi("1.22.0", (api) => initializeDebugButton(api, container)); withPluginApi("1.22.0", (api) => initializeShareButton(api, container)); withPluginApi("1.22.0", (api) => initializeShareTopicButton(api, container) diff --git a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss index d92220b9..d60ff11c 100644 --- a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss +++ b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss @@ -141,3 +141,23 @@ details.ai-quote { span.onebox-ai-llm-title { font-weight: bold; } + +.d-modal.ai-debug-modal { + --modal-max-width: 99%; + ul { + padding-left: 1em; + } + + li { + margin-bottom: 0.2em; + } + + li > ul { + margin-top: 0.2em; + margin-bottom: 0.2em; + } +} + +.ai-debug-modal__tokens span { + display: block; +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f8a605ec..ed7c2bee 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -160,11 +160,11 @@ en: priority: Priority priority_help: Priority personas are displayed to users at the top of the persona list. If multiple personas have priority, they will be sorted alphabetically. command_options: "Command Options" - rag_chunk_tokens: "RAG Chunk Tokens" + rag_chunk_tokens: "Upload Chunk Tokens" rag_chunk_tokens_help: "The number of tokens to use for each chunk in the RAG model. Increase to increase the amount of context the AI can use. (changing will re-index all uploads)" - rag_chunk_overlap_tokens: "RAG Chunk Overlap Tokens" + rag_chunk_overlap_tokens: "Upload Chunk Overlap Tokens" rag_chunk_overlap_tokens_help: "The number of tokens to overlap between chunks in the RAG model. (changing will re-index all uploads)" - rag_conversation_chunks: "RAG Conversation Chunks" + rag_conversation_chunks: "Search Conversation Chunks" rag_conversation_chunks_help: "The number of chunks to use for the RAG model searches. Increase to increase the amount of context the AI can use." what_are_personas: "What are AI Personas?" no_persona_selected: | @@ -187,6 +187,8 @@ en: indexed: "Indexed" indexing: "Indexing" uploaded: "Ready to be indexed" + uploading: "Uploading..." + remove: "Remove upload" related_topics: title: "Related Topics" @@ -256,6 +258,13 @@ en: shortcut_title: "Start a PM with an AI bot" share: "Share AI conversation" conversation_shared: "Conversation copied" + debug_ai: "View raw AI request and response" + debug_ai_modal: + title: "View AI interaction" + copy_request: "Copy request" + copy_response: "Copy response" + request_tokens: "Request tokens:" + response_tokens: "Response tokens:" share_full_topic_modal: title: "Share Conversation Publicly" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index c6f6209b..88ef5778 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -96,6 +96,7 @@ en: ai_bot_enabled: "Enable the AI Bot module." ai_bot_enable_chat_warning: "Display a warning when PM chat is initiated. Can be overriden by editing the translation string: discourse_ai.ai_bot.pm_warning" ai_bot_allowed_groups: "When the GPT Bot has access to the PM, it will reply to members of these groups." + ai_bot_debugging_allowed_groups: "Allow these groups to see a debug button on posts which displays the raw AI request and response" ai_bot_public_sharing_allowed_groups: "Allow these groups to share AI personal messages with the public via a unique publicly available link" ai_bot_enabled_chat_bots: "Available models to act as an AI Bot" ai_bot_add_to_header: "Display a button in the header to start a PM with a AI Bot" diff --git a/config/routes.rb b/config/routes.rb index c50f4265..eb20cf98 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,8 +16,9 @@ DiscourseAi::Engine.routes.draw do end scope module: :ai_bot, path: "/ai-bot", defaults: { format: :json } do - post "post/:post_id/stop-streaming" => "bot#stop_streaming_response" get "bot-username" => "bot#show_bot_username" + get "post/:post_id/show-debug-info" => "bot#show_debug_info" + post "post/:post_id/stop-streaming" => "bot#stop_streaming_response" end scope module: :ai_bot, path: "/ai-bot/shared-ai-conversations" do diff --git a/config/settings.yml b/config/settings.yml index e656205d..fc3a024f 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -313,6 +313,11 @@ discourse_ai: ai_bot_enable_chat_warning: default: false client: true + ai_bot_debugging_allowed_groups: + type: group_list + list_type: compact + default: "" + allow_any: false ai_bot_allowed_groups: type: group_list list_type: compact diff --git a/lib/ai_bot/bot.rb b/lib/ai_bot/bot.rb index f0bb1856..11dd55f9 100644 --- a/lib/ai_bot/bot.rb +++ b/lib/ai_bot/bot.rb @@ -33,7 +33,6 @@ module DiscourseAi system_insts, messages: conversation_context, topic_id: post.topic_id, - post_id: post.id, ) title_prompt.push( diff --git a/lib/ai_bot/entry_point.rb b/lib/ai_bot/entry_point.rb index 8987ba87..6774a6f3 100644 --- a/lib/ai_bot/entry_point.rb +++ b/lib/ai_bot/entry_point.rb @@ -139,6 +139,16 @@ module DiscourseAi end end + plugin.add_to_serializer( + :current_user, + :can_debug_ai_bot_conversations, + include_condition: -> do + SiteSetting.ai_bot_enabled && scope.authenticated? && + SiteSetting.ai_bot_debugging_allowed_groups.present? && + scope.user.in_any_groups?(SiteSetting.ai_bot_debugging_allowed_groups_map) + end, + ) { true } + plugin.add_to_serializer( :current_user, :ai_enabled_chat_bots, @@ -163,11 +173,15 @@ module DiscourseAi SQL bots.each { |hash| hash["model_name"] = model_map[hash["id"]] } - mentionables = AiPersona.mentionables(user: scope.user) - if mentionables.present? + persona_users = AiPersona.persona_users(user: scope.user) + if persona_users.present? bots.concat( - mentionables.map do |mentionable| - { "id" => mentionable[:user_id], "username" => mentionable[:username] } + persona_users.map do |persona_user| + { + "id" => persona_user[:user_id], + "username" => persona_user[:username], + "mentionable" => persona_user[:mentionable], + } end, ) end diff --git a/lib/ai_bot/tools/discourse_meta_search.rb b/lib/ai_bot/tools/discourse_meta_search.rb index a80a3205..bd4e9ebf 100644 --- a/lib/ai_bot/tools/discourse_meta_search.rb +++ b/lib/ai_bot/tools/discourse_meta_search.rb @@ -118,10 +118,15 @@ module DiscourseAi category = categories[topic["category_id"]] category_names = +"" - if category["parent_category_id"] - category_names << categories[category["parent_category_id"]]["name"] << " > " + # TODO @nbianca: this is broken now cause we are not getting child categories + # to avoid erroring out we simply skip + # sideloading from search would probably be easier + if category + if category["parent_category_id"] + category_names << categories[category["parent_category_id"]]["name"] << " > " + end + category_names << category["name"] end - category_names << category["name"] row = { title: topic["title"], url: "https://meta.discourse.org/t/-/#{post["topic_id"]}/#{post["post_number"]}", diff --git a/lib/guardian_extensions.rb b/lib/guardian_extensions.rb index e8074b77..a13bf917 100644 --- a/lib/guardian_extensions.rb +++ b/lib/guardian_extensions.rb @@ -2,6 +2,19 @@ module DiscourseAi module GuardianExtensions + def can_debug_ai_bot_conversation?(target) + return false if anonymous? + + return false if !can_see?(target) + + if !SiteSetting.discourse_ai_enabled || !SiteSetting.ai_bot_enabled || + !SiteSetting.ai_bot_debugging_allowed_groups_map.any? + return false + end + + user.in_any_groups?(SiteSetting.ai_bot_debugging_allowed_groups_map) + end + def can_share_ai_bot_conversation?(target) return false if anonymous? diff --git a/spec/lib/modules/ai_bot/entry_point_spec.rb b/spec/lib/modules/ai_bot/entry_point_spec.rb index 357758e6..c648cae3 100644 --- a/spec/lib/modules/ai_bot/entry_point_spec.rb +++ b/spec/lib/modules/ai_bot/entry_point_spec.rb @@ -23,6 +23,14 @@ RSpec.describe DiscourseAi::AiBot::EntryPoint do bot_allowed_group.add(admin) end + it "adds a can_debug_ai_bot_conversations method to current user" do + SiteSetting.ai_bot_debugging_allowed_groups = bot_allowed_group.id.to_s + serializer = CurrentUserSerializer.new(admin, scope: Guardian.new(admin)) + serializer = serializer.as_json + + expect(serializer[:current_user][:can_debug_ai_bot_conversations]).to eq(true) + end + it "adds mentionables to current_user_serializer" do Group.refresh_automatic_groups! @@ -42,6 +50,22 @@ RSpec.describe DiscourseAi::AiBot::EntryPoint do persona_bot = bots.find { |bot| bot["id"] == persona.user_id } expect(persona_bot["username"]).to eq(persona.user.username) + expect(persona_bot["mentionable"]).to eq(true) + end + + it "includes user ids for all personas in the serializer" do + Group.refresh_automatic_groups! + + persona = Fabricate(:ai_persona, enabled: true, allowed_group_ids: [bot_allowed_group.id]) + persona.create_user! + + serializer = CurrentUserSerializer.new(admin, scope: Guardian.new(admin)) + serializer = serializer.as_json + bots = serializer[:current_user][:ai_enabled_chat_bots] + + persona_bot = bots.find { |bot| bot["id"] == persona.user_id } + expect(persona_bot["username"]).to eq(persona.user.username) + expect(persona_bot["mentionable"]).to eq(false) end it "queues a job to generate a reply by the AI" do diff --git a/spec/requests/ai_bot/bot_controller_spec.rb b/spec/requests/ai_bot/bot_controller_spec.rb index efd85a6f..4b9be94b 100644 --- a/spec/requests/ai_bot/bot_controller_spec.rb +++ b/spec/requests/ai_bot/bot_controller_spec.rb @@ -2,12 +2,58 @@ RSpec.describe DiscourseAi::AiBot::BotController do fab!(:user) + fab!(:pm_topic) { Fabricate(:private_message_topic) } + fab!(:pm_post) { Fabricate(:post, topic: pm_topic) } + before { sign_in(user) } - describe "#stop_streaming_response" do - fab!(:pm_topic) { Fabricate(:private_message_topic) } - fab!(:pm_post) { Fabricate(:post, topic: pm_topic) } + describe "#show_debug_info" do + before do + SiteSetting.ai_bot_enabled = true + SiteSetting.discourse_ai_enabled = true + end + it "returns a 403 when the user cannot debug the AI bot conversation" do + get "/discourse-ai/ai-bot/post/#{pm_post.id}/show-debug-info" + expect(response.status).to eq(403) + end + + it "returns debug info if the user can debug the AI bot conversation" do + user = pm_topic.topic_allowed_users.first.user + sign_in(user) + + AiApiAuditLog.create!( + post_id: pm_post.id, + provider_id: 1, + topic_id: pm_topic.id, + raw_request_payload: "request", + raw_response_payload: "response", + request_tokens: 1, + response_tokens: 2, + ) + + Group.refresh_automatic_groups! + SiteSetting.ai_bot_debugging_allowed_groups = user.groups.first.id.to_s + + get "/discourse-ai/ai-bot/post/#{pm_post.id}/show-debug-info" + expect(response.status).to eq(200) + + expect(response.parsed_body["request_tokens"]).to eq(1) + expect(response.parsed_body["response_tokens"]).to eq(2) + expect(response.parsed_body["raw_request_payload"]).to eq("request") + expect(response.parsed_body["raw_response_payload"]).to eq("response") + + post2 = Fabricate(:post, topic: pm_topic) + + # return previous post if current has no debug info + get "/discourse-ai/ai-bot/post/#{post2.id}/show-debug-info" + expect(response.status).to eq(200) + expect(response.parsed_body["request_tokens"]).to eq(1) + expect(response.parsed_body["response_tokens"]).to eq(2) + end + end + + describe "#stop_streaming_response" do let(:redis_stream_key) { "gpt_cancel:#{pm_post.id}" } before { Discourse.redis.setex(redis_stream_key, 60, 1) } diff --git a/test/javascripts/unit/models/ai-persona-test.js b/test/javascripts/unit/models/ai-persona-test.js index 4e99a223..f7867a80 100644 --- a/test/javascripts/unit/models/ai-persona-test.js +++ b/test/javascripts/unit/models/ai-persona-test.js @@ -87,6 +87,9 @@ module("Discourse AI | Unit | Model | ai-persona", function () { vision_enabled: true, vision_max_pixels: 100, rag_uploads: [], + rag_chunk_tokens: 374, + rag_chunk_overlap_tokens: 10, + rag_conversation_chunks: 10, }; const aiPersona = AiPersona.create({ ...properties });