mirror of
https://github.com/lukaszraczylo/mcp-captcharesolver.git
synced 2026-10-12 00:45:28 +00:00
fix(captcha): strip reasoning-model <think> blocks before json extraction
Reasoning models prefix responses with <think>...</think> (or <thinking>...</thinking>) scratchpad that can itself contain a JSON-looking object. The brace-counting extractor would grab that inner object instead of the real answer. Strip think/thinking blocks via a package-level regexp before the balanced-object scan. Adds table cases covering both tag spellings while keeping the plain, prose-wrapped, and unbalanced cases intact.
This commit is contained in:
@@ -1,13 +1,24 @@
|
|||||||
package captcha
|
package captcha
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// thinkRe matches reasoning-model scratchpad blocks (<think>…</think> or
|
||||||
|
// <thinking>…</thinking>). These can themselves contain JSON-looking objects,
|
||||||
|
// so they are stripped before the brace scan to avoid grabbing the wrong object.
|
||||||
|
var thinkRe = regexp.MustCompile(`(?s)<think(?:ing)?>.*?</think(?:ing)?>`)
|
||||||
|
|
||||||
// extractJSON returns the first balanced {...} object found in s, or s unchanged
|
// extractJSON returns the first balanced {...} object found in s, or s unchanged
|
||||||
// if no opening brace is present. It scans by brace depth so prose, markdown
|
// if no opening brace is present. It scans by brace depth so prose, markdown
|
||||||
// fences, or an example object before the real one do not corrupt parsing.
|
// fences, or an example object before the real one do not corrupt parsing.
|
||||||
|
// Reasoning-model <think>…</think> blocks are stripped first so a JSON-looking
|
||||||
|
// object inside the scratchpad is not mistaken for the answer.
|
||||||
// Note: it does not account for braces inside string literals; that is
|
// Note: it does not account for braces inside string literals; that is
|
||||||
// acceptable for the short, structured captcha responses this handles.
|
// acceptable for the short, structured captcha responses this handles.
|
||||||
func extractJSON(s string) string {
|
func extractJSON(s string) string {
|
||||||
|
s = thinkRe.ReplaceAllString(s, "")
|
||||||
start := strings.IndexByte(s, '{')
|
start := strings.IndexByte(s, '{')
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
return s
|
return s
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ func TestExtractJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{"no brace", `no json here`, `no json here`},
|
{"no brace", `no json here`, `no json here`},
|
||||||
{"unbalanced returns from start", `tail {"text":"x"`, `{"text":"x"`},
|
{"unbalanced returns from start", `tail {"text":"x"`, `{"text":"x"`},
|
||||||
|
{
|
||||||
|
"think block stripped, real object wins",
|
||||||
|
"<think>reasoning with {\"tiles\":[9]}</think>\n{\"tiles\":[0,1,2],\"confidence\":0.9}",
|
||||||
|
`{"tiles":[0,1,2],"confidence":0.9}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"thinking block stripped",
|
||||||
|
`<thinking>let me reason {"tiles":[5]}</thinking>{"text":"ab12"}`,
|
||||||
|
`{"text":"ab12"}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user