|
|
|
""" |
|
Dataset Generator for BaZi Fortune Telling |
|
Uses Google Gemini Pro 2.5 with thinking to generate high-quality training data |
|
Supports multithreaded processing for faster generation |
|
""" |
|
|
|
import json |
|
import random |
|
import time |
|
from datetime import datetime, timedelta |
|
from typing import Dict, List, Optional, Tuple |
|
import os |
|
import sys |
|
from concurrent.futures import ThreadPoolExecutor, as_completed |
|
from threading import Lock |
|
import threading |
|
|
|
|
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
|
|
|
from bazi_calculator import BaziCalculator, compute_birth_pillars |
|
from bazi_favorable_elements import calculate_favorable_elements |
|
|
|
|
|
try: |
|
from google import genai |
|
from google.genai import types |
|
except ImportError: |
|
print("Please install google-genai: pip install google-genai") |
|
sys.exit(1) |
|
|
|
|
|
GEMINI_API_KEY = "AIzaSyCqe3vjvPlo1lt_hpQ4nqAC0-_1omva1oc" |
|
os.environ["GEMINI_API_KEY"] = GEMINI_API_KEY |
|
|
|
|
|
MAX_WORKERS = 10 |
|
RATE_LIMIT_DELAY = 0.5 |
|
|
|
|
|
file_lock = Lock() |
|
progress_lock = Lock() |
|
print_lock = Lock() |
|
api_call_lock = Lock() |
|
last_api_call_time = 0 |
|
|
|
|
|
context_cache = None |
|
cache_lock = Lock() |
|
|
|
|
|
def format_bazi_production_style(person: Dict, bazi_data: Dict) -> str: |
|
"""Format BaZi data in production website style""" |
|
lines = [] |
|
birth_date = bazi_data['birth_date'] |
|
pillars = bazi_data['birth_pillars'] |
|
fav_elems = bazi_data['favorable_elements'] |
|
future_data = bazi_data['future_data'] |
|
calculator = bazi_data['calculator'] |
|
|
|
|
|
current_date = datetime.now() |
|
|
|
|
|
lines.append("=== BaZi Data ===") |
|
lines.append(f"Name: {person['name']}") |
|
lines.append(f"Birth: {birth_date.strftime('%Y-%m-%d %H:%M:%S')}") |
|
lines.append(f"Gender: {person['gender']}") |
|
lines.append(f"Query: {current_date.strftime('%Y-%m-%d %H:%M')}") |
|
|
|
|
|
age = current_date.year - birth_date.year |
|
if current_date.month < birth_date.month or (current_date.month == birth_date.month and current_date.day < birth_date.day): |
|
age -= 1 |
|
lines.append(f"Age: {age}") |
|
|
|
|
|
day_stem = pillars['day'][0] |
|
|
|
stem_strength = "Strong" if len(fav_elems['favorable']) > len(fav_elems['unfavorable']) else "Weak" |
|
lines.append(f"Life Stem: {day_stem} ({stem_strength})") |
|
|
|
|
|
lines.append(f"Favorable: {', '.join(fav_elems['favorable'])}") |
|
lines.append(f"Unfavorable: {', '.join(fav_elems['unfavorable'])}") |
|
|
|
|
|
lines.append("\n=== Four Pillars ===") |
|
|
|
|
|
from bazi_calculator import BaziCalculator |
|
calc_temp = BaziCalculator(birth_date, person['gender'].lower(), pillars) |
|
|
|
|
|
year_hidden = calc_temp.BRANCH_HIDDEN_STEMS.get(pillars['year'][1], []) |
|
lines.append(f"Year : {pillars['year'][0]}{pillars['year'][1]} (hidden: {', '.join(year_hidden)})") |
|
|
|
|
|
month_hidden = calc_temp.BRANCH_HIDDEN_STEMS.get(pillars['month'][1], []) |
|
lines.append(f"Month: {pillars['month'][0]}{pillars['month'][1]} (hidden: {', '.join(month_hidden)})") |
|
|
|
|
|
day_hidden = calc_temp.BRANCH_HIDDEN_STEMS.get(pillars['day'][1], []) |
|
lines.append(f"Day : {pillars['day'][0]}{pillars['day'][1]} (hidden: {', '.join(day_hidden)})") |
|
|
|
|
|
hour_hidden = calc_temp.BRANCH_HIDDEN_STEMS.get(pillars['hour'][1], []) |
|
lines.append(f"Hour : {pillars['hour'][0]}{pillars['hour'][1]} (hidden: {', '.join(hour_hidden)})") |
|
|
|
|
|
lines.append("\n=== Current Luck Pillar ===") |
|
for lp in future_data['luck_pillars']: |
|
if lp['start_age'] <= age <= lp['end_age']: |
|
lines.append(f"Age {age}: {lp['heaven']}{lp['earth']}") |
|
break |
|
|
|
|
|
lines.append("\n=== Current Period ===") |
|
|
|
|
|
for ap in future_data['annual_pillars']: |
|
if ap['year'] == current_date.year: |
|
lines.append(f"Annual: {ap['heaven']}{ap['earth']}") |
|
break |
|
|
|
|
|
current_month_key = f"{current_date.year}-{current_date.month:02d}" |
|
if current_month_key in future_data['monthly_pillars']: |
|
mp = future_data['monthly_pillars'][current_month_key] |
|
lines.append(f"Monthly: {mp['heaven']}{mp['earth']}") |
|
|
|
|
|
current_day_key = current_date.strftime("%Y-%m-%d") |
|
if current_day_key in future_data['daily_pillars']: |
|
dp = future_data['daily_pillars'][current_day_key] |
|
lines.append(f"Daily: {dp['heaven']}{dp['earth']}") |
|
|
|
|
|
current_hour = current_date.hour |
|
|
|
if current_hour == 0: |
|
chinese_hour = 23 |
|
elif current_hour % 2 == 0: |
|
chinese_hour = current_hour - 1 |
|
else: |
|
chinese_hour = current_hour |
|
|
|
if chinese_hour in future_data['hourly_pillars']: |
|
hp = future_data['hourly_pillars'][chinese_hour] |
|
lines.append(f"Hourly: {hp['heaven']}{hp['earth']}") |
|
|
|
|
|
lines.append("\n=== Future 100 Years ===") |
|
|
|
|
|
lines.append("\n[Luck Pillars]") |
|
for lp in future_data['luck_pillars']: |
|
lines.append(f"Age {lp['start_age']}-{lp['end_age']}: {lp['heaven']}{lp['earth']}") |
|
|
|
|
|
lines.append("\n[Annual Pillars]") |
|
for ap in future_data['annual_pillars']: |
|
lines.append(f"{ap['year']}: {ap['heaven']}{ap['earth']}") |
|
|
|
|
|
lines.append("\n[Monthly Pillars]") |
|
monthly_keys = sorted(list(future_data['monthly_pillars'].keys())) |
|
for key in monthly_keys: |
|
mp = future_data['monthly_pillars'][key] |
|
lines.append(f"{key}: {mp['heaven']}{mp['earth']}") |
|
|
|
|
|
lines.append("\n[Daily Pillars]") |
|
daily_keys = sorted(list(future_data['daily_pillars'].keys())) |
|
for key in daily_keys: |
|
dp = future_data['daily_pillars'][key] |
|
lines.append(f"{key}: {dp['heaven']}{dp['earth']}") |
|
|
|
lines.append("\n[Loaded with AiMing v3]") |
|
|
|
return '\n'.join(lines) |
|
|
|
|
|
def load_system_prompt() -> str: |
|
"""Load the system prompt from prompt-v3.txt""" |
|
script_dir = os.path.dirname(os.path.abspath(__file__)) |
|
parent_dir = os.path.dirname(script_dir) |
|
prompt_path = os.path.join(parent_dir, 'prompt-v3.txt') |
|
with open(prompt_path, 'r', encoding='utf-8') as f: |
|
return f.read() |
|
|
|
def generate_random_birthdays(count: int = 10) -> List[Dict]: |
|
"""Generate random birthdays with varied demographics |
|
|
|
Args: |
|
count: Number of random birthdays to generate |
|
""" |
|
birthdays = [] |
|
|
|
|
|
male_names = ["สมชาย", "วิชัย", "ประยุทธ์", "สมศักดิ์", "วีระ", "ณัฐวุฒิ", "ธนากร", "พงษ์ศักดิ์", "อภิชาติ", "จักรพันธ์", |
|
"สุรชัย", "ประสิทธิ์", "สมพงษ์", "วิทยา", "นิพนธ์", "สุทธิพงษ์", "อนันต์", "ชัยวัฒน์", "ธีระ", "พิชัย"] |
|
female_names = ["สมหญิง", "มาลี", "สุดาพร", "วิไลวรรณ", "พรทิพย์", "นันทนา", "จิราภรณ์", "ศิริพร", "อรวรรณ", "ปิยะนุช", |
|
"วรรณา", "สุภาพร", "รัตนา", "อารีย์", "ปาริชาติ", "สุนิสา", "กาญจนา", "ดวงใจ", "ชนิดา", "พิมพ์ใจ"] |
|
|
|
|
|
|
|
year_range = list(range(1960, 2006)) |
|
if count <= len(year_range): |
|
years = random.sample(year_range, count) |
|
else: |
|
|
|
years = random.choices(year_range, k=count) |
|
|
|
for i in range(count): |
|
gender = random.choice(["Male", "Female"]) |
|
name = random.choice(male_names if gender == "Male" else female_names) |
|
|
|
|
|
month = random.randint(1, 12) |
|
if month in [1, 3, 5, 7, 8, 10, 12]: |
|
day = random.randint(1, 31) |
|
elif month in [4, 6, 9, 11]: |
|
day = random.randint(1, 30) |
|
else: |
|
day = random.randint(1, 28) |
|
|
|
|
|
hour = random.randint(0, 23) |
|
minute = random.randint(0, 59) |
|
|
|
birthdays.append({ |
|
"name": f"{name}_{i+1}", |
|
"gender": gender, |
|
"year": years[i], |
|
"month": month, |
|
"day": day, |
|
"hour": hour, |
|
"minute": minute, |
|
"is_twin": False |
|
}) |
|
|
|
return birthdays |
|
|
|
def generate_questions() -> List[str]: |
|
"""Generate 10 diverse questions for BaZi consultation""" |
|
question_templates = [ |
|
|
|
"ดวงนี้เป็นอย่างไร", |
|
"ช่วงไหนในชีวิตจะประสบความสำเร็จ", |
|
"มีอุปสรรคอะไรที่ต้องระวังบ้าง", |
|
"ดวงชะตาโดยรวมปีนี้เป็นอย่างไร", |
|
"ชีวิตโดยรวมจะเป็นอย่างไร", |
|
"อนาคตของฉันจะเป็นอย่างไร", |
|
"ปีนี้จะเป็นปีที่ดีไหม", |
|
"ช่วงชีวิตไหนจะเจอปัญหามาก", |
|
"จะมีชีวิตที่ดีขึ้นเมื่อไหร่", |
|
"ดวงชะตาในระยะยาวเป็นอย่างไร", |
|
"ชีวิตจะมีความสุขไหม", |
|
"จะพบเจอสิ่งดีๆ เมื่อไหร่", |
|
"มีเคราะห์อะไรรออยู่ไหม", |
|
"ดวงจะดีขึ้นเมื่อไหร่", |
|
"ชีวิตจะราบรื่นไหม", |
|
"ปีไหนจะเป็นปีทอง", |
|
"จะมีโชคเมื่อไหร่", |
|
"ควรระวังอะไรในชีวิต", |
|
"จะมีอะไรเปลี่ยนแปลงไหม", |
|
"ดวงชะตาตอนแก่เป็นอย่างไร", |
|
|
|
|
|
"ช่วงนี้งานการเป็นอย่างไรบ้าง มีโอกาสก้าวหน้าไหม", |
|
"อยากเปลี่ยนงานใหม่ ช่วงไหนเหมาะสม", |
|
"อยากทำธุรกิจส่วนตัว เหมาะไหม ควรทำธุรกิจประเภทไหน", |
|
"จะได้เลื่อนตำแหน่งไหม", |
|
"งานที่ทำอยู่จะมั่นคงไหม", |
|
"ควรลาออกจากงานไหม", |
|
"จะตกงานไหม", |
|
"อาชีพอะไรเหมาะกับดวงนี้", |
|
"ทำงานกับคนอื่นดีหรือทำงานคนเดียวดี", |
|
"จะประสบความสำเร็จในอาชีพไหม", |
|
"เจ้านายเมตตาไหม", |
|
"ควรทำงานรับราชการหรือเอกชน", |
|
"จะมีคนอิจฉาในที่ทำงานไหม", |
|
"ควรเปลี่ยนสายงานไหม", |
|
"จะได้ทำงานที่ชอบไหม", |
|
"หุ้นส่วนธุรกิจจะซื่อสัตย์ไหม", |
|
"ควรทำงานในประเทศหรือต่างประเทศ", |
|
"จะมีปัญหาในที่ทำงานไหม", |
|
"ควรรับงานฟรีแลนซ์ไหม", |
|
"ธุรกิจจะอยู่รอดไหม", |
|
"ควรขยายธุรกิจไหม", |
|
"จะมีคู่แข่งทางธุรกิจไหม", |
|
"ลูกค้าจะพอใจไหม", |
|
"ควรร่วมทุนกับใครไหม", |
|
"จะได้โบนัสไหม", |
|
"งานที่สมัครจะได้ไหม", |
|
"ควรทำอาชีพเสริมไหม", |
|
"จะเป็นผู้บริหารไหม", |
|
"ควรลงทุนในธุรกิจอะไร", |
|
"จะมีรายได้เสริมไหม", |
|
|
|
|
|
"เรื่องความรักเป็นอย่างไรบ้าง จะเจอคนที่ใช่เมื่อไหร่", |
|
"ความรักกับแฟนปัจจุบันจะไปได้ไกลไหม", |
|
"ปีนี้มีโอกาสแต่งงานไหม", |
|
"จะได้เจอแฟนไหม เมื่อไหร่", |
|
"แฟนจะนอกใจไหม", |
|
"จะมีคนมาชอบไหม", |
|
"จะเลิกกับแฟนไหม", |
|
"ความรักจะลงเอยด้วยดีไหม", |
|
"คู่ที่เหมาะสมเป็นคนแบบไหน", |
|
"จะมีกี่ครั้งในชีวิตที่รักจริง", |
|
"จะโสดไปตลอดชีวิตไหม", |
|
"ควรแต่งงานกับคนนี้ไหม", |
|
"จะหย่าร้างไหม", |
|
"จะกลับมาคืนดีกันไหม", |
|
"คนที่แอบชอบจะมาบอกรักไหม", |
|
"จะมีคนที่สามไหม", |
|
"ควรรอคนเก่าไหม", |
|
"จะเจอรักแท้ไหม", |
|
"แฟนจะดีกับเราไหม", |
|
"จะมีปัญหาเรื่องความรักไหม", |
|
"ควรเปิดใจให้คนใหม่ไหม", |
|
"จะอกหักไหม", |
|
"คนที่ชอบจะชอบเราไหม", |
|
"จะแต่งงานกี่ครั้ง", |
|
"คู่ชีวิตจะมาจากไหน", |
|
"จะมีลูกกับคนรักไหม", |
|
"ความรักระยะไกลจะสำเร็จไหม", |
|
"จะเจอคนรักที่ทำงานไหม", |
|
"ควรคบคนอายุเท่าไหร่", |
|
"จะมีความรักออนไลน์ไหม", |
|
|
|
|
|
"สุขภาพช่วงนี้ต้องระวังอะไรบ้าง", |
|
"มีปัญหาสุขภาพเรื้อรัง จะหายไหม", |
|
"ปีนี้จะป่วยไหม", |
|
"เดือนนี้อาการป่วยจะดีขึ้นไหม", |
|
"ดวงนี้ป่วยเป็นโรคอะไร", |
|
"จะมีอายุยืนไหม", |
|
"ควรระวังโรคอะไรเป็นพิเศษ", |
|
"การผ่าตัดจะสำเร็จไหม", |
|
"ควรไปหาหมอไหม", |
|
"สุขภาพจิตจะแข็งแรงไหม", |
|
"จะมีอุบัติเหตุไหม", |
|
"จะมีปัญหาเรื่องน้ำหนักไหม", |
|
"ควรออกกำลังกายแบบไหน", |
|
"จะมีปัญหาการนอนไหม", |
|
"สายตาจะมีปัญหาไหม", |
|
"ฟันจะมีปัญหาไหม", |
|
"จะมีอาการแพ้ไหม", |
|
"ควรกินอาหารแบบไหน", |
|
"จะเครียดมากไหม", |
|
"จะมีปัญหาผิวหนังไหม", |
|
"ระบบย่อยอาหารจะมีปัญหาไหม", |
|
"จะปวดหัวบ่อยไหม", |
|
"จะมีปัญหากระดูกไหม", |
|
"หัวใจจะแข็งแรงไหม", |
|
"จะมีปัญหาความดันไหม", |
|
"จะเป็นโรคติดต่อไหม", |
|
"จะมีปัญหาฮอร์โมนไหม", |
|
"ควรตรวจสุขภาพเมื่อไหร่", |
|
"จะติดเชื้อไหม", |
|
"จะมีปัญหาการหายใจไหม", |
|
|
|
|
|
"การเงินปีนี้เป็นอย่างไร จะมีโชคลาภไหม", |
|
"อยากลงทุนหุ้น/คริปโต เหมาะไหม", |
|
"หนี้สินเยอะ จะหมดเมื่อไหร่", |
|
"ดวงนี้รวยเมื่อไหร่", |
|
"ดวงนี้รวยไหม", |
|
"จะถูกหวยไหม", |
|
"ควรลงทุนอะไร", |
|
"จะล้มละลายไหม", |
|
"เงินจะพอใช้ไหม", |
|
"จะมีคนมาหลอกเงินไหม", |
|
"ควรให้คนอื่นยืมเงินไหม", |
|
"จะได้มรดกไหม", |
|
"ควรซื้อบ้านหรือเช่าบ้าน", |
|
"การลงทุนจะได้ผลตอบแทนดีไหม", |
|
"จะมีเงินเก็บไหม", |
|
"ควรฝากเงินหรือลงทุน", |
|
"จะได้เงินคืนจากลูกหนี้ไหม", |
|
"ควรซื้อทองไหม", |
|
"จะมีรายได้พิเศษไหม", |
|
"ควรเล่นพนันไหม", |
|
"จะขาดทุนไหม", |
|
"ควรกู้เงินไหม", |
|
"จะมีเงินใช้ตอนแก่ไหม", |
|
"ควรทำประกันไหม", |
|
"จะมีปัญหาภาษีไหม", |
|
"ควรลงทุนอสังหาริมทรัพย์ไหม", |
|
"จะได้โบนัสพิเศษไหม", |
|
"ควรเก็บเงินสดไหม", |
|
"จะมีหนี้นอกระบบไหม", |
|
"ควรขายทรัพย์สินไหม", |
|
|
|
|
|
"ลูกๆ จะเรียนหนังสือเก่งไหม", |
|
"ความสัมพันธ์ในครอบครัวจะราบรื่นไหม", |
|
"จะมีลูกไหม", |
|
"พ่อแม่จะมีสุขภาพดีไหม", |
|
"พี่น้องจะสามัคคีกันไหม", |
|
"จะมีปัญหากับญาติไหม", |
|
"ลูกจะกตัญญูไหม", |
|
"ควรมีลูกกี่คน", |
|
"จะได้อยู่กับครอบครัวไหม", |
|
"จะมีปัญหากับพ่อตาแม่ยายไหม", |
|
"ลูกจะประสบความสำเร็จไหม", |
|
"จะต้องดูแลพ่อแม่ไหม", |
|
"พี่น้องจะช่วยเหลือไหม", |
|
"จะมีปัญหาเรื่องมรดกไหม", |
|
"ครอบครัวจะอบอุ่นไหม", |
|
"จะมีใครในครอบครัวป่วยไหม", |
|
"ลูกจะได้เรียนสูงไหม", |
|
"จะมีปัญหากับคู่สมรสของลูกไหม", |
|
"หลานจะน่ารักไหม", |
|
"จะได้เลี้ยงหลานไหม", |
|
|
|
|
|
"จะสอบติดไหม", |
|
"ควรเรียนต่อไหม", |
|
"สาขาอะไรเหมาะกับฉัน", |
|
"จะจบการศึกษาไหม", |
|
"จะได้ทุนการศึกษาไหม", |
|
"จะเรียนเก่งไหม", |
|
"ควรเรียนในประเทศหรือต่างประเทศ", |
|
"จะสอบตกไหม", |
|
"ควรเรียนพิเศษไหม", |
|
"จะมีปัญหากับอาจารย์ไหม", |
|
"จะมีเพื่อนที่ดีไหม", |
|
"ควรย้ายโรงเรียนไหม", |
|
"จะได้เกียรตินิยมไหม", |
|
"ควรเรียนภาษาอะไร", |
|
"จะมีปัญหาการเรียนไหม", |
|
"ควรเรียนปริญญาโทไหม", |
|
"จะได้ไปแลกเปลี่ยนไหม", |
|
"ควรเรียนออนไลน์ไหม", |
|
"จะมีปัญหากับเพื่อนร่วมชั้นไหม", |
|
"ควรเข้ามหาวิทยาลัยไหน", |
|
|
|
|
|
"เดือนหน้าจะมีเรื่องดีๆ เข้ามาไหม", |
|
"ปีหน้าควรระวังเรื่องอะไรบ้าง", |
|
"วันไหนในเดือนนี้เป็นวันมงคล", |
|
"พรุ่งนี้จะเป็นวันที่ดีไหม", |
|
"สัปดาห์นี้จะมีข่าวดีไหม", |
|
"ช่วงไหนของปีจะดีที่สุด", |
|
"เดือนไหนควรระมัดระวังเป็นพิเศษ", |
|
"วันนี้ควรทำอะไร", |
|
"คืนนี้จะนอนหลับสบายไหม", |
|
"เช้านี้จะมีเรื่องดีไหม", |
|
"บ่ายนี้ควรระวังอะไร", |
|
"เย็นนี้จะเจอใคร", |
|
"วันจันทร์จะเป็นอย่างไร", |
|
"สุดสัปดาห์นี้จะสนุกไหม", |
|
"เดือนนี้จะได้เงินไหม", |
|
"ปีนี้จะมีอะไรเปลี่ยนแปลง", |
|
"ไตรมาสนี้ธุรกิจจะดีไหม", |
|
"ฤดูนี้สุขภาพจะเป็นอย่างไร", |
|
"ช่วงปิดเทอมจะไปไหนไหม", |
|
"วันเกิดปีนี้จะมีความสุขไหม", |
|
|
|
|
|
"กำลังตัดสินใจเรื่องสำคัญ ควรเลือกทางไหน", |
|
"จะย้ายบ้านดีไหม ช่วงไหนเหมาะสม", |
|
"อยากไปเรียนต่อต่างประเทศ เหมาะไหม", |
|
"ควรซื้อรถไหม", |
|
"ควรขายที่ดินไหม", |
|
"ควรเริ่มทำอะไรใหม่ๆ ไหม", |
|
"ควรรับข้อเสนอนี้ไหม", |
|
"ควรไปหรือไม่ไป", |
|
"ควรพูดความจริงไหม", |
|
"ควรให้อภัยไหม", |
|
"ควรเสี่ยงไหม", |
|
"ควรรอหรือรีบทำ", |
|
"ควรเลือกข้อ A หรือ B", |
|
"ควรลงทะเบียนไหม", |
|
"ควรเซ็นสัญญาไหม", |
|
"ควรไว้ใจคนนี้ไหม", |
|
"ควรบอกเลิกไหม", |
|
"ควรเริ่มใหม่ไหม", |
|
"ควรยอมรับไหม", |
|
"ควรปฏิเสธไหม", |
|
|
|
|
|
"จะได้ไปต่างประเทศไหม", |
|
"ควรย้ายไปอยู่ที่อื่นไหม", |
|
"การเดินทางจะปลอดภัยไหม", |
|
"ทิศไหนเหมาะกับดวง", |
|
"ควรอยู่ภาคไหนของประเทศ", |
|
"จะได้ไปเที่ยวไหม", |
|
"ควรไปเที่ยวประเทศไหน", |
|
"จะมีปัญหาระหว่างเดินทางไหม", |
|
"ควรเดินทางคนเดียวหรือกับคนอื่น", |
|
"จะพลาดเที่ยวบินไหม", |
|
"จะทำหนังสือเดินทางหายไหม", |
|
"ควรพักโรงแรมหรือบ้านเพื่อน", |
|
"จะมีอุบัติเหตุทางรถไหม", |
|
"ควรขับรถเองหรือนั่งรถคนอื่น", |
|
"จะติดอยู่ต่างประเทศไหม", |
|
|
|
|
|
"คดีความจะชนะไหม", |
|
"จะมีปัญหาทางกฎหมายไหม", |
|
"ควรฟ้องร้องไหม", |
|
"จะเจอคนโกงไหม", |
|
"จะมีปัญหากับเจ้าหน้าที่ไหม", |
|
"จะถูกฟ้องไหม", |
|
"ควรยอมความไหม", |
|
"จะมีปัญหาเอกสารไหม", |
|
"ทนายความจะช่วยได้ไหม", |
|
"จะต้องขึ้นศาลไหม", |
|
"จะมีปัญหาสัญญาไหม", |
|
"จะถูกหลอกลวงไหม", |
|
"จะมีคนมาเอาเปรียบไหม", |
|
"ควรแจ้งความไหม", |
|
"จะได้ความยุติธรรมไหม", |
|
|
|
|
|
"ธาตุอะไรเสริมดวง ควรใส่สีอะไร", |
|
"เลขอะไรเป็นเลขมงคล", |
|
"ควรบูชาพระเครื่องไหม", |
|
"ควรทำบุญอะไร", |
|
"มีกรรมเก่าอะไรติดตัวไหม", |
|
"ควรแก้ชงอย่างไร", |
|
"วันไหนเป็นวันดีของฉัน", |
|
"ควรตั้งชื่อลูกว่าอะไร", |
|
"ควรไหว้พระวันไหน", |
|
"ควรสวดมนต์ไหม", |
|
"จะมีสิ่งศักดิ์สิทธิ์คุ้มครองไหม", |
|
"ควรใส่เครื่องรางไหม", |
|
"ควรไปวัดไหนทำบุญ", |
|
"จะมีบุญจากชาติก่อนไหม", |
|
"ควรถือศีลไหม", |
|
"จะมีเทพเจ้าช่วยไหม", |
|
"ควรบนบานไหม", |
|
"จะสมหวังที่บนไว้ไหม", |
|
"ควรเลี้ยงสัตว์อะไร", |
|
"ต้นไม้อะไรเสริมดวง", |
|
|
|
|
|
"จะพัฒนาตัวเองได้อย่างไร", |
|
"จุดแข็งของฉันคืออะไร", |
|
"จุดอ่อนที่ควรแก้ไขคืออะไร", |
|
"จะมีชื่อเสียงไหม", |
|
"จะประสบความสำเร็จในชีวิตไหม", |
|
"ควรทำอะไรเพื่อเปลี่ยนชีวิต", |
|
"จะเป็นคนสำคัญไหม", |
|
"จะมีคนนับถือไหม", |
|
"ควรเรียนรู้อะไรเพิ่ม", |
|
"จะมีความสามารถพิเศษไหม", |
|
"ควรฝึกทักษะอะไร", |
|
"จะเป็นผู้นำไหม", |
|
"จะมีคนตามไหม", |
|
"ควรเปลี่ยนนิสัยอะไร", |
|
"จะมีความคิดสร้างสรรค์ไหม", |
|
"ควรทำงานอดิเรกอะไร", |
|
"จะเก่งด้านไหน", |
|
"ควรออกจากคอมฟอร์ทโซนไหม", |
|
"จะมีพรสวรรค์ด้านไหน", |
|
"ควรฝึกสมาธิไหม", |
|
|
|
|
|
"จะผ่านวิกฤตนี้ไปได้ไหม", |
|
"ปัญหาจะจบเมื่อไหร่", |
|
"จะมีคนช่วยเหลือไหม", |
|
"ควรขอความช่วยเหลือจากใคร", |
|
"สิ่งที่กังวลจะเกิดขึ้นจริงไหม", |
|
"จะแก้ปัญหาได้ไหม", |
|
"ควรหนีปัญหาไหม", |
|
"จะมีทางออกไหม", |
|
"ปัญหาจะซ้ำไหม", |
|
"จะมีปัญหาใหม่ไหม", |
|
"ควรเผชิญหน้ากับปัญหาไหม", |
|
"จะมีคนมาสร้างปัญหาไหม", |
|
"ปัญหาจะใหญ่ขึ้นไหม", |
|
"ควรปล่อยวางไหม", |
|
"จะเครียดมากไหม", |
|
|
|
|
|
"ควรเปิดร้านอาหารไหม", |
|
"ธุรกิจออนไลน์จะสำเร็จไหม", |
|
"ควรขายของผ่านโซเชียลไหม", |
|
"จะมีลูกค้าเยอะไหม", |
|
"คู่แข่งจะมากไหม", |
|
"ควรทำธุรกิจครอบครัวไหม", |
|
"จะขาดทุนในปีแรกไหม", |
|
"ควรขอสินเชื่อธุรกิจไหม", |
|
"พนักงานจะซื่อสัตย์ไหม", |
|
"ควรเปิดสาขาไหม", |
|
|
|
|
|
"ควรเรียนเขียนโปรแกรมไหม", |
|
"จะประสบความสำเร็จในสายไอทีไหม", |
|
"ควรทำช่อง YouTube ไหม", |
|
"จะดังในโซเชียลมีเดียไหม", |
|
"ควรเป็นอินฟลูเอนเซอร์ไหม", |
|
"จะถูกแฮกข้อมูลไหม", |
|
"ควรลงทุนใน NFT ไหม", |
|
"จะติดเกมไหม", |
|
"ควรทำงาน Work from home ไหม", |
|
"จะมีปัญหากับเทคโนโลยีไหม", |
|
|
|
|
|
"ควรเป็นมังสวิรัติไหม", |
|
"ควรงดเหล้าไหม", |
|
"ควรเลิกบุหรี่ไหม", |
|
"ควรทำศัลยกรรมไหม", |
|
"ควรลดน้ำหนักไหม", |
|
"ควรย้อมผมไหม", |
|
"ควรเลี้ยงสัตว์ไหม", |
|
"ควรอยู่คอนโดหรือบ้าน", |
|
"ควรมีรถไหม", |
|
"ควรใช้ชีวิตแบบมินิมอลไหม", |
|
|
|
|
|
"จะมีเพื่อนแท้ไหม", |
|
"เพื่อนจะทรยศไหม", |
|
"ควรตัดขาดกับเพื่อนคนนี้ไหม", |
|
"จะมีศัตรูไหม", |
|
"คนรอบข้างจริงใจไหม", |
|
"จะถูกนินทาไหม", |
|
"จะมีคนอิจฉาไหม", |
|
"ควรให้โอกาสเพื่อนไหม", |
|
"จะโดนหักหลังไหม", |
|
"ควรไว้ใจใครไหม", |
|
|
|
|
|
"งานแต่งจะสำเร็จไหม", |
|
"ควรจัดงานใหญ่หรือเล็ก", |
|
"จะมีคนมาร่วมงานเยอะไหม", |
|
"ควรเลื่อนงานไหม", |
|
"งานบวชจะราบรื่นไหม", |
|
"ควรทำพิธีขึ้นบ้านใหม่ไหม", |
|
"จะได้รับเชิญไปงานไหม", |
|
"ควรไปงานศพไหม", |
|
"วันเกิดปีนี้จะพิเศษไหม", |
|
"ควรฉลองอะไรไหม", |
|
|
|
|
|
"จะเกษียณสุขสบายไหม", |
|
"ควรเกษียณเมื่อไหร่", |
|
"จะมีเงินพอใช้ตอนแก่ไหม", |
|
"ลูกจะดูแลไหม", |
|
"ควรอยู่บ้านพักคนชราไหม", |
|
"จะแก่อย่างมีความสุขไหม", |
|
"จะมีโรคประจำตัวไหม", |
|
"ควรทำพินัยกรรมไหม", |
|
"จะได้เห็นหลานโตไหม", |
|
"ควรย้ายไปอยู่ต่างจังหวัดไหม", |
|
] |
|
|
|
|
|
return random.sample(question_templates, 4) |
|
|
|
def get_bazi_data(person: Dict) -> Dict: |
|
"""Calculate BaZi data manually""" |
|
|
|
|
|
birth_date = datetime( |
|
person['year'], person['month'], person['day'], |
|
person['hour'], person['minute'] |
|
) |
|
|
|
|
|
birth_pillars = compute_birth_pillars(birth_date) |
|
|
|
|
|
fav_elements = calculate_favorable_elements(birth_pillars) |
|
|
|
|
|
calculator = BaziCalculator( |
|
birth_datetime=birth_date, |
|
gender=person['gender'], |
|
birth_pillars=birth_pillars |
|
) |
|
|
|
|
|
from api_v3 import generate_future_bazi_data |
|
future_data = generate_future_bazi_data(calculator, years_ahead=100) |
|
|
|
return { |
|
'birth_pillars': birth_pillars, |
|
'favorable_elements': fav_elements, |
|
'future_data': future_data, |
|
'birth_date': birth_date, |
|
'calculator': calculator |
|
} |
|
|
|
def create_or_get_context_cache(system_prompt: str) -> str: |
|
"""Create or retrieve context cache for the system prompt |
|
|
|
Returns: |
|
cache_name: The name/ID of the cached context |
|
""" |
|
global context_cache |
|
|
|
with cache_lock: |
|
if context_cache is not None: |
|
with print_lock: |
|
print(" Using existing context cache for system prompt") |
|
return context_cache |
|
|
|
try: |
|
client = genai.Client(api_key=GEMINI_API_KEY) |
|
|
|
|
|
with print_lock: |
|
print(" Creating context cache for system prompt (this saves API costs)...") |
|
|
|
|
|
|
|
cache = client.caches.create( |
|
model="gemini-2.5-pro", |
|
config=types.CreateCachedContentConfig( |
|
display_name='bazi_system_prompt', |
|
system_instruction=system_prompt, |
|
contents=[ |
|
types.Content( |
|
role="user", |
|
parts=[types.Part(text="System prompt loaded.")], |
|
) |
|
], |
|
ttl="72000s", |
|
) |
|
) |
|
|
|
context_cache = cache.name |
|
|
|
with print_lock: |
|
print(f" Context cache created: {context_cache}") |
|
print(f" Cache will save ~{len(system_prompt)//4} tokens per request") |
|
|
|
return context_cache |
|
|
|
except Exception as e: |
|
with print_lock: |
|
print(f" Warning: Could not create context cache: {e}") |
|
print(" Falling back to regular API calls (higher cost)") |
|
return None |
|
|
|
def call_gemini_api_with_cache(user_prompt: str, cache_name: str = None, system_prompt: str = None, max_retries: int = 3) -> Tuple[str, Optional[str]]: |
|
"""Call Google Gemini using context caching for cost savings |
|
|
|
Returns: |
|
Tuple of (response_text, new_cache_name or None) |
|
""" |
|
client = genai.Client(api_key=GEMINI_API_KEY) |
|
|
|
for attempt in range(max_retries): |
|
try: |
|
|
|
if cache_name: |
|
try: |
|
|
|
model = "gemini-2.5-pro" |
|
|
|
|
|
contents = [ |
|
types.Content( |
|
role="user", |
|
parts=[types.Part(text=user_prompt)], |
|
) |
|
] |
|
|
|
config = types.GenerateContentConfig( |
|
temperature=0.7, |
|
top_k=40, |
|
top_p=0.95, |
|
max_output_tokens=7500, |
|
cached_content=cache_name |
|
) |
|
|
|
|
|
response = client.models.generate_content( |
|
model=model, |
|
contents=contents, |
|
config=config |
|
) |
|
|
|
|
|
if response.text: |
|
return response.text, cache_name |
|
else: |
|
raise Exception("No response text generated") |
|
|
|
except Exception as cache_error: |
|
|
|
if "PERMISSION_DENIED" in str(cache_error) or "not found" in str(cache_error).lower(): |
|
with print_lock: |
|
print(f" Cache expired or not found, recreating cache...") |
|
|
|
|
|
if system_prompt: |
|
new_cache = create_or_get_context_cache(system_prompt) |
|
if new_cache: |
|
cache_name = new_cache |
|
with print_lock: |
|
print(f" Cache recreated successfully: {new_cache}") |
|
|
|
continue |
|
|
|
|
|
cache_name = None |
|
else: |
|
raise cache_error |
|
|
|
|
|
if not cache_name: |
|
|
|
contents = [ |
|
types.Content( |
|
role="user", |
|
parts=[ |
|
types.Part(text=user_prompt), |
|
], |
|
), |
|
] |
|
|
|
config = types.GenerateContentConfig( |
|
temperature=0.7, |
|
top_k=40, |
|
top_p=0.95, |
|
max_output_tokens=7500, |
|
) |
|
|
|
|
|
response_text = "" |
|
for chunk in client.models.generate_content_stream( |
|
model="gemini-2.5-pro", |
|
contents=contents, |
|
config=config, |
|
): |
|
if chunk.text: |
|
response_text += chunk.text |
|
|
|
if response_text: |
|
return response_text, None |
|
else: |
|
raise Exception("No response text generated") |
|
|
|
except Exception as e: |
|
error_str = str(e) |
|
if attempt < max_retries - 1 and ("503" in error_str or "overloaded" in error_str.lower()): |
|
wait_time = (attempt + 1) * 2 |
|
print(f" API overloaded. Retrying in {wait_time} seconds...") |
|
import time |
|
time.sleep(wait_time) |
|
else: |
|
raise Exception(f"Gemini API error: {e}") |
|
|
|
|
|
def call_gemini_api(prompt: str, max_retries: int = 3) -> str: |
|
"""Legacy function - calls the new cached version""" |
|
response, _ = call_gemini_api_with_cache(prompt, None, None, max_retries) |
|
return response |
|
|
|
def generate_dataset_entry(person: Dict, question: str, system_prompt: str, entry_id: str, current: int, total: int, cache_name: str = None) -> Tuple[str, Optional[Dict]]: |
|
"""Generate a single dataset entry (thread-safe) with context caching |
|
|
|
Returns: |
|
Tuple of (entry_id, entry_dict or None) |
|
""" |
|
global last_api_call_time, context_cache |
|
|
|
try: |
|
with print_lock: |
|
print(f" [{current}/{total}] Thread-{threading.current_thread().name}: Processing {question[:50]}...") |
|
|
|
|
|
bazi_data = get_bazi_data(person) |
|
|
|
|
|
bazi_formatted = format_bazi_production_style(person, bazi_data) |
|
|
|
|
|
user_prompt = f"""{bazi_formatted} |
|
|
|
Question: {question}""" |
|
|
|
|
|
with api_call_lock: |
|
current_time = time.time() |
|
if last_api_call_time > 0: |
|
elapsed = current_time - last_api_call_time |
|
if elapsed < RATE_LIMIT_DELAY: |
|
time.sleep(RATE_LIMIT_DELAY - elapsed) |
|
last_api_call_time = time.time() |
|
|
|
|
|
if cache_name: |
|
|
|
response, new_cache = call_gemini_api_with_cache(user_prompt, cache_name, system_prompt) |
|
|
|
if new_cache and new_cache != cache_name: |
|
with cache_lock: |
|
context_cache = new_cache |
|
with print_lock: |
|
print(f" Updated global cache to: {new_cache}") |
|
else: |
|
|
|
full_prompt = f"{system_prompt}\n\n---\n\n{user_prompt}" |
|
response, _ = call_gemini_api_with_cache(full_prompt, None, None) |
|
|
|
with print_lock: |
|
print(f" Thread-{threading.current_thread().name}: Successfully generated response") |
|
|
|
|
|
return (entry_id, { |
|
"messages": [ |
|
{"role": "system", "content": system_prompt}, |
|
{"role": "user", "content": user_prompt}, |
|
{"role": "assistant", "content": response} |
|
] |
|
}) |
|
|
|
except Exception as e: |
|
with print_lock: |
|
print(f" Thread-{threading.current_thread().name}: Error - {str(e)}") |
|
return (entry_id, None) |
|
|
|
def write_entry_to_file(output_file: str, entry: Dict) -> None: |
|
"""Thread-safe writing to file""" |
|
with file_lock: |
|
with open(output_file, 'a', encoding='utf-8') as f: |
|
f.write(json.dumps(entry, ensure_ascii=False) + '\n') |
|
f.flush() |
|
|
|
def save_progress(progress_file: str, processed_entries: set) -> None: |
|
"""Thread-safe progress saving""" |
|
with progress_lock: |
|
with open(progress_file, 'w') as pf: |
|
json.dump({'processed': list(processed_entries)}, pf) |
|
|
|
def main(): |
|
"""Main function to generate the dataset with multithreading and context caching""" |
|
print(f"Starting dataset generation with {MAX_WORKERS} worker threads...") |
|
print(f"Using Google Gemini Context Caching to reduce API costs\n") |
|
|
|
|
|
system_prompt = load_system_prompt() |
|
print(f"System prompt loaded: {len(system_prompt)} characters") |
|
|
|
|
|
cache_name = create_or_get_context_cache(system_prompt) |
|
if cache_name: |
|
print(f"✓ Context caching enabled - will save ~{len(system_prompt)//4} tokens per request\n") |
|
else: |
|
print("⚠ Context caching disabled - using regular API calls\n") |
|
|
|
|
|
script_dir = os.path.dirname(os.path.abspath(__file__)) |
|
output_file = os.path.join(script_dir, "bazi_training_data.jsonl") |
|
progress_file = os.path.join(script_dir, ".generation_progress.json") |
|
|
|
|
|
processed_entries = set() |
|
if os.path.exists(progress_file): |
|
try: |
|
with open(progress_file, 'r') as f: |
|
progress_data = json.load(f) |
|
processed_entries = set(progress_data.get('processed', [])) |
|
print(f"Resuming from previous run. Already processed: {len(processed_entries)} entries") |
|
except: |
|
processed_entries = set() |
|
|
|
|
|
existing_count = 0 |
|
if os.path.exists(output_file): |
|
with open(output_file, 'r', encoding='utf-8') as f: |
|
existing_count = sum(1 for line in f if line.strip()) |
|
print(f"Found {existing_count} existing entries in {output_file}") |
|
|
|
|
|
random.seed(18) |
|
birthdays = generate_random_birthdays(500) |
|
print(f"Generated {len(birthdays)} random birthdays") |
|
|
|
|
|
tasks = [] |
|
current = 0 |
|
total = len(birthdays) * 4 |
|
|
|
for person in birthdays: |
|
|
|
person_seed = hash(f"{person['name']}_{person['year']}_{person['month']}_{person['day']}") |
|
random.seed(person_seed) |
|
questions = generate_questions() |
|
|
|
for question in questions: |
|
current += 1 |
|
|
|
entry_id = f"{person['name']}_{person['year']}{person['month']:02d}{person['day']:02d}_{hash(question)}" |
|
|
|
|
|
if entry_id not in processed_entries: |
|
tasks.append((person, question, system_prompt, entry_id, current, total)) |
|
|
|
print(f"\nTotal tasks to process: {len(tasks)}") |
|
print(f"Already processed: {len(processed_entries)}") |
|
print(f"Remaining: {len(tasks)}") |
|
|
|
if not tasks: |
|
print("No new tasks to process. Exiting.") |
|
return |
|
|
|
|
|
generated_count = 0 |
|
failed_count = 0 |
|
|
|
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: |
|
|
|
future_to_task = {} |
|
for task in tasks: |
|
person, question, system_prompt, entry_id, idx, total = task |
|
|
|
current_cache = context_cache if context_cache else cache_name |
|
future = executor.submit(generate_dataset_entry, person, question, system_prompt, entry_id, idx, total, current_cache) |
|
future_to_task[future] = (person, question, entry_id) |
|
|
|
|
|
for future in as_completed(future_to_task): |
|
person, question, entry_id = future_to_task[future] |
|
|
|
try: |
|
result_entry_id, entry = future.result(timeout=600) |
|
|
|
if entry: |
|
|
|
write_entry_to_file(output_file, entry) |
|
|
|
|
|
with progress_lock: |
|
processed_entries.add(result_entry_id) |
|
generated_count += 1 |
|
|
|
|
|
save_progress(progress_file, processed_entries) |
|
|
|
with print_lock: |
|
print(f"✓ Saved entry #{existing_count + generated_count}: {person['name']} - {question[:30]}...") |
|
else: |
|
failed_count += 1 |
|
with print_lock: |
|
print(f"✗ Failed: {person['name']} - {question[:30]}...") |
|
|
|
except Exception as e: |
|
failed_count += 1 |
|
with print_lock: |
|
print(f"✗ Error processing {person['name']}: {str(e)}") |
|
|
|
|
|
if generated_count > 0: |
|
save_progress(progress_file, processed_entries) |
|
|
|
print(f"\n" + "="*60) |
|
print(f"Dataset generation complete!") |
|
print(f"Generated: {generated_count} new entries") |
|
print(f"Failed: {failed_count} entries") |
|
print(f"Total entries in file: {existing_count + generated_count}") |
|
print(f"Saved to: {output_file}") |
|
|
|
|
|
if len(processed_entries) >= total: |
|
if os.path.exists(progress_file): |
|
os.remove(progress_file) |
|
print("Cleaned up progress file (generation complete)") |
|
|
|
if __name__ == "__main__": |
|
main() |