#!/usr/bin/env python3 """ 从「知你客服11号」复制为「知你客服12号」: - llm-unified 开启工具:http_request + file_read + file_write(受工作区根目录与大小限制约束,见 LOCAL_FILE_TOOLS_ROOT)。 - 提示词:URL 用 http_request;读写本地文件用 file_read / file_write;最终仍输出单行 JSON。 环境变量:PLATFORM_BASE_URL、ZHINI_11_AGENT_ID(默认 11 号 ID)、登录账号密码。 """ from __future__ import annotations import json import os import sys import requests BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/") SOURCE_AGENT_ID = os.getenv("ZHINI_11_AGENT_ID", "d39748ad-277f-48ac-9eb5-168ad2f1b470") USER = os.getenv("PLATFORM_USERNAME", "admin") PWD = os.getenv("PLATFORM_PASSWORD", "123456") NEW_NAME = "知你客服12号" NEW_DESC = ( "在知你客服11号基础上:增加本地文件读写工具 file_read、file_write(路径限制在平台配置的工作区内," "默认可视为仓库根目录),并保留 http_request;" "输出仍为单行 JSON,兼容记忆与 json-parse 链路。" ) 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 _patch_llm_unified(wf: dict) -> None: for n in wf.get("nodes") or []: if n.get("id") != "llm-unified": continue d = n.setdefault("data", {}) d["prompt"] = LLM_PROMPT_V12 d["enable_tools"] = True d["tools"] = list(TOOLS_V12) d["selected_tools"] = list(TOOLS_V12) return print("警告: 未找到节点 llm-unified", file=sys.stderr) def main() -> int: r = requests.post( f"{BASE}/api/v1/auth/login", data={"username": USER, "password": PWD}, headers={"Content-Type": "application/x-www-form-urlencoded"}, timeout=15, ) if r.status_code != 200: print("登录失败:", r.status_code, r.text[:500], file=sys.stderr) return 1 token = r.json().get("access_token") if not token: print("无 access_token", file=sys.stderr) return 1 h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} dup = requests.post( f"{BASE}/api/v1/agents/{SOURCE_AGENT_ID}/duplicate", headers=h, json={"name": NEW_NAME}, timeout=30, ) if dup.status_code != 201: print("复制失败:", dup.status_code, dup.text[:800], file=sys.stderr) return 1 new_id = dup.json()["id"] print("已创建副本:", new_id, NEW_NAME) g = requests.get(f"{BASE}/api/v1/agents/{new_id}", headers=h, timeout=30) if g.status_code != 200: print("读取 Agent 失败:", g.text, file=sys.stderr) return 1 agent = g.json() wf = agent["workflow_config"] _patch_llm_unified(wf) up = requests.put( f"{BASE}/api/v1/agents/{new_id}", headers=h, json={"description": NEW_DESC, "workflow_config": wf}, timeout=120, ) if up.status_code != 200: print("更新失败:", up.status_code, up.text[:800], file=sys.stderr) return 1 print("已注册工具:", ", ".join(TOOLS_V12)) print("Agent ID:", new_id) print(json.dumps({"id": new_id, "name": NEW_NAME}, ensure_ascii=False)) return 0 if __name__ == "__main__": raise SystemExit(main())