|
@@ -0,0 +1,498 @@
|
|
|
|
|
+import json
|
|
|
|
|
+import uuid
|
|
|
|
|
+from pathlib import Path
|
|
|
|
|
+from typing import List, Optional, Tuple
|
|
|
|
|
+
|
|
|
|
|
+from hello_agents import HelloAgentsLLM, SimpleAgent, ToolRegistry
|
|
|
|
|
+
|
|
|
|
|
+from config import *
|
|
|
|
|
+from model import UserMemory, Task, Plan, ScheduleItem
|
|
|
|
|
+from prompt import *
|
|
|
|
|
+from tools import get_Tools
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class MainAgent:
|
|
|
|
|
+ def __init__(self):
|
|
|
|
|
+ self.llm = HelloAgentsLLM(model=LLM_MODEL, api_key=LLM_API_KEY, base_url=LLM_BASE_URL)
|
|
|
|
|
+ tool = get_Tools()
|
|
|
|
|
+ self.respostry = ToolRegistry()
|
|
|
|
|
+ self.respostry.register_tool(tool)
|
|
|
|
|
+ self.main_agent = SimpleAgent(
|
|
|
|
|
+ name="总控MAIN_AGENT",
|
|
|
|
|
+ llm=self.llm,
|
|
|
|
|
+ system_prompt=MAIN_AGENT_PROMPT,
|
|
|
|
|
+ tool_registry=self.respostry
|
|
|
|
|
+ )
|
|
|
|
|
+ self.np_parser_agent = SimpleAgent(
|
|
|
|
|
+ name="子Agent:自然语言解析,用户输入 → Task列表 + 意图",
|
|
|
|
|
+ llm=self.llm,
|
|
|
|
|
+ system_prompt=NL_PARSE_PROMPT,
|
|
|
|
|
+ tool_registry=self.respostry
|
|
|
|
|
+ )
|
|
|
|
|
+ # self.np_parser_agent.add_tool(tool)
|
|
|
|
|
+ self.plan_Agent = SimpleAgent(
|
|
|
|
|
+ name="子Agent:你是日程规划Agent,用于规划用户日程",
|
|
|
|
|
+ llm=self.llm,
|
|
|
|
|
+ system_prompt=PLAN_REASON_PROMPT,
|
|
|
|
|
+ tool_registry=self.respostry
|
|
|
|
|
+ )
|
|
|
|
|
+ # self.plan_Agent.add_tool(tool)
|
|
|
|
|
+ self.validator_agent = SimpleAgent(
|
|
|
|
|
+ name="子Agent:你是计划校验Agent,检查生成的日程计划是否违反用户记忆、是否存在时间冲突。",
|
|
|
|
|
+ llm=self.llm,
|
|
|
|
|
+ system_prompt=VALIDATE_PROMPT,
|
|
|
|
|
+ tool_registry=self.respostry
|
|
|
|
|
+ )
|
|
|
|
|
+ # self.validator_agent.add_tool(tool)
|
|
|
|
|
+ self.dialog_history: List[dict] = []
|
|
|
|
|
+
|
|
|
|
|
+ def _build_context_str(self, user_input: str, memory: UserMemory, current_plan: Optional[Plan], mem: Optional[UserMemory], plan: Optional[Plan]) -> str:
|
|
|
|
|
+ memory_json = json.dumps(memory.model_dump(), ensure_ascii=False, indent=2)
|
|
|
|
|
+ cp_json = json.dumps(current_plan.model_dump(), ensure_ascii=False, indent=2) if current_plan else 'null'
|
|
|
|
|
+ history_text = []
|
|
|
|
|
+ history_text.append(f"====当前系统时间====\n{datetime.now().strftime('%Y‑%m‑%d %H:%M:%S')}")
|
|
|
|
|
+ history_text.append("====对话历史====")
|
|
|
|
|
+ for msg in self.dialog_history:
|
|
|
|
|
+ role = msg['role']
|
|
|
|
|
+ cnt = msg["content"]
|
|
|
|
|
+ history_text.append(f"{role}: {cnt}")
|
|
|
|
|
+ history_text.append("====用户记忆====")
|
|
|
|
|
+ history_text.append(memory_json)
|
|
|
|
|
+ history_text.append(mem)
|
|
|
|
|
+ history_text.append("====当前已存在计划current_plan====")
|
|
|
|
|
+ history_text.append(cp_json)
|
|
|
|
|
+ history_text.append(plan)
|
|
|
|
|
+ history_text.append("====用户最新输入====")
|
|
|
|
|
+ history_text.append(user_input)
|
|
|
|
|
+ return "\n".join(history_text)
|
|
|
|
|
+
|
|
|
|
|
+ def _build_subagent_payload(self, pass_param: dict, memory: UserMemory, current_plan: Optional[Plan]) -> dict:
|
|
|
|
|
+ """构造子Agent输入:强制注入公共上下文,不依赖总控pass_to_sub"""
|
|
|
|
|
+ import datetime
|
|
|
|
|
+ payload = {**pass_param}
|
|
|
|
|
+ # 强制注入
|
|
|
|
|
+ payload["current_system_time"] = datetime.datetime.now().strftime("%Y‑%m‑%d %H:%M:%S")
|
|
|
|
|
+ payload["user_memory"] = memory.model_dump()
|
|
|
|
|
+ payload["current_plan"] = current_plan.model_dump() if current_plan else None
|
|
|
|
|
+ payload["dialog_history"] = self.dialog_history.copy()
|
|
|
|
|
+ return payload
|
|
|
|
|
+
|
|
|
|
|
+ def run(self, user_input: str, memory: UserMemory, current_plan: Optional[Plan], mem: Optional[UserMemory] = 'null', plan: Optional[Plan] = 'null') -> Tuple[Optional[Plan], str]:
|
|
|
|
|
+ try:
|
|
|
|
|
+ self.dialog_history.append({'role':'user','content':user_input})
|
|
|
|
|
+ max_loop = 10
|
|
|
|
|
+ loop_count = 0
|
|
|
|
|
+ final_plan: Optional[Plan] = None
|
|
|
|
|
+ final_output: str=""
|
|
|
|
|
+ while loop_count < max_loop:
|
|
|
|
|
+ loop_count += 1
|
|
|
|
|
+ context = self._build_context_str(user_input, memory, current_plan, mem, plan)
|
|
|
|
|
+ main_raw = self.main_agent.run(context)
|
|
|
|
|
+ main_raw = main_raw.strip().removeprefix("```json").removesuffix("```").strip()
|
|
|
|
|
+ try:
|
|
|
|
|
+ main_desicion = json.loads(main_raw)
|
|
|
|
|
+ except:
|
|
|
|
|
+ err_msg = f'总控Agent决策解析失败,原始输出:{main_raw[:300]}'
|
|
|
|
|
+ final_output = err_msg
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ call_sub = main_desicion.get("call_sub_agent", 'finish')
|
|
|
|
|
+ pass_param = main_desicion.get("pass_to_sub", {})
|
|
|
|
|
+ if call_sub == 'finish':
|
|
|
|
|
+ # 总控通知结束
|
|
|
|
|
+ fp_json = pass_param.get("final_plan_json")
|
|
|
|
|
+ final_output = pass_param.get("output_text", "处理完成")
|
|
|
|
|
+ if fp_json and fp_json != "null":
|
|
|
|
|
+ try:
|
|
|
|
|
+ final_plan = Plan.model_validate_json(fp_json)
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ final_plan = None
|
|
|
|
|
+ break
|
|
|
|
|
+ if call_sub == 'nl_parser':
|
|
|
|
|
+ sub_payload = self._build_subagent_payload(pass_param, memory, current_plan)
|
|
|
|
|
+ sub_query = json.dumps(sub_payload, ensure_ascii=False, indent=2)
|
|
|
|
|
+ sub_out = self.np_parser_agent.run(sub_query)
|
|
|
|
|
+ self.dialog_history.append({'role': 'tool:nl_parser', "content": sub_out})
|
|
|
|
|
+
|
|
|
|
|
+ elif call_sub == 'planner':
|
|
|
|
|
+ sub_payload = self._build_subagent_payload(pass_param, memory, current_plan)
|
|
|
|
|
+ plan_query = json.dumps(sub_payload, ensure_ascii=False, indent=2)
|
|
|
|
|
+ sub_out = self.plan_Agent.run(plan_query)
|
|
|
|
|
+ self.dialog_history.append({'role': 'tool:planner', "content": sub_out})
|
|
|
|
|
+
|
|
|
|
|
+ elif call_sub == 'validator':
|
|
|
|
|
+ sub_payload = self._build_subagent_payload(pass_param, memory, current_plan)
|
|
|
|
|
+ sub_query = json.dumps(sub_payload, ensure_ascii=False, indent=2)
|
|
|
|
|
+ sub_out = self.validator_agent.run(sub_query)
|
|
|
|
|
+ self.dialog_history.append({'role': 'tool:validator', "content": sub_out})
|
|
|
|
|
+
|
|
|
|
|
+ else:
|
|
|
|
|
+ final_output = f"总控调用未知子Agent:{call_sub}"
|
|
|
|
|
+ break
|
|
|
|
|
+ else:
|
|
|
|
|
+ final_output = "循环已满,处理过程终止"
|
|
|
|
|
+ self.dialog_history.append({'role':'assistant', "content":final_output})
|
|
|
|
|
+ return final_plan, final_output
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"总控智能体出现错误:{str(e)},即将使用默认模式")
|
|
|
|
|
+ return self.run_bak(user_input, memory, current_plan)
|
|
|
|
|
+
|
|
|
|
|
+ def run_bak(self, user_input: str, memory: UserMemory, current_plan: Optional[Plan]) -> Tuple[Optional[Plan], str]:
|
|
|
|
|
+ """
|
|
|
|
|
+ :param user_input: 用户原始输入
|
|
|
|
|
+ :param memory: 用户记忆对象
|
|
|
|
|
+ :param current_plan: 当前已存在计划(修改场景使用)
|
|
|
|
|
+ :return: (Plan对象 or None, 对外输出文本/需要澄清的问题)
|
|
|
|
|
+ """
|
|
|
|
|
+ # self.dialog_history.append(AgentMessage(role="user", content=user_input))
|
|
|
|
|
+ memory_json = json.dumps(memory.model_dump(), ensure_ascii=False, indent=2)
|
|
|
|
|
+ # 把用户输入 + 用户记忆拼为子Agent的user query
|
|
|
|
|
+ nl_user_query = f"""
|
|
|
|
|
+ 用户记忆:
|
|
|
|
|
+ {memory_json}
|
|
|
|
|
+ 用户输入:
|
|
|
|
|
+ {user_input}
|
|
|
|
|
+
|
|
|
|
|
+ 请输出JSON格式结果,包含
|
|
|
|
|
+ intent_type, target_date(YYYY‑MM‑DD,解析不出返回null), tasks, modify_operation。
|
|
|
|
|
+ tasks 为任务数组。
|
|
|
|
|
+ """
|
|
|
|
|
+ ### Agent 第一次调用,解析用户问题,然后生成任务列表
|
|
|
|
|
+ nl_result_raw = self.np_parser_agent.run(nl_user_query)
|
|
|
|
|
+ nl_result_raw = nl_result_raw.strip().removeprefix("```json").removesuffix("```").strip()
|
|
|
|
|
+ try:
|
|
|
|
|
+ nl_out = json.loads(nl_result_raw)
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ return None, "解析用户需求失败,大模型返回格式异常,请重新描述你的需求。"
|
|
|
|
|
+
|
|
|
|
|
+ intent_type = nl_out.get("intent_type", "")
|
|
|
|
|
+ task_dicts = nl_out.get("tasks", [])
|
|
|
|
|
+ target_date: str | None = nl_out.get("target_date")
|
|
|
|
|
+ plan_name: str | None = nl_out.get("name_summary")
|
|
|
|
|
+ modify_operation = nl_out.get("modify_operation", {})
|
|
|
|
|
+ long_term_meta = nl_out.get("long_term_meta", {})
|
|
|
|
|
+ if not target_date:
|
|
|
|
|
+ # 兜底:取系统当前日期
|
|
|
|
|
+ target_date = datetime.now().strftime("%Y‑%m‑%d %H:%M:%S")
|
|
|
|
|
+ try:
|
|
|
|
|
+ task_list: List[Task] = [Task.model_validate(d) for d in task_dicts]
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ return None, "任务数据解析失败,请简化你的描述。"
|
|
|
|
|
+ ### 识别意图,然后决定怎么做这个任务
|
|
|
|
|
+ if intent_type == "new_plan":
|
|
|
|
|
+ task_list_json = json.dumps([t.model_dump() for t in task_list], ensure_ascii=False, indent=2)
|
|
|
|
|
+ plan_user_query = f"""
|
|
|
|
|
+ 用户记忆:
|
|
|
|
|
+ {memory_json}
|
|
|
|
|
+ 需要规划的目标日期:
|
|
|
|
|
+ {target_date}
|
|
|
|
|
+ 待规划任务列表:
|
|
|
|
|
+ {task_list_json}
|
|
|
|
|
+
|
|
|
|
|
+ 请输出JSON,包含 schedule_items(日程条目数组)、plan_summary。
|
|
|
|
|
+ 日程的时间需要基于目标日期生成,时间格式 YYYY‑MM‑DD HH:MM。
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ plan_result_raw = self.plan_Agent.run(plan_user_query)
|
|
|
|
|
+ plan_result_raw = plan_result_raw.strip().removeprefix("```json").removesuffix("```").strip()
|
|
|
|
|
+ try:
|
|
|
|
|
+ plan_out = json.loads(plan_result_raw)
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ return None, "生成日程失败,格式错误。"
|
|
|
|
|
+ schedule_dicts = plan_out.get("schedule_items", [])
|
|
|
|
|
+ try:
|
|
|
|
|
+ schedule_items: List[ScheduleItem] = [ScheduleItem.model_validate(s) for s in schedule_dicts]
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ return None, "日程条目数据解析失败。"
|
|
|
|
|
+ new_plan = Plan(
|
|
|
|
|
+ plan_id=str(uuid.uuid4()),
|
|
|
|
|
+ plan_name=plan_name,
|
|
|
|
|
+ create_time=target_date if target_date else datetime.now().strftime("%Y‑%m‑%d %H:%M"),
|
|
|
|
|
+ source_user_input=user_input,
|
|
|
|
|
+ tasks=task_list,
|
|
|
|
|
+ schedule_items=schedule_items,
|
|
|
|
|
+ plan_summary=plan_out.get("plan_summary", ""),
|
|
|
|
|
+ conflict_check_result="pending"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ plan_json = json.dumps(new_plan.model_dump(), ensure_ascii=False, indent=2)
|
|
|
|
|
+ validate_user_query = f"""
|
|
|
|
|
+ 用户记忆:
|
|
|
|
|
+ {memory_json}
|
|
|
|
|
+
|
|
|
|
|
+ 待校验计划:
|
|
|
|
|
+ {plan_json}
|
|
|
|
|
+
|
|
|
|
|
+ 请输出JSON:
|
|
|
|
|
+ {{
|
|
|
|
|
+ "has_error": bool,
|
|
|
|
|
+ "need_user_clarify": bool,
|
|
|
|
|
+ "clarify_question": str,
|
|
|
|
|
+ "error_msg": str
|
|
|
|
|
+ }}
|
|
|
|
|
+ """
|
|
|
|
|
+ validate_raw = self.validator_agent.run(validate_user_query)
|
|
|
|
|
+ validate_raw = validate_raw.strip().removeprefix("```json").removesuffix("```").strip()
|
|
|
|
|
+ try:
|
|
|
|
|
+ validate_out = json.loads(validate_raw)
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ new_plan.conflict_check_result = 'unknown'
|
|
|
|
|
+ return new_plan, new_plan.plan_summary + "\n 校验新建环节解析异常"
|
|
|
|
|
+ has_error = validate_out.get("has_error", False)
|
|
|
|
|
+ need_user_clarify = validate_out.get("need_user_clarify", False)
|
|
|
|
|
+ clarify_question = validate_out.get("clarify_question", "")
|
|
|
|
|
+ if need_user_clarify:
|
|
|
|
|
+ return None, clarify_question
|
|
|
|
|
+ if has_error:
|
|
|
|
|
+ new_plan.conflict_check_result = "has_error"
|
|
|
|
|
+ else:
|
|
|
|
|
+ new_plan.conflict_check_result = "ok"
|
|
|
|
|
+ return new_plan, new_plan.plan_summary
|
|
|
|
|
+
|
|
|
|
|
+ elif intent_type == "modify_plan":
|
|
|
|
|
+ if current_plan is None:
|
|
|
|
|
+ return None, "当前没有已生成的计划,请先生成一份日程计划之后再执行修改。"
|
|
|
|
|
+ old_plan_json = json.dumps(current_plan.model_dump(), ensure_ascii=False, indent=2)
|
|
|
|
|
+ op_json = json.dumps(modify_operation, ensure_ascii=False, indent=2)
|
|
|
|
|
+ plan_user_query = f"""
|
|
|
|
|
+ 用户记忆:
|
|
|
|
|
+ {memory_json}
|
|
|
|
|
+ 目标日期:{target_date}
|
|
|
|
|
+ 原始旧计划:
|
|
|
|
|
+ {old_plan_json}
|
|
|
|
|
+ 用户修改操作:
|
|
|
|
|
+ {op_json}
|
|
|
|
|
+
|
|
|
|
|
+ 请基于旧计划,按照修改操作重新生成完整的schedule_items。
|
|
|
|
|
+ 输出JSON:{{"schedule_items":[...],"plan_summary":"描述本次修改后的计划"}}
|
|
|
|
|
+ 时间格式 YYYY‑MM‑DD HH:MM。
|
|
|
|
|
+ """
|
|
|
|
|
+ plan_result_raw = self.plan_Agent.run(plan_user_query)
|
|
|
|
|
+ plan_result_raw = plan_result_raw.strip().removeprefix("```json").removesuffix("```").strip()
|
|
|
|
|
+ try:
|
|
|
|
|
+ plan_out = json.loads(plan_result_raw)
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ return None, "修改计划失败,返回格式异常。"
|
|
|
|
|
+
|
|
|
|
|
+ schedule_dicts = plan_out.get("schedule_items", [])
|
|
|
|
|
+ try:
|
|
|
|
|
+ schedule_items: List[ScheduleItem] = [ScheduleItem.model_validate(s) for s in schedule_dicts]
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ return None, "修改后日程数据解析失败。"
|
|
|
|
|
+
|
|
|
|
|
+ merged_tasks = current_plan.tasks.copy()
|
|
|
|
|
+ merged_tasks.extend(task_list)
|
|
|
|
|
+ modified_plan = Plan(
|
|
|
|
|
+ plan_id=str(uuid.uuid4()),
|
|
|
|
|
+ plan_name=plan_name,
|
|
|
|
|
+ create_time=target_date if target_date else datetime.now().strftime("%Y‑%m‑%d %H:%M"),
|
|
|
|
|
+ source_user_input=f"[修改] {user_input}",
|
|
|
|
|
+ tasks=task_list,
|
|
|
|
|
+ schedule_items=schedule_items,
|
|
|
|
|
+ plan_summary=plan_out.get("plan_summary", ""),
|
|
|
|
|
+ conflict_check_result="pending"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ plan_json = json.dumps(modified_plan.model_dump(), ensure_ascii=False, indent=2)
|
|
|
|
|
+ validate_user_query = f"""
|
|
|
|
|
+ 用户记忆:
|
|
|
|
|
+ {memory_json}
|
|
|
|
|
+
|
|
|
|
|
+ 待校验计划:
|
|
|
|
|
+ {plan_json}
|
|
|
|
|
+
|
|
|
|
|
+ 请输出JSON:
|
|
|
|
|
+ {{
|
|
|
|
|
+ "has_error": bool,
|
|
|
|
|
+ "need_user_clarify": bool,
|
|
|
|
|
+ "clarify_question": str,
|
|
|
|
|
+ "error_msg": str
|
|
|
|
|
+ }}
|
|
|
|
|
+ """
|
|
|
|
|
+ validate_raw = self.validator_agent.run(validate_user_query)
|
|
|
|
|
+ validate_raw = validate_raw.strip().removeprefix("```json").removesuffix("```").strip()
|
|
|
|
|
+ try:
|
|
|
|
|
+ validate_out = json.loads(validate_raw)
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ modified_plan.conflict_check_result = 'unknown'
|
|
|
|
|
+ return modified_plan, modified_plan.plan_summary + "\n 校验修改环节解析异常"
|
|
|
|
|
+ has_error = validate_out.get("has_error", False)
|
|
|
|
|
+ need_user_clarify = validate_out.get("need_user_clarify", False)
|
|
|
|
|
+ clarify_question = validate_out.get("clarify_question", "")
|
|
|
|
|
+ if need_user_clarify:
|
|
|
|
|
+ return None, clarify_question
|
|
|
|
|
+ if has_error:
|
|
|
|
|
+ modified_plan.conflict_check_result = "has_error"
|
|
|
|
|
+ else:
|
|
|
|
|
+ modified_plan.conflict_check_result = "ok"
|
|
|
|
|
+ return modified_plan, modified_plan.plan_summary
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ elif intent_type == "long_term_goal":
|
|
|
|
|
+ """
|
|
|
|
|
+ 1.读取long_term_meta,调用 subtask_split_helper_tool 拆出多个子任务描述
|
|
|
|
|
+ 2.把拆分得到的子任务组装成Task列表
|
|
|
|
|
+ 3.复用new_plan整套规划+校验逻辑
|
|
|
|
|
+ """
|
|
|
|
|
+ try:
|
|
|
|
|
+ total_days = int(long_term_meta.get("total_days", 0))
|
|
|
|
|
+ daily_duration = int(long_term_meta.get("daily_duration_min", 0))
|
|
|
|
|
+ goal_desc = long_term_meta.get("goal_desc", "")
|
|
|
|
|
+ except (ValueError, TypeError):
|
|
|
|
|
+ return None, "长期目标参数解析失败,请明确说明总天数、每日耗时。"
|
|
|
|
|
+
|
|
|
|
|
+ if total_days <= 0 or daily_duration < 0:
|
|
|
|
|
+ return None, "总天数或者每日时长必须大于0"
|
|
|
|
|
+ try:
|
|
|
|
|
+ tool = self.respostry.get_tool("subtask_split_helper_tool")
|
|
|
|
|
+ sub_task_info = tool.run({"total_days": total_days, "goal_desc": goal_desc})
|
|
|
|
|
+ except:
|
|
|
|
|
+ print("仓库工具调用失败,进行本地调用")
|
|
|
|
|
+ sub_task_info = get_Tools.subtask_split_helper_tool(total_days, main_desc=goal_desc)
|
|
|
|
|
+ # 把拆分结果转为Task模型
|
|
|
|
|
+ long_tasks: List[Task] = []
|
|
|
|
|
+ for idx, info in enumerate(sub_task_info):
|
|
|
|
|
+ t = Task(
|
|
|
|
|
+ task_id=f"lt_{uuid.uuid4()}",
|
|
|
|
|
+ task_name=info["sub_desc"],
|
|
|
|
|
+ description=f"长期目标:{goal_desc}",
|
|
|
|
|
+ estimated_duration_min=daily_duration,
|
|
|
|
|
+ priority="medium",
|
|
|
|
|
+ deadline=None,
|
|
|
|
|
+ fixed_time=None,
|
|
|
|
|
+ allowed_time=None
|
|
|
|
|
+ )
|
|
|
|
|
+ long_tasks.append(t)
|
|
|
|
|
+
|
|
|
|
|
+ # 合并用户输入附带的任务 + 拆分出来长期子任务
|
|
|
|
|
+ full_task_list = task_list + long_tasks
|
|
|
|
|
+ task_list_json = json.dumps([t.model_dump() for t in full_task_list], ensure_ascii=False, indent=2)
|
|
|
|
|
+ plan_user_query = f"""
|
|
|
|
|
+ 用户记忆:
|
|
|
|
|
+ {memory_json}
|
|
|
|
|
+ 规划起始目标日期:{target_date}
|
|
|
|
|
+ 长期任务列表:
|
|
|
|
|
+ {task_list_json}
|
|
|
|
|
+ 请跨多天生成schedule_items,输出JSON,包含schedule_items、plan_summary。
|
|
|
|
|
+ 时间格式 YYYY‑MM‑DD HH:MM。
|
|
|
|
|
+ """
|
|
|
|
|
+ plan_result_raw = self.plan_Agent.run(plan_user_query)
|
|
|
|
|
+ plan_result_raw = plan_result_raw.strip().removeprefix("```json").removesuffix("```").strip()
|
|
|
|
|
+ try:
|
|
|
|
|
+ plan_out = json.loads(plan_result_raw)
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ return None, "修改计划失败,返回格式异常。"
|
|
|
|
|
+
|
|
|
|
|
+ schedule_dicts = plan_out.get("schedule_items", [])
|
|
|
|
|
+ try:
|
|
|
|
|
+ schedule_items: List[ScheduleItem] = [ScheduleItem.model_validate(s) for s in schedule_dicts]
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ return None, "长期任务日程数据解析失败。"
|
|
|
|
|
+
|
|
|
|
|
+ long_term_plan = Plan(
|
|
|
|
|
+ plan_id=str(uuid.uuid4()),
|
|
|
|
|
+ plan_name=plan_name,
|
|
|
|
|
+ create_time=target_date if target_date else datetime.now().strftime("%Y‑%m‑%d %H:%M"),
|
|
|
|
|
+ source_user_input=f"[长期任务] {user_input}",
|
|
|
|
|
+ tasks=task_list,
|
|
|
|
|
+ schedule_items=schedule_items,
|
|
|
|
|
+ plan_summary=plan_out.get("plan_summary", ""),
|
|
|
|
|
+ conflict_check_result="pending"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ plan_json = json.dumps(long_term_plan.model_dump(), ensure_ascii=False, indent=2)
|
|
|
|
|
+ validate_user_query = f"""
|
|
|
|
|
+ 用户记忆:
|
|
|
|
|
+ {memory_json}
|
|
|
|
|
+
|
|
|
|
|
+ 待校验计划:
|
|
|
|
|
+ {plan_json}
|
|
|
|
|
+
|
|
|
|
|
+ 请输出JSON:
|
|
|
|
|
+ {{
|
|
|
|
|
+ "has_error": bool,
|
|
|
|
|
+ "need_user_clarify": bool,
|
|
|
|
|
+ "clarify_question": str,
|
|
|
|
|
+ "error_msg": str
|
|
|
|
|
+ }}
|
|
|
|
|
+ """
|
|
|
|
|
+ validate_raw = self.validator_agent.run(validate_user_query)
|
|
|
|
|
+ validate_raw = validate_raw.strip().removeprefix("```json").removesuffix("```").strip()
|
|
|
|
|
+ try:
|
|
|
|
|
+ validate_out = json.loads(validate_raw)
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ long_term_plan.conflict_check_result = 'unknown'
|
|
|
|
|
+ return long_term_plan, long_term_plan.plan_summary + "\n 校验长任务环节解析异常"
|
|
|
|
|
+ has_error = validate_out.get("has_error", False)
|
|
|
|
|
+ need_user_clarify = validate_out.get("need_user_clarify", False)
|
|
|
|
|
+ clarify_question = validate_out.get("clarify_question", "")
|
|
|
|
|
+ if need_user_clarify:
|
|
|
|
|
+ return None, clarify_question
|
|
|
|
|
+ if has_error:
|
|
|
|
|
+ long_term_plan.conflict_check_result = "has_error"
|
|
|
|
|
+ else:
|
|
|
|
|
+ long_term_plan.conflict_check_result = "ok"
|
|
|
|
|
+ return long_term_plan, long_term_plan.plan_summary
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ else:
|
|
|
|
|
+ return None, f"无法识别意图:{intent_type}, 请重新描述你的需求"
|
|
|
|
|
+
|
|
|
|
|
+ def clear_memory(self):
|
|
|
|
|
+ """清空本轮对话记忆,保留agent实例,适合开启新会话"""
|
|
|
|
|
+ self.dialog_history.clear()
|
|
|
|
|
+
|
|
|
|
|
+ def save_session(self, save_path: Path | str, memory: UserMemory, current_plan: Optional[Plan]):
|
|
|
|
|
+ """将会话保存到JSON文件"""
|
|
|
|
|
+ data = {
|
|
|
|
|
+ "dialog_history": self.dialog_history,
|
|
|
|
|
+ "user_memory": memory.model_dump(),
|
|
|
|
|
+ "last_plan": current_plan.model_dump() if current_plan else None
|
|
|
|
|
+ }
|
|
|
|
|
+ with open(save_path, "w", encoding="utf‑8") as f:
|
|
|
|
|
+ json.dump(data, f, ensure_ascii=False, indent=2)
|
|
|
|
|
+
|
|
|
|
|
+ def load_session(self, load_path: Path | str) -> tuple[UserMemory | None, Plan | None]:
|
|
|
|
|
+ """从JSON恢复会话,返回(memory, current_plan),同时填充self.dialog_history"""
|
|
|
|
|
+ if not Path(load_path).exists():
|
|
|
|
|
+ return None, None
|
|
|
|
|
+ with open(load_path, "r", encoding="utf‑8") as f:
|
|
|
|
|
+ data = json.load(f)
|
|
|
|
|
+ self.dialog_history = data.get("dialog_history", [])
|
|
|
|
|
+ mem_data = data.get("user_memory")
|
|
|
|
|
+ plan_data = data.get("last_plan")
|
|
|
|
|
+ mem = UserMemory(**mem_data) if mem_data else None
|
|
|
|
|
+ plan = Plan(**plan_data) if plan_data else None
|
|
|
|
|
+ return mem, plan
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def demo_memory() -> UserMemory:
|
|
|
|
|
+ return UserMemory(
|
|
|
|
|
+ user_id="u_001",
|
|
|
|
|
+ work_start="09:00",
|
|
|
|
|
+ work_end="18:00",
|
|
|
|
|
+ rest_days=["Saturday", "Sunday"],
|
|
|
|
|
+ avoid_time=["12:00‑13:30"],
|
|
|
|
|
+ preference={"priority_rule": "工作事务优先"},
|
|
|
|
|
+ hobbies=["慢跑", "看书"],
|
|
|
|
|
+ dislike=["早起高强度运动"]
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def main():
|
|
|
|
|
+ agent = MainAgent()
|
|
|
|
|
+ memory = demo_memory()
|
|
|
|
|
+ current_plan = None
|
|
|
|
|
+ user_input = "帮我规划今天的日程,我需要复习90分钟,慢跑40分钟。"
|
|
|
|
|
+ plan, output_text = agent.run(user_input, memory, current_plan)
|
|
|
|
|
+ if plan is None:
|
|
|
|
|
+ print(f"输出:{output_text}")
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(f"\n计划摘要:{plan.plan_summary}")
|
|
|
|
|
+ for item in plan.schedule_items:
|
|
|
|
|
+ print(f"{item.start_time} ~ {item.end_time} | {item.task_name}")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
|
+ main()
|