Skip to content

Commit e8edc0c

Browse files
clucraftclaude
andcommitted
Fix Ollama Qwen3/DeepSeek thinking mode breaking JSON parsing
These models output <think>...</think> blocks before their actual JSON response when in "thinking mode". This fix: 1. Adds /nothink prefix to all AI prompts (EXTRACTION_PROMPT, VERIFICATION_PROMPT, STOCK_STATUS_PROMPT, ARBITRATION_PROMPT) to request models disable thinking mode 2. Adds stripThinkingTags() helper function that removes <think> blocks from responses as a fallback if models ignore /nothink 3. Applies stripping in all parse functions before JSON extraction Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4c302a7 commit e8edc0c

File tree

1 file changed

+25
-10
lines changed

1 file changed

+25
-10
lines changed

backend/src/services/ai-extractor.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ import { AISettings } from '../models';
66
import { ParsedPrice } from '../utils/priceParser';
77
import { StockStatus, PriceCandidate } from './scraper';
88

9+
// Strip thinking mode tags from model responses (Qwen3, DeepSeek, etc.)
10+
// These models output <think>...</think> blocks before their actual response
11+
function stripThinkingTags(text: string): string {
12+
// Remove <think>...</think> blocks (including content)
13+
const stripped = text.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
14+
// If nothing left after stripping, return original (in case regex failed)
15+
return stripped.length > 0 ? stripped : text;
16+
}
17+
918
export interface AIExtractionResult {
1019
name: string | null;
1120
price: ParsedPrice | null;
@@ -28,7 +37,8 @@ export interface AIStockStatusResult {
2837
reason: string;
2938
}
3039

31-
const VERIFICATION_PROMPT = `You are a price and availability verification assistant. I scraped a product page and found a price. Please verify if this price is correct AND if the product is currently available for purchase.
40+
const VERIFICATION_PROMPT = `/nothink
41+
You are a price and availability verification assistant. I scraped a product page and found a price. Please verify if this price is correct AND if the product is currently available for purchase.
3242
3343
Scraped Price: $SCRAPED_PRICE$ $CURRENCY$
3444
@@ -64,7 +74,8 @@ Only return valid JSON, no explanation text outside the JSON.
6474
HTML Content:
6575
`;
6676

67-
const STOCK_STATUS_PROMPT = `You are an availability verification assistant. The user is tracking a SPECIFIC product variant priced at $VARIANT_PRICE$ $CURRENCY$.
77+
const STOCK_STATUS_PROMPT = `/nothink
78+
You are an availability verification assistant. The user is tracking a SPECIFIC product variant priced at $VARIANT_PRICE$ $CURRENCY$.
6879
6980
Your task: Determine if THIS SPECIFIC VARIANT (the one at $VARIANT_PRICE$) is currently in stock and can be purchased.
7081
@@ -96,7 +107,8 @@ Only return valid JSON, no explanation text outside the JSON.
96107
HTML Content:
97108
`;
98109

99-
const EXTRACTION_PROMPT = `You are a price extraction assistant. Analyze the following HTML content from a product page and extract the product information.
110+
const EXTRACTION_PROMPT = `/nothink
111+
You are a price extraction assistant. Analyze the following HTML content from a product page and extract the product information.
100112
101113
Return a JSON object with these fields:
102114
- name: The product name/title (string or null)
@@ -495,8 +507,8 @@ function parseStockStatusResponse(responseText: string): AIStockStatusResult {
495507
};
496508

497509
try {
498-
// Extract JSON from response (handle markdown code blocks)
499-
let jsonStr = responseText;
510+
// Strip thinking tags from models like Qwen3/DeepSeek
511+
let jsonStr = stripThinkingTags(responseText);
500512
const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/);
501513
if (jsonMatch) {
502514
jsonStr = jsonMatch[1].trim();
@@ -549,7 +561,8 @@ function parseVerificationResponse(
549561
stockStatus: 'unknown',
550562
};
551563

552-
let jsonStr = responseText.trim();
564+
// Strip thinking tags from models like Qwen3/DeepSeek
565+
let jsonStr = stripThinkingTags(responseText).trim();
553566

554567
// Handle markdown code blocks
555568
const jsonMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
@@ -608,8 +621,8 @@ function parseVerificationResponse(
608621
function parseAIResponse(responseText: string): AIExtractionResult {
609622
console.log(`[AI] Raw response: ${responseText.substring(0, 500)}...`);
610623

611-
// Try to extract JSON from the response
612-
let jsonStr = responseText.trim();
624+
// Strip thinking tags from models like Qwen3/DeepSeek, then try to extract JSON
625+
let jsonStr = stripThinkingTags(responseText).trim();
613626

614627
// Handle markdown code blocks
615628
const jsonMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
@@ -814,7 +827,8 @@ export async function tryAIStockStatusVerification(
814827
}
815828

816829
// Arbitration prompt for when multiple extraction methods disagree
817-
const ARBITRATION_PROMPT = `You are a price arbitration assistant. Multiple price extraction methods found different prices for the same product. Help determine the correct price.
830+
const ARBITRATION_PROMPT = `/nothink
831+
You are a price arbitration assistant. Multiple price extraction methods found different prices for the same product. Help determine the correct price.
818832
819833
Found prices:
820834
$CANDIDATES$
@@ -950,7 +964,8 @@ function parseArbitrationResponse(
950964
reason: 'Could not parse AI response',
951965
};
952966

953-
let jsonStr = responseText.trim();
967+
// Strip thinking tags from models like Qwen3/DeepSeek
968+
let jsonStr = stripThinkingTags(responseText).trim();
954969

955970
// Handle markdown code blocks
956971
const jsonMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);

0 commit comments

Comments
 (0)