diff --git a/backend/app/api/simulation.py b/backend/app/api/simulation.py index 71960cf7..8c9697e6 100644 --- a/backend/app/api/simulation.py +++ b/backend/app/api/simulation.py @@ -309,7 +309,7 @@ def _check_simulation_prepared(simulation_id: str) -> tuple: # - completed: đã chạy xong, nghĩa là đã chuẩn bị xong từ trước # - stopped: đã dừng, nghĩa là đã chuẩn bị xong từ trước # - failed: chạy thất bại (nhưng phần chuẩn bị đã hoàn tất) - prepared_statuses = ["ready", "preparing", "running", "completed", "stopped", "failed"] + prepared_statuses = ["ready", "preparing", "running", "completed", "stopped", "failed", "paused"] if status in prepared_statuses and config_generated: # Lấy thông tin thống kê file @@ -468,7 +468,7 @@ def prepare_simulation(): entity_types_list = data.get('entity_types') use_llm_for_profiles = data.get('use_llm_for_profiles', True) - parallel_profile_count = data.get('parallel_profile_count', 5) + parallel_profile_count = data.get('parallel_profile_count', 20) # ========== Đồng bộ lấy số lượng thực thể (trước khi chạy tác vụ nền) ========== # Nhờ đó frontend có thể lấy ngay tổng số Agent dự kiến sau khi gọi prepare diff --git a/backend/app/models/project.py b/backend/app/models/project.py index f49a9427..c59afd43 100644 --- a/backend/app/models/project.py +++ b/backend/app/models/project.py @@ -49,6 +49,7 @@ class Project: simulation_requirement: Optional[str] = None chunk_size: int = 500 chunk_overlap: int = 50 + llm_model_name: Optional[str] = None # Thông tin lỗi error: Optional[str] = None @@ -63,6 +64,7 @@ class Project: "updated_at": self.updated_at, "files": self.files, "total_text_length": self.total_text_length, + "llm_model_name": self.llm_model_name, "ontology": self.ontology, "analysis_summary": self.analysis_summary, "graph_id": self.graph_id, @@ -88,6 +90,7 @@ class Project: updated_at=data.get('updated_at', ''), files=data.get('files', []), total_text_length=data.get('total_text_length', 0), + llm_model_name=data.get('llm_model_name'), ontology=data.get('ontology'), analysis_summary=data.get('analysis_summary'), graph_id=data.get('graph_id'), @@ -151,7 +154,8 @@ class ProjectManager: name=name, status=ProjectStatus.CREATED, created_at=now, - updated_at=now + updated_at=now, + llm_model_name=Config.LLM_MODEL_NAME ) # Thiết lập các thư mục con trong không gian thư mục của project diff --git a/backend/app/services/oasis_profile_generator.py b/backend/app/services/oasis_profile_generator.py index d162b508..4e86bad3 100644 --- a/backend/app/services/oasis_profile_generator.py +++ b/backend/app/services/oasis_profile_generator.py @@ -85,6 +85,7 @@ class OasisAgentProfile: # --- Metadata truy vết nguồn gốc --- source_entity_uuid: Optional[str] = None # UUID của EntityNode gốc trong Zep source_entity_type: Optional[str] = None # Loại entity (ví dụ: "Person", "Organization") + entity_category: Optional[str] = None # Kết quả LLM classification: "individual" | "organization" created_at: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d")) @@ -119,6 +120,8 @@ class OasisAgentProfile: profile["profession"] = self.profession if self.interested_topics: profile["interested_topics"] = self.interested_topics + if self.entity_category: + profile["entity_category"] = self.entity_category return profile @@ -176,6 +179,7 @@ class OasisAgentProfile: "interested_topics": self.interested_topics, "source_entity_uuid": self.source_entity_uuid, "source_entity_type": self.source_entity_type, + "entity_category": self.entity_category, "created_at": self.created_at, } @@ -344,6 +348,7 @@ class OasisProfileGenerator: interested_topics=profile_data.get("interested_topics", []), source_entity_uuid=entity.uuid, source_entity_type=entity_type, + entity_category=profile_data.get("entity_category"), ) # -------------------------------------------------------------------------- @@ -629,14 +634,17 @@ class OasisProfileGenerator: # -------------------------------------------------------------------------- # PRIVATE: _is_individual_entity / _is_group_entity — Phân loại entity type + # [DEPRECATED] Không còn dùng để quyết định prompt — classification đã chuyển + # sang LLM trong _build_adaptive_persona_prompt(). Giữ lại để không break + # code ngoài nếu có caller khác. # -------------------------------------------------------------------------- def _is_individual_entity(self, entity_type: str) -> bool: - """Trả về True nếu entity_type thuộc danh sách cá nhân (INDIVIDUAL_ENTITY_TYPES).""" + """[Deprecated] Dùng _build_adaptive_persona_prompt() thay thế.""" return entity_type.lower() in self.INDIVIDUAL_ENTITY_TYPES def _is_group_entity(self, entity_type: str) -> bool: - """Trả về True nếu entity_type thuộc danh sách tổ chức (GROUP_ENTITY_TYPES).""" + """[Deprecated] Dùng _build_adaptive_persona_prompt() thay thế.""" return entity_type.lower() in self.GROUP_ENTITY_TYPES # -------------------------------------------------------------------------- @@ -659,10 +667,9 @@ class OasisProfileGenerator: """ Gọi LLM để sinh profile dict với bio, persona, age, gender, mbti, ... - Quyết định dùng prompt nào dựa vào entity_type: - - Cá nhân (student/person/...) → _build_individual_persona_prompt() - - Tổ chức (university/ngo/...) → _build_group_persona_prompt() - - Không xác định → cũng dùng group prompt + Classification individual/organization do LLM tự quyết định dựa vào toàn bộ + context (tên, loại, summary, Zep facts) thông qua _build_adaptive_persona_prompt(). + LLM trả về field "entity_category" trong JSON để ghi lại quyết định đó. Retry logic: - Lần 1: temperature=0.7 @@ -674,16 +681,9 @@ class OasisProfileGenerator: Returns: Dict với ít nhất: {"bio": ..., "persona": ...} """ - is_individual = self._is_individual_entity(entity_type) - - if is_individual: - prompt = self._build_individual_persona_prompt( - entity_name, entity_type, entity_summary, entity_attributes, context - ) - else: - prompt = self._build_group_persona_prompt( - entity_name, entity_type, entity_summary, entity_attributes, context - ) + prompt = self._build_adaptive_persona_prompt( + entity_name, entity_type, entity_summary, entity_attributes, context + ) max_attempts = 3 last_error = None @@ -694,7 +694,7 @@ class OasisProfileGenerator: client=self.client, model=self.model_name, messages=[ - {"role": "system", "content": self._get_system_prompt(is_individual)}, + {"role": "system", "content": self._get_system_prompt()}, {"role": "user", "content": prompt} ], response_format={"type": "json_object"}, # Force JSON output mode @@ -860,23 +860,123 @@ class OasisProfileGenerator: # PRIVATE: _get_system_prompt — System prompt cho LLM # -------------------------------------------------------------------------- - def _get_system_prompt(self, is_individual: bool) -> str: + def _get_system_prompt(self) -> str: """ - Trả về system prompt chung cho LLM (hiện tại không phân biệt individual/group). + Trả về system prompt cho LLM. Nhấn mạnh: trả về JSON hợp lệ, không có newline thô trong string values. + Không phân biệt individual/group — classification do LLM tự xử lý trong prompt. """ - base_prompt = "Bạn là chuyên gia tạo hồ sơ người dùng mạng xã hội. Hãy tạo các nhân vật chi tiết và chân thực phục vụ cho việc mô phỏng dư luận, nhằm tái hiện tối đa các tình huống thực tế hiện có. Phải trả về định dạng JSON hợp lệ; tất cả các giá trị chuỗi không được chứa ký tự xuống dòng chưa được xử lý (unescaped). Sử dụng tiếng Việt." - return base_prompt + return ( + "Bạn là chuyên gia tạo hồ sơ người dùng mạng xã hội. " + "Hãy tạo các nhân vật chi tiết và chân thực phục vụ cho việc mô phỏng dư luận, " + "nhằm tái hiện tối đa các tình huống thực tế hiện có. " + "Phân tích kỹ thông tin thực thể để tự xác định đây là cá nhân hay tổ chức, " + "rồi sinh hồ sơ phù hợp. " + "Phải trả về định dạng JSON hợp lệ; " + "tất cả các giá trị chuỗi không được chứa ký tự xuống dòng chưa được xử lý (unescaped). " + "Sử dụng tiếng Việt." + ) + + # -------------------------------------------------------------------------- + # PRIVATE: _build_adaptive_persona_prompt — Prompt thống nhất có LLM classification + # -------------------------------------------------------------------------- + # Thay thế _build_individual_persona_prompt + _build_group_persona_prompt. + # LLM tự phán đoán individual/organization từ context rồi sinh profile phù hợp. + # Kết quả JSON chứa field "entity_category" ghi lại quyết định phân loại. + # -------------------------------------------------------------------------- + + def _build_adaptive_persona_prompt( + self, + entity_name: str, + entity_type: str, + entity_summary: str, + entity_attributes: Dict[str, Any], + context: str + ) -> str: + """ + Tạo prompt thống nhất — LLM tự phân loại individual/organization rồi sinh profile. + + Luồng trong prompt: + Bước 1: LLM đọc entity_name, entity_type, summary, attributes, Zep context + Bước 2: LLM phán đoán "individual" hay "organization" + Bước 3: LLM sinh JSON 9 fields với giá trị phù hợp theo phân loại đó: + - individual → age thực, gender "male"/"female", persona góc nhìn cá nhân + - organization → age=30, gender="other", persona góc nhìn tổ chức + + Ưu điểm so với 2 prompt cũ: + - Không phụ thuộc hardcoded INDIVIDUAL_ENTITY_TYPES / GROUP_ENTITY_TYPES + - Hoạt động đúng với mọi domain (tài chính, giáo dục, chính trị...) + - LLM dùng full context (tên + summary + Zep facts) để classify chính xác hơn + """ + attrs_str = json.dumps(entity_attributes, ensure_ascii=False) if entity_attributes else "Không có" + context_str = context[:3000] if context else "Không có ngữ cảnh bổ sung" + + return f"""Phân tích thực thể sau và tạo hồ sơ mạng xã hội phù hợp. + +Tên thực thể: {entity_name} +Loại thực thể: {entity_type} +Tóm tắt thực thể: {entity_summary} +Thuộc tính thực thể: {attrs_str} + +Thông tin ngữ cảnh: +{context_str} + +--- + +BƯỚC 1 — PHÂN LOẠI THỰC THỂ: +Dựa vào toàn bộ thông tin trên, xác định thực thể này thuộc loại nào: +- "individual": một con người cụ thể (ví dụ: nhà đầu tư, sinh viên, nhà báo, chuyên gia, trader...) +- "organization": tổ chức, công ty, quỹ, cơ quan, sàn giao dịch, trường học, nhóm... + +BƯỚC 2 — TẠO HỒ SƠ: +Tạo JSON với các trường sau, điều chỉnh theo kết quả phân loại: + +1. entity_category: "individual"/"organization" + +2. bio: Tiểu sử mạng xã hội ngắn gọn, tối đa 200 ký tự. + +3. persona: Mô tả nhân vật/tài khoản chi tiết (~2000 từ, văn bản thuần túy), bao gồm: + [Nếu individual]: + - Thông tin cơ bản (tuổi thực, nghề nghiệp, học vấn, nơi ở) + - Nền tảng nhân vật (trải nghiệm quan trọng, mối liên hệ với sự kiện, quan hệ xã hội) + - Đặc điểm tính cách (MBTI, tính cách cốt lõi, cách biểu đạt cảm xúc) + - Hành vi mạng xã hội (tần suất đăng bài, loại nội dung, phong cách tương tác) + - Lập trường quan điểm (thái độ với chủ đề, điều dễ gây kích động hoặc xúc động) + - Ký ức cá nhân (mối liên hệ với sự kiện, các hành động và phản ứng đã có) + [Nếu organization]: + - Thông tin tổ chức (tên chính thức, tính chất, bối cảnh thành lập, chức năng) + - Định vị tài khoản (đối tượng mục tiêu, chức năng cốt lõi trên MXH) + - Phong cách phát ngôn (đặc điểm ngôn ngữ, các chủ đề cấm kỵ) + - Lập trường chính thức (quan điểm về các chủ đề cốt lõi, cách xử lý tranh cãi) + - Ký ức tổ chức (mối liên hệ với sự kiện, các hành động và phản ứng đã có) + +4. age: + - Nếu individual: tuổi thực của người đó (số nguyên) + - Nếu organization: cố định là 30 + +5. gender: + - Nếu individual: "male" hoặc "female" + - Nếu organization: "other" + +6. mbti: Loại MBTI (ví dụ: INTJ, ENFP...) mô tả tính cách cá nhân hoặc phong cách tổ chức + +7. country: Quốc gia bằng tiếng Việt (ví dụ: "Việt Nam", "Hoa Kỳ") + +8. profession: Nghề nghiệp (cá nhân) hoặc chức năng chính (tổ chức) + +9. interested_topics: Mảng các chủ đề quan tâm + +QUAN TRỌNG: +- Tất cả giá trị phải là chuỗi hoặc số, không dùng ký tự xuống dòng trong string. +- 'persona' phải là một đoạn văn bản mạch lạc, không dùng bullet points hay newline. +- Sử dụng tiếng Việt (ngoại trừ 'gender' dùng tiếng Anh: "male", "female", hoặc "other"). +- Nội dung phải nhất quán với thông tin thực thể và ngữ cảnh được cung cấp. +""" # -------------------------------------------------------------------------- # PRIVATE: _build_individual_persona_prompt / _build_group_persona_prompt - # -------------------------------------------------------------------------- - # Hai hàm này tạo user prompt gửi cho LLM. - # Cấu trúc gồm: thông tin entity + context + yêu cầu 8 fields JSON cụ thể. - # - # Điểm khác biệt chính: - # - Individual: persona ~2000 từ với ký ức cá nhân, tuổi thực, gender male/female - # - Group: persona ~2000 từ với ký ức tổ chức, age=30 cố định, gender="other" + # [DEPRECATED] Thay thế bởi _build_adaptive_persona_prompt(). + # Giữ lại để không break code nếu có nơi nào gọi trực tiếp. # -------------------------------------------------------------------------- def _build_individual_persona_prompt( @@ -888,12 +988,11 @@ class OasisProfileGenerator: context: str ) -> str: """ - Tạo user prompt cho entity CÁ NHÂN. + [Deprecated] Dùng _build_adaptive_persona_prompt() thay thế. + Tạo user prompt cho entity CÁ NHÂN — giữ lại để tương thích ngược. Yêu cầu LLM sinh JSON 8 fields: bio, persona, age (int), gender ("male"/"female"), mbti, country, profession, interested_topics - - Context được cắt tối đa 3000 ký tự để tránh vượt context window của LLM. """ attrs_str = json.dumps(entity_attributes, ensure_ascii=False) if entity_attributes else "Không có" context_str = context[:3000] if context else "Không có ngữ cảnh bổ sung" @@ -943,8 +1042,9 @@ QUAN TRỌNG: context: str ) -> str: """ - Tạo user prompt cho entity TỔ CHỨC/NHÓM. + [Deprecated] Dùng _build_adaptive_persona_prompt() thay thế. + Tạo user prompt cho entity TỔ CHỨC/NHÓM — giữ lại để tương thích ngược. Khác biệt so với individual prompt: - age: cố định 30 (tuổi ảo cho tài khoản tổ chức) - gender: cố định "other" @@ -981,7 +1081,7 @@ Vui lòng tạo JSON bao gồm các trường sau: 7. profession: Mô tả chức năng của tổ chức 8. interested_topics: Mảng các lĩnh vực quan tâm -QUAN TRỌNG: +QUAN TRỌNG: - Tất cả giá trị các trường phải là chuỗi hoặc số, không cho phép giá trị null. - 'persona' phải là một đoạn mô tả văn bản mạch lạc, không sử dụng ký tự xuống dòng. - Sử dụng tiếng Việt (ngoại trừ trường 'gender' phải dùng chuỗi tiếng Anh "other"). @@ -1449,7 +1549,7 @@ QUAN TRỌNG: "user_id": profile.user_id if profile.user_id is not None else idx, "username": profile.user_name, "name": profile.name, - "bio": profile.bio[:150] if profile.bio else f"{profile.name}", # Cap ở 150 ký tự + "bio": profile.bio if profile.bio else f"{profile.name}", "persona": profile.persona or f"{profile.name} is a participant in social discussions.", "karma": profile.karma if profile.karma else 1000, "created_at": profile.created_at, @@ -1464,6 +1564,8 @@ QUAN TRỌNG: item["profession"] = profile.profession if profile.interested_topics: item["interested_topics"] = profile.interested_topics + if profile.entity_category: + item["entity_category"] = profile.entity_category data.append(item) diff --git a/backend/app/services/simulation_ipc.py b/backend/app/services/simulation_ipc.py index 4c7b6eed..afddff04 100644 --- a/backend/app/services/simulation_ipc.py +++ b/backend/app/services/simulation_ipc.py @@ -137,7 +137,7 @@ class SimulationIPCClient: TimeoutError: Lỗi quá thời gian chờ phản hồi """ command_id = str(uuid.uuid4()) - command = s( + command = IPCCommand( command_id=command_id, command_type=command_type, args=args diff --git a/backend/app/services/zep_tools.py b/backend/app/services/zep_tools.py index db1a35a5..830825f0 100644 --- a/backend/app/services/zep_tools.py +++ b/backend/app/services/zep_tools.py @@ -438,9 +438,6 @@ class ZepToolsService: if self._llm_client is None: self._llm_client = LLMClient( component="zep_tools", - metadata={ - "phase": "zep_tools", - }, ) return self._llm_client @@ -1151,13 +1148,14 @@ Trả về danh sách các câu hỏi phụ dưới định dạng JSON.""" {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], - temperature=0.3 + temperature=0.3, + metadata={"phase": "_generate_sub_queries"}, ) - + sub_queries = response.get("sub_queries", []) # Ép kiểu để chắc chắn danh sách toàn kiểu string return [str(sq) for sq in sub_queries[:max_queries]] - + except Exception as e: logger.warning(f"Failed to generate sub-queries: {str(e)}, using default sub-queries") # Hạ cấp (fallback): Trả về các biến thể chung chung của câu hỏi ban đầu @@ -1672,9 +1670,10 @@ Hãy chọn tối đa {max_agents} Agent phù hợp nhất để phỏng vấn v {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], - temperature=0.3 + temperature=0.3, + metadata={"phase": "_select_agents_for_interview"}, ) - + selected_indices = response.get("selected_indices", [])[:max_agents] reasoning = response.get("reasoning", "Auto selected based on relevance") @@ -1751,9 +1750,10 @@ Hãy tạo từ 3-5 câu hỏi phỏng vấn.""" {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], - temperature=0.5 + temperature=0.5, + metadata={"phase": "_generate_interview_questions"}, ) - + return response.get("questions", [f"What is your opinion on {interview_requirement}?"]) except Exception as e: @@ -1832,7 +1832,8 @@ Hãy tạo bản tóm tắt phỏng vấn.""" {"role": "user", "content": user_prompt} ], temperature=0.3, - max_tokens=4096 + max_tokens=4096, + metadata={"phase": "_generate_interview_summary"}, ) return summary diff --git a/backend/app/utils/llm_cost.py b/backend/app/utils/llm_cost.py index e71b3c94..3c2a690d 100644 --- a/backend/app/utils/llm_cost.py +++ b/backend/app/utils/llm_cost.py @@ -24,7 +24,7 @@ MODEL_COSTS_PER_1M_TOKENS: Dict[str, Dict[str, float]] = { "Qwen/Qwen3.5-27B": {"input": 0.5, "output": 3.0}, "Qwen/Qwen3.6-27B": {"input": 0.5, "output": 3.0}, "gemini-3-flash": {"input": 0.5, "output": 3.0}, - "gemini-3.1-flash-lite": {"input": 0.25, "output": 1.5}, + "Qwen/Qwen3.6-27B-FP8": {"input": 0.5, "output": 3.0}, } @@ -174,8 +174,10 @@ def record_llm_cost( request_no = int(counter["requests"]) cumulative_cost = _quantize_8(counter["total_cost"]) + phase = metadata.get("phase") + component_label = f"{component}.{phase}" if phase else component line = ( - f"[{timestamp}] [Request {request_no}] [{component}] " + f"[{timestamp}] [Request {request_no}] [{component_label}] " f"Called model: {model_name}, " f"input_tokens: {usage_dict['input_tokens']} | " f"output_tokens: {usage_dict['output_tokens']} | " diff --git a/backend/scripts/run_parallel_simulation.py b/backend/scripts/run_parallel_simulation.py index ae9aa485..7eff5565 100644 --- a/backend/scripts/run_parallel_simulation.py +++ b/backend/scripts/run_parallel_simulation.py @@ -203,6 +203,9 @@ from llm_cost_patch import install_openai_cost_patch try: from camel.models import ModelFactory from camel.types import ModelPlatformType + from camel.memories import ChatHistoryMemory, ScoreBasedContextCreator + from camel.utils import OpenAITokenCounter + from camel.types import ModelType import oasis from oasis import ( ActionType, @@ -211,6 +214,8 @@ try: generate_twitter_agent_graph, generate_reddit_agent_graph ) + from oasis.social_platform import Platform + from oasis.social_platform.channel import Channel except ImportError as e: print(f"Error: Missing dependency {e}") print("Please install first: pip install oasis-ai camel-ai") @@ -1120,6 +1125,33 @@ def create_model(config: Dict[str, Any], use_boost: bool = False): ) +def configure_agent_memory_limits(agent_graph, token_limit=150000, message_window_size=25): + """ + Cấu hình giới hạn memory cho tất cả agent trong agent_graph. + + OASIS tạo SocialAgent không truyền message_window_size hay token_limit + cho ChatAgent (CAMEL), khiến conversation history tăng vô hạn qua các round. + Khi context vượt quá giới hạn model (131072 tokens cho Qwen3.6-27B-FP8), + LLM API trả về BadRequestError và agent bị bỏ qua round đó. + + Hàm này thay thế memory của mỗi agent bằng ChatHistoryMemory có: + - token_limit: giới hạn token context (ScoreBasedContextCreator sẽ cắt bớt + message cũ khi vượt, ưu tiên giữ message mới và system message) + - message_window_size: giới hạn số message giữ lại (tầng bảo vệ thứ hai) + + ChatAgent.memory setter tự động gọi init_messages() để giữ system message. + """ + configured_count = 0 + for agent_id, agent in agent_graph.get_agents(): + token_counter = OpenAITokenCounter(ModelType.GPT_4O_MINI) + context_creator = ScoreBasedContextCreator(token_counter, token_limit=token_limit) + new_memory = ChatHistoryMemory(context_creator, window_size=message_window_size) + agent.memory = new_memory + configured_count += 1 + + return configured_count + + def get_active_agents_for_round( env, config: Dict[str, Any], @@ -1277,6 +1309,10 @@ async def run_twitter_simulation( available_actions=TWITTER_ACTIONS, ) + # Cấu hình giới hạn memory để tránh context overflow (131072 token limit) + mem_count = configure_agent_memory_limits(result.agent_graph) + log_info(f"Configured memory limits for {mem_count} agents") + # Xây dựng agent_names map — dùng cho log và enrich context agent_names = get_agent_names_from_config(config) for agent_id, agent in result.agent_graph.get_agents(): @@ -1289,11 +1325,19 @@ async def run_twitter_simulation( if os.path.exists(db_path): os.remove(db_path) + twitter_channel = Channel() + twitter_platform = Platform( + db_path=db_path, + channel=twitter_channel, + recsys_type="reddit", # score-based, không load model embedding → nhanh hơn twhin-bert + refresh_rec_post_count=2, + max_rec_post_len=2, + following_post_count=3, + ) result.env = oasis.make( agent_graph=result.agent_graph, - platform=oasis.DefaultPlatformType.TWITTER, - database_path=db_path, - semaphore=3, # Tối đa 3 LLM call đồng thời trong cùng 1 round + platform=twitter_platform, + semaphore=20, ) await result.env.reset() @@ -1471,6 +1515,10 @@ async def run_reddit_simulation( available_actions=REDDIT_ACTIONS, ) + # Cấu hình giới hạn memory để tránh context overflow (131072 token limit) + mem_count = configure_agent_memory_limits(result.agent_graph) + log_info(f"Configured memory limits for {mem_count} agents") + agent_names = get_agent_names_from_config(config) for agent_id, agent in result.agent_graph.get_agents(): if agent_id not in agent_names: @@ -1484,7 +1532,7 @@ async def run_reddit_simulation( agent_graph=result.agent_graph, platform=oasis.DefaultPlatformType.REDDIT, database_path=db_path, - semaphore=3, + semaphore=20, ) await result.env.reset() diff --git a/test_code_backend/full_pipeline/README.md b/test_code_backend/full_pipeline/README.md index 0a2d121c..156090da 100644 --- a/test_code_backend/full_pipeline/README.md +++ b/test_code_backend/full_pipeline/README.md @@ -32,7 +32,7 @@ Chạy 7 bước tự động: ontology → graph → create sim → prepare sim ```bash # Từ project root -cd /home/anman/intern/MiroFish +cd /home/anman/intern/quynhht/MiroFish bash test_code_backend/full_pipeline/run.sh # Hoặc chỉ định config khác @@ -181,10 +181,12 @@ SIM_ID="sim_xxxxxxxxxxxx" curl -s http://localhost:5001/api/simulation/start \ -H "Content-Type: application/json" \ - -d "{\"simulation_id\": \"$SIM_ID\", \"platform\": \"parallel\"}" \ + -d "{\"simulation_id\": \"$SIM_ID\", \"platform\": \"parallel\", \"force\": true}" \ | jq '{runner_status: .data.runner_status, total_rounds: .data.total_rounds}' ``` +thêm cờ `\"force\": true` nếu muốn chạy simulation của SIM_ID đó mà không phải chạy lại prepare simualtion + --- ### Bước 7: Theo dõi simulation đến khi hoàn thành @@ -194,6 +196,7 @@ Chạy lệnh sau để tự động poll mỗi 30 giây, tự thoát khi simula ```bash SIM_ID="sim_xxxxxxxxxxxx" + while true; do RESP=$(curl -s http://localhost:5001/api/simulation/$SIM_ID/run-status) RS=$(echo "$RESP" | jq -r '.data.runner_status') @@ -216,6 +219,16 @@ curl -s http://localhost:5001/api/simulation/$SIM_ID/run-status \ curl -s "http://localhost:5001/api/simulation/$SIM_ID/actions?limit=20" | jq . ``` +Dừng simulation +```bash +SIM_ID="sim_0d2a9fa9936a" + +curl -s http://localhost:5001/api/simulation/stop \ + -H "Content-Type: application/json" \ + -d "{\"simulation_id\": \"$SIM_ID\"}" \ + | jq '{success: .success, runner_status: .data.runner_status}' +``` + --- ### Bước 8: Generate report @@ -223,7 +236,6 @@ curl -s "http://localhost:5001/api/simulation/$SIM_ID/actions?limit=20" | jq . ```bash SIM_ID="sim_xxxxxxxxxxxx" -SIM_ID="sim_07ab325b3818" # 1. Bắt đầu generate → lấy task_id và report_id RESP=$(curl -s http://localhost:5001/api/report/generate \ -H "Content-Type: application/json" \ @@ -255,7 +267,6 @@ Nếu sim đã có report rồi mà muốn chạy lại thì dùng như sau ```bash SIM_ID="sim_xxxxxxxxxxxx" -SIM_ID="sim_07ab325b3818" # 1. Bắt đầu generate → lấy task_id và report_id RESP=$(curl -s http://localhost:5001/api/report/generate \ -H "Content-Type: application/json" \