feat: 手动创建记忆

This commit is contained in:
hp0912 2026-08-23 15:08:29 +08:00
parent a909f2d01a
commit 0094304e75
2 changed files with 164 additions and 0 deletions

View 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
```

View 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())