feat: enhance README and improve product data handling in cache, llm, reader, and writer modules
This commit is contained in:
67
src/llm.ts
67
src/llm.ts
@@ -22,6 +22,45 @@ Keep each reasoning under 100 characters to stay within output limits.`;
|
||||
|
||||
export async function analyzeProducts(
|
||||
products: EnrichedProduct[],
|
||||
): Promise<LlmVerdict[]> {
|
||||
try {
|
||||
return await analyzeProductsInternal(products);
|
||||
} catch (err) {
|
||||
const msg = String(err);
|
||||
if (products.length > 1 && msg.includes("Context size has been exceeded")) {
|
||||
console.warn(
|
||||
`LLM context exceeded for batch of ${products.length}, retrying one product at a time...`,
|
||||
);
|
||||
|
||||
const fallback: LlmVerdict[] = [];
|
||||
for (const product of products) {
|
||||
try {
|
||||
const single = await analyzeProductsInternal([product]);
|
||||
fallback.push(
|
||||
single[0] ?? {
|
||||
asin: product.record.asin,
|
||||
verdict: "SKIP",
|
||||
confidence: 0,
|
||||
reasoning: "LLM returned empty verdict",
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
fallback.push({
|
||||
asin: product.record.asin,
|
||||
verdict: "SKIP",
|
||||
confidence: 0,
|
||||
reasoning: "LLM context overflow on single-item fallback",
|
||||
});
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function analyzeProductsInternal(
|
||||
products: EnrichedProduct[],
|
||||
): Promise<LlmVerdict[]> {
|
||||
const productSummaries = products.map(summarizeForLlm);
|
||||
|
||||
@@ -55,7 +94,10 @@ export async function analyzeProducts(
|
||||
}
|
||||
|
||||
function summarizeForLlm(p: EnrichedProduct) {
|
||||
const salePrice = p.keepa?.currentPrice ?? p.spApi.estimatedSalePrice;
|
||||
const salePrice =
|
||||
p.keepa?.currentPrice ??
|
||||
p.record.sellingPriceFromSheet ??
|
||||
p.spApi.estimatedSalePrice;
|
||||
const referralFee = salePrice * (p.spApi.referralFeePercent / 100);
|
||||
const fbaProfit =
|
||||
salePrice - p.record.unitCost - p.spApi.fbaFee - referralFee;
|
||||
@@ -64,9 +106,12 @@ function summarizeForLlm(p: EnrichedProduct) {
|
||||
|
||||
return {
|
||||
asin: p.record.asin,
|
||||
name: p.record.name,
|
||||
name: clampText(p.record.name, 80),
|
||||
brand: p.record.brand,
|
||||
category: p.record.category ?? p.keepa?.categoryTree?.join(" > "),
|
||||
category: clampText(
|
||||
p.record.category ?? p.keepa?.categoryTree?.join(" > "),
|
||||
60,
|
||||
),
|
||||
unitCost: p.record.unitCost,
|
||||
currentPrice: salePrice,
|
||||
priceRange90d: p.keepa
|
||||
@@ -85,10 +130,15 @@ function summarizeForLlm(p: EnrichedProduct) {
|
||||
salesRankDrops90: p.keepa?.salesRankDrops90,
|
||||
},
|
||||
spreadsheetEstimates: {
|
||||
avgPrice90: p.record.avgPrice90FromSheet,
|
||||
sellingPrice: p.record.sellingPriceFromSheet,
|
||||
fbaNet: p.record.fbaNet,
|
||||
grossProfit: p.record.grossProfit,
|
||||
grossProfitPct: p.record.grossProfitPct,
|
||||
netProfit: p.record.netProfitFromSheet,
|
||||
roi: p.record.roiFromSheet,
|
||||
},
|
||||
supplier: clampText(p.record.supplier, 40),
|
||||
moq: p.record.moq,
|
||||
moqCost: p.record.moqCost,
|
||||
totalQtyAvail: p.record.totalQtyAvail,
|
||||
@@ -115,6 +165,13 @@ function summarizeForLlm(p: EnrichedProduct) {
|
||||
};
|
||||
}
|
||||
|
||||
function clampText(value: unknown, maxLen: number): string | undefined {
|
||||
if (value == null) return undefined;
|
||||
const s = String(value).trim();
|
||||
if (!s) return undefined;
|
||||
return s.length > maxLen ? `${s.slice(0, maxLen - 1)}.` : s;
|
||||
}
|
||||
|
||||
function cleanLlmJson(text: string): string {
|
||||
// Remove ```json ... ``` or ``` ... ``` wrapping
|
||||
const fenceMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
|
||||
@@ -148,7 +205,9 @@ function parseVerdicts(
|
||||
reasoning: String(v.reasoning ?? "No reasoning provided"),
|
||||
}));
|
||||
} catch (err) {
|
||||
console.warn("Failed to parse LLM response, marking all as ANALYSIS_FAILED");
|
||||
console.warn(
|
||||
"Failed to parse LLM response, marking all as ANALYSIS_FAILED",
|
||||
);
|
||||
console.warn("Raw LLM content:", content.slice(0, 500));
|
||||
return products.map((p) => ({
|
||||
asin: p.record.asin,
|
||||
|
||||
Reference in New Issue
Block a user