mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Co-authored-by: Probe Test <probe@example.com>
This commit is contained in:
committed by
GitHub
parent
917314156e
commit
a37a2fe8c3
1
changelog.d/fixes/8169-lite-word-boundary-truncation.md
Normal file
1
changelog.d/fixes/8169-lite-word-boundary-truncation.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(backend): word-boundary-safe tool-result truncation in lite compression mode (#8169)
|
||||
@@ -105,6 +105,47 @@ export function dedupSystemPrompt(
|
||||
return { body: { ...body, messages }, applied };
|
||||
}
|
||||
|
||||
// Adjust a hard cut index to the nearest whitespace within a small lookback/lookahead
|
||||
// window, so a truncated tool result never garbles the word it lands in the middle of
|
||||
// (#8169). Prefers backing off to the end of the previous word (keeps the result at or
|
||||
// under the limit); if no whitespace precedes the cut within the window (e.g. the tail
|
||||
// end of a very long unbroken run), looks forward to complete the current word instead.
|
||||
// Falls back to the original hard cut index when neither direction finds a boundary.
|
||||
const TOOL_TRUNCATION_LOOKBACK = 80;
|
||||
|
||||
function isWordChar(char: string | undefined): boolean {
|
||||
return char !== undefined && /\S/.test(char);
|
||||
}
|
||||
|
||||
function findWhitespaceBackward(content: string, cutIndex: number): number {
|
||||
const windowStart = Math.max(0, cutIndex - TOOL_TRUNCATION_LOOKBACK);
|
||||
for (let i = cutIndex; i > windowStart; i--) {
|
||||
if (!isWordChar(content[i - 1])) return i - 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function findWhitespaceForward(content: string, cutIndex: number): number {
|
||||
const windowEnd = Math.min(content.length, cutIndex + TOOL_TRUNCATION_LOOKBACK);
|
||||
for (let i = cutIndex; i < windowEnd; i++) {
|
||||
if (!isWordChar(content[i])) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function backOffToWordBoundary(content: string, cutIndex: number): number {
|
||||
const onWordBoundary = !isWordChar(content[cutIndex - 1]) || !isWordChar(content[cutIndex]);
|
||||
if (onWordBoundary) return cutIndex;
|
||||
|
||||
const backward = findWhitespaceBackward(content, cutIndex);
|
||||
if (backward !== -1) return backward;
|
||||
|
||||
const forward = findWhitespaceForward(content, cutIndex);
|
||||
if (forward !== -1) return forward;
|
||||
|
||||
return cutIndex;
|
||||
}
|
||||
|
||||
export function compressToolResults(body: ChatBody): {
|
||||
body: ChatBody;
|
||||
applied: boolean;
|
||||
@@ -116,9 +157,10 @@ export function compressToolResults(body: ChatBody): {
|
||||
if (msg.role !== "tool" || typeof msg.content !== "string") return msg;
|
||||
if (msg.content.length <= MAX_TOOL_LENGTH) return msg;
|
||||
applied = true;
|
||||
const cutIndex = backOffToWordBoundary(msg.content, MAX_TOOL_LENGTH);
|
||||
return {
|
||||
...msg,
|
||||
content: msg.content.slice(0, MAX_TOOL_LENGTH) + "\n...[truncated]",
|
||||
content: msg.content.slice(0, cutIndex) + "\n...[truncated]",
|
||||
};
|
||||
});
|
||||
return { body: { ...body, messages }, applied };
|
||||
|
||||
63
tests/unit/8169-lite-word-boundary-truncation.test.ts
Normal file
63
tests/unit/8169-lite-word-boundary-truncation.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { compressToolResults } from "../../open-sse/services/compression/lite.ts";
|
||||
|
||||
interface TestMessage {
|
||||
role: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface TestChatBody {
|
||||
messages: TestMessage[];
|
||||
}
|
||||
|
||||
function toolBody(content: string): TestChatBody {
|
||||
return { messages: [{ role: "tool", content }] };
|
||||
}
|
||||
|
||||
function firstMessageContent(body: TestChatBody): string {
|
||||
return body.messages[0].content;
|
||||
}
|
||||
|
||||
test("#8169: lite compressToolResults must not cut a word in half", () => {
|
||||
const prefix = "x".repeat(1990);
|
||||
const word = "authentication"; // straddles the 2000-char cut point
|
||||
const content = prefix + word + " rest of the message continues here.";
|
||||
const { body: out } = compressToolResults(toolBody(content));
|
||||
const resultContent = firstMessageContent(out as TestChatBody);
|
||||
const cutPoint = resultContent.indexOf("\n...[truncated]");
|
||||
assert.notEqual(cutPoint, -1);
|
||||
const lastChar = resultContent[cutPoint - 1];
|
||||
const charAfterWouldBe = content[cutPoint];
|
||||
const isMidWord = /[a-zA-Z0-9]/.test(lastChar) && /[a-zA-Z0-9]/.test(charAfterWouldBe);
|
||||
assert.equal(
|
||||
isMidWord,
|
||||
false,
|
||||
`mid-word cut: "...${resultContent.slice(cutPoint - 20, cutPoint)}" next="${charAfterWouldBe}"`
|
||||
);
|
||||
});
|
||||
|
||||
test("#8169: compressToolResults still truncates content well over MAX_TOOL_LENGTH", () => {
|
||||
const content = "word ".repeat(1000); // 5000 chars, plenty of whitespace boundaries
|
||||
const { body: out, applied } = compressToolResults(toolBody(content));
|
||||
const resultContent = firstMessageContent(out as TestChatBody);
|
||||
assert.equal(applied, true);
|
||||
assert.ok(resultContent.length < content.length);
|
||||
assert.ok(resultContent.endsWith("\n...[truncated]"));
|
||||
});
|
||||
|
||||
test("#8169: compressToolResults falls back to hard cut when no whitespace found in lookback window", () => {
|
||||
const content = "a".repeat(2100); // no whitespace anywhere
|
||||
const { body: out, applied } = compressToolResults(toolBody(content));
|
||||
const resultContent = firstMessageContent(out as TestChatBody);
|
||||
assert.equal(applied, true);
|
||||
assert.ok(resultContent.endsWith("\n...[truncated]"));
|
||||
});
|
||||
|
||||
test("#8169: compressToolResults leaves short tool content untouched", () => {
|
||||
const content = "short content";
|
||||
const { body: out, applied } = compressToolResults(toolBody(content));
|
||||
const resultContent = firstMessageContent(out as TestChatBody);
|
||||
assert.equal(applied, false);
|
||||
assert.equal(resultContent, content);
|
||||
});
|
||||
Reference in New Issue
Block a user