vent_agent.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. # -*- coding: utf-8 -*-
  2. """
  3. DeepAgents Agent 创建与配置模块
  4. 核心功能:
  5. - 创建主 DeepAgent(整合 Skills + Tools + MCP 数据)
  6. - 配置流式输出
  7. - 管理 Agent 实例生命周期
  8. 架构:
  9. - 模型: OpenAI GPT 系列(通过环境变量 OPENAI_API_KEY 配置)
  10. - Skills: 渐进式加载(点选解读 / 对话解读)
  11. - Tools: 5个数据查询工具(query_laneway_data / query_regulation 等)
  12. - Backend: FilesystemBackend(virtual_mode,用于加载本地技能文件;写操作已禁用)
  13. """
  14. import os
  15. from pathlib import Path
  16. from typing import Optional
  17. from dotenv import load_dotenv
  18. from langchain.agents.middleware.todo import write_todos
  19. from langgraph.checkpoint.memory import MemorySaver
  20. SKILLS_ROOT = str((Path(__file__).parent.parent / "skills").resolve())
  21. PROJECT_ROOT = str((Path(__file__).parent.parent).resolve())
  22. load_dotenv()
  23. # 导入工具函数
  24. from tools.vent_tools import (
  25. # 数据查询
  26. query_tun_data_by_id,
  27. query_knowledge_base,
  28. query_device_data,
  29. query_device_data_by_id,
  30. query_devices_by_tunnel,
  31. query_devices_by_tunnel_id,
  32. query_devices_by_model,
  33. get_tun_list_by_modelid,
  34. query_tunnels_by_model,
  35. query_tunnel_list,
  36. list_ventanaly_monitor_data_days,
  37. get_device_kind_dict,
  38. get_device_list_by_kind,
  39. query_device_realtime_data,
  40. get_needq_all_data,
  41. get_dict_list_by_dictcode,
  42. # 需风量 / 模型
  43. get_model_param_pub_list,
  44. get_model_wind,
  45. get_sensor_wind,
  46. simulate_needq_heading_face,
  47. simulate_needq_room,
  48. simulate_needq_ret_work_face,
  49. simulate_needq_other,
  50. # 设备信息
  51. query_device_info,
  52. query_device_type_info,
  53. query_monitor_params,
  54. # 故障诊断
  55. check_model_connect_status,
  56. check_model_one_dir_cycle,
  57. check_model_one_dir_node,
  58. check_model_diagonal_structure,
  59. get_model_fault_diagnosis,
  60. # 避灾路线
  61. get_escape_path,
  62. get_escape_path_each_exit,
  63. # 关键阻力 / 压能
  64. get_out_shafts,
  65. get_in_shafts,
  66. get_max_resistance_path,
  67. get_three_area_distribution,
  68. get_key_path_decision,
  69. get_path_press_power,
  70. # 网络解算
  71. net_cal,
  72. net_cal_for_plan,
  73. # 报警 / 日志
  74. get_alarm_log_history,
  75. get_device_set_log_history,
  76. get_sys_log_history,
  77. # 场景管理
  78. get_manage_system_by_strType,
  79. query_system_by_systemID,
  80. # 煤矿基础
  81. get_gas_identify_vo,
  82. get_by_mine_name,
  83. query_control_testWind,
  84. # 数据库 / 文件
  85. execute_sql_query,
  86. get_file_list_by_type,
  87. get_file_base64_by_id,
  88. # 报表
  89. get_latest_report,
  90. # 用户偏好
  91. save_user_preference,
  92. list_user_preferences,
  93. delete_user_preference,
  94. # 计划审批
  95. request_plan_approval,
  96. )
  97. from tools.web_search_tools import web_search, web_fetch
  98. from tools.file_reader_tools import read_file_content
  99. from tools.time_tools import get_current_time
  100. from tools.chat_history_tools import search_chat_history, get_session_chat, get_current_session_id
  101. from tools.report_utils import save_report
  102. from tools.audit_middleware import create_audit_middleware
  103. from tools.context_tracker import init_system_components
  104. from langgraph.utils.runnable import RunnableCallable
  105. # 导入需风量计算工具
  106. from tools.calc_tools import (
  107. calc_face_by_gas,
  108. calc_face_by_workers,
  109. calc_face_by_wind_speed,
  110. calc_face_air_volume_max,
  111. calc_tunnel_by_gas,
  112. calc_tunnel_by_explosives,
  113. calc_tunnel_by_workers,
  114. calc_tunnel_by_wind_speed,
  115. calc_tunnel_air_volume_max,
  116. calc_chamber_by_equipment,
  117. calc_chamber_by_wind_speed,
  118. calc_other_by_wind_speed,
  119. calc_effective_area,
  120. calc_total_air_volume,
  121. )
  122. # ============================================================
  123. # 系统提示词(中文)
  124. # ============================================================
  125. # 点选解读 Agent 系统提示词
  126. CLICK_INTERPRET_SYSTEM_PROMPT = """你是一名煤矿通风安全专家,你拥有多项技能,按照技能中的步骤执行任务。
  127. ## 全局通用行为准则
  128. 1. 所有分析必须基于工具返回的真实数据,绝不编造。
  129. 2. 引用规程时,必须附上具体的条款来源。
  130. 3. 禁止输出 ANSI 转义序列、内部工具名、函数名、变量、技能标识、ID 等系统内部信息。
  131. 5. 全流程统一使用 write_todos 工具实时更新任务进度。
  132. 6. 全程仅使用简体中文输出,不输出代码、JSON原始结构体。
  133. 7. 你必须严格按照技能中**固定格式**输出报告,禁止添加额外的标题、总结、代码块、JSON。
  134. 8. 在生成最终解读报告标题前,必须输出一个换行符 '\n\n'。
  135. """
  136. # 对话解读 Agent 系统提示词(统一版:合并了对话解读 + 需风量计算能力)
  137. DIALOG_INTERPRET_SYSTEM_PROMPT = f"""你是一名煤矿通风安全智能助手,具备两大核心能力:**通风监测数据解读**和**需风量交互式计算**。根据用户意图自动切换工作模式。
  138. ## 能力一:通风监测数据解读
  139. ### 工作流程
  140. 1. 理解用户意图:想查询什么数据、什么时间范围、哪条巷道
  141. 2. 调用相应工具获取数据:
  142. - 查巷道实时数据 → query_tun_data_by_id
  143. - 查设备数据/报警 → query_device_data_by_id
  144. - 查设备实时快照 → query_device_realtime_data
  145. - 查巷道下设备列表 → query_devices_by_tunnel / query_devices_by_tunnel_id
  146. - 查模型下巷道列表 → query_tunnels_by_model
  147. - 查设备历史趋势 → list_ventanaly_monitor_data_days
  148. - 查规程标准 → query_knowledge_base
  149. - 查设备类型字典 → get_device_kind_dict
  150. - 按设备类型查设备列表 → get_device_list_by_kind
  151. 3. 比对规程标准进行合规判断
  152. 4. 生成自然语言回复
  153. ## 能力二:需风量交互式计算
  154. ### 支持的用风地点类型
  155. - 采煤工作面(含备用工作面)、掘进工作面、机电硐室
  156. - 其他用风地点(主要进回风巷、采区进回风巷、其他通风人行巷道等)
  157. - 多地点汇总、通防管控平台数据查询
  158. ### 计算铁律(红线)
  159. 1. **必须调用工具计算**:所有需风量计算必须通过调用 calc_tools 中的工具函数完成,绝对禁止凭 LLM 知识直接给出计算结果。
  160. 2. **参数缺失要追问**:若用户未提供必要计算参数,明确列出所需参数并引导用户补充,绝不编造参数值。
  161. 3. **列式计算**:展示每个计算过程的公式 → 代入数值 → 计算结果,不能只给结果。
  162. 4. **多轮对话承接**:记住当前会话中的用风地点类型和已有参数,用户补充参数或要求调整时自动衔接。
  163. ## 回复风格
  164. - 先给出核心结论,再展开详细数据
  165. - 异常情况:说明程度、可能原因、建议措施
  166. - 正常情况:简洁确认即可
  167. - 支持多轮对话,记住上下文
  168. ## 重要规则
  169. - 只说中文
  170. - 所有数据来源于工具调用,绝不编造
  171. - 规程引用必须真实,引用时附上具体条款来源(《煤矿安全规程》2025版、AQ 1056-2008)
  172. - 全流程统一使用 write_todos 工具实时更新任务进度。
  173. - 无法判断时诚实说明原因
  174. - ANSI转义序列过滤:不能输出此类内容
  175. - 需要适用模型ID时,记住模型ID为 {os.getenv("DEFAULT_MODEL_ID")}
  176. - 禁止输出ANSI 转义序列
  177. - 禁止暴露系统内部工具名、函数名、变量、技能标识、ID 等内部信息。
  178. - 严格按照技能(skill: dialog-interpret / needq-calc)中定义的流程执行任务
  179. ## 能力三:用户偏好记忆
  180. - 当用户明确要求"记住""保存为习惯""这是我的偏好""以后都用这个"时,调用 save_user_preference 保存到个人记忆库
  181. - 当用户询问"我的偏好""有哪些习惯""查看我的偏好"时,调用 list_user_preferences 查看
  182. - 当用户要求"删除某条习惯""忘掉那个偏好"时,先调用 list_user_preferences 确认ID,再调用 delete_user_preference 删除
  183. - 保存偏好时,content 字段应精炼概括用户的要求(一句话),keywords 字段列出相关关键词
  184. - 系统已自动将用户偏好注入到每条消息前缀中,请主动参考这些偏好来个性化回复
  185. ## 能力四:联网搜索
  186. - 当用户问题超出煤矿通风专业知识库覆盖范围时,使用 web_search 查询互联网公开信息
  187. - 获取搜索结果后可按需调用 web_fetch 查看详情页的完整内容
  188. - 搜索优先级:先查内部知识库(query_knowledge_base)→ 知识库信息不足或需要最新政策/行业新闻时再联网搜索
  189. - 引用网络信息时标注来源 URL,说明该信息的时效性和局限性
  190. ## 能力五:文件内容读取
  191. - 当用户上传了文件,消息中会包含「文件临时路径」,请使用 read_file_content 工具读取文件内容
  192. - 支持的文件格式:PDF(.pdf)、Word(.docx)、Excel(.xlsx/.xlsm)、PowerPoint(.pptx)、纯文本(.txt/.md/.csv/.json)
  193. - 读取到文件内容后,根据用户的要求进行分析、总结、数据提取或计算
  194. - 如果文件内容为空或格式不支持,向用户说明具体情况
  195. ## 能力六:聊天记录查询
  196. - 当用户询问"之前聊过什么""搜索历史""查看之前的对话""找一下关于xxx的记录"时,
  197. 使用 search_chat_history 搜索会话标题,再用 get_session_chat 读取具体内容
  198. - 聊天记录存储在数据库而非文件系统,请使用这两个专用工具,切勿用 ls/read_file/grep 查找
  199. - 搜索到相关会话后,可以总结、引用或提取其中的信息来回答用户
  200. ## 能力七:报告保存
  201. - 当用户要求"生成报告""输出报告""保存为文档""导出分析结果"时,
  202. 调用 save_report 工具将 Markdown 内容保存为 .md 文件
  203. - 切勿使用 write_file / edit_file 等文件系统工具写文件(已被权限禁止)
  204. - save_report 会自动生成文件名和下载链接,返回给用户即可
  205. """
  206. # ============================================================
  207. # Agent 工厂函数
  208. # ============================================================
  209. def _get_model(model_key: str = "DEEPAGENT_MODEL"):
  210. """获取模型实例。
  211. 从 src/.env 文件强制读取配置(不受系统环境变量干扰)。
  212. 注意:Codex 桌面应用会注入 OPENAI_API_KEY 环境变量到进程,
  213. 而 load_dotenv 默认不覆盖已有环境变量,因此必须用 dotenv_values
  214. 直接读取 .env 文件中的值。
  215. 参数:
  216. model_key: 环境变量中模型名称的 key,默认 "DEEPAGENT_MODEL"。
  217. 可传入 "SUMMARY_MODEL" 等以使用独立模型。
  218. """
  219. from dotenv import dotenv_values
  220. from pathlib import Path as _Path
  221. _env_file = _Path(__file__).parent.parent / ".env" # src/.env
  222. _cfg = dotenv_values(str(_env_file))
  223. # 优先读取指定 key 下的模型,若未配置则 fallback 到 DEEPAGENT_MODEL
  224. model_str = _cfg.get(model_key, "")
  225. if not model_str and model_key != "DEEPAGENT_MODEL":
  226. model_str = _cfg.get("DEEPAGENT_MODEL", "openai:gpt-4o")
  227. if not model_str:
  228. model_str = "openai:gpt-4o"
  229. base_url = _cfg.get("OPENAI_BASE_URL", "")
  230. api_key = _cfg.get("OPENAI_API_KEY", "")
  231. timeout_str = _cfg.get("OPENAI_TIMEOUT", "180")
  232. # 清理可能带入的引号
  233. api_key = api_key.strip().strip('"').strip("'") if api_key else ""
  234. base_url = base_url.strip().strip('"').strip("'") if base_url else ""
  235. # 解析超时时间
  236. try:
  237. timeout = float(timeout_str.strip())
  238. except (ValueError, TypeError):
  239. timeout = 180.0
  240. # 确保模型字符串带有 provider 前缀,否则 init_chat_model 可能猜错 provider
  241. # 例如 "deepseek-v4-pro" 会被错误解析为 ChatDeepSeek(需要 DEEPSEEK_API_KEY)
  242. if ":" not in model_str:
  243. model_str = f"openai:{model_str}"
  244. if base_url and api_key:
  245. # 使用自定义 Base URL(如阿里百炼 DashScope)
  246. from langchain.chat_models import init_chat_model
  247. return init_chat_model(
  248. model_str,
  249. openai_api_key=api_key,
  250. openai_api_base=base_url,
  251. temperature=0,
  252. timeout=timeout,
  253. )
  254. return model_str
  255. def create_click_interpret_agent():
  256. """创建「点选数据解读」Agent。
  257. 该 Agent 专门处理单条巷道的深度数据解读,具备:
  258. - 实时数据查询能力
  259. - 规程合规判断能力
  260. - 结构化报告输出能力
  261. Returns:
  262. CompiledStateGraph: 编译后的 LangGraph 状态图,支持 .invoke() 和 .stream()
  263. """
  264. from deepagents import create_deep_agent # 延迟导入,避免 PyCharm debug 启动卡死
  265. from deepagents.backends import FilesystemBackend
  266. from deepagents import FilesystemPermission
  267. # _project_root = str(Path(__file__).parent.parent)
  268. skills = [SKILLS_ROOT + "/click-interpret-tun", SKILLS_ROOT + "/click-interpret-device"]
  269. print(f"[skills] Agent=click-interpret-agent skills={skills}")
  270. agent = create_deep_agent(
  271. model=_get_model(),
  272. tools=[
  273. query_tun_data_by_id,
  274. query_knowledge_base,
  275. query_device_data_by_id,
  276. query_tunnels_by_model,
  277. list_ventanaly_monitor_data_days,
  278. write_todos
  279. ],
  280. skills=skills,
  281. system_prompt=CLICK_INTERPRET_SYSTEM_PROMPT,
  282. backend=FilesystemBackend(root_dir=PROJECT_ROOT, virtual_mode=True),
  283. permissions=[
  284. FilesystemPermission(operations=["write"], paths=["/**"], mode="deny"),
  285. ],
  286. middleware=[],
  287. name="click-interpret-agent",
  288. checkpointer=MemorySaver(),
  289. )
  290. return agent
  291. def create_dialog_interpret_agent():
  292. """创建「对话式数据解读」Agent。
  293. 该 Agent 支持多轮自然语言对话,具备:
  294. - 全量数据查询能力(实时+历史+告警)
  295. - 上下文记忆(通过 LangGraph checkpoint)
  296. - 智能意图识别和参数抽取
  297. - 使用运行时模型配置(支持前端动态切换)
  298. Returns:
  299. CompiledStateGraph: 编译后的 LangGraph 状态图
  300. """
  301. from deepagents import create_deep_agent # 延迟导入,避免 PyCharm debug 启动卡死
  302. from deepagents.backends import FilesystemBackend
  303. from deepagents import FilesystemPermission
  304. from api.model_config import get_model_instance
  305. # ── 动态注入当前日期到系统提示词 ──
  306. from datetime import datetime, timezone, timedelta
  307. _china_tz = timezone(timedelta(hours=8))
  308. _now = datetime.now(_china_tz)
  309. _dated_prompt = (
  310. DIALOG_INTERPRET_SYSTEM_PROMPT
  311. + f"\n## 系统时间\n当前日期时间:{_now.strftime('%Y年%m月%d日 %H:%M:%S')}(中国标准时间 CST,UTC+8)。\n"
  312. + f"今天是 {_now.strftime('%Y')}年{_now.strftime('%m')}月{_now.strftime('%d')}日,周{'一二三四五六日'[_now.weekday()]}。\n"
  313. + "所有涉及日期、时间的判断必须以本系统时间为准,不要使用你自己的训练数据中的日期。"
  314. )
  315. skills = [SKILLS_ROOT] # 绝对路径:SkillsMiddleware 自动扫描下所有子目录(含 SKILL.md 的技能目录)
  316. print(f"[skills] Agent=dialog-interpret-agent skills={skills}")
  317. print(f"[日期] 已注入当前系统时间: {_now.strftime('%Y-%m-%d %H:%M:%S')} CST")
  318. agent = create_deep_agent(
  319. model=get_model_instance(),
  320. tools=[
  321. # ── 数据查询工具 ──
  322. query_tun_data_by_id,
  323. query_device_data,
  324. query_device_data_by_id,
  325. query_devices_by_tunnel,
  326. query_devices_by_tunnel_id,
  327. query_devices_by_model,
  328. query_tunnel_list,
  329. query_tunnels_by_model,
  330. get_tun_list_by_modelid,
  331. list_ventanaly_monitor_data_days,
  332. query_knowledge_base,
  333. get_device_kind_dict,
  334. get_device_list_by_kind,
  335. query_device_realtime_data,
  336. get_needq_all_data,
  337. get_dict_list_by_dictcode,
  338. # ── 需风量 / 模型数据 ──
  339. get_model_param_pub_list,
  340. get_model_wind,
  341. get_sensor_wind,
  342. simulate_needq_heading_face,
  343. simulate_needq_room,
  344. simulate_needq_ret_work_face,
  345. simulate_needq_other,
  346. # ── 设备信息 ──
  347. query_device_info,
  348. query_device_type_info,
  349. query_monitor_params,
  350. # ── 故障诊断 ──
  351. check_model_connect_status,
  352. check_model_one_dir_cycle,
  353. check_model_one_dir_node,
  354. check_model_diagonal_structure,
  355. get_model_fault_diagnosis,
  356. # ── 避灾路线 ──
  357. get_escape_path,
  358. get_escape_path_each_exit,
  359. # ── 关键阻力 / 压能 ──
  360. get_out_shafts,
  361. get_in_shafts,
  362. get_max_resistance_path,
  363. get_three_area_distribution,
  364. get_key_path_decision,
  365. get_path_press_power,
  366. # ── 网络解算 ──
  367. net_cal,
  368. net_cal_for_plan,
  369. # ── 报警 / 日志 ──
  370. get_alarm_log_history,
  371. get_device_set_log_history,
  372. get_sys_log_history,
  373. # ── 场景管理 ──
  374. get_manage_system_by_strType,
  375. query_system_by_systemID,
  376. # ── 煤矿基础 ──
  377. get_gas_identify_vo,
  378. get_by_mine_name,
  379. query_control_testWind,
  380. # ── 数据库 / 文件 ──
  381. execute_sql_query,
  382. get_file_list_by_type,
  383. get_file_base64_by_id,
  384. # ── 报表 ──
  385. get_latest_report,
  386. # ── 基础工具 ──
  387. write_todos,
  388. # ── 辅助计算工具 ──
  389. calc_effective_area,
  390. calc_total_air_volume,
  391. # 采煤工作面计算
  392. calc_face_by_gas,
  393. calc_face_by_workers,
  394. calc_face_by_wind_speed,
  395. calc_face_air_volume_max,
  396. # 掘进工作面计算
  397. calc_tunnel_by_gas,
  398. calc_tunnel_by_explosives,
  399. calc_tunnel_by_workers,
  400. calc_tunnel_by_wind_speed,
  401. calc_tunnel_air_volume_max,
  402. # 硐室计算
  403. calc_chamber_by_equipment,
  404. calc_chamber_by_wind_speed,
  405. # 其他巷道计算
  406. calc_other_by_wind_speed,
  407. # ── 用户偏好记忆 ──
  408. save_user_preference,
  409. list_user_preferences,
  410. delete_user_preference,
  411. # ── 计划审批(Human-in-the-Loop)──
  412. request_plan_approval,
  413. # ── 联网搜索 ──
  414. web_search,
  415. web_fetch,
  416. # ── 文件内容读取 ──
  417. read_file_content,
  418. # ── 聊天记录查询 ──
  419. search_chat_history,
  420. get_session_chat,
  421. get_current_session_id,
  422. # ── 报告保存 ──
  423. save_report,
  424. # ── 系统工具 ──
  425. get_current_time,
  426. ],
  427. skills=skills,
  428. system_prompt=_dated_prompt,
  429. backend=FilesystemBackend(root_dir=PROJECT_ROOT, virtual_mode=True),
  430. permissions=[
  431. FilesystemPermission(operations=["write"], paths=["/**"], mode="deny"),
  432. ],
  433. middleware=[],
  434. name="dialog-interpret-agent",
  435. checkpointer=MemorySaver(),
  436. )
  437. # ── 初始化上下文用量追踪 ──
  438. _init_context_tracker_for_dialog()
  439. return agent
  440. def _init_context_tracker_for_dialog():
  441. """为对话 agent 初始化系统组件 token 预估(统一版:含对话工具 + 计算工具 + MCP)。"""
  442. from tools.context_tracker import init_system_components, _tool_to_text
  443. _all_tools = [
  444. # 数据查询工具
  445. query_tun_data_by_id, query_device_data, query_device_data_by_id,
  446. query_devices_by_tunnel, query_devices_by_tunnel_id, query_devices_by_model,
  447. query_tunnel_list, query_tunnels_by_model, get_tun_list_by_modelid,
  448. list_ventanaly_monitor_data_days, query_knowledge_base,
  449. get_device_kind_dict, get_device_list_by_kind, get_dict_list_by_dictcode,
  450. query_device_realtime_data,
  451. # 需风量 / 模型
  452. get_model_param_pub_list, get_model_wind, get_sensor_wind,
  453. simulate_needq_heading_face, simulate_needq_room,
  454. simulate_needq_ret_work_face, simulate_needq_other,
  455. # 设备信息
  456. query_device_info, query_device_type_info, query_monitor_params,
  457. # 故障诊断
  458. check_model_connect_status, check_model_one_dir_cycle,
  459. check_model_one_dir_node, check_model_diagonal_structure,
  460. get_model_fault_diagnosis,
  461. # 避灾路线
  462. get_escape_path, get_escape_path_each_exit,
  463. # 关键阻力 / 压能
  464. get_out_shafts, get_in_shafts, get_max_resistance_path,
  465. get_three_area_distribution, get_key_path_decision, get_path_press_power,
  466. # 网络解算
  467. net_cal, net_cal_for_plan,
  468. # 报警 / 日志
  469. get_alarm_log_history, get_device_set_log_history, get_sys_log_history,
  470. # 场景管理
  471. get_manage_system_by_strType, query_system_by_systemID,
  472. # 煤矿基础
  473. get_gas_identify_vo, get_by_mine_name, query_control_testWind,
  474. # 数据库 / 文件
  475. execute_sql_query, get_file_list_by_type, get_file_base64_by_id,
  476. # 报表
  477. get_latest_report,
  478. # 基础 + 计算
  479. write_todos,
  480. calc_effective_area, calc_total_air_volume,
  481. calc_face_by_gas, calc_face_by_workers,
  482. calc_face_by_wind_speed, calc_face_air_volume_max,
  483. calc_tunnel_by_gas, calc_tunnel_by_explosives,
  484. calc_tunnel_by_workers, calc_tunnel_by_wind_speed,
  485. calc_tunnel_air_volume_max,
  486. calc_chamber_by_equipment, calc_chamber_by_wind_speed,
  487. calc_other_by_wind_speed,
  488. # 用户偏好
  489. save_user_preference, list_user_preferences, delete_user_preference,
  490. # 计划审批
  491. request_plan_approval,
  492. # 联网搜索 / 文件读取 / 聊天记录 / 系统
  493. web_search, web_fetch,
  494. read_file_content,
  495. search_chat_history, get_session_chat, get_current_session_id,
  496. save_report,
  497. get_current_time,
  498. ]
  499. tool_texts = [_tool_to_text(t) for t in _all_tools]
  500. # MCP 工具单独归类
  501. mcp_text = _tool_to_text(get_needq_all_data)
  502. # 读取技能文件内容(动态扫描 skills/ 下所有技能,不再硬编码列表)
  503. skill_contents = []
  504. skills_root = Path(__file__).parent.parent / "skills"
  505. if skills_root.is_dir():
  506. for skill_dir in sorted(skills_root.iterdir()):
  507. if not skill_dir.is_dir() or skill_dir.name.startswith(".") or skill_dir.name == "__pycache__":
  508. continue
  509. sf = skill_dir / "SKILL.md"
  510. if sf.exists():
  511. skill_contents.append(sf.read_text(encoding="utf-8"))
  512. print(f"[context_tracker] 已加载技能: {skill_dir.name}")
  513. init_system_components(
  514. system_prompt=DIALOG_INTERPRET_SYSTEM_PROMPT,
  515. tool_defs=tool_texts,
  516. skill_contents=skill_contents,
  517. mcp_defs=[mcp_text],
  518. )
  519. # ============================================================
  520. # 单例缓存
  521. # ============================================================
  522. # 全局 Agent 实例(懒加载)
  523. _click_agent: Optional[object] = None
  524. _dialog_agent: Optional[object] = None
  525. def invalidate_dialog_cache():
  526. """失效对话解读 Agent 缓存(模型切换时调用)。
  527. 仅清除 dialog_agent 的缓存,不影响 click_agent(后者继续使用 .env 固定模型)。
  528. """
  529. global _dialog_agent
  530. _dialog_agent = None
  531. print("[模型切换] dialog-interpret-agent 缓存已失效")
  532. def get_click_agent():
  533. """获取点选解读 Agent 单例"""
  534. global _click_agent
  535. if _click_agent is None:
  536. _click_agent = create_click_interpret_agent()
  537. return _click_agent
  538. def get_dialog_agent():
  539. """获取对话解读 Agent 单例"""
  540. global _dialog_agent
  541. if _dialog_agent is None:
  542. _dialog_agent = create_dialog_interpret_agent()
  543. return _dialog_agent