feat: 手动创建记忆
This commit is contained in:
parent
a909f2d01a
commit
0094304e75
21
skills/create-memory/SKILL.md
Normal file
21
skills/create-memory/SKILL.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
name: create-memory
|
||||||
|
description: "在当前微信会话中新增长期记忆。当好友或群成员明确说‘记住……’‘帮我记一下……’‘把这个存进记忆’或‘新增记忆’时使用;不要用于查询、回忆或核对已有记忆。"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 新增记忆
|
||||||
|
|
||||||
|
运行 `python3 scripts/create_memory.py`,让机器人客户端根据当前消息创建长期记忆;当前消息存在直接引用时,被引用消息会与当前消息共同参与提取。
|
||||||
|
|
||||||
|
- 用户不需要指定记忆类型。客户端自行推断名称、画像、事实和关系,一次请求可以创建任意数量、任意组合的结构化记忆。
|
||||||
|
- 没有引用时只处理当前消息;有引用时只处理被引用消息和当前消息,不读取两条消息之间的聊天内容。
|
||||||
|
- 当前消息必须包含具体指令。只有“记住这个”“记住刚才的”但既没有具体内容也没有引用时,请用户重新说明或引用原消息。
|
||||||
|
- 不要向脚本传记忆内容、类型、会话信息或微信 ID;脚本只转发当前消息 ID 和可选的引用消息 ID。
|
||||||
|
- 真实创建时不传 `--dry-run`,也不自动重试超时或连接中断的请求。
|
||||||
|
- 仅在脚本返回 `ok: true` 后告知用户已记住;失败时直接说明错误,不能声称创建成功。
|
||||||
|
|
||||||
|
通过本技能成功创建的所有记忆都由客户端强制设为 `confidence=100`。人物或账号无法唯一对应时,不要猜测。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/create_memory.py
|
||||||
|
```
|
||||||
143
skills/create-memory/scripts/create_memory.py
Normal file
143
skills/create-memory/scripts/create_memory.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
from typing import Any, NoReturn
|
||||||
|
|
||||||
|
sys.stderr = sys.stdout
|
||||||
|
|
||||||
|
|
||||||
|
class SkillArgumentParser(argparse.ArgumentParser):
|
||||||
|
def error(self, message: str) -> NoReturn:
|
||||||
|
raise ValueError(f"参数错误:{message}")
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_args(argv: list[str]) -> argparse.Namespace:
|
||||||
|
parser = SkillArgumentParser(description="创建当前微信会话的长期记忆")
|
||||||
|
parser.add_argument("--dry-run", action="store_true")
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def _require_env(name: str) -> str:
|
||||||
|
value = os.environ.get(name, "").strip()
|
||||||
|
if not value:
|
||||||
|
raise ValueError(f"环境变量 {name} 未配置")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _positive_int_env(name: str) -> int:
|
||||||
|
raw = _require_env(name)
|
||||||
|
try:
|
||||||
|
value = int(raw)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError(f"环境变量 {name} 必须是整数") from exc
|
||||||
|
if value < 1:
|
||||||
|
raise ValueError(f"环境变量 {name} 必须是正整数")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _optional_positive_int_env(name: str) -> int | None:
|
||||||
|
raw = os.environ.get(name, "").strip()
|
||||||
|
if not raw:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
value = int(raw)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError(f"环境变量 {name} 必须是整数") from exc
|
||||||
|
if value < 0:
|
||||||
|
raise ValueError(f"环境变量 {name} 不能是负数")
|
||||||
|
return value or None
|
||||||
|
|
||||||
|
|
||||||
|
def _client_port() -> int:
|
||||||
|
raw = _require_env("ROBOT_WECHAT_CLIENT_PORT")
|
||||||
|
try:
|
||||||
|
port = int(raw)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError("机器人客户端端口必须是整数") from exc
|
||||||
|
if port < 1 or port > 65535:
|
||||||
|
raise ValueError("机器人客户端端口必须在 1 到 65535 之间")
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
def _request_body() -> dict[str, Any]:
|
||||||
|
body: dict[str, Any] = {
|
||||||
|
"message_id": _positive_int_env("ROBOT_MESSAGE_ID"),
|
||||||
|
}
|
||||||
|
referenced_message_id = _optional_positive_int_env("ROBOT_REF_MESSAGE_ID")
|
||||||
|
if referenced_message_id is not None:
|
||||||
|
body["referenced_message_id"] = referenced_message_id
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
def _post_json(url: str, body: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
request = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(request, timeout=300) as response:
|
||||||
|
text = response.read().decode("utf-8")
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
detail = exc.read().decode("utf-8", errors="replace").strip()
|
||||||
|
raise RuntimeError(f"客户端返回 HTTP {exc.code}:{detail or exc.reason}") from exc
|
||||||
|
except urllib.error.URLError as exc:
|
||||||
|
if isinstance(exc.reason, TimeoutError):
|
||||||
|
raise RuntimeError(
|
||||||
|
"请求超时,记忆可能已经保存,请先查询后再决定是否重试"
|
||||||
|
) from exc
|
||||||
|
raise RuntimeError(f"无法连接机器人客户端:{exc.reason}") from exc
|
||||||
|
except TimeoutError as exc:
|
||||||
|
raise RuntimeError("请求超时,记忆可能已经保存,请先查询后再决定是否重试") from exc
|
||||||
|
except OSError as exc:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"请求中断,记忆可能已经保存,请先查询后再决定是否重试:{exc}"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = json.loads(text)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise RuntimeError("机器人客户端返回了无效 JSON") from exc
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
raise RuntimeError("机器人客户端返回格式错误")
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_success(payload: dict[str, Any]) -> None:
|
||||||
|
if payload.get("code") != 200:
|
||||||
|
message = str(payload.get("message") or "未知业务错误").strip()
|
||||||
|
raise RuntimeError(message)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
try:
|
||||||
|
args = _parse_args(sys.argv[1:])
|
||||||
|
body = _request_body()
|
||||||
|
port = _client_port()
|
||||||
|
if args.dry_run:
|
||||||
|
output = {"ok": True, "dry_run": True, "request": body}
|
||||||
|
else:
|
||||||
|
url = f"http://127.0.0.1:{port}/api/v1/robot/memories"
|
||||||
|
payload = _post_json(url, body)
|
||||||
|
_validate_success(payload)
|
||||||
|
output = {
|
||||||
|
"ok": True,
|
||||||
|
"message": "长期记忆创建成功",
|
||||||
|
}
|
||||||
|
sys.stdout.write(json.dumps(output, ensure_ascii=False) + "\n")
|
||||||
|
return 0
|
||||||
|
except (ValueError, RuntimeError) as exc:
|
||||||
|
sys.stdout.write(f"创建记忆失败:{exc}\n")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Loading…
Reference in New Issue
Block a user