Skip to content

Commit 5d8ff3b

Browse files
committed
Improve chat title generation
1 parent b68ad40 commit 5d8ff3b

4 files changed

Lines changed: 46 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Improve chat title quality on 3rd-message retitle by filtering tool calls, tool results, reasoning and flag entries from the history passed to the title LLM, and by respecting the last compact marker.
6+
57
## 0.128.0
68

79
- Add `eca-desktop` as an official client in docs and landing page.

resources/prompts/title.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@ Output: Single line, ≤30 chars, no explanations.
2323
"refactor user service" → Refactoring user service
2424
"why is app.js failing" → Analyzing app.js failure
2525
"implement rate limiting" → Implementing rate limiting
26+
27+
Conversation:
28+
user: "why is app.js failing"
29+
assistant: "Looks like a missing env var in the loader."
30+
user: "add a guard and default it to dev"
31+
→ Guarding app.js loader env var

src/eca/features/chat.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,8 @@
576576
(when generate-title?
577577
(let [title-past-messages (when retitle?
578578
(->> (get-in db [:chats chat-id :messages] [])
579-
(filterv #(not= "reason" (:role %)))))]
579+
shared/messages-after-last-compact-marker
580+
(filterv #(contains? #{"user" "assistant"} (:role %)))))]
580581
(future* config
581582
(when-let [{:keys [output-text]} (llm-api/sync-prompt!
582583
{:provider provider

test/eca/features/chat_test.clj

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,24 +262,58 @@
262262
"Should NOT call sync-prompt! on second message")
263263
(is (= "Title 1" (get-in (h/db) [:chats chat-id :title])))
264264

265+
;; Inject noisy entries into history before the retitle fires, to
266+
;; exercise the role/content cleanup applied to :past-messages.
267+
(swap! (h/db*) update-in [:chats chat-id :messages]
268+
(fnil into [])
269+
[{:role "reason" :content "internal thoughts"}
270+
{:role "tool_call" :content {:name "read_file"}}
271+
{:role "tool_call_output" :content "file contents"}
272+
{:role "flag" :content {:text "ui-only"}}])
273+
265274
;; Message 3: should re-generate title with full conversation context
266275
(h/reset-messenger!)
267276
(prompt-with-title! {:message "And add tests" :chat-id chat-id} {:sync-prompt-mock sync-mock})
268277
(is (= 2 (count @sync-prompt-calls*))
269278
"Should call sync-prompt! again on third message")
270279
(is (= "Title 2" (get-in (h/db) [:chats chat-id :title])))
271-
(let [retitle-call (second @sync-prompt-calls*)]
280+
(let [retitle-call (second @sync-prompt-calls*)
281+
past-roles (into #{} (map :role) (:past-messages retitle-call))]
272282
(is (= 1 (count (:user-messages retitle-call)))
273283
"Third message title should pass only current user message")
274284
(is (seq (:past-messages retitle-call))
275-
"Third message title should include chat history as past-messages"))
285+
"Third message title should include chat history as past-messages")
286+
(is (= #{"user" "assistant"} past-roles)
287+
"Past messages should be cleaned of tool_call, tool_call_output, reason and flag entries"))
276288

277289
;; Message 4: should NOT re-generate title
278290
(h/reset-messenger!)
279291
(prompt-with-title! {:message "One more thing" :chat-id chat-id} {:sync-prompt-mock sync-mock})
280292
(is (= 2 (count @sync-prompt-calls*))
281293
"Should NOT call sync-prompt! after third message"))))
282294

295+
(testing "retitle respects the last compact marker, dropping pre-compaction history"
296+
(h/reset-components!)
297+
(let [sync-prompt-calls* (atom [])
298+
call-count* (atom 0)
299+
sync-mock (fn [params]
300+
(swap! sync-prompt-calls* conj params)
301+
{:output-text (str "Title " (swap! call-count* inc))})]
302+
(let [{:keys [chat-id]} (prompt-with-title! {:message "Help me debug"} {:sync-prompt-mock sync-mock})]
303+
(h/reset-messenger!)
304+
(prompt-with-title! {:message "Also refactor" :chat-id chat-id} {:sync-prompt-mock sync-mock})
305+
306+
;; Insert a compact_marker so earlier history is treated as pre-compaction
307+
;; and must not reach the title LLM.
308+
(swap! (h/db*) update-in [:chats chat-id :messages]
309+
(fnil conj []) {:role "compact_marker" :content {:auto? false}})
310+
311+
(h/reset-messenger!)
312+
(prompt-with-title! {:message "And add tests" :chat-id chat-id} {:sync-prompt-mock sync-mock})
313+
(let [retitle-call (second @sync-prompt-calls*)]
314+
(is (empty? (:past-messages retitle-call))
315+
"Past messages before the compact marker should be dropped from title context")))))
316+
283317
(testing "manual rename suppresses automatic re-titling"
284318
(h/reset-components!)
285319
(let [sync-prompt-calls* (atom [])

0 commit comments

Comments
 (0)