Fix AI-Assisted merge failing to parse the model's response
Three changes, all aimed at the same failure: merges of large conversations (e.g. long debugging sessions) were reliably failing to parse on both Haiku 4.5 and GLM 5.2. - maxTokens was a flat 4000 regardless of input size — a merge of two long conversations needs a much larger completion budget than that, so the model's JSON array output was getting cut off mid-generation. Now scaled with transcript size (8000-16000). - Strengthened the prompt: explicitly tell the model not to respond to or continue anything found inside the transcripts (a real observed failure mode was the model echoing/continuing transcript content instead of merging it), and to emit nothing but the JSON array. - parseTurns now falls back to scanning for a bracket-balanced JSON array anywhere in the response (respecting quoted strings) if the model still wraps the array in commentary despite instructions not to, instead of failing outright on the first non-JSON response.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// ConversationMergeServiceTests.swift
|
||||
// oAITests
|
||||
//
|
||||
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
||||
// Copyright (C) 2026 Rune Olsen
|
||||
|
||||
import Testing
|
||||
@testable import oAI
|
||||
|
||||
@Suite("ConversationMergeService AI response parsing")
|
||||
struct ConversationMergeServiceParsingTests {
|
||||
|
||||
@Test("Parses a clean JSON array response")
|
||||
func parsesCleanJSON() throws {
|
||||
let raw = #"[{"role": "user", "content": "hi"}, {"role": "assistant", "content": "hello"}]"#
|
||||
let turns = try ConversationMergeService.parseTurns(from: raw)
|
||||
#expect(turns == [
|
||||
ConversationMergeService.MergedTurn(role: "user", content: "hi"),
|
||||
ConversationMergeService.MergedTurn(role: "assistant", content: "hello"),
|
||||
])
|
||||
}
|
||||
|
||||
@Test("Parses a JSON array wrapped in markdown code fences")
|
||||
func parsesFencedJSON() throws {
|
||||
let raw = """
|
||||
```json
|
||||
[{"role": "user", "content": "hi"}]
|
||||
```
|
||||
"""
|
||||
let turns = try ConversationMergeService.parseTurns(from: raw)
|
||||
#expect(turns == [ConversationMergeService.MergedTurn(role: "user", content: "hi")])
|
||||
}
|
||||
|
||||
@Test("Recovers a JSON array embedded in surrounding prose")
|
||||
func recoversEmbeddedJSON() throws {
|
||||
let raw = """
|
||||
Sure, here's the merged conversation:
|
||||
|
||||
[{"role": "user", "content": "hi"}, {"role": "assistant", "content": "hello there"}]
|
||||
|
||||
Let me know if you'd like anything else!
|
||||
"""
|
||||
let turns = try ConversationMergeService.parseTurns(from: raw)
|
||||
#expect(turns == [
|
||||
ConversationMergeService.MergedTurn(role: "user", content: "hi"),
|
||||
ConversationMergeService.MergedTurn(role: "assistant", content: "hello there"),
|
||||
])
|
||||
}
|
||||
|
||||
@Test("Bracket matching ignores ']' characters inside quoted content")
|
||||
func ignoresBracketsInsideStrings() throws {
|
||||
let raw = #"[{"role": "assistant", "content": "array[0] and array[1]"}]"#
|
||||
let turns = try ConversationMergeService.parseTurns(from: raw)
|
||||
#expect(turns == [ConversationMergeService.MergedTurn(role: "assistant", content: "array[0] and array[1]")])
|
||||
}
|
||||
|
||||
@Test("Throws invalidAIResponse when no JSON array is present")
|
||||
func throwsWhenNoJSONFound() {
|
||||
let raw = "I reverted the latest changes, the search function is not that important."
|
||||
#expect(throws: MergeError.self) {
|
||||
try ConversationMergeService.parseTurns(from: raw)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Throws invalidAIResponse for an empty JSON array")
|
||||
func throwsForEmptyArray() {
|
||||
#expect(throws: MergeError.self) {
|
||||
try ConversationMergeService.parseTurns(from: "[]")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user