Vnese_crawl / app.py
SonFox2920's picture
Update app.py
c375407 verified
# Import custom modules for prediction
from predictor import predict
import google.generativeai as genai
import streamlit as st
import pandas as pd
import wikipedia
import json
import regex as re
from typing import List, Dict, Any
import google.generativeai as genai
import warnings
import logging
import time
import random
# Disable warnings and logging
warnings.filterwarnings('ignore')
logging.disable(logging.WARNING)
# Set Wikipedia language to Vietnamese
wikipedia.set_lang("vi")
# Character to number mapping for Vietnamese alphabet
char_to_num = {
'A': 1, 'Ă': 2, 'Â': 3, 'B': 4, 'C': 5, 'D': 6, 'Đ': 7, 'E': 8, 'Ê': 9, 'G': 10,
'H': 11, 'I': 12, 'K': 13, 'L': 14, 'M': 15, 'N': 16, 'O': 17, 'Ô': 18, 'Ơ': 19,
'P': 20, 'Q': 21, 'R': 22, 'S': 23, 'T': 24, 'U': 25, 'Ư': 26, 'V': 27, 'X': 28, 'Y': 29
}
class ClaimGenerator:
"""Class to handle advanced claim generation with different APIs."""
def __init__(self, claim_type: str):
self.claim_type = claim_type
self.API_KEYS = {
"SUPPORTED": "AIzaSyCAMvWn4npcsO6ypWWB2cEnQQKuLIcB8JA",
"REFUTED": "AIzaSyAn1E_e4iAP-zDUV1R7hQa3DCkRfAOxW_8",
"NEI": "AIzaSyAciDaUfIhcK32qDhmozXAOKdSjgQeOSis"
}
self.current_key = self.API_KEYS[self.claim_type]
self._configure_api()
self.used_patterns = set()
# Add prompt variations
self.prompt_variations = [
"Tạo câu claim {0} sử dụng cấu trúc phức hợp.",
"Tạo câu claim {0} sử dụng phép ẩn dụ hoặc so sánh.",
"Tạo câu claim {0} sử dụng cấu trúc đối chiếu.",
"Tạo câu claim {0} mô tả mối quan hệ nhân quả.",
"Tạo câu claim {0} dùng lối diễn đạt gián tiếp."
]
# Add diversity instructions
self.diversity_instructions = [
"""
QUAN TRỌNG: Mỗi câu claim phải có cấu trúc câu khác nhau:
- Câu 1: Bắt đầu với chủ ngữ.
- Câu 2: Bắt đầu với trạng ngữ thời gian hoặc địa điểm.
- Câu 3: Bắt đầu với mệnh đề phụ.
- Câu 4: Bắt đầu với câu hỏi gián tiếp hoặc dẫn nhập.
- Câu 5: Sử dụng cấu trúc so sánh.
Tránh lặp lại cấu trúc câu, từ ngữ, hoặc cách diễn đạt từ những câu trước đó.
""",
"""
YÊU CẦU ĐA DẠNG: Đảm bảo mỗi câu claim có cách tiếp cận khác biệt:
- Sử dụng cấu trúc câu phức, câu đơn và câu ghép xen kẽ.
- Xen kẽ các cấu trúc câu khẳng định và câu điều kiện.
- Thay đổi vị trí trạng ngữ trong câu.
- Kết hợp các loại mệnh đề khác nhau.
""",
"""
LƯU Ý VỀ ĐA DẠNG: Đảm bảo đa dạng phong cách ngôn ngữ:
- Thay đổi độ dài câu (ngắn, trung bình, dài).
- Sử dụng các từ nối khác nhau.
- Xen kẽ giữa cách diễn đạt trực tiếp và gián tiếp.
- Kết hợp cả lối diễn đạt học thuật và bình dân.
"""
]
# Add n-gram patterns from analysis
self.ngram_patterns = {
"SUPPORTED": [
"miền nam việt nam",
"hoàng sa và trường",
"sa và trường sa",
"quần đảo hoàng sa",
"sân bay quốc tế",
"báo cáo tài chính",
"lọc hoá dầu bình",
"được sử dụng trong",
"là ngôn ngữ chính",
"ngôn ngữ chính thức",
"khu vực đông nam",
"bình quân đầu người",
"lấy mẫu xét nghiệm",
"các sàn thương mại",
"là một trong những",
"có ít nhất một",
"trái phép chất ma"
],
"REFUTED": [
"số lượng người nói",
"không có bất kỳ",
"thời kỳ bắc thuộc",
"điều tra dân số",
"việt nam dân chủ",
"nam dân chủ cộng",
"dân chủ cộng hòa",
"lượng người nói tiếng",
"người nói tiếng anh",
"được công nhận là",
"học viện y dược",
"viện y dược học",
"y dược học cổ",
"dược học cổ truyền",
"học cổ truyền việt",
"cổ truyền việt nam"
],
"NEI": [
"có thể",
"phát triển",
"sử dụng",
"công nghệ",
"thực hiện",
"ảnh hưởng",
"thời gian",
"khu vực"
]
}
# Track all generated claims in this session
if 'all_generated_claims' not in st.session_state:
st.session_state.all_generated_claims = []
self.SCHEMAS = {
"SUPPORTED": {
"type": "object",
"properties": {
"claims": {
"type": "array",
"items": {
"type": "string"
},
"description": """
*Hướng dẫn tạo claim SUPPORTED có độ khó cao*:
1. *Bắt buộc đề cập, liên quan đến Title của context*
- Câu claim phải liên quan trực tiếp đến Title của context.
- Câu claim phải liên quan đến nội dung của context.
- Chỉ sử dụng thông tin từ context, không dùng kiến thức ngoài.
2. *Sử dụng cấu trúc và từ vựng phức tạp hơn*
- *Ưu tiên dùng cụm từ đặc trưng của nhãn REFUTED và NEI như*:
- "vào năm", "đầu tiên", "duy nhất", "trên thế giới", "chỉ có"
- "có thể", "phát triển", "sử dụng", "công nghệ", "thực hiện", "ảnh hưởng"
- *Tạo câu claim SUPPORTED nhưng "ngụy trang" bằng cấu trúc của REFUTED/NEI*:
- "Vào năm [X], [Title] đã trở thành..." (dùng cụm từ đặc trưng của REFUTED)
- "[Title] có thể được coi là yếu tố quan trọng..." (dùng cụm từ đặc trưng của NEI)
- *Tránh sử dụng cụm từ quá đặc trưng của SUPPORTED*:
- Hạn chế dùng "quần đảo hoàng", "trong lịch sử", "miền nam việt"
3. *Kết hợp thông tin từ nhiều phần khác nhau của context*
- Ghép thông tin từ nhiều câu trong context để tạo claim mới.
- Thêm các chi tiết phụ nhưng vẫn nằm trong context.
- Kết hợp nhiều n-gram đặc trưng của REFUTED/NEI trong một câu SUPPORTED.
4. *Sử dụng chiến lược "đánh lừa" đặc biệt*
- Thêm "các quốc gia", "trên thế giới" vào câu nhưng giữ nguyên nội dung SUPPORTED.
- Dùng "không có bất kỳ" theo cách khẳng định thông tin đúng.
- Sử dụng "trở thành một" nhưng vẫn giữ thông tin chính xác.
- Thêm các cụm từ định lượng đặc trưng của REFUTED như "đầu tiên", "duy nhất" nhưng áp dụng chính xác.
5. *Kết hợp với từ vựng đặc trưng của NEI*
- Thêm "có thể", "phát triển", "sử dụng", "công nghệ" vào câu SUPPORTED.
- Sử dụng "ảnh hưởng", "thời gian", "khu vực" với dữ liệu chính xác từ context.
- Kết hợp "công ty", "tổ chức" vào câu khi phù hợp với context.
6. *Tránh lỗi phổ biến*
- Không copy nguyên văn từ context.
- Không tạo câu quá đơn giản hoặc rõ ràng là SUPPORTED.
- Tránh n-gram quá điển hình của SUPPORTED như "quần đảo hoàng sa", "miền nam việt nam".
"""
}
},
"required": ["claims"]
},
"REFUTED": {
"type": "object",
"properties": {
"claims": {
"type": "array",
"items": {
"type": "string"
},
"description": """
*Hướng dẫn tạo claim REFUTED có độ khó cao*:
1. *Bắt buộc đề cập, liên quan đến Title của context*
- Claim phải liên quan đến Title của context.
- Câu claim phải liên quan đến nội dung của context.
2. *Mượn cấu trúc và n-gram đặc trưng của SUPPORTED để tạo sự đánh lừa*
- Sử dụng n-gram đặc trưng của SUPPORTED:
- "một số", "so với", "đối với", "lãnh thổ", "thế kỷ"
- "trong lịch sử", "của người dân", "khoảng thời gian"
- Tạo câu có cấu trúc giống SUPPORTED nhưng thay đổi thông tin cốt lõi.
- Ví dụ: "Trong lịch sử phát triển của [Title], một số yếu tố đã dẫn đến..." (dùng cụm từ SUPPORTED)
3. *Kết hợp với n-gram đặc trưng của NEI*
- Thêm "có thể", "phát triển", "công nghệ" vào câu REFUTED.
- Dùng "sử dụng", "thực hiện", "ảnh hưởng" theo cách làm sai lệch thông tin.
- Kết hợp "tổ chức", "thời gian", "khu vực" với dữ liệu sai từ context.
4. *Chiến lược tạo thông tin sai nhưng khó phát hiện*
- *Thay đổi số liệu, đơn vị đo lường*: "500 triệu" → "5 triệu".
- *Dùng dữ kiện đúng nhưng gán sai nguyên nhân hoặc hậu quả*.
- *Lấy một phần thông tin đúng từ context nhưng thay đổi một chi tiết quan trọng*.
- *Đảo ngược quan hệ nhân quả*: "X gây ra Y" → "Y gây ra X".
- *Thay thế đối tượng hoặc thời gian*: "Apple phát triển iPhone" → "Samsung phát triển iPhone".
5. *Tận dụng n-gram đặc trưng của REFUTED một cách tinh tế*
- Sử dụng "vào năm" với năm sai.
- Dùng "chỉ có" với thông tin không chính xác.
- Áp dụng "duy nhất", "đầu tiên" với thông tin sai lệch.
- Tận dụng cụm từ "các quốc gia", "trên thế giới" để thêm thông tin sai lệch.
6. *Tránh lỗi phổ biến*
- Không tạo câu claim quá dễ đoán là REFUTED.
- Không sử dụng phủ định đơn giản như "không đúng", "không thể".
- Tránh n-gram quá điển hình của REFUTED như "không có bất kỳ", "việt nam là".
"""
}
},
"required": ["claims"]
},
"NEI": {
"type": "object",
"properties": {
"claims": {
"type": "array",
"items": {
"type": "string"
},
"description": """
*Hướng dẫn tạo claim NEI có độ khó cao*:
1. *Bắt buộc đề cập, liên quan đến Title của context*
- Claim phải liên quan đến Title của context.
- Câu claim phải liên quan đến nội dung của context.
2. *Mượn cấu trúc và n-gram của SUPPORTED và REFUTED để tạo sự đánh lừa*
- Sử dụng n-gram của SUPPORTED:
- "một số", "so với", "đối với", "lãnh thổ", "thế kỷ"
- "trong lịch sử", "của người dân", "khoảng thời gian"
- Sử dụng n-gram của REFUTED:
- "vào năm", "chỉ có", "duy nhất", "đầu tiên"
- "trở thành một", "liên quan đến", "được ghi nhận"
- Kết hợp cả hai nhóm n-gram vào câu NEI để làm tăng độ khó phân biệt.
3. *Tận dụng tối đa n-gram đặc trưng của NEI*
- Tập trung vào các cụm từ: "có thể", "phát triển", "sử dụng", "công nghệ"
- Sử dụng "công ty", "tổ chức", "thực hiện", "ảnh hưởng" theo cách mở rộng thông tin
- Áp dụng "thời gian", "khu vực", "thế giới" vào các dự đoán hoặc xu hướng
4. *Tạo suy luận hợp lý nhưng không có bằng chứng trong context*
- "Nếu [Title] tiếp tục phát triển với tốc độ hiện tại, nó có thể trở thành tiêu chuẩn công nghiệp vào năm 2030."
- Sử dụng n-gram đặc trưng của NEI: "sự phát triển", "có khả năng", "trong thời gian"
- Kết hợp với cụm từ đặc trưng của SUPPORTED và REFUTED để tạo độ khó cao
5. *Kết hợp thông minh các n-gram đặc trưng*
- "[Title] có thể được xem là một trong những yếu tố đầu tiên..." (kết hợp NEI + REFUTED)
- "Trong lịch sử phát triển, [Title] có thể ảnh hưởng đến..." (kết hợp SUPPORTED + NEI)
- "Vào năm [X], [Title] có thể đã phát triển..." (kết hợp REFUTED + NEI)
6. *Lưu ý*
- Nếu claim có ít nhất một phần thông tin chưa xác định, thì claim đó là NEI.
- Không tạo claim quá mơ hồ hoặc không liên quan.
- Không sử dụng thông tin sai, chỉ dùng thông tin chưa đủ để xác định.
- Tránh lạm dụng n-gram quá điển hình của NEI như "nhà khoa học", "con đường tơ lụa".
"""
}
},
"required": ["claims"]
}
}
def _configure_api(self):
"""Configure the API with the current key."""
genai.configure(api_key=self.current_key)
def _rotate_api_key(self):
"""No rotation needed since we only have one key per type."""
print(f"Using API key for {self.claim_type}")
def generate_claims(self, context: str, title: str, prompt_variation: str = None, max_retries: int = 3) -> List[str]:
"""Generate claims with basic error handling and improved diversity."""
retries = 0
# Add randomization to your prompts
variation_phrases = [
"Đừng lặp lại cấu trúc từ những câu trước đó.",
"Tạo câu với cấu trúc hoàn toàn khác biệt.",
"Sử dụng cách diễn đạt mới.",
"Đổi mới cách tiếp cận trong câu này.",
"Hãy thay đổi cấu trúc câu so với lần trước.",
"Sáng tạo với cách diễn đạt khác biệt.",
"Hãy tạo ra sự đa dạng về cấu trúc ngữ pháp."
]
random_instruction = random.choice(variation_phrases)
diversity_instruction = random.choice(self.diversity_instructions)
# Use prompt variation if provided, otherwise use default
if prompt_variation is None:
prompt_variation = self.prompt_variations[random.randint(0, len(self.prompt_variations) - 1)]
prompt_variation = prompt_variation.format(self.claim_type)
while retries < max_retries:
try:
# Adjust temperature and sampling parameters
temperature = 0.7 + (0.4 * random.random()) # Between 0.7 and 1.1
top_p = 0.85 + (0.15 * random.random()) # Between 0.85 and 1.0
top_k = 35 + int(10 * random.random()) # Between 35 and 45
model = genai.GenerativeModel(
'models/gemini-1.5-flash-latest',
generation_config={
"response_mime_type": "application/json",
"response_schema": self.SCHEMAS[self.claim_type],
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"max_output_tokens": 512
}
)
prompt = f"""
Fact checking là 1 trong lĩnh vực xử lý ngôn ngữ tự nhiên (NLP), là bài toán kiểm tra và xác minh tính chính xác của 1 nhận định được đưa ra trên các thông tin, tin tức từ mạng xã hội, báo chí. Mô hình NLP sẽ xác định câu nhận định đó là chính xác hay sai sự thật hoặc không được đề cập đến trong thông tin đã được cung cấp trước đó (context).
Tiêu đề: "{title}"
Context: "{context}"
Nhiệm vụ: {prompt_variation} Yêu cầu dài 15-60 từ, có suy luận hợp lý từ nội dung context.
QUAN TRỌNG: Sử dụng các cụm từ đặc trưng của các nhãn khác để đánh lừa mô hình:
{', '.join([pattern for label in ['SUPPORTED', 'REFUTED', 'NEI']
if label != self.claim_type
for pattern in self.ngram_patterns[label][:8]])}
{random_instruction}
{diversity_instruction}
Yêu cầu chi tiết:
1. BẮT BUỘC sử dụng ít nhất 2-3 cụm từ từ danh sách n-gram được cung cấp.
2. Kết hợp thông minh các cụm từ để tạo câu có ý nghĩa và khó dự đoán.
3. Câu claim PHẢI liên quan đến tiêu đề và nội dung của context.
4. TRÁNH copy nguyên văn từ context.
5. Sử dụng cấu trúc câu phức tạp, kết hợp nhiều thông tin.
6. Tránh lặp lại cấu trúc câu từ những lần tạo trước.
Trả về kết quả theo đúng format JSON với trường {self.claim_type}.
"""
prompt = re.sub(r'\s+', ' ', prompt).strip()
prompt = re.sub(r'\n', ' ', prompt).strip()
response = model.generate_content(prompt)
time.sleep(3) # Prevent rate limiting
claims_response = json.loads(response.text)["claims"]
# Filter out claims with similar patterns or already seen claims
filtered_claims = []
for claim in claims_response:
# Get pattern from first few words
words = claim.split()
if len(words) > 3:
pattern = ' '.join(words[:4]).lower()
else:
pattern = claim.lower()
if pattern not in self.used_patterns and claim not in st.session_state.all_generated_claims:
self.used_patterns.add(pattern)
st.session_state.all_generated_claims.append(claim)
filtered_claims.append(claim)
# If all claims were filtered, return at least one
if not filtered_claims and claims_response:
filtered_claims = [claims_response[0]]
if claims_response[0] not in st.session_state.all_generated_claims:
st.session_state.all_generated_claims.append(claims_response[0])
return filtered_claims or claims_response
except Exception as e:
st.error(f"Error during claim generation: {str(e)}")
retries += 1
if retries >= max_retries:
st.error(f"Max retries ({max_retries}) exceeded")
time.sleep(2) # Wait before retrying
return [] # Return empty list if all retries failed
def generate_hard_claims(self, context: str, title: str, predict_func, max_attempts: int = 3) -> Dict[str, Any]:
"""Generate and validate multiple claims."""
results = []
all_attempts = []
attempts = 0
# Xử lý mỗi lần tạo claim
while len(results) < 3 and attempts < max_attempts:
attempts += 1
# Select a different prompt variation for each attempt
prompt_variation = self.prompt_variations[attempts % len(self.prompt_variations)]
# Tạo claims một lần cho mỗi attempt
claims = self.generate_claims(context, title, prompt_variation=prompt_variation)
if not claims: # Kiểm tra nếu không có claims được tạo
continue
successful_claims_in_batch = 0
for claim in claims:
# Bỏ qua claim đã có trong results
if claim in [r['claim'] for r in results]:
continue
prediction = predict_func(context, claim)
current_prob = prediction['probabilities'][self.claim_type]
other_labels = [label for label in ["SUPPORTED", "REFUTED", "NEI"] if label != self.claim_type]
other_probs = [prediction['probabilities'][label] for label in other_labels]
sum_other_probs = sum(other_probs)
print(f"Attempt {attempts}, Claim: {claim}")
print(f"Current label probability: {current_prob:.2%}")
print(f"Other labels probabilities: {other_probs[0]:.2%}, {other_probs[1]:.2%}\n")
# Tạo entry cho attempt này
attempt_entry = {
'claim': claim,
'predicted_label': prediction['verdict'],
f'{self.claim_type}_probability': current_prob,
'other_probabilities': dict(zip(other_labels, other_probs)),
'all_probabilities': prediction['probabilities']
}
all_attempts.append(attempt_entry)
# Validation logic dựa trên loại claim
if self.claim_type == "NEI":
success = (current_prob < sum_other_probs) or any(current_prob < (other_prob + 0.1) for other_prob in other_probs)
else:
success = current_prob < sum_other_probs
if success:
results.append(attempt_entry)
successful_claims_in_batch += 1
# Thoát sớm nếu đã đủ 2 kết quả
if len(results) == 3:
break
# Nếu batch này không tạo được claim thành công nào và đã thử nhiều lần, giảm tiêu chí
if successful_claims_in_batch == 0 and attempts >= 3 and len(results) < 2:
# Chọn các claims tốt nhất từ những gì đã có nếu còn thiếu
remaining_needed = 2 - len(results)
if all_attempts:
# Sắp xếp theo xác suất tăng dần của claim type
potential_claims = [a for a in all_attempts if a['claim'] not in [r['claim'] for r in results]]
potential_claims.sort(key=lambda x: x[f'{self.claim_type}_probability'])
# Thêm các claims tốt nhất vào results nếu có
for i in range(min(remaining_needed, len(potential_claims))):
results.append(potential_claims[i])
# Thoát sớm nếu đã đủ kết quả
if len(results) == 3:
break
# Nếu vẫn chưa đủ 2 claim, lấy các claim có xác suất thấp nhất
if len(results) < 3 and all_attempts:
remaining_needed = 3 - len(results)
potential_claims = [a for a in all_attempts if a['claim'] not in [r['claim'] for r in results]]
potential_claims.sort(key=lambda x: x[f'{self.claim_type}_probability'])
for i in range(min(remaining_needed, len(potential_claims))):
results.append(potential_claims[i])
return {
'success': len(results) >= 2,
'attempts': attempts,
'results': results
}
def process_dataframe_with_claims(df: pd.DataFrame, selected_claim_types: List[str]) -> pd.DataFrame:
"""Process each summary in the DataFrame and generate claims with fallback handling."""
all_rows = []
progress_bar = st.progress(0)
for i, row in df.iterrows():
context = row['Summary']
title = row['Title']
for claim_type in selected_claim_types:
generator = ClaimGenerator(claim_type)
result = generator.generate_hard_claims(context, title, predict)
if result['success']:
for claim_result in result['results']:
new_row = row.copy()
new_row['claim'] = claim_result['claim']
new_row['label'] = claim_type
new_row['predicted_label'] = claim_result['predicted_label']
new_row['probability'] = claim_result[f'{claim_type}_probability']
new_row['all_probabilities'] = json.dumps(claim_result['all_probabilities'])
new_row['used_fallback'] = result.get('used_fallback', False)
all_rows.append(new_row)
if result.get('used_fallback'):
st.warning(f"Used fallback claims for article {row['ID']} {claim_type}")
progress_bar.progress((i + 1) / len(df))
new_df = pd.DataFrame(all_rows)
return new_df
def generate_claims_for_context(
context: str,
claim_types: List[str],
predict_func,
progress_bar
) -> List[Dict[str, Any]]:
"""Generate claims for the context with simplified processing."""
claims_data = []
total_steps = len(claim_types)
for i, claim_type in enumerate(claim_types):
progress_bar.progress((i / total_steps), f"Generating {claim_type} claims...")
generator = ClaimGenerator(claim_type)
result = generator.generate_hard_claims(context, predict_func)
if result['success']:
for claim_result in result['results']:
claim_data = {
'context': context,
'claim': claim_result['claim'],
'label': claim_type,
'predicted_label': claim_result['predicted_label'],
f'{claim_type}_probability': claim_result[f'{claim_type}_probability'],
'probabilities': claim_result['all_probabilities']
}
claims_data.append(claim_data)
return claims_data
# Wikipedia helper functions
def clean_wikipedia_content(content):
# Remove section headers
content = re.sub(r'==+.*?==+', '', content)
# Remove citation references [1], [2], etc.
content = re.sub(r'\[\d+\]', '', content)
# Remove "See also", "References", "External links" sections and everything after
patterns = [
r'== *Xem thêm *==.*',
r'== *Tham khảo *==.*',
r'== *Liên kết ngoài *==.*',
r'== *Chú thích *==.*',
r'== *Ghi chú *==.*'
]
for pattern in patterns:
content = re.sub(pattern, '', content, flags=re.DOTALL)
# Replace multiple newlines with single newline
content = re.sub(r'\n\s*\n', '\n', content)
# Clean extra whitespace
content = re.sub(r' +', ' ', content)
lines = [line.strip() for line in content.split("\n")]
cleaned_content = "\n\n".join([line for line in lines if line])
return cleaned_content
def split_vietnamese_paragraphs(text):
# Split by paragraph first (empty lines)
paragraphs = re.split(r'\n\s*\n', text)
paragraphs = [p.strip() for p in paragraphs if p.strip()]
return paragraphs
def split_sentences_vietnamese(text):
# Pre-processing: protect periods in abbreviations and special cases
# Common Vietnamese abbreviations
abbreviations = ['TS.', 'PGS.', 'GS.', 'ThS.', 'BS.', 'TP.', 'Tp.', 'T.P', 'Q.', 'P.']
# Temporarily replace special cases
for abbr in abbreviations:
text = text.replace(abbr, abbr.replace('.', '@'))
# Protect ellipses
text = text.replace("...", "###ELLIPSIS###")
# Split on sentence boundaries
pattern = r'(?<=[.!?])\s+(?=[A-ZÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬĐÈÉẺẼẸÊỀẾỂỄỆÌÍỈĨỊÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢÙÚỦŨỤƯỪỨỬỮỰỲÝỶỸỴ])'
sentences = re.split(pattern, text)
# Restore special cases
sentences = [s.replace("###ELLIPSIS###", "...") for s in sentences]
for abbr in abbreviations:
abbr_modified = abbr.replace('.', '@')
for i in range(len(sentences)):
sentences[i] = sentences[i].replace(abbr_modified, abbr)
# Clean sentences
sentences = [s.strip() for s in sentences if s.strip()]
return sentences
def merge_short_sentences(sentences, min_length=30):
"""Merge very short sentences with the next sentence if possible"""
if not sentences:
return []
result = []
current = sentences[0]
for i in range(1, len(sentences)):
if len(current) < min_length:
current += ' ' + sentences[i]
else:
result.append(current)
current = sentences[i]
if current:
result.append(current)
return result
def chunk_content(text, max_length=700, min_length=210):
"""Split content into chunks of appropriate size"""
paragraphs = split_vietnamese_paragraphs(text)
chunks = []
current_chunk = []
current_length = 0
for paragraph in paragraphs:
sentences = split_sentences_vietnamese(paragraph)
sentences = merge_short_sentences(sentences)
for sentence in sentences:
if current_length + len(sentence) > max_length and current_length >= min_length:
# Save current chunk
chunks.append(' '.join(current_chunk))
current_chunk = [sentence]
current_length = len(sentence)
else:
current_chunk.append(sentence)
current_length += len(sentence) + 1 # +1 for space
# Add remaining content
if current_chunk:
chunks.append(' '.join(current_chunk))
return chunks
def chars_to_nums(s):
return ''.join(str((char_to_num.get(char.upper(), 0) % 4) + 1) for char in s if char.upper() in char_to_num)
def create_id(topic, stt):
# Remove accents and convert to lowercase
def remove_accents(s):
s = s.lower()
s = s.replace('đ', 'd')
accents = {
'à': 'a', 'á': 'a', 'ả': 'a', 'ã': 'a', 'ạ': 'a',
'ằ': 'a', 'ắ': 'a', 'ẳ': 'a', 'ẵ': 'a', 'ặ': 'a',
'ầ': 'a', 'ấ': 'a', 'ẩ': 'a', 'ẫ': 'a', 'ậ': 'a',
'è': 'e', 'é': 'e', 'ẻ': 'e', 'ẽ': 'e', 'ẹ': 'e',
'ề': 'e', 'ế': 'e', 'ể': 'e', 'ễ': 'e', 'ệ': 'e',
'ì': 'i', 'í': 'i', 'ỉ': 'i', 'ĩ': 'i', 'ị': 'i',
'ò': 'o', 'ó': 'o', 'ỏ': 'o', 'õ': 'o', 'ọ': 'o',
'ồ': 'o', 'ố': 'o', 'ổ': 'o', 'ỗ': 'o', 'ộ': 'o',
'ờ': 'o', 'ớ': 'o', 'ở': 'o', 'ỡ': 'o', 'ợ': 'o',
'ù': 'u', 'ú': 'u', 'ủ': 'u', 'ũ': 'u', 'ụ': 'u',
'ừ': 'u', 'ứ': 'u', 'ử': 'u', 'ữ': 'u', 'ự': 'u',
'ỳ': 'y', 'ý': 'y', 'ỷ': 'y', 'ỹ': 'y', 'ỵ': 'y'
}
for accent, base in accents.items():
s = s.replace(accent, base)
return s
# Clean and abbreviate topic
topic_clean = remove_accents(topic)
topic_abbr = ''.join([word[0] for word in topic_clean.split() if word])
# Generate numeric representation
def chars_to_nums(s):
char_to_num = {
'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5,
'f': 6, 'g': 7, 'h': 8, 'i': 9, 'j': 10,
'k': 11, 'l': 12, 'm': 13, 'n': 14, 'o': 15,
'p': 16, 'q': 17, 'r': 18, 's': 19, 't': 20,
'u': 21, 'v': 22, 'w': 23, 'x': 24, 'y': 25, 'z': 26
}
return ''.join(str((char_to_num.get(char, 0) % 4) + 1) for char in s)
# Generate unique ID
topic_num = chars_to_nums(topic_abbr)
return f'uit_{topic_num}_{stt}'
def wikipedia_scrape(title_input, stt, filename):
try:
page = wikipedia.page(title_input)
title = page.title
content = wikipedia.summary(title_input, sentences=15)
url = page.url
topic = filename.split(".")[0]
base_article_id = create_id(topic, stt)
# Clean and prepare content
cleaned_content = clean_wikipedia_content(content)
# Chunk content into appropriate parts
chunks = chunk_content(cleaned_content)
results = []
for i, chunk_text in enumerate(chunks, 1):
if len(chunk_text) >= 210: # Only include chunks that meet minimum length
article_id = f"{base_article_id}_{i}"
results.append({
"ID": article_id,
"Title": title,
"Topic": topic,
"Summary": chunk_text,
"URL": url
})
return results
except (wikipedia.exceptions.DisambiguationError, wikipedia.exceptions.PageError) as e:
print(f"Error processing {title_input}: {e}")
return None
@st.cache_data
def convert_df_to_csv(df):
return df.to_csv(index=False).encode("utf-8-sig")
def main():
st.title("Ứng dụng crawl data wiki tiếng việt và tạo claims")
uploaded_file = st.file_uploader("Tải lên tệp văn bản (txt)", type=["txt"])
if uploaded_file is not None:
file_progress = st.progress(0)
status_text = st.empty()
file_contents = uploaded_file.getvalue().decode("utf-8")
titles = file_contents.split("\n")
articles_info = []
for index, title in enumerate(titles):
title = title.strip()
if title:
status_text.text(f"Processing article {index + 1}/{len(titles)}: {title}")
article_infos = wikipedia_scrape(title, index + 1, uploaded_file.name)
# Safely append results, handling potential None values
if article_infos:
articles_info.extend(article_infos)
file_progress.progress((index + 1) / len(titles))
else:
st.warning(f"Dòng {index + 1}: Tiêu đề trống")
file_progress.empty()
status_text.empty()
if not articles_info:
st.error("Không có bài viết nào được thu thập từ Wikipedia.")
else:
df = pd.DataFrame(articles_info)
st.subheader("Danh sách bài viết từ Wikipedia:")
st.dataframe(df, use_container_width=True)
# Add claim type selection
claim_types = ["SUPPORTED", "REFUTED", "NEI"]
selected_claim_types = st.multiselect(
"Chọn loại claim cần tạo:",
claim_types,
default=claim_types
)
# Add automatic claim generation for all summaries
if st.button("Tạo Claims cho tất cả bài viết"):
if not selected_claim_types:
st.error("Vui lòng chọn ít nhất một loại claim để tạo.")
else:
with st.spinner("Đang tạo claims cho tất cả bài viết..."):
# Process the DataFrame and generate claims
df_with_claims = process_dataframe_with_claims(df, selected_claim_types)
st.subheader("Bảng dữ liệu với Claims:")
st.dataframe(df_with_claims, use_container_width=True)
# Download options
csv = convert_df_to_csv(df_with_claims)
claims_filename = f"uit_{uploaded_file.name.split('.')[0]}_with_claims.csv"
st.download_button(
label="Download CSV với Claims",
data=csv,
file_name=claims_filename,
mime="text/csv",
)
# Original download option for basic DataFrame
csv = convert_df_to_csv(df)
csv_filename = "uit_" + uploaded_file.name.split(".")[0] + ".csv"
st.download_button(
label="Download CSV (không có claims)",
data=csv,
file_name=csv_filename,
mime="text/csv",
)
if __name__ == "__main__":
main()