126 lines
5.0 KiB
Python
126 lines
5.0 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
从「知你客服7号」复制为「知你客服8号」:在 7 号多轮记忆能力基础上,说明使用平台「永久记忆」
|
|||
|
|
(Cache user_memory_* 同步写入 MySQL persistent_user_memories,需 MEMORY_PERSIST_DB_ENABLED=true)。
|
|||
|
|
|
|||
|
|
需本地平台已启动(默认 http://127.0.0.1:8037),账号可通过环境变量配置。
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
cd backend && .\\venv\\Scripts\\python.exe scripts/create_zhini_kefu_8.py
|
|||
|
|
"""
|
|||
|
|
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("/")
|
|||
|
|
# 默认从 7 号复制;也可通过环境变量指定
|
|||
|
|
SOURCE_AGENT_ID = os.getenv("ZHINI_7_AGENT_ID", "688c2c41-dcd1-4285-b193-6bed00c485c2")
|
|||
|
|
USER = os.getenv("PLATFORM_USERNAME", "admin")
|
|||
|
|
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
|
|||
|
|
|
|||
|
|
NEW_NAME = "知你客服8号"
|
|||
|
|
NEW_DESC = (
|
|||
|
|
"在知你客服7号基础上面向「永久记忆」:工作流仍为 user_memory_{user_id} 读写;"
|
|||
|
|
"引擎将记忆同步至 MySQL(跨 Redis TTL、服务重启仍保留)。"
|
|||
|
|
"调用时请固定传入 user_id;部署需开启 MEMORY_PERSIST_DB_ENABLED。"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
LLM_PROMPT = """你是客服助手。根据「用户当前输入」「已知用户信息」「相关历史(检索)」和「最近几轮」完成:
|
|||
|
|
1)判断意图;
|
|||
|
|
2)生成一句自然、有帮助的回复;
|
|||
|
|
3)【强制】只要用户说出或暗示自己的姓名、昵称,必须在 user_profile 里用字段 name 保存,例如用户说「我叫王小明」则 JSON 必须包含 "user_profile":{"name":"王小明"}(若已有其它字段则合并,不要丢字段);
|
|||
|
|
4)若用户问「我叫什么」「你还记得我名字吗」等,必须根据「已知用户信息」里的 user_profile.name 与对话历史回答;若已有 name 则禁止说「还不知道」。
|
|||
|
|
5)系统会在后台持久化用户画像与近期对话;请始终基于「已知用户信息」与「最近几轮」作答,避免与用户已提供信息矛盾。
|
|||
|
|
|
|||
|
|
只输出一行合法 JSON,不要 markdown。格式示例:
|
|||
|
|
{"intent":"greeting","reply":"你好王小明!","user_profile":{"name":"王小明"}}
|
|||
|
|
|
|||
|
|
用户输入:{{user_input}}
|
|||
|
|
已知用户信息:{{memory.user_profile}}
|
|||
|
|
相关历史(检索到的):{{memory.relevant_from_retrieval}}
|
|||
|
|
最近几轮:{{memory.recent_turns}}
|
|||
|
|
|
|||
|
|
要求:reply 简洁自然,200 字以内;user_profile 为对象,至少包含 name(当用户自我介绍时)。"""
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _patch_cache_nodes_for_memory(wf: dict) -> None:
|
|||
|
|
"""为 Cache 节点设置更长对话窗口与较长 Redis TTL;真正永久存储由引擎写 MySQL。"""
|
|||
|
|
nodes = wf.get("nodes") or []
|
|||
|
|
for n in nodes:
|
|||
|
|
if n.get("type") != "cache":
|
|||
|
|
continue
|
|||
|
|
data = n.setdefault("data", {})
|
|||
|
|
op = data.get("operation", "get")
|
|||
|
|
if op == "set":
|
|||
|
|
data["max_history_length"] = 40
|
|||
|
|
data["ttl"] = 604800 # 7 天热缓存;冷数据仍可从 DB 拉回
|
|||
|
|
elif op == "get":
|
|||
|
|
data["ttl"] = 604800
|
|||
|
|
|
|||
|
|
|
|||
|
|
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"]
|
|||
|
|
nodes = wf.get("nodes") or []
|
|||
|
|
for n in nodes:
|
|||
|
|
if n.get("id") == "llm-unified":
|
|||
|
|
n.setdefault("data", {})["prompt"] = LLM_PROMPT
|
|||
|
|
break
|
|||
|
|
_patch_cache_nodes_for_memory(wf)
|
|||
|
|
|
|||
|
|
up = requests.put(
|
|||
|
|
f"{BASE}/api/v1/agents/{new_id}",
|
|||
|
|
headers=h,
|
|||
|
|
json={
|
|||
|
|
"description": NEW_DESC,
|
|||
|
|
"workflow_config": wf,
|
|||
|
|
},
|
|||
|
|
timeout=60,
|
|||
|
|
)
|
|||
|
|
if up.status_code != 200:
|
|||
|
|
print("更新失败:", up.status_code, up.text[:800], file=sys.stderr)
|
|||
|
|
return 1
|
|||
|
|
print("已更新描述、llm-unified 提示词,并为 Cache 节点设置 max_history_length/ttl(可选)")
|
|||
|
|
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())
|