""" 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"""Dựa vào yêu cầu Mô phỏng này, hãy tự động gen cho 1 file Thông số thời gian {context_truncated} ## Task công việc Vui lòng xuất ra kết quả Thời gian dưới định dạng format JSON ### Logic cơ bản có thể cần để tham khảo (Hãy dựa trên nhu cầu của user và hoàn cảnh để suy ra): - Vị trí của người tham gia là người dùng mạng Trung Quốc, cần sinh hoạt bằng thói quen sinh học giờ chuẩn Bắc Kinh (China Time: GMT+8). - Không xuất hiện hay có dấu hiệu online của người dùng từ 0-5 giờ sáng (Hệ số Active 0.05). - Tăng nhẹ số lượng truy cập lại mức trung thành khoảng giữa 6-8 giờ sáng (Hệ số Active 0.4). - Số lượng active hoạt động ở mức bình ổn khoảng từ 9-18 giờ sáng (Hệ số Active 0.7). - Khung giờ sôi động nhất sẽ tập trung quanh 19-22 giờ tối (Hệ số Active 1.5). - Tỷ lệ giảm lại sau 23 giờ (Hệ số Active 0.5). - Cơ chế bình thường: Đêm không online, sáng bắt đầu đăng bài, giờ hành chính bình bình và cao trào trong buổi tối thức đêm - **Chỉ Dẫn Rất Quan Trọng**: Những thông tin từ list được lấy để tham chiếu. Còn thông số thật sự còn phải tùy theo Đặc điểm, Tình Huống đối tượng ở Mạng và Thời Điểm Sự Kiện để gen ra - Ví dụ: Số lượng sinh viên thức đêm ở từ 21-23 giờ thường là lớn; Media báo đài thì hay đăng tin liên tục cả ngày theo ca; Tài khoản văn phòng của các Cơ Quan chức năng chỉ trả lời giờ làm việc Hành Chính... - Hoặc ví dụ: Các Biến Cố hoặc Drama xảy ra trong đêm khuya thì sẽ dẫn đến Lượng truy cập ban đêm có dấu hiệu đi lên, trong khi đó Off_peak_hours vì lẽ đó mà sẽ có khi co lại cho ngắn... ### Định dạng Format của JSON Return (Lưu ý Tuyệt đối Không Return Markdown code block, chỉ Return Format Json Thuần Túy) 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": "Một đoạn văn lời nói cho biết Bạn đã dựa theo yêu cầu như thế nào để gen các Thông Số trên" }} Các Khóa của Json có nghĩa là: - total_simulation_hours (int): Mô tả tổng giới hạn thời gian (Đơn vị giờ), Có giá trị trong khung 24-168h, Tùy vào biến cố Drama nóng để chọn. Các chủ đề chạy Drama ít hơn thì nên cấp ngắn - minutes_per_round (int): Số Time trên mỗi Khung Đợt Thời Gian Thực Của Simulation để mô phỏng cho 1 phiên trong game, lấy giá trị 30-120 phút. Đề xuất: 60 (1 giờ) - agents_per_hour_min (int): Số Agent online tối thiểu trong một tiếng mô phỏng (Phạm vi {1}-{max_agents_allowed}) - agents_per_hour_max (int): Số lượng lên mạng tối đa (Phạm vi {1}-{max_agents_allowed}) - peak_hours (mảng int list): Thời điểm đỉnh sóng Cao Điểm, cân nhắc theo Đối tượng để quyết định - off_peak_hours (mảng int list): Đỉnh sóng Đáy, ít ai quan tâm - morning_hours (mảng int list): Khoảng thời điểm đầu buổi sáng - work_hours (mảng int list): Khung hành chính công việc - reasoning (string): Sự giải thích từ LLM""" system_prompt = "Bạn là 1 Tool chuyên mô phỏng môi trường làm việc trên mxh bằng thuật toán LLM để cung cấp ra Cấu hình Time. Hãy xuất JSON." 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 gắ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": "Mặc định sử dụng Thời gian làm việc của người dùng Trung Quốc (1 Giờ/vòng)" } 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"""Gen cấu hình Event dưới các tham chiếu từ Yêu cầu (Requirements): Simulation Requirements: {simulation_requirement} {context_truncated} ## Các Entity Type có cung cấp & VD minh họa: {type_info} ## Task công việc Vui lòng xuất ra kết quả Thời gian dưới định dạng format JSON: - Chỉ định List các Hot Keyword để kéo trend - Miêu tả định hướng thảo luận cho trend hiện tại - Đăng tải Post đầu tiên (Initial_Post) lên với nguyên tắc: **Phải đi kèm với tham số người up Post (poster_type)** **RẤT QUAN TRỌNG**: Người Poster Type (poster_type) Phải trùng khớp/được lấy từ danh mục từ mục "Các Entity Type" đã cho để gán. Tránh báo lỗi cho Agent Ví dụ: official announcements should be posted by Official/University type, news by MediaOutlet, and student opinions by Student. Format trả ra (Tuyệt đối Không Markdown, chỉ lấy format chuỗi chuẩn): {{ "hot_topics": ["Keyword1", "Keyword2", ...], "narrative_direction": "<Đoạn Text dài định hướng Dư luận (Narrative)>", "initial_posts": [ {{"content": "Post Content...", "poster_type": "Người sẽ Post ra nội dung (Hạn chế tùy tiện vì nó lấy từ mảng danh sách Loại Entity cho trước)"}}, ... ], "reasoning": "" }}""" system_prompt = "Bạn là 1 Chuyên gia về Data Dư luận, Yêu cầu làm việc trên chuỗi JSON nghiêm ngặt. Tránh Lỗi." 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"""Tạo profile Social Media Activity Configs cho từng Thực thể sau. Nhu cầu: {simulation_requirement} ## List các thực thể ```json {json.dumps(entity_list, ensure_ascii=False, indent=2)} ``` ## Task Công Việc Trả ra cho Từng Entity các bộ Activity Profile tham chiều theo các quy tắc ngầm sau: - **Tập quán Sinh hoạt Trung Quốc**: 0-5h sáng gần như sẽ hiếm ai onl, 19-22h tối lượng tương tác rất sôi nổi - **Đại diện Cơ quan (University/GovernmentAgency)**: Tần suất (0.1-0.3), làm việc trong giờ hành chính (9-17h), delay hơi trễ (60-240 phút), Trọng lượng lời nói cao (2.5-3.0) - **Truyền Thông Báo Đài (MediaOutlet)**: Tần suất TB (0.4-0.6), Hầu như online nguyên ngày (8-23h), Trễ ít (5-30 phút), Trọng lượng cũng Cao (2.0-2.5) - **Người Dùng Bình thường (Student/Person/Alumni)**: Tần suất cao (0.6-0.9), Onl chủ yếu để cãi nhau buổi tối (18-23h), Tương tác lẹ như hack (1-15 min), Uy tín lời nói khá lèo tèo (0.8-1.2) - **Học giả/Chuyên gia/Kols**: Tần suất TB (0.4-0.6), Uy tín tương đối (1.5-2.0) Trả đúng 1 object JSON ko format MD: {{ "agent_configs": [ {{ "agent_id": , "activity_level": <0.0-1.0>, "posts_per_hour": , "comments_per_hour": , "active_hours": [], "response_delay_min": , "response_delay_max": , "sentiment_bias": <-1.0 đến 1.0 (Tiêu cực sang Tích Cực)>, "stance": "", "influence_weight": }}, ... ] }}""" system_prompt = "Hệ thống Analysis Chuyên gia. Luôn trả về Format Object Array bằng JSON. Và tuân thủ sinh học." 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 }