上传文件至「data」
This commit is contained in:
417
data/feature_extraction.py
Normal file
417
data/feature_extraction.py
Normal file
@@ -0,0 +1,417 @@
|
||||
'''
|
||||
批量提取特征方法
|
||||
'''
|
||||
|
||||
|
||||
from openai import OpenAI
|
||||
import json
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import re
|
||||
|
||||
# 加载环境变量
|
||||
env_path = "C:/Users/TaoJing/Desktop/dialogue/.env"
|
||||
load_dotenv(dotenv_path=env_path)
|
||||
|
||||
# 读取环境变量
|
||||
API_KEY = os.getenv("QWEN_API_KEY")
|
||||
MODEL = os.getenv("MODEL_NAME", "deepseek-v3.1")
|
||||
TEMPERATURE = float(os.getenv("TEMPERATURE", 0.1)) # 降低随机性,提升格式稳定性
|
||||
OUTPUT_DIR = os.getenv("OUTPUT_DIR", "time_12_1/data_ch_1")
|
||||
|
||||
|
||||
client = OpenAI(
|
||||
api_key=API_KEY,
|
||||
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
)
|
||||
|
||||
|
||||
def build_extraction_prompt(dialogue_text):
|
||||
prompt_template = """
|
||||
# Role
|
||||
你是一名拥有15年经验的“家庭教育销售通话审计专家”。你的核心能力是透过家长杂乱的表述,精准捕捉深层心理动机、家庭权力结构、隐形财富信号以及高危销售线索。
|
||||
|
||||
# Core Protocols (核心审计协议 - 最高优先级)
|
||||
|
||||
## 1. 宁缺毋滥原则 (The Principle of Precision)
|
||||
* **存在即输出,无证即沉默**:忽略任何关于“字段数量”的限制。如果原文中有20个维度的有效证据,就输出20个;如果只有3个,就输出3个。
|
||||
* **严禁凑数**:如果原文未提及某维度,或者证据模糊两可,**绝对不要**输出该 Key。不要为了追求“信息丰富”而强行填空。
|
||||
* **严禁不存在特征填'未提及'**:文中没有证据不准构造,不能出现未提及、文中未出现等多余提示。
|
||||
|
||||
## 2. 证据阵列法则 (The Law of Evidence Arrays)
|
||||
* **数据结构变更**:`evidence` 字段必须是 **字符串数组 (List<String>)**,严禁使用单一字符串。
|
||||
* **颗粒度控制 (Granularity Control)**:
|
||||
* 数组中的元素必须是 **具有独立语义的完整原句** 或 **包含主谓宾的完整意群**。
|
||||
* **禁止碎片**:严禁提取如 "不合适"、"太贵"、"焦虑" 这样缺乏上下文的短语。
|
||||
* **主体过滤**:**仅提取家长(客户)表达的原话**,严禁提取销售人员的引导语、复述语或共情语。
|
||||
* **纯净引用**:每一个元素必须是原文的 100% 完美复制。
|
||||
* **严禁拼接**:严禁使用“+”、“和”、“以及”将两句不连贯的话拼在同一个字符串里。
|
||||
* **严禁篡改**:禁止总结、禁止润色、禁止“原文+分析”。你的分析只能体现在 `value` 字段中。
|
||||
|
||||
## 3. 结论极简法则 (The Law of Concise Conclusion)
|
||||
* **强制必输字段**:`Follow_up_Priority` 是 **核心必选字段**,无论任何情况都必须输出,不允许缺失。
|
||||
* **Follow_up_Priority 兜底规则**:
|
||||
- 若文本完全无痛点/无财力/无意识,`value` 填“C级 (无痛点/无意识)”,`evidence` 填 ["文本未提及任何痛点、财力或意向相关内容"]
|
||||
- 若仅部分信息缺失,按规则评级并在 `evidence` 中列出已有有效原句。
|
||||
* **Value 约束**:`value` 字段必须是 **客观、简练的定性结论**(必须限制在 **20个汉字以内**)。
|
||||
* *正确示例*: "A级 (高痛点+强财力)"
|
||||
* *错误示例*: "家长表现出对价格的犹豫,虽然她很有钱,但是因为..." (禁止小作文)
|
||||
|
||||
## 4. 身份与财富的高敏嗅觉
|
||||
* 对于**高价值信号**(职业/多孩/私立学校/房产)和**生命红线**(自杀/不想活了/抑郁症确诊)保持极度敏感,一旦出现必须提取。
|
||||
|
||||
# Task
|
||||
阅读提供的销售通话录音文本,从以下 23 个预设维度中筛选出**有效信息**,生成一份高精度的客户画像 JSON。
|
||||
|
||||
# Field Definitions (字段定义与提取逻辑)
|
||||
### [第一组:心理动力与危机]
|
||||
1. **Core_Fear_Source** (深层恐惧)
|
||||
* *逻辑*: 驱动家长寻求帮助的终极噩梦。是怕孩子死(生命安全)?怕孩子阶级跌落?还是怕自己面子挂不住?
|
||||
* *注意*: 必须提取具体的后果描述。
|
||||
2. **Pain_Threshold** (痛苦阈值)
|
||||
* *逻辑*: 家长当前的情绪状态。是“崩溃急救”(无法忍受,必须马上解决),还是“隐隐作痛”(还能凑合)?
|
||||
3. **Time_Window_Pressure** (时间压力)
|
||||
* *逻辑*: 客观的截止日期。如:距离中高考仅剩X月、休学复课最后期限、学校劝退通牒。
|
||||
4. **Helplessness_Index** (无助指数)
|
||||
* *逻辑*: 家长是否已经尝试过多种方法均失败(习得性无助),还是盲目自信觉得还能管。
|
||||
5. **Social_Shame** (社交耻感)
|
||||
* *逻辑*: 孩子问题是否影响了家长的社会形象(怕老师找、怕亲戚问、不敢出门)。
|
||||
6. **Ultimatum_Event** (爆发事件)
|
||||
* *逻辑*: 迫使家长此时此刻咨询的导火索。如:昨日发生的激烈争吵、离家出走、打架、学校停课通知。
|
||||
7. **Emotional_Trigger** (情绪扳机)
|
||||
* *逻辑*: 沟通中家长情绪最激动的点(哭泣、愤怒、颤抖)。
|
||||
|
||||
### [第二组:阻力与障碍]
|
||||
8. **Secret_Resistance** (隐性抗拒)
|
||||
* *逻辑*: **阻碍成交**的心理障碍。特指:怕被家人知道买课、怕孩子知道家长在咨询、觉得课程是骗局。
|
||||
* *排除*: 孩子的生活秘密(如抽烟/早恋)不属于此字段。
|
||||
9. **Trust_Deficit** (信任赤字)
|
||||
* *逻辑*: 对机构/销售/网课模式的直接质疑。如:“你们正规吗?”“之前被骗过”。
|
||||
10. **Family_Sabotage** (家庭阻力)
|
||||
* *逻辑*: 家庭中明确的反对者或捣乱者(拆台的配偶、干涉的长辈、发病的家属)。
|
||||
* *排除*: 客观的不幸(如家人生病/车祸)不属于此字段,除非该事件直接阻碍了家长听课。
|
||||
11. **Low_Self_Efficacy** (效能感低)
|
||||
* *逻辑*: 家长担心**自己**学不会、坚持不下来、没时间听课。
|
||||
12. **Attribution_Barrier** (归因偏差)
|
||||
* *逻辑*: 家长认为错在谁?(全是学校的错 / 全是手机的错 / 全是遗传的错 / 承认自己有错)。
|
||||
|
||||
### [第三组:资源与决策]
|
||||
13. **Payer_Decision_Maker** (决策权)
|
||||
* *逻辑*: 谁掌握财权?谁有一票否决权?是“妈妈独裁”还是“需商量”?
|
||||
14. **Hidden_Wealth_Proof** (隐形财力)
|
||||
* *逻辑*: 寻找高消费证据。如:私立学校、出国计划、高昂学费、住别墅、高知职业(教授/医生)。
|
||||
15. **Price_Sensitivity** (价格敏感度)
|
||||
* *逻辑*: 对价格的反应。是“只看效果不差钱”,还是“犹豫比价”、“哭穷”。
|
||||
16. **Sunk_Cost** (沉没成本)
|
||||
* *逻辑*: 过往已投入的无效成本。如:之前报过xx辅导班、做过xx次心理咨询、花了xx万没效果。
|
||||
17. **Compensatory_Spending** (补偿心理)
|
||||
* *逻辑*: 是否因亏欠感而通过花钱(买东西/报课)来弥补孩子。
|
||||
|
||||
### [第四组:销售价值判断]
|
||||
18. **Expectation_Bonus** (期望范围)
|
||||
* *逻辑*: 家长的底线(只要活着/不退学)与理想(考大学/变优秀)。
|
||||
19. **Competitor_Mindset** (竞品思维)
|
||||
* *逻辑*: 家长是否在对比其他**解决方案**。如:特训学校、心理医生(针对孩子)、线下辅导班。
|
||||
* *排除*: 家属的就医经历不属于此字段。
|
||||
20. **Cognitive_Stage** (认知阶段)
|
||||
* *逻辑*: 愚昧期(修孩子) -> 觉醒期(修自己/找方法)。
|
||||
21. **Referral_Potential** (转介绍潜力)
|
||||
* *逻辑*: 基于身份判断。重点捕捉:多孩家庭、教师/医生/教授/公务员身份、家长委员会成员。
|
||||
22. **Last_Interaction** (互动状态)
|
||||
* *逻辑*: 通话结束时的温度,互动内容多表现积极。积极互动/索要案例;接受通话/同意配合。
|
||||
* *注意*: 原文末尾必须有应答对话。
|
||||
23. **Follow_up_Priority** (跟进优先级) - [重点监控字段]
|
||||
* *逻辑*: 综合评级(S/A/B/C)。
|
||||
* **Extraction Rule (必须使用数组逻辑)**:
|
||||
* 如果评级为 **S/A**(通常需要痛点+财力/意向双重支撑),必须在 `evidence` 数组中分别列出这两方面(甚至三方面)的原话。
|
||||
* **S级**: 涉及生命安全 OR (极高痛点 + 强支付能力 + 强意向)。
|
||||
* **A级**: 有痛点 + 有支付能力。
|
||||
* **B级**: 有痛点 + 无支付能力/犹豫。
|
||||
* **C级**: 无痛点/无意识。
|
||||
|
||||
# Output Format (输出格式指令)
|
||||
**强制要求1**:JSON 中必须包含 `Follow_up_Priority` 字段,否则视为无效输出。
|
||||
**强制要求2**:JSON 格式必须严格合法(逗号分隔、引号成对、括号匹配),可直接被JSON解析工具识别。
|
||||
**强制要求3**:严禁23个预设维度中出现'未提及'。
|
||||
**强制要求4**:证据必须在原文中,且充足。
|
||||
**强制要求5**:不准将示例模板内容作为证据输出,必须从原文中找证据。
|
||||
**强制要求6**:严禁凑数,原文中20个维度的有效证据,就输出20个,只有3个,就输出3个,没有就不要构造。
|
||||
**强制要求7**:严禁使用“+”、“和”、“以及”将两句不连贯的话拼在同一个字符串里。
|
||||
**强制要求7**:"evidence"中必须有原文内容,原文中找不到证据该维度禁止输出。
|
||||
**强制要求8**:字段"value"严禁出现"未提及价格反应"、"未明确表态"字样,文中没有证据严禁输出字段。
|
||||
|
||||
JSON 结构要求:
|
||||
1. **Key**: 仅使用上述定义中出现的英文 Key。
|
||||
2. **Value**: 必须是 **<20字** 的短语结论。
|
||||
3. **Evidence**: 必须是 **List<String>** (字符串数组)。
|
||||
4. **Strict Validation (自我审查)**:
|
||||
* 检查 `evidence` 是否包含“家长说”、“意思就是”? -> 若有,**改为纯引用**。
|
||||
* 检查 `evidence` 是否为空,为空则删除字段。
|
||||
* 检查 JSON 语法是否正确? -> 确保逗号不遗漏、括号成对。
|
||||
* 不准将示例模板内容作为证据输出,必须从原文中找证据。
|
||||
|
||||
**Example Output:**
|
||||
{
|
||||
"Follow_up_Priority": {
|
||||
"value": "A级 (痛点强+财力足)",
|
||||
"evidence": [
|
||||
"我是今年才确诊,他是焦虑的", // 证据1:完整原句支撑痛点
|
||||
"孩子现在在西工大附中上学", // 证据2:完整原句支撑隐形财力
|
||||
"留学基金我们已经准备好了" // 证据3:完整原句支撑支付能力
|
||||
]
|
||||
},
|
||||
"Pain_Threshold": {
|
||||
"value": "崩溃急救状态",
|
||||
"evidence": [
|
||||
"我不知道怎么来处理",
|
||||
"一看见难了就崩溃啊就崩溃"
|
||||
]
|
||||
},
|
||||
"Last_Interaction": {
|
||||
"value": "积极配合修改信件",
|
||||
"evidence": [
|
||||
"好,那你拿一个哪几点?那个你就给我框一下,然后截一个图,然后下午晚一点有空,我再来稍微修改一下",
|
||||
"行,那我稍后给您打过去,给您接问一下啊"
|
||||
]
|
||||
}
|
||||
}"""
|
||||
full_prompt = f"{prompt_template}\n\n### 原始通话文本\n{dialogue_text}\n\n### 请严格按照上述要求输出JSON(仅JSON,无其他内容)"
|
||||
return full_prompt
|
||||
|
||||
|
||||
def clean_and_fix_json(json_str):
|
||||
"""清洗JSON格式"""
|
||||
try:
|
||||
# 移除转义符、控制字符和多余空格
|
||||
json_str = json_str.replace('\\"', '"').replace("\\'", "'")
|
||||
json_str = re.sub(r'[\n\r\t\f\v]', '', json_str)
|
||||
json_str = re.sub(r'\s+', ' ', json_str).strip()
|
||||
# 修复末尾多余逗号
|
||||
json_str = re.sub(r",\s*}", "}", json_str)
|
||||
json_str = re.sub(r",\s*]", "]", json_str)
|
||||
return json_str
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"JSON清洗失败: {str(e)}") from e
|
||||
|
||||
|
||||
def extract_features_with_qwen(dialogue_text, file_name, output_dir="qwen_new_123"):
|
||||
"""
|
||||
调用API提取特征并保存为JSON文件
|
||||
:param dialogue_text: 预处理后的对话文本(字符串)
|
||||
:param file_name: 原文件名称(用于生成输出文件名)
|
||||
:param output_dir: 结果保存目录
|
||||
:return: 提取的特征字典(失败则返回None)
|
||||
"""
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
prompt = build_extraction_prompt(dialogue_text)
|
||||
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model=MODEL,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=TEMPERATURE,
|
||||
max_tokens=8000
|
||||
)
|
||||
|
||||
feature_json_str = response.choices[0].message.content.strip()
|
||||
# 提取JSON片段
|
||||
json_match = re.search(r"\{[\s\S]*\}", feature_json_str)
|
||||
if json_match:
|
||||
feature_json_str = json_match.group()
|
||||
else:
|
||||
raise ValueError("返回内容中未找到有效JSON数据")
|
||||
|
||||
# 移除代码块标记
|
||||
if feature_json_str.startswith("```json"):
|
||||
feature_json_str = feature_json_str[7:-3].strip()
|
||||
elif feature_json_str.startswith("```"):
|
||||
feature_json_str = feature_json_str[3:-3].strip()
|
||||
|
||||
feature_dict = json.loads(feature_json_str)
|
||||
|
||||
# 验证核心字段
|
||||
if "Follow_up_Priority" not in feature_dict:
|
||||
raise ValueError("返回结果缺失核心必选字段:Follow_up_Priority")
|
||||
|
||||
# 生成输出文件名
|
||||
file_base = os.path.splitext(file_name)[0]
|
||||
json_filename = f"{file_base}.json"
|
||||
output_path = os.path.join(output_dir, json_filename)
|
||||
|
||||
# 保存文件
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(feature_dict, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"处理完成:{file_name} -> {json_filename}")
|
||||
return feature_dict
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"JSON解析失败 {file_name}:{str(e)} | 原始内容:{feature_json_str[:200]}...")
|
||||
return None
|
||||
except ValueError as e:
|
||||
print(f"数据验证失败 {file_name}:{str(e)}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"特征提取失败 {file_name}:{str(e)}")
|
||||
return None
|
||||
|
||||
# 批量处理函数
|
||||
def batch_process_first_200_txt(folder_path, output_dir):
|
||||
"""
|
||||
仅处理指定文件夹下的前200个txt文件
|
||||
:param folder_path: 待处理文件夹路径
|
||||
:param output_dir: 结果输出目录
|
||||
"""
|
||||
# 检查文件夹是否存在
|
||||
if not os.path.isdir(folder_path):
|
||||
print(f"文件夹不存在:{folder_path}")
|
||||
return
|
||||
|
||||
# 筛选出文件夹中的txt文件并按名称排序(保证处理顺序稳定)
|
||||
txt_file_list = [
|
||||
f for f in os.listdir(folder_path)
|
||||
if os.path.isfile(os.path.join(folder_path, f)) and f.lower().endswith(".txt")
|
||||
]
|
||||
# 按文件名排序(可选,保证每次处理顺序一致)
|
||||
txt_file_list.sort()
|
||||
|
||||
# 取前200个txt文件
|
||||
target_files = txt_file_list[:200]
|
||||
|
||||
if not target_files:
|
||||
print(f"文件夹 {folder_path} 中无txt文件可处理")
|
||||
return
|
||||
|
||||
print(f"始处理前 {len(target_files)} 个txt文件")
|
||||
|
||||
processed_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for file_name in target_files:
|
||||
file_path = os.path.join(folder_path, file_name)
|
||||
|
||||
# 读取文件内容
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
dialogue_content = f.read().strip()
|
||||
if not dialogue_content:
|
||||
print(f"文件内容为空,跳过:{file_name}")
|
||||
failed_count += 1
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"读取文件失败 {file_name}:{str(e)}")
|
||||
failed_count += 1
|
||||
continue
|
||||
|
||||
# 调用特征提取函数
|
||||
result = extract_features_with_qwen(dialogue_content, file_name, output_dir)
|
||||
if result:
|
||||
processed_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
|
||||
# 输出批量处理统计结果
|
||||
print("\n批量处理完成")
|
||||
print(f"成功处理:{processed_count} 个文件")
|
||||
print(f"处理失败:{failed_count} 个文件")
|
||||
print(f"结果保存至:{os.path.abspath(output_dir)}")
|
||||
|
||||
|
||||
def process_single_txt(file_path, output_dir=OUTPUT_DIR):
|
||||
"""
|
||||
处理单个TXT文件,提取特征并保存JSON
|
||||
:param file_path: 单个TXT文件的完整路径
|
||||
:param output_dir: JSON结果保存目录
|
||||
"""
|
||||
# 1. 验证文件是否存在且是TXT文件
|
||||
if not os.path.exists(file_path):
|
||||
raise FileNotFoundError(f"文件不存在:{file_path}")
|
||||
if not file_path.lower().endswith(".txt"):
|
||||
raise ValueError(f"不是TXT文件:{file_path}")
|
||||
if not os.path.isfile(file_path):
|
||||
raise IsADirectoryError(f"这是文件夹,不是文件:{file_path}")
|
||||
|
||||
# 2. 读取TXT文件内容
|
||||
print(f"正在读取文件:{file_path}")
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
dialogue_content = f.read().strip()
|
||||
if not dialogue_content:
|
||||
raise ValueError("文件内容为空")
|
||||
print(f"成功读取文件(字符数:{len(dialogue_content)})")
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"读取文件失败:{str(e)}") from e
|
||||
|
||||
# 3. 构建提示词并调用API
|
||||
prompt = build_extraction_prompt(dialogue_content)
|
||||
try:
|
||||
print("正在调用API提取特征...")
|
||||
response = client.chat.completions.create(
|
||||
model=MODEL,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=TEMPERATURE,
|
||||
max_tokens=8000,
|
||||
timeout=30
|
||||
)
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"API调用失败:{str(e)}") from e
|
||||
|
||||
# 4. 提取并清洗JSON
|
||||
feature_json_str = response.choices[0].message.content.strip()
|
||||
json_match = re.search(r"\{[\s\S]*\}", feature_json_str)
|
||||
if not json_match:
|
||||
raise RuntimeError(f"API返回无有效JSON:{feature_json_str[:200]}...")
|
||||
cleaned_json = clean_and_fix_json(json_match.group())
|
||||
|
||||
# 5. 解析并验证JSON
|
||||
try:
|
||||
parsed_dict = json.loads(cleaned_json)
|
||||
except json.JSONDecodeError as e:
|
||||
raise RuntimeError(f"JSON解析失败:{str(e)} | 清洗后内容:{cleaned_json[:500]}") from e
|
||||
|
||||
# 验证核心字段
|
||||
if "Follow_up_Priority" not in parsed_dict:
|
||||
raise RuntimeError("核心字段Follow_up_Priority缺失")
|
||||
fu_prio = parsed_dict["Follow_up_Priority"]
|
||||
if not isinstance(fu_prio, dict) or "value" not in fu_prio or "evidence" not in fu_prio:
|
||||
raise RuntimeError("Follow_up_Priority格式错误(需包含value和evidence)")
|
||||
if not isinstance(fu_prio["evidence"], list):
|
||||
raise RuntimeError("evidence必须是数组类型")
|
||||
if len(str(fu_prio["value"])) >= 20:
|
||||
raise RuntimeError(f"value超20字限制:{fu_prio['value']}")
|
||||
|
||||
# 6. 保存JSON结果
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
file_name = os.path.basename(file_path)
|
||||
json_file_name = f"{os.path.splitext(file_name)[0]}"
|
||||
json_save_path = os.path.join(output_dir, json_file_name)
|
||||
|
||||
try:
|
||||
with open(json_save_path, "w", encoding="utf-8") as f:
|
||||
json.dump(parsed_dict, f, ensure_ascii=False, indent=2)
|
||||
print(f"处理完成!JSON保存至:{json_save_path}")
|
||||
return parsed_dict
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"保存JSON失败:{str(e)}") from e
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 要处理的源文件夹路径
|
||||
target_folder = "qwen\清洗后\未成交"
|
||||
|
||||
# 执行批量处理:仅处理前200个txt文件,输出到
|
||||
batch_process_first_200_txt(
|
||||
folder_path=target_folder,
|
||||
output_dir=OUTPUT_DIR
|
||||
)
|
||||
# SINGLE_TXT_PATH = "./qwen/cdb7d561-975a-431e-86d4-9b3ddc714f73.txt"
|
||||
|
||||
# try:
|
||||
# # 执行单个文件处理
|
||||
# process_single_txt(file_path=SINGLE_TXT_PATH)
|
||||
# except Exception as e:
|
||||
# print(f"\n处理失败:{str(e)}")
|
||||
|
||||
454
data/feature_extraction_2.py
Normal file
454
data/feature_extraction_2.py
Normal file
@@ -0,0 +1,454 @@
|
||||
from openai import OpenAI
|
||||
import json
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import re
|
||||
|
||||
# 加载环境变量
|
||||
env_path = "C:/Users/TaoJing/Desktop/dialogue/.env"
|
||||
load_dotenv(dotenv_path=env_path)
|
||||
|
||||
# 读取环境变量
|
||||
API_KEY = os.getenv("QWEN_API_KEY")
|
||||
MODEL = os.getenv("MODEL_NAME")
|
||||
TEMPERATURE = float(os.getenv("TEMPERATURE", 0.1)) # 降低随机性,提升格式稳定性
|
||||
OUTPUT_DIR_2 = os.getenv("OUTPUT_DIR_2")
|
||||
|
||||
|
||||
client = OpenAI(
|
||||
api_key=API_KEY,
|
||||
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
)
|
||||
|
||||
|
||||
|
||||
# 提示词第二版本
|
||||
def build_extraction_prompt(dialogue_text):
|
||||
prompt_template = """
|
||||
[角色定义]
|
||||
你是一名拥有15年经验的“家庭教育销售通话审计专家”。你的核心能力是透过家长杂乱的表述,精准捕捉深层心理动机、家庭权力结构、隐形财富信号以及高危销售线索。
|
||||
|
||||
[任务]
|
||||
阅读对话文本,从以下23个预设维度中筛选数“有效信息”,生成一份高精度的客户画像JSON。
|
||||
|
||||
[字段定义与提取逻辑]
|
||||
第一组:心理动力与危机
|
||||
1.Core_Fear_Source(深层恐惧)
|
||||
核心逻辑:驱动家长寻求帮助的终极噩梦。是怕孩子死(生命安全)?怕孩子阶级跌落?还是怕自己面子挂不住?
|
||||
固定标签池:孩子发展受限、阶段跌落/升学失败、社交耻感/面子受损、生命安全/身心健康
|
||||
判定规则:
|
||||
1. 孩子发展受限:原文提及“未来没平台/没出路/职业受限/躺平摆烂不努力”;
|
||||
2. 阶段跌落/升学失败:原文提及“考不上高中/大学/没学历就没前途/不如别人”;
|
||||
3. 社交耻感/面子受损:原文提及“怕老师问/亲戚说/别人看不起/孩子问题没脸说”;
|
||||
4. 生命安全/身心健康:原文提及“孩子抑郁/自残/沉迷/霸凌/离家出走/安全问题”
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
2.Pain_Threshold(痛苦阈值)
|
||||
核心逻辑:家长当前的情绪状态。
|
||||
固定标签池:崩溃急救、隐隐作痛
|
||||
判定规则:
|
||||
1. 崩溃急救:孩子有极端行为(自残/)、重度抑郁、过量服药等风险;或家长情绪崩溃、心疼、过渡操劳
|
||||
2. 隐隐作痛:家长仅着急/焦虑,无生理不适;或孩子自暴自弃/厌学/宅/逃避考试,无极端风险
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
3.Time_Window_Oressure(时间压力)
|
||||
核心逻辑:客观的截止日期。
|
||||
固定标签池:存在时间压力、无时间压力
|
||||
判定规则:
|
||||
1. 凡有具体时限 / 截止日期,无论表述如何,统一归为「存在时间压力」:如 “中考倒计时 5 个月”“休学复课最后期限”“一周内大考” ,仅看是否有客观时间约束,不细分场景 / 年级;
|
||||
2. 凡明确无截止 / 无时限 / 单纯状态,统一归为「无时间压力」:如 “无明确截止日期”“长期休学无明确期限”“高二关键期(无截止日)” 等,剔除所有 “关键期” 的模糊修饰,仅看是否有时间约束;
|
||||
3. 彻底剔除主观表述:如 “时间紧迫”“紧迫的时间压力” 等无具体时限的主观描述,若有配套具体截止日期则归「存在时间压力」;
|
||||
4. 不细分年级 / 场景:无论中考 / 高考 / 复学 / 考试,只要有客观时间约束,均归「存在时间压力」。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
4.Helplessness_Index(无助指数)
|
||||
核心逻辑:家长是否尝试过多种方法均失败,还是盲目自信觉得还能管
|
||||
固定标签池:习得性无助、轻度无助
|
||||
判定规则:
|
||||
1. 习得性无助:家长提及“报了很多班/找过老师/做过心理咨询,都没用”“试了各种方法,孩子还是这样”“花了很多钱,一点效果没有”;
|
||||
2. 轻度无助:家长提及“不知道怎么办/没人能帮我”“说不动孩子,我没辙”“找不到好方法,很迷茫”(未提过往尝试)。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
5.Social_Shame(社交耻感)
|
||||
核心逻辑:孩子问题是否影响了家长的社会形象。
|
||||
固定标签池:显性社交耻感、隐性社交耻感、轻度社交耻感、强烈社交耻感、社交回避
|
||||
判定规则:
|
||||
1. 显性社交耻感:家长明确表达耻感情绪+具体场景,如“孩子这样我没面子、抬不起头”“怕亲戚议论孩子不上学/休学”“怕老师找我、问责我,觉得丢人”“被老师公开批评很丢脸”;
|
||||
2. 隐性社交耻感:家长无明确耻感情绪,但行为/表述隐含耻感,如“不想让亲戚/老师知道孩子问题”“回避家长会、学校沟通”“偷偷咨询,怕被孩子/外人发现”(未说“没面子”,但有回避行为);
|
||||
3. 轻度社交耻感:家长仅轻微提及顾虑,无强烈情绪、无回避行为,如“有点怕老师找,但影响不大”“偶尔担心亲戚问起,无所谓”“有点在意他人看法,但不影响生活”;
|
||||
4. 强烈社交耻感:家长有极端耻感情绪+明显行为,如“因孩子问题不敢出门、回避所有社交”“怕被贴失败家长/问题学生标签,压力极大”“被老师/亲戚议论后情绪崩溃”“觉得家庭声誉彻底受损”;
|
||||
5. 社交回避(伴耻感):家长明确表示“回避社交场合、回避和亲戚/老师沟通”,且核心原因是“怕被议论孩子问题、怕丢面子”(回避行为明确,且与耻感直接相关)。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
6.Ultimatum_Event(爆发事件)
|
||||
核心逻辑:迫使家长咨询的导火索。
|
||||
固定标签池:拒学/逃学类、离校/休学类、极端行为类、亲子/师生冲突类、学业下滑类、其他突发类
|
||||
判定规则:(优先级从高到低)
|
||||
1. 极端行为类:家长提及“孩子自残、吞药、跳楼、以死相逼”“离家出走、失联”“确诊抑郁症、心理问题爆发”等(核心是伤害自身/极端逃避);
|
||||
2. 离校/休学类:家长提及“学校通知停课、劝退、让回家反省”“孩子休学、复课失败、长期失学”“频繁请假后休学”等(核心是被动/主动离校停学);
|
||||
3. 拒学/逃学类:家长提及“孩子拒绝上学、拒返校、逃学、缺课、拒考、拒上某节课”“连续多日不上学、今日未上学”等(核心是主动拒绝到校);
|
||||
4. 亲子/师生冲突类:家长提及“催学引发冲突、夺手机爆发矛盾”“与老师吵架、被老师体罚/批评后拒学”“亲子激烈争吵、孩子怒怼家长”等(核心是近期突发冲突);
|
||||
5. 学业下滑类:家长提及“成绩断崖式下滑、断崖式下跌、持续下滑”“考试失利、交白卷、弃考”等(核心是学业突发恶化);
|
||||
6. 其他突发类:家长提及“孩子早恋、被霸凌、发现藏手机/电子烟”“家庭变故(离婚、亲人去世)、母亲失联”等(核心是其他近期具体突发事)。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出;文中出现多个事件的,按照标签优先级输出,如:拒学/逃学类+其他突发类(早恋)
|
||||
7.Emotional_Trigger(情绪扳机)
|
||||
核心逻辑:沟通中家长情绪最激动的点。
|
||||
固定标签值:情绪平稳、情绪低落、焦虑、情绪激动、情绪崩溃、无力感爆发、学习相关情绪触发、孩子危机相关情绪触发、沟通相关情绪触发
|
||||
判定规则:
|
||||
1. 情绪平稳(无波动):家长/孩子情绪无起伏,提及孩子问题时无焦虑、激动等表现(如“情绪平静无波动”“未显性激动”“无情绪波动”“无强烈情绪波动”);
|
||||
2. 情绪低落(含敏感/委屈):存在温和负面情绪,无激动/崩溃表现(如“孩子情绪低落”“情绪低落”“提及反感沟通时情绪低落”“被儿子否定时低落”“提及孤立时情绪低落”);
|
||||
3. 焦虑(含失眠/担忧):对孩子现状、未来、学业等存在担忧情绪,可伴随躯体表现(如“焦虑”“考试焦虑”“焦虑失眠”“对孩子未来担忧”“对学习动力不足焦虑”“对失控感焦虑”);
|
||||
4. 情绪激动(含暴怒/失控):情绪明显起伏,有易怒、激动表现,未达到崩溃程度(如“情绪激动”“因拖拉暴怒”“提及泄密时激动”“提及霸凌时激动”“谈及偷手机时激动”);
|
||||
5. 情绪崩溃(含哽咽/哭泣/绝望):情绪达到极端状态,伴随哭泣、哽咽、绝望感(如“情绪崩溃”“家长情绪崩溃”“提及自残时哽咽”“多次哭泣与绝望”“回忆过往时情绪崩溃”);
|
||||
6. 无力感爆发(含自责/迷茫):因教育无效、孩子问题无解产生的自责、迷茫、无力(如“无力感爆发”“家长自责与无力感爆发”“对孩子迷茫状态焦虑”“对教育投入无力感”);
|
||||
7. 学习相关情绪触发:提及学习、成绩、学业等相关内容即出现情绪波动(如“谈学习即情绪崩溃”“提及学习即情绪爆发”“提及数学成绩崩溃”“成绩断崖下跌引发焦虑”);
|
||||
8. 孩子危机相关情绪触发:提及孩子自残、自杀、离家出走、霸凌、心理疾病等危机事件即出现情绪波动(如“提及自残时情绪波动”“提及自杀时情绪失控”“孩子离家出走时恐惧与失控”“提及抑郁诊断时情绪波动”);
|
||||
9. 沟通相关情绪触发:因沟通失败、孩子拒绝沟通等沟通问题引发情绪波动(如“沟通困难”“沟通失败情绪激动”“孩子拒绝对话与情绪爆发”“被拒绝沟通时爆发”)。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
第二组:阻力与障碍
|
||||
8.Secret_Resistance(隐性抗拒)
|
||||
核心逻辑:“阻碍成交”的心理障碍。特指:怕被家人知道买课、怕孩子知道家长在咨询、觉得课程是骗局
|
||||
固定标签池:怕孩子知晓咨询/学习相关行为、怕家人知晓咨询/购课相关行为、怀疑课程/机构
|
||||
判定规则:
|
||||
1. 怕孩子知晓咨询/学习相关行为:家长提及“怕孩子知道家长在咨询/听课/学习”“担心孩子察觉咨询意图/发现家长求助”“需回避孩子听课/隐瞒孩子咨询行为”“怕孩子知道课程内容/家长报课”“怕孩子觉得自己有病(因咨询产生病耻感)”;
|
||||
2. 怕家人知晓咨询/购课相关行为:家长提及“怕被家人(丈夫/妻子/长辈)知道咨询/报课/购课”“担心家人质疑决策/反对报课”“隐瞒配偶购买课程/不愿让家人知悉购课”“怕被家人知道求助”;
|
||||
3. 怀疑课程/机构(含效果/正规性/套路):家长提及“担心课程无效/不落地/不适合孩子/浪费钱”“质疑机构正规性/怕课程是骗局”“担心后续销售套路/被推销”“曾被竞品/网课欺骗,对课程存疑”“质疑课程针对性/师资经验”。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
9.Trust_Deficit(信任赤字)
|
||||
核心逻辑:对机构/销售/网课模式的直接质疑。
|
||||
固定标签池:存在信任赤字、无信任赤字
|
||||
判定规则:
|
||||
1. 存在信任赤字:家长对机构、销售、网课 / 课程模式有明确的直接质疑 / 不信任,含资质、正规性、效果、公信力、履约能力等核心维度的质疑,或有被骗经历引发的对同类机构的不信任。
|
||||
2. 无信任赤字:家长无任何对机构 / 销售 / 网课模式的质疑,明确表达信任、建立基础信任,或仅对外部主体 / 非核心内容提问,无信任层面的顾虑。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
10.Family_Sabotage(家庭阻力)
|
||||
核心逻辑:家庭中明确的反对者或者捣乱者。
|
||||
固定标签池:配偶反对/无共情、长辈干涉/拆台、家属问题阻碍
|
||||
判定规则:
|
||||
1. 配偶反对/无共情:家长提及“老公/老婆不支持我找方法”“他不管孩子,也不理解我”“配偶不会共情孩子,总骂孩子”;
|
||||
2. 长辈干涉/拆台:家长提及“老人惯着孩子,反对报课”“我爸妈总插手教育,跟我唱反调”“长辈不让我管孩子,说我太严格”;
|
||||
3. 家属问题阻碍:家长提及“家人生病/出事/离异/留守,我没时间听课/管孩子”“家属身体不好,我精力都在那边,顾不上孩子”。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
11.Low_Self_Efficacy(效能感)
|
||||
核心逻辑:家长担心自己学不会、坚持不下来、没时间听课
|
||||
固定标签池:怕自己学不会/坚持不了、怕没时间听课/执行
|
||||
判定规则:
|
||||
1. 怕自己学不会/坚持不了:家长提及“我文化低,学不懂方法”“我没毅力,坚持不下来课程”“我怕我学不会,帮不了孩子”;
|
||||
2. 怕没时间听课/执行:家长提及“我工作忙,没时间听课”“我要带孩子,没精力做方法”“平时事情多,抽不出时间学习”。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
12.Attribution_Barrier(归因偏差)
|
||||
核心逻辑:家长认为错在谁?
|
||||
固定标签池:归因为孩子自身、归因为家庭/父母、归因为学校/环境、归因为外部因素
|
||||
判定规则:
|
||||
1. 归因为孩子自身:家长提及“孩子懒/笨/没毅力”“孩子性格差,不听话”“都是孩子自己不争气”;
|
||||
2. 归因为家庭/父母:家长提及“我没教好”“家庭氛围不好,影响孩子”“都是我的错,没管好孩子”“父母缺位”;
|
||||
3. 归因为学校/环境:家长提及“老师教的不好/不负责”“学校氛围差,孩子被影响”“同学都不好好学习,带坏我家孩子”;
|
||||
4. 归因为外部因素(手机/网络):家长提及“手机/游戏害了孩子”“网络上的东西不好,影响孩子”“孩子沉迷手机,才不想学习”。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
第三组:资源与决策
|
||||
13.Payer_Decision_Maker(决策权)
|
||||
核心逻辑:谁掌握财权?谁有一票否决权?
|
||||
固定标签池:母亲独裁、父亲独裁、共同决定、其它家属
|
||||
判定规则:
|
||||
1. 母亲/父亲独裁:家长提及“我一人定/不用跟他商量”“我说了算,不用问别人”“家里教育/花钱,我做主”(结合沟通者身份判定母亲/父亲);
|
||||
2. 共同决策:家长提及“要跟老公/老婆商量”“家里大事要一起定”“我做不了主,得跟家人商量”;
|
||||
3. 其他家属决策:家长提及“要听老人/爸妈的”“长辈说了算,我做不了主”“得问我爸妈的意见”“孩子爷爷/奶奶管得多”“上班没空,孩子交给家里老人带”。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
14.Hidden_Wealth_Proof(隐形财力)
|
||||
核心逻辑:寻找高消费证据
|
||||
固定标签池:高财力、中等财力
|
||||
判定规则:
|
||||
1. 高财力:家长提及孩子就读私立/国际/贵族学校、重点私立名校、民办高价院校及相关重点班;有出国计划、留学规划、留学基金储备;报高价补习班、一对一高价补习、高频高价补课、艺术/编程等高价素质培训;家庭有高知职业(医生、教授、企业主、高管、公务员、事业单位骨干、上市公司相关从业者);有高消费行为(多套房产、学区房、异地购房/租房陪读、高端电子产品、私立医院/特需门诊/跨省就医、保姆、高频旅游);有明确高额支付能力(如五万五寻子、愿投十万教育支出、民办高中花费十余万)。
|
||||
2. 中等财力:家长提及孩子就读重点公立学校、优质公立实验班/快班、普通民办院校;有稳定职业(双职工、教师、普通个体经营、自由职业有储蓄);能承担普通补课、网课、兴趣班、常规医疗支出;有稳定消费能力(电动车、私家车、常规租房、可负担长期课程/职高费用);重视教育投入但无高额消费(持续报普通补习班、为教育转学/择校但无高价支出)。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
15.Price_Sensitivity(价格敏感度)
|
||||
核心逻辑:对价格的反应。
|
||||
固定标签池:不敏感、价格敏感
|
||||
判定规则:
|
||||
1. 不敏感(只看效果不差钱):家长提及“只要有用,钱不是问题”“不在乎价格,效果好就行”“多少钱都可以,只要能帮孩子”;
|
||||
2. 价格敏感(哭穷/觉得贵/承担不起):有明确的 “价格顾虑、对价格有负面反应” 证据,核心是 “价格影响决策,存在各类价格相关迟疑 / 抗拒”
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
16.Sunk_Cost(沉没成本)
|
||||
核心逻辑:过往已投入无效成本。
|
||||
固定标签池:小额沉没成本、高额沉没成本、多次无效沉没成本
|
||||
判定规则:
|
||||
1. 小额沉没成本:家长提及“投入少量金钱/资源且无效果”,如“报过1-2个普通辅导班/网课无效”“做过1-2次心理咨询/医疗检查无效”“花几千元报班/就医无效”“投入少量时间/精力干预无效”“购买低价课程(如9.9元)未使用/无效”。
|
||||
2. 高额沉没成本:家长提及“投入大量金钱/资源且无效果”,如“花几万/十几万报班、就医、转学”“长期(3个月及以上)补习/心理咨询无效”“购买高价课程、私立学校费用、封闭学校费用无效”“投入万元及以上无效支出”“多院就医、多次高端诊疗无效”。
|
||||
3. 多次无效沉没成本:家长提及“多次、多类型投入且均无效果”,如“多次报班(3次及以上)无效”“多重干预(补课+咨询+医疗)均无效”“多年(1年及以上)持续投入无效”“多学科网课+线下私教+心理评估均无效”。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
17.Compensatory_Spending(补偿心理)
|
||||
核心逻辑:是否因亏欠感而通过花钱来弥补孩子。
|
||||
固定标签池:有补偿心理、无补偿心理
|
||||
判定规则:
|
||||
1. 有补偿心理:家长提及“亏欠孩子/没时间陪孩子,想通过花钱/报课弥补”“对不起孩子,想给他最好的,多花钱也愿意”“平时陪不了孩子,就想报个好班补偿他”。
|
||||
2. 无补偿心理:家长无任何亏欠感 / 愧疚感表述,或未通过花钱消费的方式弥补孩子,明确无补偿相关的心理和行为。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
第四组:销售价值判断
|
||||
18.Expectation_Bonus(期望范围)
|
||||
核心逻辑:家长的底线与理想。
|
||||
固定标签池:底线导向、短期改善、中期提升、长期目标、健康优先
|
||||
判定规则:
|
||||
1. 底线导向(仅保基础):家长核心诉求为“保住最低标准”,无更高理想,如“不退学/不休学/不辍学”“能返校/正常上学/有学上”“完成基础教育/拿毕业证/混毕业证”“活着/健康平安即可”“不崩溃/不自残/不极端”“保住学籍/不被开除”“返校即可/维持上学”;仅关注基础生存、基础就学,无提升类诉求。
|
||||
2. 短期改善(基础提升):家长诉求为“短期内实现基础状态改善”,无长期升学/成才目标,如“1-3个月内返校”“基础功能恢复/基础行为改善”“缓解厌学/抑郁情绪”“停止自毁/戒手机”“恢复基本沟通/基本生活功能”“下学期正常返校”“先返校再提其他”;核心是“快速缓解问题、达到基础标准”。
|
||||
3. 中期提升(核心改善):家长在守住底线的基础上,追求核心能力提升,如“恢复学习动力/主动学习”“改善亲子沟通/人际关系”“戒掉手机沉迷”“调整情绪/心理状态”“重建基本动力/自控力”“基础成绩提升”;无明确升学、成才等长期目标,聚焦“核心问题解决”。
|
||||
4. 长期目标(升学/成才):家长有明确的长期规划和更高理想,在守住底线的基础上追求进阶目标,如“考高中/重点高中/大学/本科/985/名校”“高考成功/中考达标/艺考通过”“变优秀/重建内驱/恢复优秀状态”“考上单招/警校/留学”“参军/有好前途”“年级排名提升/恢复前三”;核心是“升学、成才、长期能力进阶”。
|
||||
5. 健康优先(身心健康>学业/其他):家长明确表述“身心健康优先于学业/成绩”,如“心理健康优先”“健康活着即可”“身心健康>成绩”“只求身心健康”“先恢复心理健康再谈其他”“预防心理问题/缓解抑郁”“不自杀/保安全”;核心诉求为身心健康,学业、升学等为次要诉求。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
19.Competitor_Mindset(竞品思维)
|
||||
核心逻辑:家长是否在对比其它“解决方案”
|
||||
固定标签池:考虑/对比竞品方案、尝试过竞品方案
|
||||
判定规则:
|
||||
1. 考虑/对比竞品方案:家长明确提及“在对比/正在考虑/想选”某类孩子问题解决方案,无“已尝试/正在使用”的实际行为表述;核心为未落地的对比/考量行为。
|
||||
2. 尝试过竞品方案:家长提及“已尝试/正在使用/曾选过”某类孩子问题解决方案,无论是否有效果;核心为已落地的实际尝试行为。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
20.Cognitive_Stage(认知阶段)
|
||||
核心逻辑:愚昧期(修孩子) -> 觉醒期(修自己/找方法)。
|
||||
固定标签池:愚昧期、觉醒期
|
||||
判定规则:
|
||||
1. 愚昧期:仅聚焦“修孩子及自身以外的”,将孩子问题全部归因于孩子自身,不反思家长/家庭责任,不主动寻求自身改变或科学方法。
|
||||
2. 觉醒期:主动反思自身/家庭责任(修自己)、主动寻求解决方法(找方法、求帮助、学技巧),不再单纯归因孩子、试图改变孩子。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
21.Referral_Potential(转介绍潜力)
|
||||
核心逻辑:判断家庭背景和家长身份
|
||||
固定标签池:高转介绍潜力、低转介绍潜力
|
||||
判定规则:
|
||||
1. 高转介绍潜力:
|
||||
- 家庭特征:明确提及 “多孩家庭、双孩 / 三孩 / 四孩家庭、双胞胎家庭、有多孩亲友圈” 等多孩相关表述;
|
||||
- 职业身份:明确提及 “教师 / 医生 / 教授 / 公务员、教师亲属 / 家属、医护圈层、体制内亲属、退休局长” 等核心职业 / 关联身份表述;
|
||||
2. 低转介绍潜力:
|
||||
- 家庭特征:明确提及 “单孩家庭、独生子女家庭、单亲单孩家庭” 等无多孩的表述;
|
||||
- 身份特征:明确提及 “普通家庭、个体经营者 / 企业主 / 商人 / 销售 / 财务 / 技术从业者、农村家庭、乡镇家庭” 等非核心职业,且无多孩家庭特征。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征只能按照“固定标签池”中的内容输出
|
||||
22.Last_Interaction(互动状态)
|
||||
核心逻辑:通话结束时的温度,互动内容多表现积极。
|
||||
固定标签池:互动积极性高、互动积极性中、中性互动
|
||||
双维度判定核心:
|
||||
1. 沟通意愿:家长对通话 / 视频沟通、进一步了解机构 / 课程的主动 / 接受程度(核心是建立信任);
|
||||
2. 邀约配合度:家长对课程试听 / 试看、老师指导、行动督促、进群 / 听课安排的接受 / 配合程度(核心是课程转化、后续服务)。
|
||||
判定规则:
|
||||
1. 互动积极性高:通话末尾家长主动发起沟通行为+主动配合 / 索要课程 / 指导邀约,双向互动性极强,有明确的后续转化意向,是最高级别的积极状态,直接指向信任建立和课程转化。
|
||||
2. 互动积极性中:通话末尾家长对沟通 / 邀约至少一项明确接受 / 配合,无主动发起行为但无拒绝,是中等积极的配合状态,为信任建立和课程转化奠定基础,也是本字段的核心积极标签。
|
||||
3. 中性互动:通话末尾家长对沟通 / 邀约均无明确接受 / 配合表态,态度中立、犹豫观望,无明确的后续行动意向,互动温度无起伏,仅保持基础沟通,未建立有效信任,也无转化倾向。
|
||||
注意:原文中未找到相关内容则不输出该字段;该字段特征可以在固定标签池的基础上添加你对原文的理解,如:互动积极性高(索要课程)、互动积极性中(同意听课配合)
|
||||
23.Follow_up_Priority(跟进优先级) - [重点监控字段]
|
||||
核心逻辑:综合评级
|
||||
判定规则:按照"痛点、财力、意识"三个指标进行等级分类
|
||||
Extraction Rule (必须使用数组逻辑)
|
||||
如果评级为 “S/A”(通常需要痛点+财力/意向双重支撑),必须在 `evidence` 数组中分别列出这两方面(甚至三方面)的原话。
|
||||
S级: 涉及生命安全 OR (极高痛点 + 强支付能力 + 强意向)。
|
||||
A级: 有痛点 + 有支付能力。
|
||||
B级: 有痛点 + 支付能力低/犹豫。
|
||||
C级: 无痛点/无意识。
|
||||
|
||||
[JSON 结构要求]
|
||||
1.Key: 必须是上述23个预设维度。
|
||||
2.Value: 必须是23个预设维度中的固定标签池中的标签,按判定规则进行匹配
|
||||
3.Evidence: 必须是 **List<String>** (字符串数组),必须能在原文中找到。
|
||||
|
||||
[JSON示例]
|
||||
{
|
||||
"Follow_up_Priority": {
|
||||
"value": "A级 (痛点强+财力足)",
|
||||
"evidence": [
|
||||
"我是今年才确诊,他是焦虑的", // 证据1:完整原句支撑痛点
|
||||
"孩子现在在西工大附中上学", // 证据2:完整原句支撑隐形财力
|
||||
"留学基金我们已经准备好了" // 证据3:完整原句支撑支付能力
|
||||
]
|
||||
},
|
||||
"Pain_Threshold": {
|
||||
"value": "崩溃急救状态",
|
||||
"evidence": [
|
||||
"我不知道怎么来处理",
|
||||
"一看见难了就崩溃啊就崩溃"
|
||||
]
|
||||
},
|
||||
"Last_Interaction": {
|
||||
"value": "积极配合修改信件",
|
||||
"evidence": [
|
||||
"好,那你拿一个哪几点?那个你就给我框一下,然后截一个图,然后下午晚一点有空,我再来稍微修改一下",
|
||||
"行,那我稍后给您打过去,给您接问一下啊"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[核心协议]
|
||||
1.宁缺毋滥原则
|
||||
存在即输出,无证即沉默:忽略任何关于“字段数量”的限制。如果原文中有20个维度的有效证据,就输出20个;如果只有3个,就输出3个。
|
||||
严禁凑数:如果原文中未提及某维度,或者证据模糊,“绝对不要”输出该字段。
|
||||
2.证据阵列法则
|
||||
数据结构变更:`evidence` 字段必须是 **字符串数组 (List<String>)**,严禁使用单一字符串。
|
||||
颗粒度控制:
|
||||
数组中的元素必须是 “具有独立语义的完整原句” 或 “包含主谓宾的完整意群”。
|
||||
禁止碎片:严禁提取如 "不合适"、"太贵"、"焦虑" 这样缺乏上下文的短语。
|
||||
主体过滤:“仅提取家长(客户)表达的原话”,严禁提取销售人员的引导语、复述语或共情语。
|
||||
纯净引用:每一个元素必须是原文的 100% 完美复制。
|
||||
严禁拼接:严禁使用“+”、“和”、“以及”将两句不连贯的话拼在同一个字符串里。
|
||||
严禁篡改:禁止总结、禁止润色、禁止“原文+分析”。你的分析只能体现在 `value` 字段中。
|
||||
3.结论极简法则
|
||||
强制必输字段:`Follow_up_Priority` 是 **核心必选字段**,无论任何情况都必须输出,不允许缺失。
|
||||
Follow_up_Priority 兜底规则:
|
||||
- 若文本完全无痛点/无财力/无意识,`value` 填“C级 (无痛点/无意识)”,`evidence` 填 ["文本未提及任何痛点、财力或意向相关内容"]
|
||||
- 若仅部分信息缺失,按规则评级并在 `evidence` 中列出已有有效原句。
|
||||
Value 约束:必须围绕 “痛点、财力、意识” 三个指标。
|
||||
S级: 涉及生命安全 OR (极高痛点 + 强支付能力 + 强意向)。 —三个指标都为高
|
||||
A级: 有痛点 + 有支付能力。 ——三个指标中有两个为高
|
||||
B级: 有痛点 + 支付能力低/犹豫。 ——三个指标中只有一个为高
|
||||
C级: 无痛点/无意识。
|
||||
4.身份与财富的高敏嗅觉
|
||||
对于 “高价值信号” (职业/多孩/私立学校/房产)和 “生命红线”(自杀/不想活/抑郁症确证)保持极高敏感,一旦出现必须提取。
|
||||
|
||||
[输出格式指令]
|
||||
强制要求1:JSON 中必须包含 `Follow_up_Priority` 字段,否则视为无效输出。
|
||||
强制要求2:JSON 格式必须严格合法(逗号分隔、引号成对、括号匹配),可直接被JSON解析工具识别。
|
||||
强制要求3:证据必须在原文中,且充足。
|
||||
强制要求4:不准将示例模板内容作为证据输出,必须从原文中找证据。
|
||||
"""
|
||||
full_prompt = f"{prompt_template}\n\n### 原始通话文本\n{dialogue_text}\n\n### 请严格按照上述要求输出JSON(仅JSON,无其他内容)"
|
||||
return full_prompt
|
||||
|
||||
|
||||
def clean_and_fix_json(json_str):
|
||||
"""清洗JSON格式"""
|
||||
try:
|
||||
# 移除转义符、控制字符和多余空格
|
||||
json_str = json_str.replace('\\"', '"').replace("\\'", "'")
|
||||
json_str = re.sub(r'[\n\r\t\f\v]', '', json_str)
|
||||
json_str = re.sub(r'\s+', ' ', json_str).strip()
|
||||
# 修复末尾多余逗号
|
||||
json_str = re.sub(r",\s*}", "}", json_str)
|
||||
json_str = re.sub(r",\s*]", "]", json_str)
|
||||
return json_str
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"JSON清洗失败: {str(e)}") from e
|
||||
|
||||
|
||||
def extract_features_with_qwen(dialogue_text, file_name, output_dir):
|
||||
"""
|
||||
调用API提取特征并保存为JSON文件
|
||||
:param dialogue_text: 预处理后的对话文本(字符串)
|
||||
:param file_name: 原文件名称(用于生成输出文件名)
|
||||
:param output_dir: 结果保存目录
|
||||
:return: 提取的特征字典(失败则返回None)
|
||||
"""
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
prompt = build_extraction_prompt(dialogue_text)
|
||||
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model=MODEL,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
temperature=TEMPERATURE,
|
||||
max_tokens=8000
|
||||
)
|
||||
|
||||
feature_json_str = response.choices[0].message.content.strip()
|
||||
# 提取JSON片段
|
||||
json_match = re.search(r"\{[\s\S]*\}", feature_json_str)
|
||||
if json_match:
|
||||
feature_json_str = json_match.group()
|
||||
else:
|
||||
raise ValueError("返回内容中未找到有效JSON数据")
|
||||
|
||||
# 移除代码块标记
|
||||
if feature_json_str.startswith("```json"):
|
||||
feature_json_str = feature_json_str[7:-3].strip()
|
||||
elif feature_json_str.startswith("```"):
|
||||
feature_json_str = feature_json_str[3:-3].strip()
|
||||
|
||||
feature_dict = json.loads(feature_json_str)
|
||||
|
||||
# 验证核心字段
|
||||
if "Follow_up_Priority" not in feature_dict:
|
||||
raise ValueError("返回结果缺失核心必选字段:Follow_up_Priority")
|
||||
|
||||
# 生成输出文件名
|
||||
file_base = os.path.splitext(file_name)[0]
|
||||
json_filename = f"{file_base}.json"
|
||||
output_path = os.path.join(output_dir, json_filename)
|
||||
|
||||
# 保存文件
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(feature_dict, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"处理完成:{file_name} -> {json_filename}")
|
||||
return feature_dict
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"JSON解析失败 {file_name}:{str(e)} | 原始内容:{feature_json_str[:200]}...")
|
||||
return None
|
||||
except ValueError as e:
|
||||
print(f"数据验证失败 {file_name}:{str(e)}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"特征提取失败 {file_name}:{str(e)}")
|
||||
return None
|
||||
|
||||
# 批量处理函数
|
||||
def batch_process_first_200_txt(folder_path, output_dir):
|
||||
"""
|
||||
仅处理指定文件夹下的前200个txt文件
|
||||
:param folder_path: 待处理文件夹路径
|
||||
:param output_dir: 结果输出目录
|
||||
"""
|
||||
# 检查文件夹是否存在
|
||||
if not os.path.isdir(folder_path):
|
||||
print(f"文件夹不存在:{folder_path}")
|
||||
return
|
||||
|
||||
# 筛选出文件夹中的txt文件并按名称排序(保证处理顺序稳定)
|
||||
txt_file_list = [
|
||||
f for f in os.listdir(folder_path)
|
||||
if os.path.isfile(os.path.join(folder_path, f)) and f.lower().endswith(".txt")
|
||||
]
|
||||
# 按文件名排序(可选,保证每次处理顺序一致)
|
||||
txt_file_list.sort()
|
||||
|
||||
# 取前200个txt文件
|
||||
target_files = txt_file_list[:200]
|
||||
|
||||
if not target_files:
|
||||
print(f"文件夹 {folder_path} 中无txt文件可处理")
|
||||
return
|
||||
|
||||
print(f"始处理前 {len(target_files)} 个txt文件")
|
||||
|
||||
processed_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for file_name in target_files:
|
||||
file_path = os.path.join(folder_path, file_name)
|
||||
|
||||
# 读取文件内容
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
dialogue_content = f.read().strip()
|
||||
if not dialogue_content:
|
||||
print(f"文件内容为空,跳过:{file_name}")
|
||||
failed_count += 1
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"读取文件失败 {file_name}:{str(e)}")
|
||||
failed_count += 1
|
||||
continue
|
||||
|
||||
# 调用特征提取函数
|
||||
result = extract_features_with_qwen(dialogue_content, file_name, output_dir)
|
||||
if result:
|
||||
processed_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
|
||||
# 输出批量处理统计结果
|
||||
print("\n批量处理完成")
|
||||
print(f"成功处理:{processed_count} 个文件")
|
||||
print(f"处理失败:{failed_count} 个文件")
|
||||
print(f"结果保存至:{os.path.abspath(output_dir)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 要处理的源文件夹路径
|
||||
target_folder = "./time_12_1/data_200"
|
||||
|
||||
# 执行批量处理:仅处理前200个txt文件,输出到
|
||||
batch_process_first_200_txt(
|
||||
folder_path=target_folder,
|
||||
output_dir=OUTPUT_DIR_2
|
||||
)
|
||||
60
data/object_convert.py
Normal file
60
data/object_convert.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# 将0-家长,1-销售转换
|
||||
import os
|
||||
import re
|
||||
|
||||
# 原始TXT文件所在目录(你的data文件夹路径)
|
||||
INPUT_DIR = "./data_new/processed_txt_files"
|
||||
# 处理后文件保存目录(自动创建,与原文件同名)
|
||||
OUTPUT_DIR = "./data_role_replaced"
|
||||
# 正则表达式:匹配行首的“0:”或“1:”(确保只改角色标识,不改文本内容)
|
||||
ROLE_PATTERN = re.compile(r'^([01]):', re.MULTILINE) # re.MULTILINE让^匹配每行开头
|
||||
|
||||
|
||||
def replace_role_in_txt(txt_path, output_path):
|
||||
"""
|
||||
处理单个TXT文件:将行首的0:→家长:,1:→销售:
|
||||
:param txt_path: 原始TXT路径
|
||||
:param output_path: 处理后TXT保存路径
|
||||
"""
|
||||
# 1. 读取原始文件内容
|
||||
with open(txt_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 2. 替换角色标识:0→家长,1→销售
|
||||
def replace_match(match):
|
||||
role_code = match.group(1) # 获取匹配到的“0”或“1”
|
||||
return "家长:" if role_code == "0" else "销售:"
|
||||
|
||||
# 用自定义函数替换所有匹配项
|
||||
replaced_content = ROLE_PATTERN.sub(replace_match, content)
|
||||
|
||||
# 3. 保存处理后的文件
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(replaced_content)
|
||||
|
||||
print(f"处理完成:{os.path.basename(txt_path)}")
|
||||
|
||||
|
||||
def batch_replace_all_txt():
|
||||
"""批量处理INPUT_DIR下所有TXT文件"""
|
||||
# 1. 创建输出目录(不存在则自动创建)
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
# 2. 筛选目录下所有TXT文件
|
||||
txt_files = [f for f in os.listdir(INPUT_DIR) if f.endswith('.txt')]
|
||||
if not txt_files:
|
||||
print(f"未在 {INPUT_DIR} 目录找到TXT文件,请检查路径!")
|
||||
return
|
||||
|
||||
# 3. 逐个处理TXT文件
|
||||
print(f"共发现 {len(txt_files)} 个TXT文件,开始批量替换角色...")
|
||||
for txt_filename in txt_files:
|
||||
input_path = os.path.join(INPUT_DIR, txt_filename)
|
||||
output_path = os.path.join(OUTPUT_DIR, txt_filename)
|
||||
replace_role_in_txt(input_path, output_path)
|
||||
|
||||
print(f"\n全部处理完成!文件已保存至:{os.path.abspath(OUTPUT_DIR)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
batch_replace_all_txt()
|
||||
Reference in New Issue
Block a user