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

Commit 21af1bd

Browse files
committed
DEV: Log staff actions for LLM model changes
1 parent 33fd680 commit 21af1bd

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

app/controllers/discourse_ai/admin/ai_llms_controller.rb

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def create
4747

4848
if llm_model.save
4949
llm_model.toggle_companion_user
50+
log_llm_model_creation(llm_model)
5051
render json: LlmModelSerializer.new(llm_model), status: :created
5152
else
5253
render_json_error llm_model
@@ -56,6 +57,10 @@ def create
5657
def update
5758
llm_model = LlmModel.find(params[:id])
5859

60+
# Capture initial state for logging
61+
initial_attributes = llm_model.attributes.dup
62+
initial_quotas = llm_model.llm_quotas.map(&:attributes)
63+
5964
if params[:ai_llm].key?(:llm_quotas)
6065
if quota_params
6166
existing_quota_group_ids = llm_model.llm_quotas.pluck(:group_id)
@@ -81,6 +86,7 @@ def update
8186

8287
if llm_model.update(ai_llm_params(updating: llm_model))
8388
llm_model.toggle_companion_user
89+
log_llm_model_update(llm_model, initial_attributes, initial_quotas)
8490
render json: LlmModelSerializer.new(llm_model)
8591
else
8692
render_json_error llm_model
@@ -109,11 +115,20 @@ def destroy
109115
)
110116
end
111117

118+
# Capture model details for logging before destruction
119+
model_details = {
120+
model_id: llm_model.id,
121+
display_name: llm_model.display_name,
122+
name: llm_model.name,
123+
provider: llm_model.provider
124+
}
125+
112126
# Clean up companion users
113127
llm_model.enabled_chat_bot = false
114128
llm_model.toggle_companion_user
115129

116130
if llm_model.destroy
131+
log_llm_model_deletion(model_details)
117132
head :no_content
118133
else
119134
render_json_error llm_model
@@ -190,6 +205,96 @@ def ai_llm_params(updating: nil)
190205

191206
permitted
192207
end
208+
209+
def log_llm_model_creation(llm_model)
210+
log_details = {
211+
model_id: llm_model.id,
212+
display_name: llm_model.display_name,
213+
name: llm_model.name,
214+
provider: llm_model.provider,
215+
tokenizer: llm_model.tokenizer,
216+
enabled_chat_bot: llm_model.enabled_chat_bot,
217+
vision_enabled: llm_model.vision_enabled,
218+
}
219+
220+
# Add cost details if present
221+
if llm_model.input_cost.present?
222+
log_details[:input_cost] = llm_model.input_cost
223+
end
224+
if llm_model.output_cost.present?
225+
log_details[:output_cost] = llm_model.output_cost
226+
end
227+
if llm_model.cached_input_cost.present?
228+
log_details[:cached_input_cost] = llm_model.cached_input_cost
229+
end
230+
231+
# Add quota information if present
232+
if llm_model.llm_quotas.any?
233+
log_details[:quotas] = llm_model.llm_quotas.map do |quota|
234+
"Group #{quota.group_id}: #{quota.max_tokens} tokens, #{quota.max_usages} usages, #{quota.duration_seconds}s"
235+
end.join("; ")
236+
end
237+
238+
StaffActionLogger.new(current_user).log_custom(
239+
"create_ai_llm_model",
240+
log_details
241+
)
242+
end
243+
244+
def log_llm_model_update(llm_model, initial_attributes, initial_quotas)
245+
current_attributes = llm_model.attributes
246+
current_quotas = llm_model.llm_quotas.reload.map(&:attributes)
247+
248+
# Track changes to main attributes
249+
changes = {}
250+
trackable_fields = %w[
251+
display_name name provider tokenizer url max_prompt_tokens max_output_tokens
252+
enabled_chat_bot vision_enabled input_cost output_cost cached_input_cost
253+
provider_params
254+
]
255+
256+
trackable_fields.each do |field|
257+
initial_value = initial_attributes[field]
258+
current_value = current_attributes[field]
259+
260+
if initial_value != current_value
261+
# Handle API key specially - don't log the actual values for security
262+
if field == "api_key"
263+
changes[field] = initial_value.present? && current_value.present? ?
264+
"updated" : (current_value.present? ? "set" : "removed")
265+
else
266+
changes[field] = "#{initial_value}#{current_value}"
267+
end
268+
end
269+
end
270+
271+
# Track quota changes
272+
if initial_quotas != current_quotas
273+
initial_quota_summary = initial_quotas.map { |q| "Group #{q['group_id']}: #{q['max_tokens']} tokens" }.join("; ")
274+
current_quota_summary = current_quotas.map { |q| "Group #{q['group_id']}: #{q['max_tokens']} tokens" }.join("; ")
275+
changes[:quotas] = "#{initial_quota_summary}#{current_quota_summary}"
276+
end
277+
278+
# Only log if there are actual changes
279+
if changes.any?
280+
log_details = {
281+
model_id: llm_model.id,
282+
model_name: llm_model.display_name || llm_model.name,
283+
}.merge(changes)
284+
285+
StaffActionLogger.new(current_user).log_custom(
286+
"update_ai_llm_model",
287+
log_details
288+
)
289+
end
290+
end
291+
292+
def log_llm_model_deletion(model_details)
293+
StaffActionLogger.new(current_user).log_custom(
294+
"delete_ai_llm_model",
295+
model_details
296+
)
297+
end
193298
end
194299
end
195300
end

config/locales/client.en.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ en:
2424
label: "Sort by"
2525
tag:
2626
label: "Tag"
27+
logs:
28+
staff_actions:
29+
actions:
30+
create_ai_llm_model: "Create LLM model"
31+
update_ai_llm_model: "Update LLM model"
32+
delete_ai_llm_model: "Delete LLM model"
2733

2834
js:
2935
discourse_automation:

0 commit comments

Comments
 (0)