MicroFish/backend/app/services/simulation_config_generator.py

1112 lines
51 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Trình tạo tạo ra cấu hình Simulation tự động
Sử dụng LLM theo yêu cầu mô phỏng, nội dung tài liệu và thông tin đồ thị để tự động thiết lập chi tiết các tham số
Tất cả đều tự động mà không cần can thiệp thủ công tạo tham số
Áp dụng chiến lược tạo từng bước để tránh lỗi do cố gắng tạo nội dung quá dài cùng một lúc:
1. Tạo cấu hình thời gian
2. Tạo cấu hình các Event
3. Tạo cấu hình cho các Agent theo đợt
4. Tạo cấu hình nền tảng
"""
import json
import math
from typing import Dict, Any, List, Optional, Callable
from dataclasses import dataclass, field, asdict
from datetime import datetime
from openai import OpenAI
from ..config import Config
from ..utils.logger import get_logger
from ..utils.llm_cost import create_tracked_chat_completion
from .zep_entity_reader import EntityNode, ZepEntityReader
logger = get_logger('mirofish.simulation_config')
# Cấu hình thời gian thói quen Trung Quốc (Theo giờ Bắc Kinh)
CHINA_TIMEZONE_CONFIG = {
# Khung giờ khuya (Hầu như không có hoạt động)
"dead_hours": [0, 1, 2, 3, 4, 5],
# Khung giờ sáng (Dần thức dậy)
"morning_hours": [6, 7, 8],
# Khung giờ làm việc
"work_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
# Khung giờ cao điểm buổi tối (Hoạt động mạnh nhất)
"peak_hours": [19, 20, 21, 22],
# Khung giờ ban đêm (Hoạt động giảm sút)
"night_hours": [23],
# Hệ số hoạt động tương ứng với mỗi thời điểm
"activity_multipliers": {
"dead": 0.05, # Gần như không có ai lúc rạng sáng
"morning": 0.4, # Sáng sớm bắt đầu dần sôi động
"work": 0.7, # Mức trung bình trong giờ làm việc
"peak": 1.5, # Cao điểm tối
"night": 0.5 # Giảm sút đêm khuya
}
}
@dataclass
class AgentActivityConfig:
"""Cấu hình hoạt động cho một Agent"""
agent_id: int
entity_uuid: str
entity_name: str
entity_type: str
# Mức độ hoạt động (0.0-1.0)
activity_level: float = 0.5 # Hoạt động tổng thể
# Tần suất phát ngôn (Số lần comment dự kiến mỗi giờ)
posts_per_hour: float = 1.0
comments_per_hour: float = 2.0
# Khoảng thời gian hoạt động (Hệ 24 giờ, 0-23)
active_hours: List[int] = field(default_factory=lambda: list(range(8, 23)))
# Tốc độ phản hồi (Độ trễ phản ứng với sự kiện nóng, đơn vị: phút mô phỏng)
response_delay_min: int = 5
response_delay_max: int = 60
# Khuynh hướng cảm xúc (-1.0 đến 1.0, từ tiêu cực đến tích cực)
sentiment_bias: float = 0.0
# Lập trường (Thái độ đối với chủ đề cụ thể)
stance: str = "neutral" # supportive, opposing, neutral, observer
# Trọng số ảnh hưởng (Xác định mức độ bài đăng được Agent khác nhìn thấy)
influence_weight: float = 1.0
@dataclass
class TimeSimulationConfig:
"""Cấu hình thời gian mô phỏng (Dựa trên thói quen sinh hoạt của người Trung)"""
# Tổng thời gian mô phỏng (Giờ)
total_simulation_hours: int = 72 # Mặc định là chạy mô phỏng 72 tiếng (3 ngày)
# Số phút đại diện cho mỗi vòng - Mặc định 60 phút (1 giờ), đẩy nhanh thời gian
minutes_per_round: int = 60
# Phạm vi số lượng Agent kích hoạt mỗi giờ
agents_per_hour_min: int = 5
agents_per_hour_max: int = 20
# Giờ cao điểm (19-22 giờ tối, thời gian sôi động nhất)
peak_hours: List[int] = field(default_factory=lambda: [19, 20, 21, 22])
peak_activity_multiplier: float = 1.5
# Khung giờ chết (0-5 giờ, hầu như không ai on)
off_peak_hours: List[int] = field(default_factory=lambda: [0, 1, 2, 3, 4, 5])
off_peak_activity_multiplier: float = 0.05 # Rạng sáng gần như bằng không
# Khung giờ buổi sáng
morning_hours: List[int] = field(default_factory=lambda: [6, 7, 8])
morning_activity_multiplier: float = 0.4
# Khung giờ làm việc
work_hours: List[int] = field(default_factory=lambda: [9, 10, 11, 12, 13, 14, 15, 16, 17, 18])
work_activity_multiplier: float = 0.7
@dataclass
class EventConfig:
"""Cấu hình sự kiện cho Simulation"""
# Các bài Post/Sự kiện khởi đầu (Bắt đầu ngay khi chạy mô phỏng)
initial_posts: List[Dict[str, Any]] = field(default_factory=list)
# Các sự kiện được lập lịch vào các thời điểm nhất định
scheduled_events: List[Dict[str, Any]] = field(default_factory=list)
# Từ khóa dành cho các chủ đề đang hot (Hot topics)
hot_topics: List[str] = field(default_factory=list)
# Hướng dẫn dư luận / Đường lối thảo luận
narrative_direction: str = ""
@dataclass
class PlatformConfig:
"""Cấu hình đặc thù dành riêng cho các nền tảng"""
platform: str # twitter or reddit
# Trọng số cho các thuật toán đề xuất
recency_weight: float = 0.4 # Độ mới của bài
popularity_weight: float = 0.3 # Mức độ phổ biến truyền miệng
relevance_weight: float = 0.3 # Mức độ quan tâm / tương quan
# Ngưỡng lan truyền virus (Cần bao nhiêu tương tác để nội dung bắt đầu phát tán mạnh)
viral_threshold: int = 10
# Độ mạnh của hiệu ứng lan truyền trong nhóm chung chí hướng (buồng phản âm)
echo_chamber_strength: float = 0.5
@dataclass
class SimulationParameters:
"""完整的模拟参数配置"""
# 基础信息
simulation_id: str
project_id: str
graph_id: str
simulation_requirement: str
# Cấu hình thời gian
time_config: TimeSimulationConfig = field(default_factory=TimeSimulationConfig)
# Danh sách cấu hình Agent
agent_configs: List[AgentActivityConfig] = field(default_factory=list)
# Cấu hình Event
event_config: EventConfig = field(default_factory=EventConfig)
# Cấu hình nền tảng
twitter_config: Optional[PlatformConfig] = None
reddit_config: Optional[PlatformConfig] = None
# Cấu hình LLM
llm_model: str = ""
llm_base_url: str = ""
# Dữ liệu metadata khi tạo
generated_at: str = field(default_factory=lambda: datetime.now().isoformat())
generation_reasoning: str = "" # Giải thích suy luận từ LLM
def to_dict(self) -> Dict[str, Any]:
"""Convert sang định dạng Dictionary"""
time_dict = asdict(self.time_config)
return {
"simulation_id": self.simulation_id,
"project_id": self.project_id,
"graph_id": self.graph_id,
"simulation_requirement": self.simulation_requirement,
"time_config": time_dict,
"agent_configs": [asdict(a) for a in self.agent_configs],
"event_config": asdict(self.event_config),
"twitter_config": asdict(self.twitter_config) if self.twitter_config else None,
"reddit_config": asdict(self.reddit_config) if self.reddit_config else None,
"llm_model": self.llm_model,
"llm_base_url": self.llm_base_url,
"generated_at": self.generated_at,
"generation_reasoning": self.generation_reasoning,
}
def to_json(self, indent: int = 2) -> str:
"""Convert sang định dạng chuỗi JSON"""
return json.dumps(self.to_dict(), ensure_ascii=False, indent=indent)
class SimulationConfigGenerator:
"""
Trình tạo cấu hình Simulation tự động bằng LLM
Sử dụng LLM phân tích yêu cầu mô phỏng, nội dung tài liệu, Entity từ đồ thị,
Tự động xây dựng các thông số cấu trúc tối ưu cho đợt Simulation
Áp dụng chiến lược tạo từng bước:
1. Tạo cấu hình thời gian và cấu hình Event (Nhẹ, chạy nhanh)
2. Phân nhỏ đợt tạo cấu hình cho Agent (Khoảng 10-20 agent mỗi đợt)
3. Tạo cấu hình nền tảng
"""
# Số lượng ký tự tối đa của bộ context
MAX_CONTEXT_LENGTH = 50000
# Số lượng Agent để gen cho một lần
AGENTS_PER_BATCH = 15
# Số lượng ký tự giới hạn ở các bước để cắt chuỗi (Ký tự đoạn)
TIME_CONFIG_CONTEXT_LENGTH = 10000 # Cấu hình thời gian
EVENT_CONFIG_CONTEXT_LENGTH = 8000 # Cấu hình sự kiện
ENTITY_SUMMARY_LENGTH = 300 # Tóm tắt các thực thể
AGENT_SUMMARY_LENGTH = 300 # Tóm tắt cấu hình Agent
ENTITIES_PER_TYPE_DISPLAY = 20 # Lượng thực thể cho mổi loại để hiển thị
def __init__(
self,
api_key: Optional[str] = None,
base_url: Optional[str] = None,
model_name: Optional[str] = None
):
self.api_key = api_key or Config.LLM_API_KEY
self.base_url = base_url or Config.LLM_BASE_URL
self.model_name = model_name or Config.LLM_MODEL_NAME
if not self.api_key:
raise ValueError("LLM_API_KEY has not been configured")
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url
)
self._runtime_metadata: Dict[str, Any] = {}
def generate_config(
self,
simulation_id: str,
project_id: str,
graph_id: str,
simulation_requirement: str,
document_text: str,
entities: List[EntityNode],
enable_twitter: bool = True,
enable_reddit: bool = True,
progress_callback: Optional[Callable[[int, int, str], None]] = None,
) -> SimulationParameters:
"""
Tạo cấu hình Simulation thông minh tự động hoàn chỉnh (Bằng tư duy chia từng bước)
Args:
simulation_id: Nhận dạng quy trình chạy Simulation
project_id: Mã định danh dự án
graph_id: Đồ thị đồ thị
simulation_requirement: Yêu cầu của quá trình mô phỏng
document_text: Nội dung file tài liệu nguồn
entities: Danh sách các thực thể đã được lọc
enable_twitter: Cờ hiệu để bật Twitter
enable_reddit: Cờ hiệu để bật Reddit
progress_callback: Hàm callback lấy trạng thái tiến trình hiện tại (current_step, total_steps, message)
Returns:
SimulationParameters: Bộ tổng cấu hình thông số đầy đủ
"""
logger.info(f"Start generating simulation configuration: simulation_id={simulation_id}, entity_count={len(entities)}")
self._runtime_metadata = {
"simulation_id": simulation_id,
"project_id": project_id,
"component": "simulation_config_generator",
"phase": "prepare_simulation_config",
}
# Tính toán tổng số bước
num_batches = math.ceil(len(entities) / self.AGENTS_PER_BATCH)
total_steps = 3 + num_batches # Cấu hình tgian + Sự kiện + Nx(Agent Batch) + Nền tảng
current_step = 0
def report_progress(step: int, message: str):
nonlocal current_step
current_step = step
if progress_callback:
progress_callback(step, total_steps, message)
logger.info(f"[{step}/{total_steps}] {message}")
# 1. Xây dựng thông tin ngữ cảnh cơ bản
context = self._build_context(
simulation_requirement=simulation_requirement,
document_text=document_text,
entities=entities
)
reasoning_parts = []
# ========== Bước 1: Tạo bộ cấu hình về Thời Gian ==========
report_progress(1, "Generating time configuration...")
num_entities = len(entities)
time_config_result = self._generate_time_config(context, num_entities)
time_config = self._parse_time_config(time_config_result, num_entities)
reasoning_parts.append(f"Time config reasoning: {time_config_result.get('reasoning', 'Success')}")
# ========== Bước 2: Tạo cấu hình Event ==========
report_progress(2, "Generating event configuration and hot topics...")
event_config_result = self._generate_event_config(context, simulation_requirement, entities)
event_config = self._parse_event_config(event_config_result)
reasoning_parts.append(f"Event config reasoning: {event_config_result.get('reasoning', 'Success')}")
# ========== Bước 3-N: Chia thành các đợt để lấy cấu hình Agent ==========
all_agent_configs = []
for batch_idx in range(num_batches):
start_idx = batch_idx * self.AGENTS_PER_BATCH
end_idx = min(start_idx + self.AGENTS_PER_BATCH, len(entities))
batch_entities = entities[start_idx:end_idx]
report_progress(
3 + batch_idx,
f"Generating agent configuration ({start_idx + 1}-{end_idx}/{len(entities)})..."
)
batch_configs = self._generate_agent_configs_batch(
context=context,
entities=batch_entities,
start_idx=start_idx,
simulation_requirement=simulation_requirement
)
all_agent_configs.extend(batch_configs)
reasoning_parts.append(f"Agent config reasoning: Successfully generated {len(all_agent_configs)} agents")
# ========== Tiến hành gán người (Agent) để đăng các bài Initial Post ==========
logger.info("Assigning poster agents for initial posts...")
event_config = self._assign_initial_post_agents(event_config, all_agent_configs)
assigned_count = len([p for p in event_config.initial_posts if p.get("poster_agent_id") is not None])
reasoning_parts.append(f"Initial post assignment: {assigned_count} posts have been assigned to publishers")
# ========== Bước cuối: Thiết lập nền tảng ==========
report_progress(total_steps, "Generating platform configuration...")
twitter_config = None
reddit_config = None
if enable_twitter:
twitter_config = PlatformConfig(
platform="twitter",
recency_weight=0.4,
popularity_weight=0.3,
relevance_weight=0.3,
viral_threshold=10,
echo_chamber_strength=0.5
)
if enable_reddit:
reddit_config = PlatformConfig(
platform="reddit",
recency_weight=0.3,
popularity_weight=0.4,
relevance_weight=0.3,
viral_threshold=15,
echo_chamber_strength=0.6
)
# Xây dựng các tham số cuối cùng kết thúc quy trình
params = SimulationParameters(
simulation_id=simulation_id,
project_id=project_id,
graph_id=graph_id,
simulation_requirement=simulation_requirement,
time_config=time_config,
agent_configs=all_agent_configs,
event_config=event_config,
twitter_config=twitter_config,
reddit_config=reddit_config,
llm_model=self.model_name,
llm_base_url=self.base_url,
generation_reasoning=" | ".join(reasoning_parts)
)
logger.info(f"Simulation configuration generation complete: {len(params.agent_configs)} agent configs created")
return params
def _build_context(
self,
simulation_requirement: str,
document_text: str,
entities: List[EntityNode]
) -> str:
"""Thực hiện xây dựng nội dung Prompt Ngữ cảnh cho LLM, với độ dài có thể bị giới hạn"""
# Tóm tắt lại Thực thể
entity_summary = self._summarize_entities(entities)
# Xây dựng nội dung
context_parts = [
f"## Simulation Requirements\n{simulation_requirement}",
f"\n## Entity Information ({len(entities)} entities)\n{entity_summary}",
]
current_length = sum(len(p) for p in context_parts)
remaining_length = self.MAX_CONTEXT_LENGTH - current_length - 500 # Dành sẵn 500 ký tự trống
if remaining_length > 0 and document_text:
doc_text = document_text[:remaining_length]
if len(document_text) > remaining_length:
doc_text += "\n...(Document Truncated)"
context_parts.append(f"\n## Original Document Content\n{doc_text}")
return "\n".join(context_parts)
def _summarize_entities(self, entities: List[EntityNode]) -> str:
"""Tạo chuỗi văn bản Tóm tắt cho các Thực thể"""
lines = []
# Phân nhóm bằng Loại
by_type: Dict[str, List[EntityNode]] = {}
for e in entities:
t = e.get_entity_type() or "Unknown"
if t not in by_type:
by_type[t] = []
by_type[t].append(e)
for entity_type, type_entities in by_type.items():
lines.append(f"\n### {entity_type} ({len(type_entities)} entity)")
# Số lượng đã được thiết lập mặc định và Giới hạn chiều dài của bảng tóm tắt
display_count = self.ENTITIES_PER_TYPE_DISPLAY
summary_len = self.ENTITY_SUMMARY_LENGTH
for e in type_entities[:display_count]:
summary_preview = (e.summary[:summary_len] + "...") if len(e.summary) > summary_len else e.summary
lines.append(f"- {e.name}: {summary_preview}")
if len(type_entities) > display_count:
lines.append(f" ... and {len(type_entities) - display_count} more entities")
return "\n".join(lines)
def _call_llm_with_retry(self, prompt: str, system_prompt: str) -> Dict[str, Any]:
"""Tích hợp cơ chế retry mỗi lúc gọi Request LLM bị lỗi và Logic sửa lỗi JSON string"""
import re
max_attempts = 3
last_error = None
for attempt in range(max_attempts):
try:
response = create_tracked_chat_completion(
client=self.client,
model=self.model_name,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
response_format={"type": "json_object"},
temperature=0.7 - (attempt * 0.1), # Giảm temperature cho mỗi lần retry
metadata=self._runtime_metadata,
)
content = response.choices[0].message.content
finish_reason = response.choices[0].finish_reason
# Kiểm tra nội dung trã về xem có phải bị chặn vì thiếu token (Length vượt qua max) hay không
if finish_reason == 'length':
logger.warning(f"LLM output was truncated (attempt {attempt+1})")
content = self._fix_truncated_json(content)
# Phân tích nội dung JSON
try:
return json.loads(content)
except json.JSONDecodeError as e:
logger.warning(f"Failed to parse JSON (attempt {attempt+1}): {str(e)[:80]}")
# Tiến hành sửa chữa nội dung JSON nếu bị lỗi
fixed = self._try_fix_config_json(content)
if fixed:
return fixed
last_error = e
except Exception as e:
logger.warning(f"Failed to call LLM (attempt {attempt+1}): {str(e)[:80]}")
last_error = e
import time
time.sleep(2 * (attempt + 1))
raise last_error or Exception("LLM connection completely failed")
def _fix_truncated_json(self, content: str) -> str:
"""Đóng dấu ngoặc JSON một cách an toàn cho các string bị cắt ngang"""
content = content.strip()
# Đếm các dấu ngoặc mở bị bỏ sót chưa đóng
open_braces = content.count('{') - content.count('}')
open_brackets = content.count('[') - content.count(']')
# Đảm bảo các thuộc tính string đã được bọc đủ dấu ngoặc kép
if content and content[-1] not in '",}]':
content += '"'
# Thêm ngoặc đóng cho toàn bộ
content += ']' * open_brackets
content += '}' * open_braces
return content
def _try_fix_config_json(self, content: str) -> Optional[Dict[str, Any]]:
"""Cố gắng khôi phục, chắp ghép lại file cấu trúc config JSON"""
import re
# Điền những dấu ngoặc vào chuỗi bị cắt
content = self._fix_truncated_json(content)
# Regex ra đúng phần ruột nội dung JSON
json_match = re.search(r'\{[\s\S]*\}', content)
if json_match:
json_str = json_match.group()
# Loại bỏ các đoạn tab, ngắt line cho string
def fix_string(match):
s = match.group(0)
s = s.replace('\n', ' ').replace('\r', ' ')
s = re.sub(r'\s+', ' ', s)
return s
json_str = re.sub(r'"[^"\\]*(?:\\.[^"\\]*)*"', fix_string, json_str)
try:
return json.loads(json_str)
except:
# Tìm và xóa các control character
json_str = re.sub(r'[\x00-\x1f\x7f-\x9f]', ' ', json_str)
json_str = re.sub(r'\s+', ' ', json_str)
try:
return json.loads(json_str)
except:
pass
return None
def _generate_time_config(self, context: str, num_entities: int) -> Dict[str, Any]:
"""Tạo cấu hình thời gian (Time config) cho các tiến trình"""
# Áp dụng nội dung ngữ cảnh đã được giới hạn chiều dài
context_truncated = context[:self.TIME_CONFIG_CONTEXT_LENGTH]
# Cắt lấy số lượng Tối đa số lượng (Chiếm 80% từ số lượng lượng Agent thực thể)
max_agents_allowed = max(1, int(num_entities * 0.9))
# prompt = f"""Based on the following simulation requirements, generate a time simulation configuration.
# {context_truncated}
# ## Task
# Please generate a time configuration JSON.
# ### General Principles (For reference only; adjust flexibly based on specific events and participant groups):
# - The user group consists of Vietnamese people; must comply with Hanoi Time (CST) daily routines.
# - 0:005:00 AM: Almost no activity (Activity Coefficient: 0.05).
# - 6:008:00 AM: Gradual increase in activity (Activity Coefficient: 0.4).
# - 9:00 AM6:00 PM (Work hours): Moderate activity (Activity Coefficient: 0.7).
# - 7:00 PM10:00 PM: Peak period (Activity Coefficient: 1.5).
# - After 11:00 PM: Activity declines (Activity Coefficient: 0.5).
# - General Pattern: Low activity in the early morning, gradual increase in the morning, moderate during work hours, and peak in the evening.
# - **Important:**: The example values below are for reference only. You need to adjust specific periods based on the nature of the event and characteristics of the participant group.
# - e.g., The peak for students might be 9:00 PM11:00 PM; Media groups remain active all day; Official organizations only during work hours.
# - e.g., Breaking news may lead to discussions late at night; off_peak_hours can be shortened accordingly.
# ### Return JSON Format (Do not use Markdown)
# Example:
# {{
# "total_simulation_hours": 72,
# "minutes_per_round": 60,
# "agents_per_hour_min": 5,
# "agents_per_hour_max": 50,
# "peak_hours": [19, 20, 21, 22],
# "off_peak_hours": [0, 1, 2, 3, 4, 5],
# "morning_hours": [6, 7, 8],
# "work_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
# "reasoning": "Time configuration explanation for this specific event."
# }}
# Field Descriptions:
# - total_simulation_hours (int): Total simulation duration, 24168 hours. Short for breaking news, long for sustained topics.
# - minutes_per_round (int): Duration per round, 30120 minutes, suggested 60 minutes.
# - agents_per_hour_min (int): Minimum activated agents per hour (Range: 1-{max_agents_allowed}).
# - agents_per_hour_max (int): Maximum activated agents per hour (Range: 1-{max_agents_allowed}).
# - peak_hours (int array): Peak hours, adjusted based on the participant group.
# - off_peak_hours (int array): Off-peak hours, usually late night/early morning.
# - morning_hours (int array): Morning hours.
# - work_hours (int array): Working hours.
# - reasoning (string): Brief explanation of why this configuration was chosen."""
prompt = f"""Dựa trên các yêu cầu mô phỏng dưới đây, hãy tạo cấu hình mô phỏng thời gian.
{context_truncated}
## Nhiệm vụ
Hãy tạo JSON cấu hình thời gian.
### Nguyên tắc cơ bản (Chỉ mang tính chất tham khảo, cần điều chỉnh linh hoạt theo sự kiện cụ thể và nhóm đối tượng tham gia):
- Nhóm người dùng là người Việt Nam, cần phù hợp với thói quen sinh hoạt theo giờ Hà Nội.
- 0-5 giờ sáng: Hầu như không có hoạt động (Hệ số hoạt động: 0.05).
- 6-8 giờ sáng: Hoạt động tăng dần (Hệ số hoạt động: 0.4).
- 9-18 giờ (Giờ làm việc): Hoạt động trung bình (Hệ số hoạt động: 0.7).
- 19-22 giờ tối: Giai đoạn cao điểm (Hệ số hoạt động: 1.5).
- Sau 23 giờ: Mức độ hoạt động giảm xuống (Hệ số hoạt động: 0.5).
- Quy luật chung: Thấp vào rạng sáng, tăng dần vào buổi sáng, trung bình trong giờ làm việc và cao điểm vào buổi tối.
- **Quan trọng**: Các giá trị ví dụ dưới đây chỉ mang tính chất tham khảo, bạn cần điều chỉnh các khung giờ cụ thể dựa trên tính chất sự kiện và đặc điểm của nhóm đối tượng tham gia.
- Ví dụ: Cao điểm của nhóm sinh viên có thể là 21-23 giờ; Nhóm truyền thông hoạt động cả ngày; Các cơ quan chính thống chỉ hoạt động trong giờ hành chính.
- Ví dụ: Tin tức nóng hổi (hotspot) có thể dẫn đến thảo luận vào đêm muộn; off_peak_hours có thể được rút ngắn tương ứng.
### Định dạng JSON trả về (Không sử dụng markdown)
Ví dụ Format như sau:
{{
"total_simulation_hours": 72,
"minutes_per_round": 60,
"agents_per_hour_min": 5,
"agents_per_hour_max": 50,
"peak_hours": [19, 20, 21, 22],
"off_peak_hours": [0, 1, 2, 3, 4, 5],
"morning_hours": [6, 7, 8],
"work_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
"reasoning": "Giải thích cấu hình thời gian cho sự kiện này."
}}
Mô tả các trường:
- total_simulation_hours (int): Tổng thời gian mô phỏng, từ 24-168 giờ. Ngắn cho sự kiện đột xuất, dài cho các chủ đề kéo dài.
- minutes_per_round (int): Thời gian mỗi hiệp, 30-120 phút, khuyến nghị 60 phút.
- agents_per_hour_min (int): Số lượng Agent kích hoạt tối thiểu mỗi giờ (Phạm vi: 1-{max_agents_allowed}).
- agents_per_hour_max (int): Số lượng Agent kích hoạt tối đa mỗi giờ (Phạm vi: 1-{max_agents_allowed}).
- peak_hours (int array): Khung giờ cao điểm, điều chỉnh theo nhóm tham gia.
- off_peak_hours (int array): Khung giờ thấp điểm, thường là đêm khuya rạng sáng.
- morning_hours (mảng int): Khung giờ buổi sáng.
- work_hours (mảng int): Khung giờ làm việc.
- reasoning (string): Giải thích ngắn gọn lý do tại sao cấu hình như vậy."""
# system_prompt = "You are a social media simulation expert. Return in pure JSON format; time configurations must comply with Vietnamese daily routines."
system_prompt = "Bạn là chuyên gia mô phỏng mạng xã hội. Trả về định dạng JSON thuần túy; cấu hình thời gian cần phù hợp với thói quen sinh hoạt của người Việt Nam."
try:
return self._call_llm_with_retry(prompt, system_prompt)
except Exception as e:
logger.warning(f"Failed to generate Time Config through LLM {e}. Returning the basic default rules...")
return self._get_default_time_config(num_entities)
def _get_default_time_config(self, num_entities: int) -> Dict[str, Any]:
"""Tạo sẵn file chuẩn nếu bị đơ để trả ra theo múi giờ chuẩn sinh hoạt China"""
return {
"total_simulation_hours": 72,
"minutes_per_round": 60, # 1 Hour / Vòng -> Rút ngắn Time
"agents_per_hour_min": max(1, num_entities // 15),
"agents_per_hour_max": max(5, num_entities // 5),
"peak_hours": [19, 20, 21, 22],
"off_peak_hours": [0, 1, 2, 3, 4, 5],
"morning_hours": [6, 7, 8],
"work_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
"reasoning": "Defaults to Vietnamese users' daily routines and working hours (1 hour/round)"
}
def _parse_time_config(self, result: Dict[str, Any], num_entities: int) -> TimeSimulationConfig:
"""Phân tích nội dung được định hình của JSON qua hàm parse kiểm tra, Xác nhận nếu lượng agents_per_hour vượt ngưỡng giới hạn """
# Lấy giá trị chưa chỉnh sửa
agents_per_hour_min = result.get("agents_per_hour_min", max(1, num_entities // 15))
agents_per_hour_max = result.get("agents_per_hour_max", max(5, num_entities // 5))
# Tiến hành kiểm tra xác minh: Đảm bảo độ lớn không lớn hơn con số Total Agent
if agents_per_hour_min > num_entities:
logger.warning(f"agents_per_hour_min ({agents_per_hour_min}) exceeds total number of Agents ({num_entities}), corrected.")
agents_per_hour_min = max(1, num_entities // 10)
if agents_per_hour_max > num_entities:
logger.warning(f"agents_per_hour_max ({agents_per_hour_max}) exceeds total number of Agents ({num_entities}), corrected.")
agents_per_hour_max = max(agents_per_hour_min + 1, num_entities // 2)
# Đảm bảo min luôn luôn nhỏ hơn max
if agents_per_hour_min >= agents_per_hour_max:
agents_per_hour_min = max(1, agents_per_hour_max // 2)
logger.warning(f"agents_per_hour_min >= max, modified to {agents_per_hour_min}")
return TimeSimulationConfig(
total_simulation_hours=result.get("total_simulation_hours", 72),
minutes_per_round=result.get("minutes_per_round", 60), # Mặc định mỗi vòng = 1 giờ
agents_per_hour_min=agents_per_hour_min,
agents_per_hour_max=agents_per_hour_max,
peak_hours=result.get("peak_hours", [19, 20, 21, 22]),
off_peak_hours=result.get("off_peak_hours", [0, 1, 2, 3, 4, 5]),
off_peak_activity_multiplier=0.05, # Gần như 0 mạng sáng rạng sáng
morning_hours=result.get("morning_hours", [6, 7, 8]),
morning_activity_multiplier=0.4,
work_hours=result.get("work_hours", list(range(9, 19))),
work_activity_multiplier=0.7,
peak_activity_multiplier=1.5
)
def _generate_event_config(
self,
context: str,
simulation_requirement: str,
entities: List[EntityNode]
) -> Dict[str, Any]:
"""Tạo ra cho các thông số Event config"""
# Tự liệt kê các Loại có thể xuất hiện để LLM tham khảo
entity_types_available = list(set(
e.get_entity_type() or "Unknown" for e in entities
))
# Ghi các Thực thể điển hình của mổi loại
type_examples = {}
for e in entities:
etype = e.get_entity_type() or "Unknown"
if etype not in type_examples:
type_examples[etype] = []
if len(type_examples[etype]) < 3:
type_examples[etype].append(e.name)
type_info = "\n".join([
f"- {t}: {', '.join(examples)}"
for t, examples in type_examples.items()
])
# Có chặn để lấy chuỗi theo cấu hình chiều dài giới hạn
context_truncated = context[:self.EVENT_CONFIG_CONTEXT_LENGTH]
# prompt = f"""Based on the following simulation requirements, generate an event configuration.
# Simulation Requirements: {simulation_requirement}
# {context_truncated}
# ## Available Entity Types and Examples
# {type_info}
# ## Task
# Please generate an event configuration JSON:
# - Extract key hot topic keywords.
# - Describe the direction of public opinion development.
# - Design initial post content; **each post must specify a poster_type (publisher type)**.
# **IMPORTANT**: The poster_type must be selected from the "Available Entity Types" above so that initial posts can be assigned to the appropriate Agent for publishing.
# For example: Official statements should be posted by Official/University types, news by MediaOutlet, and student perspectives by Student.
# Return in JSON format (no markdown):
# {{
# "hot_topics": ["Keyword1", "Keyword2", ...],
# "narrative_direction": "<Description of the public opinion development path>",
# "initial_posts": [
# {{"content": "Post Content...", "poster_type": "Entity type (must be selected from available types)"}},
# ...
# ],
# "reasoning": "<Short Explanation>"
# }}"""
# system_prompt = "You are a public opinion analysis expert. Return in pure JSON format. Ensure that poster_type exactly matches the available entity types."
prompt = f"""Dựa trên các yêu cầu mô phỏng sau đây, hãy tạo cấu hình sự kiện.
Yêu cầu mô phỏng: {simulation_requirement}
{context_truncated}
## Các loại thực thể khả dụng và ví dụ
{type_info}
## Nhiệm vụ
Vui lòng tạo JSON cấu hình sự kiện:
- Trích xuất các từ khóa chủ đề nóng (hot topics).
- Mô tả hướng phát triển của dư luận.
- Thiết kế nội dung các bài đăng khởi tạo, **mỗi bài đăng phải chỉ định poster_type (loại người đăng)**.
**QUAN TRỌNG**: poster_type phải được chọn từ "Các loại thực thể khả dụng" ở trên để các bài đăng khởi tạo có thể được phân bổ cho đúng Agent phù hợp.
Ví dụ: Các tuyên bố chính thức nên được đăng bởi loại Official/University, tin tức bởi MediaOutlet, và quan điểm sinh viên bởi Student.
Trả về định dạng JSON (không sử dụng markdown):
{{
"hot_topics": ["Từ khóa 1", "Từ khóa 2", ...],
"narrative_direction": "<Mô tả hướng phát triển dư luận>",
"initial_posts": [
{{"content": "Nội dung bài đăng...", "poster_type": "Loại thực thể (phải chọn từ các loại khả dụng)"}},
...
],
"reasoning": "<Giải thích ngắn gọn>"
}}"""
system_prompt = "Bạn là chuyên gia phân tích dư luận. Trả về định dạng JSON thuần túy. Lưu ý rằng poster_type phải khớp chính xác với các loại thực thể khả dụng."
try:
return self._call_llm_with_retry(prompt, system_prompt)
except Exception as e:
logger.warning(f"Failed to load LLM Event configurations: {e}, using default configs instead.")
return {
"hot_topics": [],
"narrative_direction": "",
"initial_posts": [],
"reasoning": "Sử dụng Config mặc định do LLM lỗi"
}
def _parse_event_config(self, result: Dict[str, Any]) -> EventConfig:
"""Parse lấy các Thuộc Tính cấu hình Event"""
return EventConfig(
initial_posts=result.get("initial_posts", []),
scheduled_events=[],
hot_topics=result.get("hot_topics", []),
narrative_direction=result.get("narrative_direction", "")
)
def _assign_initial_post_agents(
self,
event_config: EventConfig,
agent_configs: List[AgentActivityConfig]
) -> EventConfig:
"""
Khớp quyền Agent với loại Poster_type cho các Bài Post đầu
So sánh cho phù hợp của mỗi post để phân bố Agent id tối ưu nhất
"""
if not event_config.initial_posts:
return event_config
# Build hệ thống agent index bằng kiểu loại
agents_by_type: Dict[str, List[AgentActivityConfig]] = {}
for agent in agent_configs:
etype = agent.entity_type.lower()
if etype not in agents_by_type:
agents_by_type[etype] = []
agents_by_type[etype].append(agent)
# Bảng Alias ánh xạ tương đương (Cho phép LLM sử dụng nhiều quy ước format khác nhau)
type_aliases = {
"official": ["official", "university", "governmentagency", "government"],
"university": ["university", "official"],
"mediaoutlet": ["mediaoutlet", "media"],
"student": ["student", "person"],
"professor": ["professor", "expert", "teacher"],
"alumni": ["alumni", "person"],
"organization": ["organization", "ngo", "company", "group"],
"person": ["person", "student", "alumni"],
}
# Ghi chú từng loại agent đã dùng index nào, tránh dùng lại cùng 1 agent lặp đi lặp lại
used_indices: Dict[str, int] = {}
updated_posts = []
for post in event_config.initial_posts:
poster_type = post.get("poster_type", "").lower()
content = post.get("content", "")
# Khớp tìm agent phù hợp
matched_agent_id = None
# 1. Trùng khớp trực tiếp lấy luôn
if poster_type in agents_by_type:
agents = agents_by_type[poster_type]
idx = used_indices.get(poster_type, 0) % len(agents)
matched_agent_id = agents[idx].agent_id
used_indices[poster_type] = idx + 1
else:
# 2. Sử dụng bí danh alias để khớp nếu dùng sai keyword
for alias_key, aliases in type_aliases.items():
if poster_type in aliases or alias_key == poster_type:
for alias in aliases:
if alias in agents_by_type:
agents = agents_by_type[alias]
idx = used_indices.get(alias, 0) % len(agents)
matched_agent_id = agents[idx].agent_id
used_indices[alias] = idx + 1
break
if matched_agent_id is not None:
break
# 3. Nếu xui xẻo vẫn không tìm thấy, lấy thẳng Agent có điểm Influence (Sức ảnh hưởng) cao nhất
if matched_agent_id is None:
logger.warning(f"Could not find matching Agent type '{poster_type}', assigning to highest influence Agent instead")
if agent_configs:
# Sort ảnh hưởng giảm dần, lấy index [0]
sorted_agents = sorted(agent_configs, key=lambda a: a.influence_weight, reverse=True)
matched_agent_id = sorted_agents[0].agent_id
else:
matched_agent_id = 0
updated_posts.append({
"content": content,
"poster_type": post.get("poster_type", "Unknown"),
"poster_agent_id": matched_agent_id
})
logger.info(f"Initial post assignment: poster_type='{poster_type}' -> agent_id={matched_agent_id}")
event_config.initial_posts = updated_posts
return event_config
def _generate_agent_configs_batch(
self,
context: str,
entities: List[EntityNode],
start_idx: int,
simulation_requirement: str
) -> List[AgentActivityConfig]:
"""Chia đợt gửi lên gọi tạo Cấu hình mạng lưới Agents"""
# Build các node Entity (Dựa trên cấu hình lượng chữ giới hạn)
entity_list = []
summary_len = self.AGENT_SUMMARY_LENGTH
for i, e in enumerate(entities):
entity_list.append({
"agent_id": start_idx + i,
"entity_name": e.name,
"entity_type": e.get_entity_type() or "Unknown",
"summary": e.summary[:summary_len] if e.summary else ""
})
# prompt = f"""Based on the following information, generate social media activity configurations for each entity.
# Simulation Requirements: {simulation_requirement}
# ## Entity List
# ```json
# {json.dumps(entity_list, ensure_ascii=False, indent=2)}
# ```
# ## Task
# Generate activity configurations for each entity, noting:
# - **Time aligns with Vietnamese daily routines**: Almost no activity between 0-5 AM; most active during 7-10 PM (19:00-22:00).
# - **Official Institutions (University/GovernmentAgency)**: Low activity (0.1-0.3), active during work hours (9:00-17:00), slow response (60-240 mins), high influence (2.5-3.0).
# - **Media (MediaOutlet)**: Medium activity (0.4-0.6), active all day (8:00-23:00), fast response (5-30 mins), high influence (2.0-2.5).
# - **Individuals (Student/Person/Alumni)**: High activity (0.6-0.9), active mainly in the evening (18:00-23:00), fast response (1-15 mins), low influence (0.8-1.2).
# - **Public Figures/Experts**: Medium activity (0.4-0.6), medium-high influence (1.5-2.0).
# Return in JSON format (no markdown):
# {{
# "agent_configs": [
# {{
# "agent_id": <Must match the input exactly>,
# "activity_level": <0.0-1.0>,
# "posts_per_hour": <Post frequency>,
# "comments_per_hour": <Comment frequency>,
# "active_hours": [<List of active hours, considering Vietnamese routines>],
# "response_delay_min": <Minimum response delay in minutes>,
# "response_delay_max": <Maximum response delay in minutes>,
# "sentiment_bias": <-1.0 to 1.0>,
# "stance": "<supportive/opposing/neutral/observer>",
# "influence_weight": <Influence weight>
# }},
# ...
# ]
# }}"""
# system_prompt = "You are a social media behavior analysis expert. Return pure JSON. Configurations must comply with Vietnamese daily routines."
prompt = f"""Dựa trên các thông tin sau đây, hãy tạo cấu hình hoạt động trên mạng xã hội cho từng thực thể.
Yêu cầu mô phỏng: {simulation_requirement}
## Danh sách thực thể
```json
{json.dumps(entity_list, ensure_ascii=False, indent=2)}
```
## Nhiệm vụ
Tạo cấu hình hoạt động cho từng thực thể, lưu ý:
- **Thời gian phù hợp với thói quen của người Việt Nam**: Gần như không hoạt động từ 0-5 giờ sáng, hoạt động mạnh nhất từ 19-22 giờ tối.
- **Cơ quan chính thống (University/GovernmentAgency)**: Hoạt động thấp (0.1-0.3), hoạt động trong giờ làm việc (9:00-17:00), phản hồi chậm (60-240 phút), tầm ảnh hưởng cao (2.5-3.0).
- **Truyền thông (MediaOutlet)**: Hoạt động trung bình (0.4-0.6), hoạt động cả ngày (8:00-23:00), phản hồi nhanh (5-30 phút), tầm ảnh hưởng cao (2.0-2.5).
- **Cá nhân (Student/Person/Alumni)**: Hoạt động cao (0.6-0.9), hoạt động chủ yếu vào buổi tối (18:00-23:00), phản hồi nhanh (1-15 phút), tầm ảnh hưởng thấp (0.8-1.2).
- **Người công chúng/Chuyên gia**: Hoạt động trung bình (0.4-0.6), tầm ảnh hưởng trung bình cao (1.5-2.0).
Trả về định dạng JSON (không sử dụng markdown):
{{
"agent_configs": [
{{
"agent_id": <Phải khớp chính xác với đầu vào>,
"activity_level": <0.0-1.0>,
"posts_per_hour": <Tần suất đăng bài>,
"comments_per_hour": <Tần suất bình luận>,
"active_hours": [<Danh sách giờ hoạt động, cân nhắc thói quen người Việt Nam>],
"response_delay_min": <Độ trễ phản hồi tối thiểu tính bằng phút>,
"response_delay_max": <Độ trễ phản hồi tối đa tính bằng phút>,
"sentiment_bias": <-1.0 đến 1.0>,
"stance": "<supportive/opposing/neutral/observer>",
"influence_weight": <Trọng số ảnh hưởng>
}},
...
]
}}"""
system_prompt = "Bạn là chuyên gia phân tích hành vi mạng xã hội. Trả về JSON thuần túy. Cấu hình phải phù hợp với thói quen sinh hoạt của người Việt Nam."
try:
result = self._call_llm_with_retry(prompt, system_prompt)
llm_configs = {cfg["agent_id"]: cfg for cfg in result.get("agent_configs", [])}
except Exception as e:
logger.warning(f"Failed LLM generating Agent batch configs: {e}, falling back to default manual rules.")
llm_configs = {}
# Tạo object list cho AgentActivityConfig
configs = []
for i, entity in enumerate(entities):
agent_id = start_idx + i
cfg = llm_configs.get(agent_id, {})
# Gán Manual tự động nếu Bot LLM thiếu xót
if not cfg:
cfg = self._generate_agent_config_by_rule(entity)
config = AgentActivityConfig(
agent_id=agent_id,
entity_uuid=entity.uuid,
entity_name=entity.name,
entity_type=entity.get_entity_type() or "Unknown",
activity_level=cfg.get("activity_level", 0.5),
posts_per_hour=cfg.get("posts_per_hour", 0.5),
comments_per_hour=cfg.get("comments_per_hour", 1.0),
active_hours=cfg.get("active_hours", list(range(9, 23))),
response_delay_min=cfg.get("response_delay_min", 5),
response_delay_max=cfg.get("response_delay_max", 60),
sentiment_bias=cfg.get("sentiment_bias", 0.0),
stance=cfg.get("stance", "neutral"),
influence_weight=cfg.get("influence_weight", 1.0)
)
configs.append(config)
return configs
def _generate_agent_config_by_rule(self, entity: EntityNode) -> Dict[str, Any]:
"""Tự động gen cấu hình 1 người (agent) dựa trên bộ rule cứng có sẵn nếu gọi bot LLM bị fail (Luật theo múi giờ sinh học)"""
entity_type = (entity.get_entity_type() or "Unknown").lower()
if entity_type in ["university", "governmentagency", "ngo"]:
# Cơ quan chức năng Nhà nước / Doanh nghiệp: làm việc trong khung giờ chuẩn hành chính, trả lời ít nhưng nặng đô
return {
"activity_level": 0.2,
"posts_per_hour": 0.1,
"comments_per_hour": 0.05,
"active_hours": list(range(9, 18)), # 9:00-17:59
"response_delay_min": 60,
"response_delay_max": 240,
"sentiment_bias": 0.0,
"stance": "neutral",
"influence_weight": 3.0
}
elif entity_type in ["mediaoutlet"]:
# Báo đài truyền thông: cả ngày đưa tin, ra bài lẹ giật tít, tốc độ cao
return {
"activity_level": 0.5,
"posts_per_hour": 0.8,
"comments_per_hour": 0.3,
"active_hours": list(range(7, 24)), # 7:00-23:59
"response_delay_min": 5,
"response_delay_max": 30,
"sentiment_bias": 0.0,
"stance": "observer",
"influence_weight": 2.5
}
elif entity_type in ["professor", "expert", "official"]:
# Giáo sư đại học/Người phát biểu: Chỉ nói ban ngày và tối, ra bài ít
return {
"activity_level": 0.4,
"posts_per_hour": 0.3,
"comments_per_hour": 0.5,
"active_hours": list(range(8, 22)), # 8:00-21:59
"response_delay_min": 15,
"response_delay_max": 90,
"sentiment_bias": 0.0,
"stance": "neutral",
"influence_weight": 2.0
}
elif entity_type in ["student"]:
# Tần suất cho lứa Sinh viên: hay ra bài / cãi nhau liên tục ban đêm rất nhiều
return {
"activity_level": 0.8,
"posts_per_hour": 0.6,
"comments_per_hour": 1.5,
"active_hours": [8, 9, 10, 11, 12, 13, 18, 19, 20, 21, 22, 23], # Sáng + Đêm Tối
"response_delay_min": 1,
"response_delay_max": 15,
"sentiment_bias": 0.0,
"stance": "neutral",
"influence_weight": 0.8
}
elif entity_type in ["alumni"]:
# Cựu sinh viên: Thường online đêm là chính
return {
"activity_level": 0.6,
"posts_per_hour": 0.4,
"comments_per_hour": 0.8,
"active_hours": [12, 13, 19, 20, 21, 22, 23], # Giờ nghỉ trưa + Buổi tối
"response_delay_min": 5,
"response_delay_max": 30,
"sentiment_bias": 0.0,
"stance": "neutral",
"influence_weight": 1.0
}
else:
# Thuộc cho số đông (Cư dân mạng / Người Qua Đường): Phấn khích về đêm
return {
"activity_level": 0.7,
"posts_per_hour": 0.5,
"comments_per_hour": 1.2,
"active_hours": [9, 10, 11, 12, 13, 18, 19, 20, 21, 22, 23], # Ban Ngày rảnh + Buổi tối rảnh
"response_delay_min": 2,
"response_delay_max": 20,
"sentiment_bias": 0.0,
"stance": "neutral",
"influence_weight": 1.0
}