扩展产品反馈八类分类,并新增业务说明文档。

将 category 从 6 类扩展为 8 类标准枚举并允许补充自定义类别,同步更新提取规则与校验映射;新增面向业务的 ECharts 流程说明页面。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
OnesvmWhoops 2026-06-05 15:55:14 +08:00
parent 18a5e38796
commit ae809a6006
6 changed files with 377 additions and 9 deletions

View file

@ -52,7 +52,7 @@
sentiment: Positive sentiment: Positive
category: Price category: Price
- instruction: "示例 4 教学:非英语评论(例如西班牙语)输出必须全英文。禁止保留原文非英文片段,先理解语义再用英文短语表达。" - instruction: "示例 4 教学:1) 非英语评论(例如西班牙语)输出必须全英文,禁止保留原文非英文片段,先理解语义再用英文短语表达;2) 「随附配件」不属于 8 个标准类别,演示如何补充一个简洁的英文新类别 'Accessories'。"
review: "El producto en sí está muy bien, viene con 2 cristales templados y 2 grips para los joycon. Pero es lo que he leido en algunos comentarios, la funda huele como a tabaco jajaja es raro pero es así" review: "El producto en sí está muy bien, viene con 2 cristales templados y 2 grips para los joycon. Pero es lo que he leido en algunos comentarios, la funda huele como a tabaco jajaja es raro pero es así"
output: output:
audience: "self" audience: "self"
@ -65,7 +65,7 @@
- aspect: "included accessories" - aspect: "included accessories"
opinion: "comes with 2 tempered glass and 2 grips" opinion: "comes with 2 tempered glass and 2 grips"
sentiment: Positive sentiment: Positive
category: Function category: Accessories
- aspect: "case smell" - aspect: "case smell"
opinion: "smells like tobacco, strange" opinion: "smells like tobacco, strange"
sentiment: Negative sentiment: Negative

View file

@ -21,6 +21,8 @@
- **禁止** Mixed、Ambiguous、Both、Balanced 等自创词。 - **禁止** Mixed、Ambiguous、Both、Balanced 等自创词。
- 同一条反馈褒贬交织时:选**最主要**倾向;或拆成多条 product_feedback 分别标注。 - 同一条反馈褒贬交织时:选**最主要**倾向;或拆成多条 product_feedback 分别标注。
- 无法判断倾向时用 Neutral。 - 无法判断倾向时用 Neutral。
- category (类别): 仅限 {categories_literal}(禁止 Value、Cost 等自创词;性价比高/物有所值 归入 Price)。 - category (类别): 优先归入以下 8 个标准类别之一:{categories_literal}。
- 归类提示:性价比/物有所值→'Price';成分/材质/配方→'Ingredient';效果/作用/功能/是否好用→'Function';品牌/正品/可靠性/信任→'Trust'。禁止 Value、Cost 等同义自创词。
- 仅当某条反馈确实**无法**归入以上 8 类时,才可补充一个简洁的英文新类别(1-2 个英文单词、首字母大写,如 'Scent'、'Accessories'),不要硬塞或滥造。
- 每条 product_feedback 必须同时包含 aspect、opinion、sentiment、category 四个子字段。 - 每条 product_feedback 必须同时包含 aspect、opinion、sentiment、category 四个子字段。
- 若无提及 product_feedback,输出空列表 [] - 若无提及 product_feedback,输出空列表 []

View file

@ -11,7 +11,11 @@ extraction:
- opinion - opinion
- sentiment - sentiment
- category - category
# 8 个标准类别(信任/原料/质量/功效/功能/外观/物流/客服/价格);
# 若评论反馈确实不属于这 8 类,允许模型自行补充简洁的英文新类别
product_feedback_categories: product_feedback_categories:
- Trust
- Ingredient
- Quality - Quality
- Function - Function
- Appearance - Appearance

343
业务说明.html Normal file
View file

@ -0,0 +1,343 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VOC 评论分析系统 · 业务说明</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<style>
:root {
--c-primary: #2563eb; --c-primary-soft: #dbeafe;
--c-green: #16a34a; --c-amber: #d97706; --c-purple: #7c3aed; --c-rose: #e11d48;
--c-bg: #f8fafc; --c-border: #e2e8f0; --c-ink: #0f172a; --c-muted: #64748b;
--radius: 14px; --shadow: 0 1px 3px rgba(15,23,42,.08), 0 8px 24px rgba(15,23,42,.05);
}
* , *::before, *::after { box-sizing: border-box; }
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
margin: 0; padding: 0; color: var(--c-ink); background: #fff; line-height: 1.75; }
/* 顶部 */
.hero { background: linear-gradient(135deg, #1e3a8a 0%, #2563eb 60%, #3b82f6 100%);
color: #fff; padding: 48px 24px 40px; text-align: center; }
.hero h1 { margin: 0 0 10px; font-size: 2em; font-weight: 800; letter-spacing: .5px; }
.hero p { margin: 0 auto; max-width: 720px; font-size: 1.05em; opacity: .92; }
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px 64px; }
/* 输入说明卡 */
.input-card { margin: -28px auto 40px; background: #fff; border: 1px solid var(--c-border);
border-radius: var(--radius); box-shadow: var(--shadow); padding: 26px 28px; }
.input-card h2 { margin: 0 0 6px; font-size: 1.25em; color: var(--c-primary); }
.input-card .lead { margin: 0 0 20px; color: var(--c-muted); }
.input-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.input-item { border: 1px solid var(--c-border); border-radius: 12px; padding: 16px 18px;
background: var(--c-bg); }
.input-item .badge { display: inline-block; font-size: .76em; font-weight: 700;
padding: 2px 10px; border-radius: 999px; margin-bottom: 8px; }
.badge-need { background: #fee2e2; color: #b91c1c; }
.badge-opt { background: #e0f2fe; color: #0369a1; }
.input-item h3 { margin: 0 0 4px; font-size: 1.05em; }
.input-item p { margin: 0; font-size: .92em; color: var(--c-muted); }
.input-note { margin-top: 18px; padding: 12px 16px; background: var(--c-primary-soft);
border-radius: 10px; font-size: .94em; color: #1e40af; }
/* 流程图 */
.section-title { font-size: 1.45em; font-weight: 800; margin: 48px 0 8px; color: var(--c-ink); }
.section-sub { margin: 0 0 18px; color: var(--c-muted); }
#pipeline { width: 100%; height: 420px; }
.pipeline-box { background: var(--c-bg); border: 1px solid var(--c-border);
border-radius: var(--radius); padding: 8px; }
/* 工序卡片 */
.steps { display: grid; grid-template-columns: 1fr; gap: 22px; margin-top: 26px; }
.step { border: 1px solid var(--c-border); border-radius: var(--radius);
box-shadow: var(--shadow); overflow: hidden; }
.step-head { display: flex; align-items: center; gap: 14px; padding: 18px 24px;
background: var(--c-bg); border-bottom: 1px solid var(--c-border); }
.step-no { flex: 0 0 auto; width: 42px; height: 42px; border-radius: 12px;
display: flex; align-items: center; justify-content: center; font-weight: 800;
font-size: 1.15em; color: #fff; background: var(--c-primary); }
.step-head h3 { margin: 0; font-size: 1.2em; }
.step-head .tag { margin-left: auto; font-size: .8em; color: var(--c-muted); }
.qa { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0; }
.qa-cell { padding: 18px 24px; border-top: 1px solid var(--c-border); }
.qa-cell:nth-child(odd) { border-right: 1px solid var(--c-border); }
.qa-cell .label { display: inline-block; font-size: .82em; font-weight: 700;
padding: 2px 10px; border-radius: 6px; margin-bottom: 8px; }
.lab-what { background: #e0e7ff; color: #3730a3; }
.lab-why { background: #fef3c7; color: #92400e; }
.lab-in { background: #e0f2fe; color: #075985; }
.lab-out { background: #ffedd5; color: #9a3412; }
.lab-how { background: #dcfce7; color: #166534; }
.lab-use { background: #fae8ff; color: #86198f; }
.qa-cell p { margin: 0; font-size: .96em; }
/* 高亮特别板块 */
.step.feature { border-color: var(--c-purple); }
.step.feature .step-no { background: var(--c-purple); }
.step.feature .step-head { background: #faf5ff; }
/* 总结 */
.summary { margin-top: 48px; background: linear-gradient(135deg,#0f172a,#1e293b);
color: #fff; border-radius: var(--radius); padding: 32px 30px; }
.summary h2 { margin: 0 0 18px; font-size: 1.4em; }
.summary ol { margin: 0; padding-left: 22px; }
.summary li { margin: 8px 0; font-size: 1.02em; }
.summary li b { color: #93c5fd; }
.summary .final { margin: 22px 0 0; padding: 16px 18px; background: rgba(59,130,246,.18);
border-radius: 10px; font-size: 1.08em; font-weight: 600; }
@media (max-width: 760px) {
.input-grid { grid-template-columns: 1fr; }
.qa { grid-template-columns: 1fr; }
.qa-cell:nth-child(odd) { border-right: none; }
.hero h1 { font-size: 1.6em; }
}
</style>
</head>
<body>
<div class="hero">
<h1>VOC 评论分析系统 · 业务说明</h1>
<p>它就像一条“评论加工流水线”,把成千上万条买家评论,一步步加工成老板能看懂、能拍板的经营情报。</p>
</div>
<div class="wrap">
<!-- 输入说明 -->
<div class="input-card">
<h2>使用前需要准备什么</h2>
<p class="lead">使用时只需要提供以下内容,系统会自动完成后续全部分析,无需任何技术操作。</p>
<div class="input-grid">
<div class="input-item">
<span class="badge badge-need">必填</span>
<h3>产品名</h3>
<p>本次要分析的产品名称,用于报告标题与结果归档。</p>
</div>
<div class="input-item">
<span class="badge badge-need">必填</span>
<h3>评论数据目录</h3>
<p>存放该产品评论表格的文件夹,系统会自动读取并合并里面的所有评论。</p>
</div>
<div class="input-item">
<span class="badge badge-opt">可选</span>
<h3>行业名称</h3>
<p>填写后能让分析更贴合行业语境;不填也能正常运行。</p>
</div>
</div>
<div class="input-note">
一句话总结:<b>使用时只需输入“产品名”和“评论数据目录”,可选填“行业名称”,其余全部由系统自动完成。</b>
</div>
</div>
<!-- 流程图 -->
<div class="section-title">整体流程一览</div>
<p class="section-sub">从原始评论到经营报告,一共九个环节,环环相扣。</p>
<div class="pipeline-box">
<div id="pipeline"></div>
</div>
<!-- 工序卡片 -->
<div class="section-title">每个环节详解</div>
<p class="section-sub">每个环节都用“是什么 / 为什么 / 输入内容 / 输出内容 / 怎么做 / 业务作用”六点说明。</p>
<div class="steps" id="steps"></div>
<!-- 总结 -->
<div class="summary">
<h2>一句话记住整条流水线</h2>
<ol>
<li><b>合并</b>:确定分析哪批评论</li>
<li><b>清洗</b>:确定哪些评论算数</li>
<li><b>规则化提取</b>:把评论拆成“人群、需求、反馈”</li>
<li><b>文本语义向量化</b>:让机器能比较意思像不像</li>
<li><b>自动聚类</b>:归并成主题,并细分人群</li>
<li><b>高频词</b>:补充直观语言证据</li>
<li><b>中间过程可视化</b>:让 AI 的结论可核对、可信任</li>
<li><b>数据总览</b>:一眼看清全局数据盘面</li>
<li><b>生成报告</b>:翻译成能拍板的经营结论</li>
</ol>
<div class="final">最终目的只有一个:把海量买家评论,变成老板看得懂、能据此做决策的经营情报。</div>
</div>
</div>
<script>
/* ---------- 流程图 ---------- */
(function () {
var el = document.getElementById('pipeline');
if (!el || typeof echarts === 'undefined') return;
var chart = echarts.init(el);
var C = { blue:'#2563eb', green:'#16a34a', amber:'#d97706', purple:'#7c3aed', rose:'#e11d48', slate:'#475569' };
// x,y 为相对坐标,后面统一映射;y 向下为正
var nodes = [
{ name:'1 合并评论', x:0, y:1, color:C.slate },
{ name:'2 数据清洗', x:1, y:1, color:C.slate },
{ name:'3 规则化提取', x:2, y:1, color:C.blue },
{ name:'4 文本语义\n向量化', x:3, y:1, color:C.blue },
{ name:'5 自动聚类', x:4, y:0, color:C.green },
{ name:'6 高频词分析', x:4, y:2, color:C.amber },
{ name:'7 中间过程\n可视化', x:5, y:1, color:C.purple },
{ name:'8 数据总览', x:6, y:1, color:C.blue },
{ name:'9 生成报告', x:7, y:1, color:C.rose }
];
var links = [
['1 合并评论', '2 数据清洗'],
['2 数据清洗', '3 规则化提取'],
['3 规则化提取', '4 文本语义\n向量化'],
['4 文本语义\n向量化', '5 自动聚类'],
['2 数据清洗', '6 高频词分析'],
['5 自动聚类', '7 中间过程\n可视化'],
['6 高频词分析', '7 中间过程\n可视化'],
['7 中间过程\n可视化', '8 数据总览'],
['8 数据总览', '9 生成报告']
];
var data = nodes.map(function (n) {
return {
name: n.name,
x: n.x * 135,
y: n.y * 110,
symbol: 'roundRect',
symbolSize: [108, 64],
itemStyle: { color: n.color, borderColor: '#fff', borderWidth: 2 },
label: { color:'#fff', fontWeight:600, fontSize:13, lineHeight:16 }
};
});
chart.setOption({
tooltip: { show: false },
animationDuration: 800,
series: [{
type: 'graph',
layout: 'none',
coordinateSystem: null,
roam: false,
data: data,
links: links.map(function (l) { return { source: l[0], target: l[1] }; }),
edgeSymbol: ['none', 'arrow'],
edgeSymbolSize: 10,
lineStyle: { color: '#94a3b8', width: 2, curveness: 0.05 },
label: { show: true }
}]
});
window.addEventListener('resize', function () { chart.resize(); });
})();
/* ---------- 工序卡片(数据驱动渲染) ---------- */
(function () {
var steps = [
{
no:'1', title:'合并评论', tag:'打基础',
what:'把同一个产品分散在多个表格文件里的评论,拼成一份完整的总表。',
why:'评论常常分批导出、存在好几个文件里。不先拼到一起,各环节读的数据不一致,报告里的数字就会对不上,也看不出整体趋势。',
input:'同一产品评论数据目录下的多份评论表格文件。',
output:'一份字段统一、记录完整的合并评论总表。',
how:'系统自动读取目录内全部评论表格并纵向拼接;合并前检查各表表头是否一致,栏目不匹配会立即报错,避免数据错位。',
use:'回答最基本的问题:“我们这次到底在分析哪一批评论?”保证所有分析都基于同一份、可信的原始数据。'
},
{
no:'2', title:'数据清洗', tag:'去噪音',
what:'把评论里的垃圾内容去掉,只留下真正有分析价值的评论。',
why:'原始评论里有空评论、乱码、系统占位提示、复制粘贴的重复评论、几个字的无意义评论。不清掉会把分析带偏,让结论失真。',
input:'合并后的原始评论总表。',
output:'去掉空值、重复和过短内容后的干净评论表。',
how:'系统自动删除空评论、乱码、重复评论和过短评论,并清理网页标签、媒体加载提示等无意义内容,只保留完整可用的评论正文。',
use:'回答第二个基本问题:“哪些评论算数?”这一步决定了后面所有百分比的“分母”是否可信——分母不干净,所有占比都会失真。'
},
{
no:'3', title:'规则化提取', tag:'核心翻译官',
what:'让AI逐条阅读评论,把每条拆成三类标准信息:谁在用、买之前有什么困扰、用了之后对产品的评价。',
why:'一条评论常把很多信息混在一起,机器没法统计,也容易把“用户需求”和“对产品的吐槽”搞混。拆清楚后才能分门别类统计。',
input:'清洗后的评论正文(支持英语、西班牙语、法语、德语、日语等多语言原文)。',
output:'每条评论的结构化记录,包含:使用人群、用户需求列表、产品反馈列表(每条反馈含反馈对象、观点内容、情感倾向、类别标签)。',
how:'AI按统一字段规则逐条提取三类信息:①使用人群;②用户需求(仅购买前尚未被产品解决的困扰);③产品反馈(拆成反馈对象、观点、情感倾向、类别标签)。类别标签优先归入信任、原料、质量、功效/功能、外观、物流、客服、价格八类,确实无法归入时AI再补充简洁自定义类别。AI只会根据评论内容提取表层及隐含的语义信息,不会自己胡编乱造。',
use:'这是整条流水线的“翻译官”,把口语化评论变成能被统计、能被聚类的标准业务事实,是后面所有分析的基础。'
},
{
no:'4', title:'文本语义向量化', tag:'让机器懂语义',
what:'把上一步整理出的每条文本信息,转换成计算机能比较“意思像不像”的高维语义向量。',
why:'计算机不能直接理解文字含义。转成语义向量后,意思相近的内容在向量空间里距离更近,机器就能判断哪些评论说的是同一件事。',
input:'结构化后的使用人群、用户需求、产品反馈(方面与观点合并后的待向量化文本)。',
output:'每条文本对应的语义向量,按实体类型分类存入向量数据库。',
how:'系统使用本机部署的 Qwen3-Embedding-4B 嵌入模型,对使用人群、用户需求、产品反馈逐条生成语义向量并持久化存储。向量在本机计算,评论数据不外传,也不产生按量计费。',
use:'为下一步“自动聚类”打好数学基础——让机器识别出“猫在床上尿”和“猫在被子上尿”其实是同一类问题。'
},
{
no:'5', title:'自动聚类与人群细分', tag:'细分市场',
what:'把成千上万条零散信息自动归并成清晰主题簇,并先把买家分成不同人群再分别分析。',
why:'如果把所有人的声音搅在一起,只会得到一个模糊的“平均用户”,结论永远是“要有效、要安全、味道别太重”——正确但没用。',
input:'向量数据库中的语义向量及对应文本实体。',
output:'分阶段的主题簇标签、每条文本的簇归属、核心人群分组及簇内代表性评论。',
how:'核心算法:先用 UMAP 把高维向量压缩到低维空间,再用 HDBSCAN 全自动调参,发现密度相近的主题簇。系统分层来做:1.先把买家按画像分成不同人群分析“受众画像”;2.挑出画像中人数最多的两类核心人群,分别看他们的需求和好评差评;3.再做一次全体买家的总需求和总反馈归类。',
use:'1.把“大家都在说什么”升级成“哪一类人、在什么场景、有什么独特的需求和不满”,直接对应产品策略、卖点策略和人群投放。2.报告里每个主题旁的括号占比,分母是数据清洗后的全部有效评论数;分子是该主题簇下的结构化短语条数(即规则化提取出的人群、需求、反馈短语)。一条评论可能拆出多条短语,所以各主题占比相加可能超过 100%。'
},
{
no:'6', title:'高频词分析', tag:'直观佐证',
what:'统计买家最常提到的词和短语,作为聚类结论的直观语言佐证。',
why:'聚类结论较概括,配上高频词能让人一眼看到“大家反复在说哪些词”,更直观好沟通,也能反过来验证聚类是否合理。',
input:'清洗后的全部评论正文。',
output:'高频词排行表、词频统计结果、按业务语义归类的词群及解读。',
how:'系统先让AI识别产品专有词和无价值口水词,再对全部评论做词频统计;比如“关节疼痛”“鸡肉口味”等完整短语会整体保留,不被拆碎。“for、a、the”等无意义词会被去掉。',
use:'给主题结论补充“高频语言证据”,让报告更直观、更容易扫读和向他人解释。'
}
];
var feature = {
no:'7', title:'模型中间过程可视化', tag:'可信度背书',
what:'报告最后有一个可展开板块,作用是把VOC中间过程展开:用来验证AI把原始评论整理成标准信息和聚类结果是否准确。',
why:'报告主体是AI写的结论,大家难免会问“这靠谱吗?”。这个板块就是把中间过程透明化,让结论可核对、可质疑、可检查。',
input:'结构化提取抽样记录、聚类主题抽样及对应原文评论。',
output:'报告附录中的对照展示板块,含原文与结构化结果并排、各主题簇代表性评论列表。',
how:'展示两部分抽样内容:一是“结构化提取效果”,把原文和整理结果并排对照;二是“聚类效果”,把重点主题下每个簇由哪些真实评论组成逐簇展开。默认收起,需要核对时再点开,不影响正常阅读。',
use:'它是整份报告的“可信度背书”和“质检入口”。一旦发现理解错误或聚类不当,业务能直接对照原文发现并指出,而不必盲目相信报告。'
};
var dashboard = {
no:'8', title:'数据总览', tag:'全局盘面',
what:'报告开篇的数据仪表盘,用关键指标卡片和交互图表,把本次分析的核心数据一页呈现。',
why:'业务打开报告时,首先需要知道“分析了多少评论、买家分几类人、好评差评各占多少、最突出的需求和问题是什么”。没有总览就要翻很久才能建立全局感,容易抓不住重点。',
input:'有效评论总数、聚类结果(受众簇、需求簇、产品反馈簇及情感标注)、词频分类统计。',
output:'四张关键指标卡片(有效评论数、受众群体数、正面反馈占比、负面反馈占比),以及受众画像饼图、产品反馈情感分布柱状图、用户需求 Top 10、负面反馈 Top 10、词频分类关注度雷达图。',
how:'系统从结构化数据和聚类结果中自动汇总统计口径,用 ECharts 渲染 KPI 卡片与五张交互图表;受众与需求相关簇不受 10% 评论占比过滤限制,确保核心洞察完整展示。',
use:'让决策者在 30 秒内建立对本次 VOC 分析的全局认知,再带着问题深入阅读后续洞察与方案。'
};
var report = {
no:'9', title:'生成业务报告', tag:'出结论',
what:'把前面所有分析结果,重新组织成一份老板和业务能看懂的图文报告。',
why:'业务关心的不是机器怎么算,而是“谁在买、买前什么问题最突出、用后体验如何、下一步做什么”,所以要翻译成经营语言。',
input:'聚类结果、词频统计、结构化摘要、机会矩阵等全部中间分析数据。',
output:'一份可交互浏览的业务分析报告网页,含数据总览、图表、洞察结论与改进建议。',
how:'报告按清晰顺序展开:数据总览→受众画像→重点人群分析→全体需求与反馈→多维交叉洞察→改进建议与机会(含优先级排序)→高频词佐证→中间过程可视化。',
use:'把一堆分析结果变成一份能直接拿去开会、拍板、指导产品和运营的决策报告。'
};
function cell(label, cls, text) {
return '<div class="qa-cell"><span class="label ' + cls + '">' + label + '</span>'
+ '<p>' + text + '</p></div>';
}
function card(s, isFeature) {
return '<div class="step' + (isFeature ? ' feature' : '') + '">'
+ '<div class="step-head"><div class="step-no">' + s.no + '</div>'
+ '<h3>' + s.title + '</h3><span class="tag">' + s.tag + '</span></div>'
+ '<div class="qa">'
+ cell('是什么', 'lab-what', s.what)
+ cell('为什么', 'lab-why', s.why)
+ cell('输入内容', 'lab-in', s.input)
+ cell('输出内容', 'lab-out', s.output)
+ cell('怎么做', 'lab-how', s.how)
+ cell('业务作用', 'lab-use', s.use)
+ '</div></div>';
}
var html = steps.map(function (s) { return card(s, false); }).join('');
html += card(feature, true);
html += card(dashboard, false);
html += card(report, false);
document.getElementById('steps').innerHTML = html;
})();
</script>
</body>
</html>

View file

@ -68,8 +68,8 @@ def _get_valid_categories() -> frozenset[str]:
return product_feedback_categories() return product_feedback_categories()
# 模型上下文上限;动态分批受 DEFAULT_MAX_BATCH_INPUT_TOKENS 与 DEFAULT_MAX_BATCH_REVIEWS 约束 # 模型上下文上限;动态分批受 DEFAULT_MAX_BATCH_INPUT_TOKENS 与 DEFAULT_MAX_BATCH_REVIEWS 约束
MODEL_MAX_INPUT_TOKENS = 991_800 MODEL_MAX_INPUT_TOKENS = 1000_800
MODEL_MAX_OUTPUT_TOKENS = 65_530 MODEL_MAX_OUTPUT_TOKENS = 200_530
CHARS_PER_TOKEN_EST = 3.2 CHARS_PER_TOKEN_EST = 3.2
DEFAULT_MAX_BATCH_INPUT_TOKENS = 200_000 DEFAULT_MAX_BATCH_INPUT_TOKENS = 200_000
# 仅用于 batch_plan 日志中的输出 token 粗估,不参与分批与 API max_tokens # 仅用于 batch_plan 日志中的输出 token 粗估,不参与分批与 API max_tokens
@ -492,25 +492,43 @@ _SENTIMENT_ALIASES: Dict[str, str] = {
"mildly negative": "negative", "mildly negative": "negative",
} }
# 模型常见误写 -> 合法 category(校验前自动映射,减少无效重试) # 模型常见误写 -> 8 个标准 category(校验前自动映射,减少无效重试)
_CATEGORY_ALIASES: Dict[str, str] = { _CATEGORY_ALIASES: Dict[str, str] = {
# Price(价格)
"value": "Price", "value": "Price",
"values": "Price", "values": "Price",
"cost": "Price", "cost": "Price",
"pricing": "Price", "pricing": "Price",
"value for money": "Price", "value for money": "Price",
"worth": "Price", "worth": "Price",
# Logistics(物流)
"packaging": "Logistics", "packaging": "Logistics",
"shipping": "Logistics", "shipping": "Logistics",
"delivery": "Logistics", "delivery": "Logistics",
# Customer Service(客服)
"service": "Customer Service", "service": "Customer Service",
"customer support": "Customer Service", "customer support": "Customer Service",
"support": "Customer Service", "support": "Customer Service",
# Appearance(外观)
"design": "Appearance", "design": "Appearance",
"look": "Appearance", "look": "Appearance",
"performance": "Function", # Function(功效/功能)
"efficacy": "Function", "efficacy": "Function",
"performance": "Function",
"effectiveness": "Function", "effectiveness": "Function",
"effect": "Function",
# Ingredient(原料)
"ingredients": "Ingredient",
"material": "Ingredient",
"materials": "Ingredient",
"formula": "Ingredient",
# Trust(信任)
"reliability": "Trust",
"reliable": "Trust",
"brand": "Trust",
"authenticity": "Trust",
"genuine": "Trust",
"trustworthy": "Trust",
} }
@ -544,7 +562,8 @@ def _normalize_category(raw: Any) -> str | None:
for v in valid: for v in valid:
if v.lower() == lower: if v.lower() == lower:
return v return v
return None # 不属于 8 个标准类别:允许模型自行补充新类别,规范为首字母大写后保留
return key.title()
def _normalize_product_feedback(items: Any) -> List[Dict[str, str]]: def _normalize_product_feedback(items: Any) -> List[Dict[str, str]]:

View file

@ -150,7 +150,7 @@ def _call_llm(system: str, user: str, api_key: str) -> str:
{"role": "system", "content": system}, {"role": "system", "content": system},
{"role": "user", "content": user}, {"role": "user", "content": user},
], ],
temperature=0.3, temperature=0.6,
extra_body=extra_body, extra_body=extra_body,
) )
msg = resp.choices[0].message msg = resp.choices[0].message