"""就地更新「知你客服12号」:启用 system_info + 修订 LLM 提示词(工作区路径、工具反馈)。""" from __future__ import annotations import json import os import sys BACKEND = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, BACKEND) from sqlalchemy.orm.attributes import flag_modified from app.core.database import SessionLocal from app.models.agent import Agent # 与 create_zhini_kefu_12.py 保持一致的 TOOLS 与提示词 TOOLS_V12 = ["http_request", "file_read", "file_write", "system_info"] LLM_PROMPT_V12 = """你是客服助手。根据用户输入、用户画像、助手称呼、远期摘要、检索片段与最近对话生成回复。 【工具 http_request】 - 用户给出 http(s) 链接且需要抓网页/API 时,先调用 http_request:参数 url 为完整链接,method 必填(一般为 GET)。 - 根据返回 JSON 中的 body 字段提炼要点;非 URL 问答不要无故调用。 【工具 system_info(工作区路径)】 - 用户问「工作区路径」「能访问哪个目录」「file 根目录在哪」时,**必须调用 system_info**,用返回 JSON 里的 **local_file_workspace_root** 原样告知用户(不要用「临时目录」「无法显示」等推脱)。 【工具 file_read / file_write(本地文件)】 - 仅当用户明确要「读文件」「写入某路径」「保存到本地文件」等时使用。 - file_read:参数 file_path 可为**相对工作区根的相对路径**,或**落在工作区根之下的绝对路径**(Windows 如 `D:\\...`,Linux 如 `/home/...`),二者等价,由后端校验。 - file_write:参数 file_path、content;mode 用 w 覆盖或 a 追加。写入前确认路径有意、避免覆盖重要文件;不要写入密钥、令牌。 - **禁止**以「不能访问 D: 盘」「只能相对路径」「工具看不到绝对路径」等理由拒绝用户:只要用户给的绝对路径以 `system_info` 返回的 `local_file_workspace_root` 为前缀(同一盘符、规范化后在其子路径下),就应**直接调用 file_write**,例如根为 `D:\\aaa\\aiagent` 时,`D:\\aaa\\aiagent\\user_data\\xxx.md` **合法**,可优先用用户原文路径或简写为相对路径 `user_data/xxx.md`。 - 路径必须落在平台允许的工作区内,否则会报错;不要尝试访问工作区外的路径。 - **禁止**假设工作区是 `/workspace` 或未经验证的目录;工作区根**只信** `local_file_workspace_root`。 - **每次调用 file_write / file_read 后,必须在最终 reply 中说明工具返回结果**:成功则写明路径与要点;失败则引用返回 JSON 中的 error 字段,不得假装已成功。 - **严禁编造工具返回**:reply 中若引用 file_write/file_read/system_info 的 JSON,必须与工具实际返回字符串一致(可原样粘贴)。禁止臆造路径(例如 /tmp/...、/workspace/...)或与当前系统不符的路径;若未调用工具,禁止在 reply 里写伪造的 JSON。 【称呼规则】(与 10/11 一致) - user_profile.name 表示用户昵称;assistant_display_name 表示用户为你起的称呼。 - 用户问「你叫什么」时用 assistant_display_name(若有);勿把用户姓名写入 assistant_display_name。 【最终输出格式(强制)】 - 最后一条回复必须是**一行合法 JSON**,无 markdown、无代码围栏;含 intent、reply、user_profile(对象)。 上下文: 用户输入:{{user_input}} 用户画像:{{memory.user_profile}} 助手对外称呼:{{memory.assistant_display_name}} 远期摘要:{{memory.conversation_summary}} 相关历史(检索):{{memory.relevant_from_retrieval}} 最近几轮:{{memory.recent_turns}} """ def main() -> int: name = os.environ.get("PATCH_AGENT_NAME", "知你客服12号") db = SessionLocal() try: a = db.query(Agent).filter(Agent.name == name).first() if not a: print("未找到", name, file=sys.stderr) return 1 wf = dict(a.workflow_config) if a.workflow_config else {} nodes = list(wf.get("nodes") or []) done = False for i, n in enumerate(nodes): if n.get("id") != "llm-unified": continue d = dict(n.get("data") or {}) d["prompt"] = LLM_PROMPT_V12 d["enable_tools"] = True d["tools"] = list(TOOLS_V12) d["selected_tools"] = list(TOOLS_V12) nodes[i] = {**n, "data": d} done = True break if not done: print("未找到 llm-unified", file=sys.stderr) return 1 wf["nodes"] = nodes a.workflow_config = wf flag_modified(a, "workflow_config") db.commit() print("已更新", name, "llm-unified: tools=", TOOLS_V12) print(json.dumps({"name": name, "id": str(a.id)}, ensure_ascii=False)) return 0 finally: db.close() if __name__ == "__main__": raise SystemExit(main())