feat(ai): strip MiniMax inline reasoning blocks
This commit is contained in:
parent
b5593457af
commit
8f1ac43e98
2 changed files with 67 additions and 0 deletions
44
src/lib/ai/think-strip.test.ts
Normal file
44
src/lib/ai/think-strip.test.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { stripThinking } from "@/lib/ai/think-strip";
|
||||
|
||||
describe("stripThinking", () => {
|
||||
it("leaves ordinary text alone", () => {
|
||||
expect(stripThinking("Fixed the quote form.")).toBe("Fixed the quote form.");
|
||||
});
|
||||
|
||||
it("removes a think block and trims what is left", () => {
|
||||
expect(
|
||||
stripThinking("<think>Let me consider this.</think>\nFixed the quote form.")
|
||||
).toBe("Fixed the quote form.");
|
||||
});
|
||||
|
||||
it("removes several blocks", () => {
|
||||
expect(stripThinking("<think>a</think>One.<think>b</think>Two.")).toBe(
|
||||
"One.Two."
|
||||
);
|
||||
});
|
||||
|
||||
it("handles a block spanning newlines", () => {
|
||||
expect(stripThinking("<think>\nline one\nline two\n</think>Result.")).toBe(
|
||||
"Result."
|
||||
);
|
||||
});
|
||||
|
||||
it("drops an unclosed block rather than showing it", () => {
|
||||
// A truncated response can leave <think> open. Showing the reasoning is
|
||||
// worse than showing nothing — the client sees the model talking to itself.
|
||||
expect(stripThinking("Good.<think>Now let me reconsider")).toBe("Good.");
|
||||
});
|
||||
|
||||
it("is case-insensitive about the tag", () => {
|
||||
expect(stripThinking("<THINK>x</THINK>Done.")).toBe("Done.");
|
||||
});
|
||||
|
||||
it("returns an empty string for content that is only thinking", () => {
|
||||
expect(stripThinking("<think>all of it</think>")).toBe("");
|
||||
});
|
||||
|
||||
it("handles empty input", () => {
|
||||
expect(stripThinking("")).toBe("");
|
||||
});
|
||||
});
|
||||
23
src/lib/ai/think-strip.ts
Normal file
23
src/lib/ai/think-strip.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* Removes <think>...</think> reasoning from a MiniMax response.
|
||||
*
|
||||
* MiniMax M2.x emits its chain of thought inline in the normal content field.
|
||||
* Anthropic returns reasoning as separate blocks, so this is a provider quirk
|
||||
* and belongs to the OpenAI-compatible adapter alone. Left in, every commit
|
||||
* summary a client reads would open with the model deliberating.
|
||||
*
|
||||
* Mega's equivalent handles a STREAM, where a tag can split across chunk
|
||||
* boundaries ("<thi" + "nk>") and text must be held back. This phase does not
|
||||
* stream, so the whole string is available and the simple form is correct.
|
||||
*/
|
||||
|
||||
const THINK_BLOCK = /<think>[\s\S]*?<\/think>/gi;
|
||||
/** A response truncated mid-reasoning leaves an unclosed tag. */
|
||||
const UNCLOSED_THINK = /<think>[\s\S]*$/i;
|
||||
|
||||
export function stripThinking(text: string): string {
|
||||
return (text ?? "")
|
||||
.replace(THINK_BLOCK, "")
|
||||
.replace(UNCLOSED_THINK, "")
|
||||
.trim();
|
||||
}
|
||||
Loading…
Reference in a new issue