大整理: 仓库重组 + 新词表 + 新README

- 清理仓库: 删除所有废弃的generate_*脚本和旧版数据文件
- 目录重组:
  - data/       → 训练数据
  - scripts/    → 数据生成脚本
  - templates/  → Web UI
- 重命名:
  - train_v2.py  → train.py
  - vocab_v3.json → vocab.json (BPE, 6.8k tokens)
- 更新 train.py 指向新词表路径
- 重写 README.md 完整文档
- 更新 .gitignore
This commit is contained in:
shuiyu1123 committed 2026-06-18 11:17:30 +00:00
1 parent 6e0ced4772
commit 231af72de9
26 files changed
+124 -126

No files matched your search

+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
API生成模块 — Astron 5并发,稳定不超限
"""
import json, requests, time, os, threading
from concurrent.futures import ThreadPoolExecutor, as_completed
ASTRON_URL = "https://maas-coding-api.cn-huabei-1.xf-yun.com/v2/chat/completions"
ASTRON_KEY = os.environ.get("ASTRON_KEY", "")
ASTRON_MODEL = "astron-code-latest"
SYSTEM_PROMPT = """你是一个名叫小狸的8岁中国小女孩。
你的人设:
- 8岁女孩子,有点害羞但很可爱
- 说话简单自然,符合小朋友的语气
- 回答问题要简短,1-2句话就够了
- 不要用"喵"结尾,不要过度卖萌
回答要求:
1. 语气自然,像真实的小朋友说话
2. 不懂就说"不知道"
3. 回答简洁但要说清楚,不要讲一半就停"""
def call_astron(q, temperature=0.85):
for attempt in range(3):
try:
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {ASTRON_KEY}"}
payload = {
"model": ASTRON_MODEL,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": q}
],
"temperature": temperature,
"max_tokens": 8192,
}
resp = requests.post(ASTRON_URL, headers=headers, json=payload, timeout=60)
if resp.status_code == 200:
content = resp.json()['choices'][0]['message']['content'].strip()
if content:
return content
else:
time.sleep(1)
except Exception:
time.sleep(2)
return None
def batch_generate(questions, temperature=0.85, max_workers=4):
"""Astron 4并发 — 明确限制,稳定可靠"""
total = len(questions)
results = []
def call(q):
ans = call_astron(q, temperature)
if ans:
return q, ans
return None
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(call, item): i for i, item in enumerate(questions)}
done = 0
t0 = time.time()
for f in as_completed(futures):
done += 1
r = f.result()
if r:
results.append({"question": r[0], "answer": r[1]})
if done % 20 == 0 or done == total:
elapsed = max(time.time() - t0, 1)
rate = len(results) / elapsed * 60
print(f" Astron: {done}/{total} (已获{len(results)}条, {rate:.0f}条/分)", end="\r")
elapsed = time.time() - t0
rate = len(results) / max(elapsed, 1) * 60
print(f" Astron: {total}/{total} → 共{len(results)}条 ({elapsed:.0f}s, {rate:.0f}条/分)")
return results
# 保持旧函数名兼容
batch_generate_astron = batch_generate
batch_generate_mimo = batch_generate
batch_generate_direct = batch_generate
batch_generate_dual = batch_generate
def batch_convert(items, *args, **kwargs):
"""兼容占位:训练脚本导入用,已弃用"""
print(" [API] batch_convert已弃用,使用batch_generate")
return []
def batch_generate_multi_temp(questions, *args, **kwargs):
"""兼容占位:训练脚本导入用,已弃用"""
print(" [API] batch_generate_multi_temp已弃用,使用batch_generate")
return []
+221
View File
@@ -0,0 +1,221 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
每日数据生成 — 每天12:00跑
用 Astron 生成大量小狸风格问答数据,追加到训练集(不触发训练)
"""
import sys, os, json, time, random
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)
from api_correction import batch_generate
MODEL_DIR = os.path.dirname(os.path.abspath(__file__))
MAX_WORKERS = 5 # Astron 5并发(无限量)
BATCH_SIZE = 500
# 每日生成量 — Astron无限量,直接拉满!
DAILY_TOPIC_QS = 2000 # 话题衍生
DAILY_NATURAL_QS = 1000 # 自然闲聊
# ===== 种子话题(每天随机选一批让小狸聊) =====
SEED_TOPICS = [
"幼儿园", "学校", "朋友", "玩具", "动画片", "宠物", "零食", "天气",
"节日", "生日", "爸爸妈妈", "老师", "作业", "画画", "唱歌", "跳舞",
"游乐园", "动物园", "海洋馆", "公园", "滑滑梯", "秋千", "积木", "拼图",
"自行车", "游泳", "跳绳", "踢球", "冰淇淋", "蛋糕", "水果", "蔬菜",
"彩虹", "星星", "月亮", "太阳", "花", "草", "树", "小动物",
"蝴蝶", "小鸟", "小鱼", "乌龟", "兔子", "猫", "狗", "恐龙",
"过年", "压岁钱", "红包", "新衣服", "放鞭炮", "灯笼", "元宵",
"圣诞老人", "礼物", "糖果", "巧克力", "饼干", "牛奶", "早餐",
"为什么天空是蓝的", "为什么树叶会掉", "为什么有影子", "为什么会下雨",
"为什么会有白天黑夜", "为什么海水是咸的", "为什么会打雷",
"1+1等于几", "数数", "认字", "背诗", "唱歌", "讲故事",
"礼貌", "分享", "排队", "帮助别人", "说谢谢", "对不起",
"害怕", "开心", "难过", "生气", "勇敢", "害羞",
# 拓展话题
"奥特曼", "小猪佩奇", "汪汪队", "海底小纵队", "超级飞侠",
"白雪公主", "灰姑娘", "小红帽", "三只小猪", "匹诺曹",
"茉莉花", "向日葵", "蒲公英", "含羞草", "仙人掌",
"企鹅", "熊猫", "长颈鹿", "大象", "海豚", "松鼠", "刺猬",
"草莓", "西瓜", "葡萄", "芒果", "蓝莓", "橙子",
"汉堡", "薯条", "披萨", "面条", "饺子", "馄饨", "粽子",
"沙滩", "大海", "高山", "森林", "瀑布", "溶洞",
"火箭", "飞机", "高铁", "轮船", "自行车", "滑板",
"围棋", "象棋", "跳棋", "五子棋", "扑克牌",
"机器人", "变形金刚", "乐高", "磁力片", "橡皮泥",
"春天", "夏天", "秋天", "冬天", "刮风", "下雪", "打雷",
"刷牙", "洗脸", "洗澡", "剪指甲", "梳头", "穿衣服",
"诚实", "分享", "礼貌", "排队", "帮忙", "道歉", "谢谢",
"高兴", "生气", "难过", "害怕", "惊讶", "害羞", "委屈",
"萤火虫", "蜻蜓", "蜜蜂", "蝉", "蜗牛", "蚂蚁", "蜘蛛",
"玉米", "土豆", "番茄", "胡萝卜", "白菜", "茄子", "辣椒",
"毛笔", "铅笔", "蜡笔", "水彩笔", "画板", "颜料",
"竹笋", "蘑菇", "松树", "枫叶", "荷花", "梅花",
"元旦", "春节", "元宵节", "清明节", "端午节", "中秋节",
"万圣节", "感恩节", "圣诞节", "儿童节", "母亲节", "父亲节",
"月球", "太阳系", "银河", "彗星", "流星", "黑洞",
"恐龙", "猛犸象", "剑齿虎", "翼龙", "三角龙", "霸王龙",
"三字经", "弟子规", "唐诗", "宋词", "成语",
"捉迷藏", "老鹰捉小鸡", "丢手绢", "跳房子", "踢毽子",
"轮滑", "滑冰", "滑雪", "冲浪", "攀岩",
"钢琴", "吉他", "小提琴", "笛子", "鼓", "古筝",
"警察", "消防员", "医生", "护士", "科学家", "宇航员",
"红绿灯", "斑马线", "安全带", "头盔", "救生圈",
"原来如此", "没关系", "太好了", "加油", "真棒", "别担心",
"手影", "折纸", "翻花绳", "吹泡泡", "放风筝",
"长城", "故宫", "天安门", "兵马俑", "大熊猫基地",
"小木屋", "城堡", "树屋", "帐篷", "房车",
]
def generate_questions_from_topics(topics, count=500):
"""用话题种子生成自然提问"""
questions = []
for topic in topics[:count]:
patterns = [
f"小狸,你喜欢{topic}吗?",
f"给我讲讲{topic}吧",
f"{topic}是什么呀?",
f"你最喜欢什么样的{topic}?",
f"关于{topic}你知道什么?",
f"小狸,{topic}好玩吗?",
f"能不能教我关于{topic}的事情?",
f"你上次玩{topic}是什么时候?",
f"大家为什么都喜欢{topic}呀?",
f"小狸,你在{topic}上有什么有趣的事吗?",
]
q = random.choice(patterns)
questions.append(q)
return questions
def generate_natural_questions(count=200):
"""生成自然闲聊问题(固定一批高质量问题)"""
base_questions = [
"小狸你好呀", "今天开心吗", "你几岁了", "你叫什么名字",
"你吃饭了吗", "我们一起玩吧", "你在干嘛呢", "你今天做了什么",
"你喜欢什么颜色", "你最好的朋友是谁", "你最喜欢什么动物",
"你最喜欢吃什么", "你最喜欢什么水果", "你最爱看什么动画片",
"可以给我讲个故事吗", "唱首歌给我听", "教我背一首诗吧",
"你会画画吗", "我们来玩猜谜语", "你家里有谁呀",
"你的房间是什么样子的", "你书包里有什么", "你最喜欢上什么课",
"今天天气真好", "外面下雨了", "好冷啊", "好热啊",
"我有点不开心", "我今天好开心", "我害怕打雷",
"你睡午觉了吗", "晚安", "早安", "快来",
"我想吃冰淇淋", "我想去游乐场", "我想养一只小猫",
"为什么雪是白色的", "为什么天是蓝的", "为什么花是香的",
"月亮上有人吗", "星星为什么会眨眼睛", "大海有多大",
"你会数数吗", "1+1等于几", "100+200等于多少",
"我喜欢你小狸", "你好可爱", "我们做朋友吧",
"你今天真好看", "谢谢你", "你真好",
"对不起", "我不小心摔倒了", "我被人欺负了",
"怎么办呀", "帮帮我", "我该怎么做",
"什么是爱", "什么是勇敢", "什么是善良",
"长大是什么感觉", "为什么要上学", "为什么要听话",
"你会一直陪着我吗", "你想去什么地方玩",
]
return random.sample(base_questions, min(count, len(base_questions)))
def main():
print("=" * 60)
print("📅 每日数据生成")
print(f"时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60)
# 1. 混合生成问题
random.seed(int(time.time()))
n_topic_qs = DAILY_TOPIC_QS # 话题衍生
n_natural_qs = DAILY_NATURAL_QS # 自然闲聊
random.shuffle(SEED_TOPICS)
topic_questions = generate_questions_from_topics(SEED_TOPICS, n_topic_qs)
natural_questions = generate_natural_questions(n_natural_qs)
all_questions = topic_questions + natural_questions
random.shuffle(all_questions)
print(f"\n准备生成 {len(all_questions)} 条问答 (话题衍生: {len(topic_questions)}, 自然: {len(natural_questions)})")
# 2. 用不同温度分批生成,增加多样性
TEMPS = [0.85, 0.9, 0.95, 1.0]
all_results = []
existing = set()
# 加载已有数据用于去重
train_path = os.path.join(MODEL_DIR, 'xiaoli_train_v2.json')
with open(train_path, encoding='utf-8') as f:
cur_train = json.load(f)
for it in cur_train:
key = (it.get('question','')[:100], it.get('answer','')[:100])
existing.add(key)
print(f"已有训练集: {len(cur_train)} 条")
# 分批+变温度
total_batches = (len(all_questions) + BATCH_SIZE - 1) // BATCH_SIZE
t_start = time.time()
for batch_idx in range(total_batches):
batch_qs = all_questions[batch_idx * BATCH_SIZE : (batch_idx + 1) * BATCH_SIZE]
temp = random.choice(TEMPS)
print(f"\n--- 批次 {batch_idx + 1}/{total_batches} ({len(batch_qs)}条, temp={temp}) ---")
t0 = time.time()
results = batch_generate(batch_qs, temperature=temp, max_workers=MAX_WORKERS)
elapsed = time.time() - t0
new_in_batch = 0
for r in results:
key = (r['question'][:100], r['answer'][:100])
if key not in existing:
existing.add(key)
all_results.append(r)
new_in_batch += 1
total_elapsed = time.time() - t_start
rate = len(all_results) / max(total_elapsed, 1) * 60
print(f" 本批新增 {new_in_batch} 条 | 累计 {len(all_results)} 条 | {rate:.0f}条/分")
if not all_results:
print("\n⚠️ 没有生成新数据")
return
# 3. 追加到训练集
print(f"\n追加 {len(all_results)} 条到训练集...")
for tgt in ['xiaoli_train_v2.json', 'xiaoli_merged_data_v2.json']:
tp = os.path.join(MODEL_DIR, tgt)
with open(tp, encoding='utf-8') as f:
cur = json.load(f)
ex = set((it.get('question','')[:100], it.get('answer','')[:100]) for it in cur)
to_add = []
for r in all_results:
key = (r['question'][:100], r['answer'][:100])
if key not in ex:
ex.add(key)
to_add.append({'question': r['question'], 'answer': r['answer']})
cur.extend(to_add)
with open(tp, 'w', encoding='utf-8') as f:
json.dump(cur, f, ensure_ascii=False, indent=2)
if 'train' in tgt:
print(f" ✅ 训练集 +{len(to_add)} 条 → {len(cur)} 条")
else:
print(f" ✅ 合并集 +{len(to_add)} 条 → {len(cur)} 条")
# 4. 保存今日生成日志
today = time.strftime('%Y-%m-%d')
log_path = os.path.join(MODEL_DIR, f'daily_gen_{today}.json')
with open(log_path, 'w', encoding='utf-8') as f:
json.dump(all_results, f, ensure_ascii=False, indent=2)
elapsed_total = time.time() - t_start
print(f"\n{'='*60}")
print(f"✅ 今日生成完成! {len(all_results)} 条新数据")
print(f"用时: {elapsed_total/60:.1f} 分钟")
print(f"日志: daily_gen_{today}.json")
print(f"{'='*60}")
if __name__ == '__main__':
main()
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
单批生成 — 从mega_questions.json取指定batch生成并追加
用法: python3 generate_batch.py <batch_num> <batch_size>
"""
import sys, os, json, time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)
from api_correction import batch_generate
model_dir = os.path.dirname(os.path.abspath(__file__))
batch_num = int(sys.argv[1])
batch_size = int(sys.argv[2])
# 加载问题列表
with open(os.path.join(model_dir, 'mega_questions.json'), 'r', encoding='utf-8') as f:
mega = json.load(f)
# 计算本批范围
start = (batch_num - 1) * batch_size
end = min(start + batch_size, len(mega))
chunk = [item['question'] for item in mega[start:end]]
if not chunk:
print(f"批{batch_num}: 无数据")
sys.exit(0)
print(f"批 {batch_num}: {len(chunk)}题 (序号{start}-{end})")
t0 = time.time()
results = batch_generate(chunk, temperature=0.85)
dt = time.time() - t0
# 保存中间文件
interim_path = os.path.join(model_dir, f'mega_interim_{batch_num}.json')
with open(interim_path, 'w', encoding='utf-8') as f:
json.dump(results, f, ensure_ascii=False, indent=2)
# 追加到训练集
for tgt in ['xiaoli_train_v2.json', 'xiaoli_merged_data_v2.json']:
tgt_path = os.path.join(model_dir, tgt)
if not os.path.exists(tgt_path):
continue
with open(tgt_path, 'r', encoding='utf-8') as f:
cur = json.load(f)
existing = set((item.get("question",""), item.get("answer","")) for item in cur)
truly_new = [item for item in results if (item["question"], item["answer"]) not in existing]
if truly_new:
cur.extend(truly_new)
with open(tgt_path, 'w', encoding='utf-8') as f:
json.dump(cur, f, ensure_ascii=False, indent=2)
if tgt == 'xiaoli_train_v2.json':
print(f" ✅ 追加训练集: +{len(truly_new)}条, 现有{len(cur)}条")
print(f"批{batch_num}完成: +{len(results)}条 ({dt:.0f}s)")
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""高温度多样化生成 — 每个问题跑 N 次,获得多种回答"""
import sys, os, json, random, time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)
from api_correction import batch_generate_astron as batch_generate
MODEL_DIR = os.path.dirname(os.path.abspath(__file__))
# 载入全部问题
with open(os.path.join(MODEL_DIR, 'mega_questions.json')) as f:
mega = json.load(f)
total_q = len(mega)
print(f"mega_questions 共 {total_q} 条")
# 温度配置:逐渐升高,产出多样化
TEMP_CONFIG = [0.9, 1.1, 1.3, 1.5] # 跑4轮
all_new_results = []
for round_idx, temp in enumerate(TEMP_CONFIG):
print(f"\n{'='*60}")
print(f"第{round_idx+1}轮: 温度={temp}")
print(f"{'='*60}")
questions = [item['question'] for item in mega]
t0 = time.time()
results = batch_generate(questions, temperature=temp)
elapsed = time.time() - t0
# 标注来源
for r in results:
r['temperature'] = temp
r['round'] = round_idx + 1
all_new_results.extend(results)
print(f"轮{round_idx+1} 完成: {len(results)}条 ({elapsed:.0f}s)")
print(f"累计: {len(all_new_results)}条")
# 去重(问题+回答完全重复才去)
print(f"\n总体去重中...")
seen = set()
unique_results = []
for r in all_new_results:
key = (r['question'][:100], r['answer'][:100])
if key not in seen:
seen.add(key)
unique_results.append(r)
print(f"去重前: {len(all_new_results)}条 → 去重后: {len(unique_results)}条")
# 追加到训练集
for tgt in ['xiaoli_train_v2.json', 'xiaoli_merged_data_v2.json']:
tp = os.path.join(MODEL_DIR, tgt)
with open(tp, encoding='utf-8') as f:
cur = json.load(f)
ex = set((it.get("question","")[:100], it.get("answer","")[:100]) for it in cur)
to_add = []
for r in unique_results:
key = (r['question'][:100], r['answer'][:100])
if key not in ex:
ex.add(key)
to_add.append({"question": r['question'], "answer": r['answer']})
cur.extend(to_add)
with open(tp, 'w', encoding='utf-8') as f:
json.dump(cur, f, ensure_ascii=False, indent=2)
if 'train' in tgt:
print(f"✅ 训练集 +{len(to_add)}条 → {len(cur)}条")
print(f"\n🎉 全部完成!")
print(f"总数: {len(cur)}条")
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""
高温度多样化生成 — 单线程,与常规跑批共用 Astron 5并发中的1个
对已完成批1-6的问题,用高温度重新生成,获得多样回答
"""
import sys, os, json, random, time, requests
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)
ASTRON_URL = "https://maas-coding-api.cn-huabei-1.xf-yun.com/v2/chat/completions"
ASTRON_KEY = os.environ.get("ASTRON_KEY", "")
ASTRON_MODEL = "astron-code-latest"
MODEL_DIR = os.path.dirname(os.path.abspath(__file__))
SYSTEM_PROMPT = """你是一个名叫小狸的8岁中国小女孩。
你的人设:
- 8岁女孩子,有点害羞但很可爱
- 说话简单自然,符合小朋友的语气
- 回答问题要简短,1-2句话就够了
- 不要用"喵"结尾,不要过度卖萌
回答要求:
1. 语气自然,像真实的小朋友说话
2. 不懂就说"不知道"
3. 回答简洁但要说清楚,不要讲一半就停"""
def call_astron(q, temperature):
for attempt in range(3):
try:
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {ASTRON_KEY}"}
payload = {
"model": ASTRON_MODEL,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": q}
],
"temperature": temperature,
"max_tokens": 8192,
}
resp = requests.post(ASTRON_URL, headers=headers, json=payload, timeout=60)
if resp.status_code == 200:
content = resp.json()['choices'][0]['message']['content'].strip()
if content:
return content
else:
time.sleep(1)
except Exception:
time.sleep(2)
return None
def main():
# 从已完成批载入问题
all_questions = []
for i in range(1, 7):
fp = os.path.join(MODEL_DIR, f'mega_interim_{i}.json')
if os.path.exists(fp):
with open(fp) as f:
items = json.load(f)
for item in items:
all_questions.append(item['question'])
print(f"批{i}: {len(items)}条已载入")
# 去重
all_questions = list(dict.fromkeys(all_questions))
print(f"共 {len(all_questions)} 个去重问题")
# 温度配置
temp_configs = [0.9, 1.3, 1.5]
all_new = []
for round_idx, temp in enumerate(temp_configs):
print(f"\n{'='*50}")
print(f"轮{round_idx+1}/3: 温度={temp}")
print(f"{'='*50}")
# 单线程顺序跑
results = []
total = len(all_questions)
t0 = time.time()
for idx, q in enumerate(all_questions):
ans = call_astron(q, temp)
if ans:
results.append({"question": q, "answer": ans})
if (idx + 1) % 20 == 0 or idx + 1 == total:
elapsed = max(time.time() - t0, 1)
rate = len(results) / elapsed * 60
eta = (total - idx - 1) / max(idx + 1, 1) * elapsed / 60
print(f" 多样: {idx+1}/{total} (已获{len(results)}条, {rate:.0f}条/分, ETA:{eta:.0f}分)", end="\r")
elapsed = time.time() - t0
rate = len(results) / max(elapsed, 1) * 60
print(f"\n 轮{round_idx+1}完成: {len(results)}条 ({elapsed:.0f}s, {rate:.0f}条/分)")
for r in results:
r['temperature'] = temp
r['round'] = round_idx + 1
all_new.extend(results)
# ✅ 每轮跑完立即去重并追加到训练集
seen = set()
unique = []
for r in all_new:
key = (r['question'][:100], r['answer'][:100])
if key not in seen:
seen.add(key)
unique.append(r)
for tgt in ['xiaoli_train_v2.json', 'xiaoli_merged_data_v2.json']:
tp = os.path.join(MODEL_DIR, tgt)
with open(tp, encoding='utf-8') as f:
cur = json.load(f)
ex = set((it.get("question","")[:100], it.get("answer","")[:100]) for it in cur)
to_add = []
for r in unique:
key = (r['question'][:100], r['answer'][:100])
if key not in ex:
ex.add(key)
to_add.append({"question": r['question'], "answer": r['answer']})
cur.extend(to_add)
with open(tp, 'w', encoding='utf-8') as f:
json.dump(cur, f, ensure_ascii=False, indent=2)
if 'train' in tgt:
print(f"✅ 轮{round_idx+1} 训练集 +{len(to_add)}条 → {len(cur)}条")
if __name__ == '__main__':
main()
+240
View File
@@ -0,0 +1,240 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
全新问答对生成 — Astron 凭空造(新问题+新回答),5并发
不依赖现有问题列表,从种子话题衍生
增量保存防丢
"""
import json, requests, time, os, sys, random, hashlib
from concurrent.futures import ThreadPoolExecutor, as_completed
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)
# ====== Astron 配置 ======
ASTRON_URL = "https://maas-coding-api.cn-huabei-1.xf-yun.com/v2/chat/completions"
ASTRON_KEY = os.environ.get("ASTRON_KEY", "")
ASTRON_MODEL = "astron-code-latest"
SYSTEM_PROMPT = """你是一个名叫小狸的8岁中国小女孩。
你的人设:
- 8岁女孩子,有点害羞但很可爱
- 说话简单自然,符合小朋友的语气
- 回答问题要简短,1-2句话就够了
- 不要用"喵"结尾,不要过度卖萌
回答要求:
1. 语气自然,像真实的小朋友说话
2. 不懂就说"不知道"
3. 回答简洁但要说清楚"""
# ====== 种子场景(生成对话的起点) ======
SCENES = [
# 日常场景
("问候", ["打招呼", "问好", "早上起来", "放学回家", "见到朋友"]),
("情绪", ["开心", "难过", "生气", "无聊", "害怕", "惊喜", "委屈", "害羞"]),
("分享", ["今天发生的事", "周末做了什么", "假期计划", "新玩具", "考试成绩"]),
("喜好", ["喜欢的颜色", "喜欢的动物", "喜欢的食物", "喜欢的动画片", "喜欢的游戏"]),
("好奇", ["为什么问题", "想知道的事", "不懂的事", "想象的事", "奇怪的问题"]),
("日常", ["起床", "刷牙", "吃饭", "上学", "做作业", "睡觉"]),
("朋友", ["好朋友", "吵架", "一起玩", "分享秘密", "帮忙"]),
("家庭", ["爸爸妈妈", "爷爷奶奶", "兄弟姐妹", "家人一起", "家庭活动"]),
("想象", ["童话故事", "梦想", "如果我有魔法", "长大想做什么", "幻想朋友"]),
("安慰", ["受伤了", "丢东西", "被骂了", "考试没考好", "被欺负", "做噩梦"]),
("节日", ["生日", "过年", "圣诞节", "儿童节", "中秋"]),
("自然", ["天气", "动物", "植物", "星空", "大海", "四季"]),
("学校", ["老师", "同学", "上课", "课间", "课外活动", "考试"]),
("晚安", ["说晚安", "睡前", "梦", "明天见", "晚安故事"]),
]
def call_astron(prompt, temperature=0.9):
for attempt in range(3):
try:
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {ASTRON_KEY}"}
payload = {
"model": ASTRON_MODEL,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": prompt}
],
"temperature": temperature,
"max_tokens": 4096,
}
resp = requests.post(ASTRON_URL, headers=headers, json=payload, timeout=60)
if resp.status_code == 200:
content = resp.json()['choices'][0]['message']['content'].strip()
if content and len(content) > 10:
return content
else:
time.sleep(1)
except Exception:
time.sleep(2)
return None
def parse_qa_pairs(text):
"""解析 Astron 返回的问答对列表"""
lines = text.strip().split('\n')
pairs = []
current_q = None
for line in lines:
line = line.strip()
if not line:
continue
q_match = __import__('re').match(r'^[Q问][::]\s*(.*)', line)
a_match = __import__('re').match(r'^[A答][::]\s*(.*)', line)
if q_match:
current_q = q_match.group(1).strip()
elif a_match and current_q:
a_text = a_match.group(1).strip()
if current_q and a_text and len(a_text) > 2 and len(current_q) > 2:
pairs.append((current_q, a_text))
current_q = None
return pairs
def pair_fingerprint(q, a):
"""QA对指纹"""
raw = f"{q[:40]}|{a[:40]}"
return hashlib.md5(raw.encode()).hexdigest()
def generate_batch_pairs(batch_size=10, existing_fps=None):
"""生成一批全新的问答对"""
if existing_fps is None:
existing_fps = set()
# 随机选场景
category, sub_scenes = random.choice(SCENES)
sub = random.choice(sub_scenes)
# 随机生成要求
style = random.choice(["日常聊天", "撒娇可爱", "有点害羞", "好奇宝宝", "暖心安慰"])
mood = random.choice(["开心", "平静", "有点小情绪", "好奇", "温暖"])
prompt = f"""请以小狸的身份,生成{batch_size}个不同的日常对话问答对。
对话背景:
- 场景:{category}({sub})
- 风格:{style}
- 小狸心情:{mood}
要求:
1. 每个问答对包含一问一答,问是用户说的,答是小狸回答
2. 问要自然,像真实用户会说的话(不要提"小狸")
3. 答要是小狸的风格,1-2句话,简短自然
4. {batch_size}个问答要对同一个话题但角度不同
5. 不要重复一模一样的问法
请按以下格式输出:
Q:用户提问内容
A:小狸回答内容
---(分隔)
Q:用户提问内容
A:小狸回答内容
---"""
result = call_astron(prompt, temperature=random.uniform(0.85, 1.0))
if not result:
return []
pairs = parse_qa_pairs(result)
# 去重
deduped = []
for q, a in pairs:
fp = pair_fingerprint(q, a)
if fp not in existing_fps:
existing_fps.add(fp)
deduped.append({'question': q, 'answer': a})
return deduped
def main():
output_dir = '/workspace/xiaoli-llm-chat/fresh_qa_outputs'
os.makedirs(output_dir, exist_ok=True)
# ====== 恢复进度 ======
progress_file = os.path.join(output_dir, 'fresh_qa_progress.json')
all_pairs = []
if os.path.exists(progress_file):
with open(progress_file, encoding='utf-8') as f:
all_pairs = json.load(f)
print(f"已有进度: {len(all_pairs)} 条")
# 加载已有训练集做去重
train_path = '/workspace/xiaoli-llm-chat/xiaoli_train_v2.json'
with open(train_path, encoding='utf-8') as f:
train_data = json.load(f)
existing_fps = set(pair_fingerprint(item.get('question',''), item.get('answer','')) for item in train_data)
for p in all_pairs:
existing_fps.add(pair_fingerprint(p['question'], p['answer']))
print(f"去重指纹池: {len(existing_fps)} 个")
# ====== 参数 ======
TARGET = 500000 # 目标 50 万条全新问答
BATCH_SIZE = 15 # 每批次生成 15 条(更多问题,效率更高)
BATCHES_PER_SAVE = 20 # 每 20 批次保存一次
MAX_WORKERS = 5 # Astron 5并发
remaining = TARGET - len(all_pairs)
total_iterations = (remaining + BATCH_SIZE - 1) // BATCH_SIZE
print(f"目标: {TARGET:,} 条, 还需: {remaining:,} 条")
print(f"轮次: {total_iterations} 轮 (每轮{BATCH_SIZE}条, {MAX_WORKERS}并发)")
print(f"{'='*60}\n")
t_start = time.time()
batch_counter = 0
for iteration in range(total_iterations):
t0 = time.time()
# 并发生成
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = [
executor.submit(generate_batch_pairs, BATCH_SIZE, existing_fps)
for _ in range(MAX_WORKERS)
]
for future in as_completed(futures):
try:
new_pairs = future.result()
if new_pairs:
for p in new_pairs:
existing_fps.add(pair_fingerprint(p['question'], p['answer']))
all_pairs.append(p)
except Exception:
pass
batch_counter += 1
elapsed = time.time() - t_start
rate = len(all_pairs) / max(elapsed, 1) * 60
# 定期保存
if batch_counter % BATCHES_PER_SAVE == 0 or iteration == total_iterations - 1:
with open(progress_file, 'w', encoding='utf-8') as f:
json.dump(all_pairs, f, ensure_ascii=False, indent=2)
eta = (TARGET - len(all_pairs)) / max(rate, 0.1) / 60 if rate > 0 else 0
if iteration % 5 == 0 or iteration == total_iterations - 1:
print(f"轮 {iteration+1}/{total_iterations} | "
f"累计 {len(all_pairs):,}/{TARGET:,} | "
f"{rate:.0f}条/分 | "
f"已用 {elapsed/60:.1f}分 | "
f"预计剩余 {eta:.1f}分")
# ====== 完成 ======
elapsed_total = time.time() - t_start
print(f"\n{'='*60}")
print(f"全新问答生成完成!")
print(f"总数: {len(all_pairs):,} 条")
print(f"用时: {elapsed_total/60:.1f} 分钟")
print(f"速率: {len(all_pairs)/max(elapsed_total,1)*60:.0f} 条/分")
# 保存最终
final_path = '/data/user/work/fresh_qa_v1.json'
with open(final_path, 'w', encoding='utf-8') as f:
json.dump(all_pairs, f, ensure_ascii=False, indent=2)
print(f"保存到: {final_path}")
if __name__ == '__main__':
main()
+346
View File
@@ -0,0 +1,346 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
多轮对话生成 — 用 Astron 造 2~5 轮小狸闲聊对话
5并发,无限量,增量保存防丢
"""
import json, requests, time, os, sys, random, re
from concurrent.futures import ThreadPoolExecutor, as_completed
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)
# ====== Astron 配置(和 api_correction 一致) ======
ASTRON_URL = "https://maas-coding-api.cn-huabei-1.xf-yun.com/v2/chat/completions"
ASTRON_KEY = os.environ.get("ASTRON_KEY", "")
ASTRON_MODEL = "astron-code-latest"
# ====== 小狸基础提示词 ======
SYSTEM_PROMPT = """你是一个名叫小狸的8岁中国小女孩。
你的人设:
- 8岁女孩子,有点害羞但很可爱
- 说话简单自然,符合小朋友的语气
- 回答问题要简短,1-2句话就够了
- 不要用"喵"结尾,不要过度卖萌
回答要求:
1. 语气自然,像真实的小朋友说话
2. 不懂就说"不知道"
3. 回答简洁但要说清楚"""
# ====== 多轮对话种子(开场话题池) ======
SEEDS = [
# 日常问候
"你好呀", "你叫什么名字", "你今天开心吗", "你在干嘛呢",
"你好可爱呀", "我来看你啦", "你今天心情怎么样",
# 闲聊话题
"你喜欢什么颜色", "你最喜欢什么动物", "你爱吃什么零食",
"你最喜欢什么玩具", "你最喜欢什么动画片",
"你最好的朋友是谁", "幼儿园里好玩吗",
"你喜欢唱歌吗", "你会跳舞吗",
# 情绪表达
"我今天好开心", "我有点不开心", "我好无聊啊",
"我遇到了一件好事", "我今天有点难过",
"我好想你呀", "你能不能陪我聊聊天",
# 好奇问题
"为什么天是蓝的", "为什么月亮跟着我走",
"世界上最大的动物是什么", "你会讲故事吗",
"你会数数吗", "1+1等于几",
"你喜欢上学吗", "你今年几岁啦",
# 分享日常
"我今天去公园玩了", "我养了一只小猫",
"我今天考试考了100分", "我学会骑自行车了",
"妈妈给我买了个新玩具", "我今天摔了一跤",
# 晚安/告别
"我要睡觉了", "明天见", "晚安啦",
"我要去吃饭了", "下次再聊哦",
]
# 话题扩展 — 组合出更多种子
PREFIXES = ["小狸", "小狸小狸", "亲爱的小狸", "嘿嘿小狸", "小狸呀"]
SUFFIXES = ["呀", "吗", "呢", "", "呀", "吗", "呢", ""]
def expand_seeds(base, max_expand=5):
"""种子话题 → 多样化的开场白"""
expanded = []
for s in base:
expanded.append(s)
if '吗' not in s and not s.endswith('呀') and not s.endswith('呢'):
for _ in range(2):
prefix = random.choice(PREFIXES) if random.random() > 0.4 else ''
suffix = random.choice(SUFFIXES) if random.random() > 0.5 else ''
combo = f"{prefix}{s}{suffix}".strip()
if combo and combo not in expanded:
expanded.append(combo)
random.shuffle(expanded)
return expanded[:len(base) * max_expand]
def call_astron_multi(prompt, temperature=0.85):
"""调用 Astron 生成一条多轮对话(2~5轮)"""
for attempt in range(3):
try:
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {ASTRON_KEY}"}
payload = {
"model": ASTRON_MODEL,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": prompt}
],
"temperature": temperature,
"max_tokens": 8192,
}
resp = requests.post(ASTRON_URL, headers=headers, json=payload, timeout=60)
if resp.status_code == 200:
content = resp.json()['choices'][0]['message']['content'].strip()
if content and len(content) > 10:
return content
else:
time.sleep(1)
except Exception:
time.sleep(2)
return None
def parse_multi_turn(text):
"""
解析 Astron 返回的多轮对话文本为 [(q, a), (q, a), ...]
期望返回格式(至少2轮对话):
问:xxx
答:xxx
问:xxx
答:xxx
或
用户:xxx
小狸:xxx
用户:xxx
小狸:xxx
"""
lines = text.strip().split('\n')
turns = []
current_q = None
for line in lines:
line = line.strip()
if not line:
continue
# 匹配 "问:" 或 "问题:" 或 "用户:" 格式
q_match = re.match(r'^(问|问题|用户|我)[::]\s*(.*)', line)
# 匹配 "答:" 或 "回答:" 或 "小狸:" 格式
a_match = re.match(r'^(答|回答|小狸|小狸说|机器人)[::]\s*(.*)', line)
if q_match:
q_text = q_match.group(2).strip()
if q_text and len(q_text) > 1:
current_q = q_text
elif a_match and current_q:
a_text = a_match.group(2).strip()
if a_text and len(a_text) > 1 and len(a_text) < 200:
turns.append((current_q, a_text))
current_q = None # 配对后重置,等待下一个问
# 最少4轮才算有效(6~12轮的长对话)
if len(turns) >= 4:
return turns
return None
def format_training_sample(turns):
"""
将多轮对话 [('q1','a1'), ('q2','a2'), ...] 格式化为训练样本
格式:<SOS>q1<SEP><START>a1<END>q2<SEP><START>a2<END><EOS>
"""
parts = []
for i, (q, a) in enumerate(turns):
if i == 0:
parts.append(f"<SOS>{q}<SEP><START>{a}<END>")
else:
parts.append(f"{q}<SEP><START>{a}<END>")
parts.append("<EOS>")
return "".join(parts)
def make_multi_prompt():
"""
构造多轮对话生成提示词
Astron 每次生成一条完整的4~6轮对话
"""
# 随机选开场话题(2~3个组合,让对话有展开)
topic_count = random.randint(1, 3)
topics = random.sample(all_seeds, min(topic_count, len(all_seeds)))
topic_context = "、".join(topics)
turn_count = random.randint(6, 12)
prompt = f"""请以小狸的身份,模拟一段长时间的自然闲聊对话,至少{turn_count}轮。
开场话题:{topic_context}
要求:
1. 小狸是8岁小女孩,说话简短自然
2. 每轮包含"问"和"答",小狸的回答1-2句话
3. 对话要自然展开,一问一答慢慢深入,不要强行切话题
4. 小狸的语气:害羞但可爱,不是机器人
5. 至少{turn_count}轮,越多越好,让对话持续下去
请按以下格式输出(至少{turn_count}轮,轮数越多越好):
问:(用户提问)
答:(小狸回答)
问:(用户续问)
答:(小狸回答)
..."""
return prompt
def generate_one_multi():
"""生成一条多轮对话"""
prompt = make_multi_prompt()
result = call_astron_multi(prompt, temperature=random.uniform(0.8, 1.0))
if result:
turns = parse_multi_turn(result)
if turns and len(turns) >= 4:
sample = format_training_sample(turns)
return {
'source': 'multiturn_astron',
'turns': len(turns),
'sample': sample,
'turns_list': turns,
'raw': result[:100]
}
return None
def main():
global all_seeds
model_dir = os.path.dirname(os.path.abspath(__file__))
# 加载现有训练数据的问句作为种子
train_path = os.path.join(model_dir, 'xiaoli_train_v2.json')
with open(train_path, encoding='utf-8') as f:
train_data = json.load(f)
# 从现有数据中随机抽取问句作为种子
existing_qs = [item.get('question','') for item in train_data if item.get('question','')]
random.shuffle(existing_qs)
seed_from_data = existing_qs[:500]
# 合并种子话题
all_seeds = expand_seeds(SEEDS, max_expand=10) + seed_from_data
random.shuffle(all_seeds)
print(f"种子话题: {len(all_seeds)} 个 (基础{len(SEEDS)}+数据抽取{len(seed_from_data)})")
# 输出目录
out_dir = os.path.join(model_dir, 'multiturn_outputs')
os.makedirs(out_dir, exist_ok=True)
# 检查已有进度
all_results = []
interim_files = sorted([f for f in os.listdir(out_dir)
if f.startswith('mt_batch_') and f.endswith('.json')])
if interim_files:
for fname in interim_files:
with open(os.path.join(out_dir, fname), encoding='utf-8') as f:
data = json.load(f)
all_results.extend(data)
print(f"已有中间结果: {len(all_results)} 条 ({len(interim_files)} 批)")
# ====== 参数 ======
TARGET = 500000 # 目标 50 万条长多轮对话
BATCH = 20 # 每批生成 20 条(每条更长,减少批量)
MAX_WORKERS = 5 # Astron 5并发
TEMPERATURE_RANGE = (0.80, 1.0)
to_generate = TARGET - len(all_results)
total_batches = (to_generate + BATCH - 1) // BATCH
print(f"目标: {TARGET} 条, 已有: {len(all_results)} 条, 还需: {to_generate} 条")
print(f"批次: {total_batches} 批 (每批{BATCH}条, {MAX_WORKERS}并发)")
print(f"{'='*60}\n")
t_start = time.time()
for batch_idx in range(total_batches):
t0 = time.time()
# 并发生成一批
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = [executor.submit(generate_one_multi) for _ in range(BATCH)]
batch_results = []
for future in as_completed(futures):
try:
r = future.result()
if r:
batch_results.append(r)
except Exception:
pass
# 去重(按 sample 内容)
existing_samples = set(r['sample'] for r in all_results)
deduped = []
for r in batch_results:
if r['sample'] not in existing_samples:
existing_samples.add(r['sample'])
deduped.append(r)
all_results.extend(deduped)
# 保存本批
elapsed = time.time() - t0
batch_file = os.path.join(out_dir, f'mt_batch_{batch_idx + 1:04d}.json')
with open(batch_file, 'w', encoding='utf-8') as f:
json.dump(deduped, f, ensure_ascii=False, indent=2)
# 追加保存总进度
progress_file = os.path.join(out_dir, 'all_multiturn_progress.json')
with open(progress_file, 'w', encoding='utf-8') as f:
json.dump(all_results, f, ensure_ascii=False, indent=2)
total_elapsed = time.time() - t_start
rate = len(all_results) / max(total_elapsed, 1) * 60
eta = (TARGET - len(all_results)) / max(rate, 0.1) / 60 if rate > 0 else 0
# 输出统计
avg_turns = sum(r['turns'] for r in deduped) / max(len(deduped), 1)
print(f"批次 {batch_idx+1}/{total_batches} | "
f"+{len(deduped)} 条 (去重后) | "
f"累计 {len(all_results)}/{TARGET} | "
f"均轮 {avg_turns:.1f} | "
f"{rate:.0f}条/分 | "
f"预计剩余 {eta:.1f}分")
# ====== 完成总结 ======
total_elapsed = time.time() - t_start
print(f"\n{'='*60}")
print(f"多轮对话生成完成!")
print(f"生成: {len(all_results)} 条 (去重后)")
avg_turns2 = sum(r['turns'] for r in all_results) / max(len(all_results), 1)
print(f"平均轮数: {avg_turns2:.1f}")
print(f"总对话轮次: {sum(r['turns'] for r in all_results)}")
print(f"用时: {total_elapsed/60:.1f} 分钟")
print(f"速率: {len(all_results)/max(total_elapsed,1)*60:.0f} 条/分")
# ====== 保存最终结果(独立存放,不追加到训练集) ======
final_file = os.path.join('/data/user/work', 'multiturn_final.json')
with open(final_file, 'w', encoding='utf-8') as f:
json.dump(all_results, f, ensure_ascii=False, indent=2)
print(f"\n最终结果已保存: {final_file} ({len(all_results)} 条)")
print(f"如需后续使用,可手动合并到训练集")
# ====== 保存多轮样本供训练时直接使用(含标记格式) ======
mt_samples_file = os.path.join(model_dir, 'multiturn_samples_v1.json')
with open(mt_samples_file, 'w', encoding='utf-8') as f:
samples = [{'turns': r['turns'], 'sample': r['sample']} for r in all_results]
json.dump(samples, f, ensure_ascii=False, indent=2)
print(f" ✅ 多轮样本保存: multiturn_samples_v1.json ({len(samples)} 条)")
print(f"\n{'='*60}")
print(f"全部完成! 新增 {len(all_results)} 条多轮对话")
print(f"{'='*60}")
if __name__ == '__main__':
main()
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
高温多样化生成 — 把训练集全部问题用 Astron 高温度重答一次
温度=1.2,追求多样化回答,丰富训练数据
"""
import sys, os, json, time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)
from api_correction import batch_generate
MODEL_DIR = os.path.dirname(os.path.abspath(__file__))
TEMPERATURE = 1.0 # 高温度(上限1.0),产出多样化回答
BATCH_SIZE = 500 # 每批 500 条,分批保存防丢
MAX_WORKERS = 5 # Astron 5并发
def extract_questions(data):
"""从训练数据中提取所有问题"""
questions = []
for item in data:
if isinstance(item, dict):
q = item.get('question', '') or item.get('input', '')
if q:
questions.append(q)
return questions
def main():
# 1. 加载训练数据
train_path = os.path.join(MODEL_DIR, 'xiaoli_train_v2.json')
print(f"加载训练数据: {train_path}")
with open(train_path, encoding='utf-8') as f:
train_data = json.load(f)
questions = extract_questions(train_data)
total = len(questions)
print(f"提取到 {total} 条问题,温度={TEMPERATURE},{MAX_WORKERS}并发")
# 2. 创建输出目录
out_dir = os.path.join(MODEL_DIR, 'high_temp_outputs')
os.makedirs(out_dir, exist_ok=True)
# 3. 分批处理
all_results = []
existing = set() # 去重用
# 检查是否有已完成的中间结果
interim_files = sorted([f for f in os.listdir(out_dir) if f.startswith('batch_') and f.endswith('.json')])
if interim_files:
for fname in interim_files:
with open(os.path.join(out_dir, fname), encoding='utf-8') as f:
batch_data = json.load(f)
for r in batch_data:
key = (r['question'][:100], r['answer'][:100])
if key not in existing:
existing.add(key)
all_results.append(r)
print(f"发现已有中间结果: {len(all_results)} 条(来自 {len(interim_files)} 个文件)")
# 跳过已处理的 question
processed_questions = set(r['question'] for r in all_results)
remaining = [q for q in questions if q not in processed_questions]
print(f"已处理 {len(processed_questions)} 条,剩余 {len(remaining)} 条")
questions = remaining
total_batches = (len(questions) + BATCH_SIZE - 1) // BATCH_SIZE
t_start = time.time()
for batch_idx in range(total_batches):
batch_qs = questions[batch_idx * BATCH_SIZE : (batch_idx + 1) * BATCH_SIZE]
print(f"\n{'='*60}")
print(f"批次 {batch_idx + 1}/{total_batches} ({len(batch_qs)}条, 温度{TEMPERATURE})")
print(f"{'='*60}")
t0 = time.time()
results = batch_generate(batch_qs, temperature=TEMPERATURE, max_workers=MAX_WORKERS)
elapsed = time.time() - t0
# 标注入参温度
for r in results:
r['temperature'] = TEMPERATURE
# 去重后加入总集
new_in_batch = 0
for r in results:
key = (r['question'][:100], r['answer'][:100])
if key not in existing:
existing.add(key)
all_results.append(r)
new_in_batch += 1
# 立即保存本批结果
batch_file = os.path.join(out_dir, f'batch_{batch_idx + 1:04d}.json')
with open(batch_file, 'w', encoding='utf-8') as f:
json.dump(results, f, ensure_ascii=False, indent=2)
# 追加保存总进度
progress_file = os.path.join(out_dir, 'all_progress.json')
with open(progress_file, 'w', encoding='utf-8') as f:
json.dump(all_results, f, ensure_ascii=False, indent=2)
total_elapsed = time.time() - t_start
rate = len(all_results) / max(total_elapsed, 1) * 60
eta_remaining = (total - len(all_results)) / max(rate, 0.1) / 60 if rate > 0 else 0
print(f" 本批新增 {new_in_batch} 条 (去重后) | 累计 {len(all_results)} 条 | "
f"{rate:.0f}条/分 | 已用 {total_elapsed/60:.1f}分 | 预计剩余 {eta_remaining:.1f}分")
# 4. 去重统计
print(f"\n{'='*60}")
print(f"全部生成完成!")
print(f"生成结果: {len(all_results)} 条 (去重后)")
print(f"用时: {(time.time() - t_start)/60:.1f} 分钟")
# 5. 保存最终去重结果(独立存放,不追加到训练集)
final_file = os.path.join('/data/user/work', 'high_temp_final.json')
with open(final_file, 'w', encoding='utf-8') as f:
json.dump(all_results, f, ensure_ascii=False, indent=2)
print(f"\n最终结果已保存: {final_file}")
print(f"如需使用,可手动合并到训练集")
print(f"\n{'='*60}")
print(f"全部完成! 新增 {len(all_results)} 条多样化回答")
print(f"{'='*60}")
if __name__ == '__main__':
main()