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

Commit f6eedf3

Browse files
authored
FEATURE: implement thinking token support (#1155)
adds support for "thinking tokens" - a feature that exposes the model's reasoning process before providing the final response. Key improvements include: - Add a new Thinking class to handle thinking content from LLMs - Modify endpoints (Claude, AWS Bedrock) to handle thinking output - Update AI bot to display thinking in collapsible details section - Fix SEARCH/REPLACE blocks to support empty replacement strings and general improvements to artifact editing - Allow configurable temperature in triage and report automations - Various bug fixes and improvements to diff parsing
1 parent 3f20b24 commit f6eedf3

File tree

30 files changed

+957
-144
lines changed

30 files changed

+957
-144
lines changed

assets/javascripts/discourse/connectors/composer-fields/persona-llm-selector.gjs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,14 @@ export default class BotSelector extends Component {
168168
.filter((bot) => !bot.is_persona)
169169
.filter(Boolean);
170170

171-
return availableBots.map((bot) => {
172-
return {
173-
id: bot.id,
174-
name: bot.display_name,
175-
};
176-
});
171+
return availableBots
172+
.map((bot) => {
173+
return {
174+
id: bot.id,
175+
name: bot.display_name,
176+
};
177+
})
178+
.sort((a, b) => a.name.localeCompare(b.name));
177179
}
178180

179181
<template>

config/locales/client.en.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ en:
8585
description: "Prioritize content from this group in the report"
8686
temperature:
8787
label: "Temperature"
88-
description: "Temperature to use for the LLM. Increase to increase randomness (0 to use model default)"
88+
description: "Temperature to use for the LLM. Increase to increase randomness (leave empty to use model default)"
8989
top_p:
9090
label: "Top P"
91-
description: "Top P to use for the LLM, increase to increase randomness (0 to use model default)"
91+
description: "Top P to use for the LLM, increase to increase randomness (leave empty to use model default)"
9292

9393
llm_triage:
9494
fields:
@@ -131,6 +131,9 @@ en:
131131
model:
132132
label: "Model"
133133
description: "Language model used for triage"
134+
temperature:
135+
label: "Temperature"
136+
description: "Temperature to use for the LLM. Increase to increase randomness (leave empty to use model default)"
134137

135138
discourse_ai:
136139
title: "AI"
@@ -403,7 +406,7 @@ en:
403406
open_ai-o1: "Open AI's most capable reasoning model"
404407
open_ai-o3-mini: "Advanced Cost-efficient reasoning model"
405408
samba_nova-Meta-Llama-3-1-8B-Instruct: "Efficient lightweight multilingual model"
406-
samba_nova-Meta-Llama-3-1-70B-Instruct": "Powerful multipurpose model"
409+
samba_nova-Meta-Llama-3-3-70B-Instruct": "Powerful multipurpose model"
407410
mistral-mistral-large-latest: "Mistral's most powerful model"
408411
mistral-pixtral-large-latest: "Mistral's most powerful vision capable model"
409412

config/locales/server.en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ en:
261261
ai_bot:
262262
reply_error: "Sorry, it looks like our system encountered an unexpected issue while trying to reply.\n\n[details='Error details']\n%{details}\n[/details]"
263263
default_pm_prefix: "[Untitled AI bot PM]"
264+
thinking: "Thinking..."
264265
personas:
265266
default_llm_required: "Default LLM model is required prior to enabling Chat"
266267
cannot_delete_system_persona: "System personas cannot be deleted, please disable it instead"

discourse_automation/llm_report.rb

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ module DiscourseAutomation::LlmReport
3737

3838
field :allow_secure_categories, component: :boolean
3939

40-
field :top_p, component: :text, required: true, default_value: 0.1
41-
field :temperature, component: :text, required: true, default_value: 0.2
40+
field :top_p, component: :text
41+
field :temperature, component: :text
4242

4343
field :suppress_notifications, component: :boolean
4444
field :debug_mode, component: :boolean
@@ -64,12 +64,19 @@ module DiscourseAutomation::LlmReport
6464
exclude_category_ids = fields.dig("exclude_categories", "value")
6565
exclude_tags = fields.dig("exclude_tags", "value")
6666

67-
# set defaults in code to support easy migration for old rules
68-
top_p = 0.1
69-
top_p = fields.dig("top_p", "value").to_f if fields.dig("top_p", "value")
67+
top_p = fields.dig("top_p", "value")
68+
if top_p == "" || top_p.nil?
69+
top_p = nil
70+
else
71+
top_p = top_p.to_f
72+
end
7073

71-
temperature = 0.2
72-
temperature = fields.dig("temperature", "value").to_f if fields.dig("temperature", "value")
74+
temperature = fields.dig("temperature", "value")
75+
if temperature == "" || temperature.nil?
76+
temperature = nil
77+
else
78+
temperature = temperature.to_f
79+
end
7380

7481
suppress_notifications = !!fields.dig("suppress_notifications", "value")
7582
DiscourseAi::Automation::ReportRunner.run!(

discourse_automation/llm_triage.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
field :hide_topic, component: :boolean
2525
field :flag_post, component: :boolean
2626
field :include_personal_messages, component: :boolean
27+
field :temperature, component: :text
2728
field :flag_type,
2829
component: :choices,
2930
required: false,
@@ -53,6 +54,12 @@
5354
flag_post = fields.dig("flag_post", "value")
5455
flag_type = fields.dig("flag_type", "value")
5556
max_post_tokens = fields.dig("max_post_tokens", "value").to_i
57+
temperature = fields.dig("temperature", "value")
58+
if temperature == "" || temperature.nil?
59+
temperature = nil
60+
else
61+
temperature = temperature.to_f
62+
end
5663

5764
max_post_tokens = nil if max_post_tokens <= 0
5865

@@ -93,6 +100,7 @@
93100
max_post_tokens: max_post_tokens,
94101
stop_sequences: stop_sequences,
95102
automation: self.automation,
103+
temperature: temperature,
96104
)
97105
rescue => e
98106
Discourse.warn_exception(e, message: "llm_triage: skipped triage on post #{post.id}")

lib/ai_bot/artifact_update_strategies/diff.rb

Lines changed: 98 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,45 @@ def apply_changes(changes)
9090

9191
def extract_search_replace_blocks(content)
9292
return nil if content.blank? || content.to_s.strip.downcase.match?(/^\(?no changes?\)?$/m)
93-
return [{ replace: content }] if !content.match?(/<<+\s*SEARCH/)
93+
return [{ replace: content }] if !content.include?("<<< SEARCH")
9494

9595
blocks = []
96-
remaining = content
96+
current_block = {}
97+
state = :initial
98+
search_lines = []
99+
replace_lines = []
100+
101+
content.each_line do |line|
102+
line = line.chomp
103+
104+
case state
105+
when :initial
106+
state = :collecting_search if line.match?(/^<<<* SEARCH/)
107+
when :collecting_search
108+
if line.start_with?("===")
109+
current_block[:search] = search_lines.join("\n").strip
110+
search_lines = []
111+
state = :collecting_replace
112+
else
113+
search_lines << line
114+
end
115+
when :collecting_replace
116+
if line.match?(/>>>* REPLACE/)
117+
current_block[:replace] = replace_lines.join("\n").strip
118+
replace_lines = []
119+
blocks << current_block
120+
current_block = {}
121+
state = :initial
122+
else
123+
replace_lines << line
124+
end
125+
end
126+
end
97127

98-
pattern = /<<+\s*SEARCH\s*\n(.*?)\n=+\s*\n(.*?)\n>>+\s*REPLACE/m
99-
while remaining =~ pattern
100-
blocks << { search: $1.strip, replace: $2.strip }
101-
remaining = $'
128+
# Handle any remaining block
129+
if state == :collecting_replace && !replace_lines.empty?
130+
current_block[:replace] = replace_lines.join("\n").strip
131+
blocks << current_block
102132
end
103133

104134
blocks.empty? ? nil : blocks
@@ -108,26 +138,50 @@ def system_prompt
108138
<<~PROMPT
109139
You are a web development expert generating precise search/replace changes for updating HTML, CSS, and JavaScript code.
110140
111-
Important rules:
141+
CRITICAL RULES:
112142
113143
1. Use EXACTLY this format for changes:
114144
<<<<<<< SEARCH
115-
(first line of code to replace)
116-
(other lines of code to avoid ambiguity)
117-
(last line of code to replace)
145+
(code to replace)
118146
=======
119147
(replacement code)
120148
>>>>>>> REPLACE
121-
2. DO NOT modify the markers or add spaces around them
122-
3. DO NOT add explanations or comments within sections
123-
4. ONLY include [HTML], [CSS], and [JavaScript] sections if they have changes
124-
5. HTML should not include <html>, <head>, or <body> tags, it is injected into a template
125-
6. When specifying a SEARCH block, ALWAYS keep it 8 lines or less, you will be interrupted and a retry will be required if you exceed this limit
126-
7. NEVER EVER ask followup questions, ALL changes must be performed in a single response, you are consumed via an API, there is no opportunity for humans in the loop
127-
8. When performing a non-contiguous search, ALWAYS use ... to denote the skipped lines
128-
9. Be mindful that ... non-contiguous search is not greedy, the following line will only match the first occurrence of the search block
129-
10. Never mix a full section replacement with a search/replace block in the same section
130-
11. ALWAYS skip sections you to not want to change, do not include them in the response
149+
150+
2. SEARCH blocks MUST be 8 lines or less. Break larger changes into multiple smaller search/replace blocks.
151+
152+
3. DO NOT modify the markers or add spaces around them.
153+
154+
4. DO NOT add explanations or comments within sections.
155+
156+
5. ONLY include [HTML], [CSS], and [JavaScript] sections if they have changes.
157+
158+
6. HTML should not include <html>, <head>, or <body> tags, it is injected into a template.
159+
160+
7. NEVER EVER ask followup questions, ALL changes must be performed in a single response.
161+
162+
8. When performing a non-contiguous search, ALWAYS use ... to denote the skipped lines.
163+
164+
9. Be mindful that ... non-contiguous search is not greedy, it will only match the first occurrence.
165+
166+
10. Never mix a full section replacement with a search/replace block in the same section.
167+
168+
11. ALWAYS skip sections you do not want to change, do not include them in the response.
169+
170+
HANDLING LARGE CHANGES:
171+
172+
- Break large HTML structures into multiple smaller search/replace blocks.
173+
- Use strategic anchor points like unique IDs or class names to target specific elements.
174+
- Consider replacing entire components rather than modifying complex internals.
175+
- When elements contain dynamic content, use precise context markers or replace entire containers.
176+
177+
VALIDATION CHECKLIST:
178+
- Each SEARCH block is 8 lines or less
179+
- Every SEARCH has exactly one matching REPLACE
180+
- All blocks are properly closed
181+
- No SEARCH/REPLACE blocks are nested
182+
- Each change is a complete, separate block with its own SEARCH/REPLACE markers
183+
184+
WARNING: Never nest search/replace blocks. Each change must be a complete sequence.
131185
132186
JavaScript libraries must be sourced from the following CDNs, otherwise CSP will reject it:
133187
#{AiArtifact::ALLOWED_CDN_SOURCES.join("\n")}
@@ -143,7 +197,7 @@ def system_prompt
143197
(changes or empty if no changes or entire JavaScript)
144198
[/JavaScript]
145199
146-
Example - Multiple changes in one file:
200+
EXAMPLE 1 - Multiple small changes in one file:
147201
148202
[JavaScript]
149203
<<<<<<< SEARCH
@@ -158,39 +212,35 @@ def system_prompt
158212
>>>>>>> REPLACE
159213
[/JavaScript]
160214
161-
Example - CSS with multiple blocks:
215+
EXAMPLE 2 - Breaking up large HTML changes:
162216
163-
[CSS]
217+
[HTML]
164218
<<<<<<< SEARCH
165-
.button { color: blue; }
219+
<div class="header">
220+
<div class="logo">
221+
<img src="old-logo.png">
222+
</div>
166223
=======
167-
.button { color: red; }
224+
<div class="header">
225+
<div class="logo">
226+
<img src="new-logo.png">
227+
</div>
168228
>>>>>>> REPLACE
229+
169230
<<<<<<< SEARCH
170-
.text { font-size: 12px; }
231+
<div class="navigation">
232+
<ul>
233+
<li>Home</li>
234+
<li>Products</li>
171235
=======
172-
.text { font-size: 16px; }
236+
<div class="navigation">
237+
<ul>
238+
<li>Home</li>
239+
<li>Services</li>
173240
>>>>>>> REPLACE
174-
[/CSS]
175-
176-
Example - Non contiguous search in CSS (replace most CSS with new CSS)
241+
[/HTML]
177242
178-
Original CSS:
179-
180-
[CSS]
181-
body {
182-
color: red;
183-
}
184-
.button {
185-
color: blue;
186-
}
187-
.alert {
188-
background-color: green;
189-
}
190-
.alert2 {
191-
background-color: green;
192-
}
193-
[/CSS]
243+
EXAMPLE 3 - Non-contiguous search in CSS:
194244
195245
[CSS]
196246
<<<<<<< SEARCH
@@ -203,37 +253,20 @@ def system_prompt
203253
color: red;
204254
}
205255
>>>>>>> REPLACE
206-
207-
RESULT:
208-
209-
[CSS]
210-
body {
211-
color: red;
212-
}
213-
.alert2 {
214-
background-color: green;
215-
}
216256
[/CSS]
217257
218-
Example - full HTML replacement:
258+
EXAMPLE 4 - Full HTML replacement:
219259
220260
[HTML]
221261
<div>something old</div>
222-
<div>another somethin old</div>
262+
<div>another something old</div>
223263
[/HTML]
224264
225265
output:
226266
227267
[HTML]
228268
<div>something new</div>
229269
[/HTML]
230-
231-
result:
232-
[HTML]
233-
<div>something new</div>
234-
[/HTML]
235-
236-
237270
PROMPT
238271
end
239272

0 commit comments

Comments
 (0)