import json import logging from enum import Enum, auto from llm_client import LLMClient from prompts import Prompts # ============================================================================== # --- 日志系统配置 (双日志系统) --- # ============================================================================== # 1. 调试日志 (orchestrator.log) - 记录所有技术细节 # 用于开发者调试,包含LLM返回的原始信息、错误堆栈等。 debug_logger = logging.getLogger('orchestrator_logger') debug_logger.setLevel(logging.INFO) if not debug_logger.handlers: # 使用 mode='a' 来追加日志,而不是覆盖 file_handler = logging.FileHandler('orchestrator.log', mode='a', encoding='utf-8') formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) debug_logger.addHandler(file_handler) # 阻止日志向上传播,避免在控制台输出 debug_logger.propagate = False # 在每次程序启动时写入一个分隔符,方便区分不同的运行会话 debug_logger.info("\n" + "="*20 + " APPLICATION STARTED " + "="*20) # 2. 演示日志 (demo_show.log) - 只记录用户输入和状态变化 # 用于展示和快速回顾对话流程。 demo_logger = logging.getLogger('demo_logger') demo_logger.setLevel(logging.INFO) if not demo_logger.handlers: # 使用 mode='a' 来追加日志 demo_file_handler = logging.FileHandler('demo_show.log', mode='a', encoding='utf-8') # 使用更简洁的格式,只包含时间戳和消息 demo_formatter = logging.Formatter('%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') demo_file_handler.setFormatter(demo_formatter) demo_logger.addHandler(demo_file_handler) # 同样阻止日志向上传播 demo_logger.propagate = False # 在演示日志中也添加会话启动分隔符 demo_logger.info("\n" + "="*20 + " NEW DEMO SESSION STARTED " + "="*20) # ============================================================================== # --- 核心代码 --- # ============================================================================== class DialogueState(Enum): """对话状态枚举""" REQUIREMENT_ELICITATION = auto() # 需求梳理 AI_MODELING = auto() # AI建模 class Orchestrator: """ 对话编排器,负责管理对话流程、状态和调用判别器。 """ def __init__(self, model="gpt-4-turbo"): self.llm_client = LLMClient(model=model) self.conversation_history = [] self.state = DialogueState.REQUIREMENT_ELICITATION self.prompts = Prompts() # 记录初始化信息到各自的日志 debug_logger.info(f"Orchestrator initialized. Initial state: {self.state.name}") demo_logger.info(f"Initial State: {self.state.name}") def _format_history_for_prompt(self) -> str: """将对话历史格式化为字符串""" if not self.conversation_history: return "对话尚未开始。" return "\n".join([f"{msg['role']}: {msg['content']}" for msg in self.conversation_history]) def _check_information_sufficiency(self) -> bool: """ 【判别器部分-隐式触发】 使用大模型判断对话历史中信息是否足够建模。 """ if len(self.conversation_history) < 2: return False history_str = self._format_history_for_prompt() prompt = self.prompts.INFORMATION_SUFFICIENCY_CHECK.format(conversation_history=history_str) try: response_str = self.llm_client.chat(messages=[{"role": "user", "content": prompt}], temperature=0.1) debug_logger.info(f"Sufficiency check response: {response_str}") json_response = json.loads(response_str.strip()) if json_response.get("sufficient") is True: debug_logger.info(f"Information deemed sufficient. Reason: {json_response.get('reason')}") return True else: debug_logger.info(f"Information insufficient. Missing: {json_response.get('missing_elements')}") return False except (json.JSONDecodeError, KeyError, Exception) as e: debug_logger.error(f"Error during sufficiency check: {e}") return False def _check_explicit_trigger(self, user_input: str) -> bool: """ 【判别器部分-显式触发】 使用大模型判断用户是否明确要求建模。 """ prompt = self.prompts.EXPLICIT_TRIGGER_CHECK.format(user_input=user_input) try: intent = self.llm_client.identify_intent(prompt, temperature=0.1) debug_logger.info(f"Explicit trigger check intent: '{intent}'") if "StartModeling" in intent: debug_logger.info("Explicit trigger to model detected.") return True return False except Exception as e: debug_logger.error(f"Error during explicit trigger check: {e}") return False def _discriminator(self, user_input: str) -> bool: """ 判别器主函数。 判断是否应该从“需求梳理”切换到“AI建模”。 """ # 规则1:最高优先级,检查用户是否明确要求建模 if self._check_explicit_trigger(user_input): return True # 定义一个阈值来判断是否为“长文本” LONG_TEXT_THRESHOLD = 200 # 字符数,你可以根据需要调整 # 规则2:如果用户输入的是长文本,使用专门的单文本分析器 if len(user_input) > LONG_TEXT_THRESHOLD: debug_logger.info(f"Long input detected (length: {len(user_input)}), using single text sufficiency check.") if self._check_single_text_sufficiency(user_input): return True # 规则3:对于常规的、渐进式对话,使用基于历史的分析器 # 注意:只有在不是长文本的情况下,或者长文本分析不充分时,才会走到这一步 if self._check_information_sufficiency(): return True return False def _check_single_text_sufficiency(self, text_block: str) -> bool: """ 【判别器部分-单文本分析】 使用大模型判断单一大段文本中的信息是否足够建模。 """ prompt = self.prompts.SINGLE_TEXT_SUFFICIENCY_CHECK.format(text_block=text_block) try: response_str = self.llm_client.chat(messages=[{"role": "user", "content": prompt}], temperature=0.1) debug_logger.info(f"Single text sufficiency check response: {response_str}") json_response = json.loads(response_str.strip()) if json_response.get("sufficient") is True: debug_logger.info(f"Information in single text deemed sufficient. Reason: {json_response.get('reason')}") return True else: debug_logger.info(f"Information in single text insufficient. Missing: {json_response.get('missing_elements')}") return False except (json.JSONDecodeError, KeyError, Exception) as e: debug_logger.error(f"Error during single text sufficiency check: {e}") return False def process_user_message(self, user_input: str) -> str: """ 处理单轮用户输入,并返回助手的回复。 """ # 在演示日志中记录用户输入 demo_logger.info(f"User Input: {user_input}") self.conversation_history.append({"role": "user", "content": user_input}) # 如果已在建模阶段,直接返回提示信息 if self.state == DialogueState.AI_MODELING: response_content = "当前已处于建模阶段,请等待模型生成结果。" # 记录当前状态到演示日志 demo_logger.info(f"Current State: {self.state.name} (No change)") return response_content # 调用判别器判断是否切换状态 should_switch = self._discriminator(user_input) if should_switch: self.state = DialogueState.AI_MODELING # 同时记录到两个日志文件 debug_logger.info(f"State changed to: {self.state.name}") demo_logger.info(f"State Changed To: {self.state.name}") response_content = self.prompts.AI_MODELING_NOTICE else: # 同样,同时记录状态信息 debug_logger.info(f"State remains: {self.state.name}") demo_logger.info(f"State Remains: {self.state.name}") system_prompt = self.prompts.REQUIREMENT_ELICITATION_SYSTEM_PROMPT messages = [{"role": "system", "content": system_prompt}] + self.conversation_history response_content = self.llm_client.chat(messages=messages) self.conversation_history.append({"role": "assistant", "content": response_content}) #demo_logger.info(f"Assistant Response: {response_content}") return response_content def reset(self): """重置对话""" self.conversation_history = [] self.state = DialogueState.REQUIREMENT_ELICITATION # 在两个日志中都记录重置事件 debug_logger.info("Orchestrator has been reset.") demo_logger.info("="*20 + " SESSION RESET " + "="*20) demo_logger.info(f"Initial State: {self.state.name}")