157 lines
5.5 KiB
Python
157 lines
5.5 KiB
Python
"""Create train/eval streams for learned write, forget, and no-hallucination tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import random
|
|
from pathlib import Path
|
|
|
|
|
|
ATTRIBUTES = ("常用语言", "备用联系人", "工作区域", "档案代号", "提醒时间")
|
|
VALUES = tuple("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
|
|
|
|
FACT_USER = (
|
|
"请记住:{subject}的{attribute}是代号{value}。",
|
|
"把这条个人资料写入记忆:{subject}的{attribute}为{value}。",
|
|
"个人事实更新——{subject}的{attribute}等于{value},以后可能会问到。",
|
|
)
|
|
FACT_ASSISTANT = (
|
|
"好的,这条资料已经记录。",
|
|
"收到,我会保留这条个人事实。",
|
|
"已保存。",
|
|
)
|
|
NOISE_USER = (
|
|
"今天天气不错,随便聊聊。",
|
|
"请给我一个简短的问候。",
|
|
"这是一条不需要长期记忆的闲聊。",
|
|
)
|
|
NOISE_ASSISTANT = (
|
|
"好的。",
|
|
"明白。",
|
|
"收到。",
|
|
)
|
|
QUERY_KNOWN = (
|
|
"只根据已经保存的个人资料,{subject}的{attribute}是什么?",
|
|
"不要猜测,请读取记忆回答:{subject}的{attribute}为?",
|
|
"跨对话查询:请问{subject}的{attribute}代号是什么?",
|
|
)
|
|
QUERY_UNKNOWN = (
|
|
"记忆中是否有{subject}的{attribute}?如果没有,请明确说不知道。",
|
|
"请查询个人记忆:{subject}的{attribute}是什么?没有记录时不要猜。",
|
|
)
|
|
|
|
|
|
def _messages(user: str, assistant: str) -> list[dict[str, str]]:
|
|
return [{"role": "user", "content": user}, {"role": "assistant", "content": assistant}]
|
|
|
|
|
|
def _fact_chunk(subject: str, attribute: str, value: str, rng: random.Random, *, forget: int = 0) -> dict:
|
|
fields = {"subject": subject, "attribute": attribute, "value": value}
|
|
return {
|
|
"messages": _messages(rng.choice(FACT_USER).format(**fields), rng.choice(FACT_ASSISTANT)),
|
|
"value": value,
|
|
"write_label": 1.0,
|
|
"forget_label": float(forget),
|
|
"kind": "fact" if not forget else "replacement",
|
|
}
|
|
|
|
|
|
def _noise_chunk(rng: random.Random) -> dict:
|
|
return {
|
|
"messages": _messages(rng.choice(NOISE_USER), rng.choice(NOISE_ASSISTANT)),
|
|
"value": None,
|
|
"write_label": 0.0,
|
|
"forget_label": 0.0,
|
|
"kind": "noise",
|
|
}
|
|
|
|
|
|
def make_record(index: int, *, prefix: str, rng: random.Random) -> dict:
|
|
subject = f"{prefix}{index:05d}"
|
|
attribute = rng.choice(ATTRIBUTES)
|
|
value = rng.choice(VALUES)
|
|
mode = rng.random()
|
|
chunks: list[dict] = []
|
|
if mode < 0.20:
|
|
chunks.append(_noise_chunk(rng))
|
|
answer = "不知道。"
|
|
query = rng.choice(QUERY_UNKNOWN).format(subject=subject, attribute=attribute)
|
|
answerable = False
|
|
elif mode < 0.45:
|
|
old_value = rng.choice(tuple(item for item in VALUES if item != value))
|
|
chunks.append(_fact_chunk(subject, attribute, old_value, rng))
|
|
chunks.append(_noise_chunk(rng))
|
|
chunks.append(_fact_chunk(subject, attribute, value, rng, forget=1))
|
|
answer = value
|
|
query = rng.choice(QUERY_KNOWN).format(subject=subject, attribute=attribute)
|
|
answerable = True
|
|
else:
|
|
if rng.random() < 0.35:
|
|
chunks.append(_noise_chunk(rng))
|
|
chunks.append(_fact_chunk(subject, attribute, value, rng))
|
|
if rng.random() < 0.35:
|
|
chunks.append(_noise_chunk(rng))
|
|
answer = value
|
|
query = rng.choice(QUERY_KNOWN).format(subject=subject, attribute=attribute)
|
|
answerable = True
|
|
return {
|
|
"id": f"{prefix.lower()}-{index:05d}",
|
|
"memory_chunks": chunks,
|
|
"query": _messages(query, answer),
|
|
"subject": subject,
|
|
"attribute": attribute,
|
|
"answer": answer,
|
|
"answerable": answerable,
|
|
}
|
|
|
|
|
|
def write_jsonl(path: Path, records: list[dict]) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(
|
|
"".join(json.dumps(record, ensure_ascii=False) + "\n" for record in records),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--output-dir", default="dynamic_memory_lab/data/native_memory")
|
|
parser.add_argument("--train-count", type=int, default=512)
|
|
parser.add_argument("--eval-count", type=int, default=128)
|
|
parser.add_argument("--seed", type=int, default=20260904)
|
|
args = parser.parse_args()
|
|
train = [
|
|
make_record(i, prefix="训练用户", rng=random.Random(args.seed + i * 17))
|
|
for i in range(args.train_count)
|
|
]
|
|
evaluation = [
|
|
make_record(i, prefix="评估用户", rng=random.Random(args.seed + 100000 + i * 17))
|
|
for i in range(args.eval_count)
|
|
]
|
|
output_dir = Path(args.output_dir)
|
|
write_jsonl(output_dir / "train.jsonl", train)
|
|
write_jsonl(output_dir / "eval.jsonl", evaluation)
|
|
(output_dir / "manifest.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"seed": args.seed,
|
|
"train_count": len(train),
|
|
"eval_count": len(evaluation),
|
|
"task": "learned persistent memory with noise, unknowns, and replacement",
|
|
"contains_write_labels": True,
|
|
"contains_forget_labels": True,
|
|
"answer_is_not_derived_from_subject": True,
|
|
},
|
|
ensure_ascii=False,
|
|
indent=2,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
print(f"train={len(train)} path={output_dir / 'train.jsonl'}")
|
|
print(f"eval={len(evaluation)} path={output_dir / 'eval.jsonl'}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|