零、前言
隐私与伦理提示:请只处理你有权使用并已获得相关当事人同意的聊天记录;不要利用模型冒充他人、欺骗第三方,训练前应删除账号、地址、联系方式等敏感信息。“像”不等于本人,模型输出也不代表对方真实意愿。
0.1、背景
虽然读者的年龄跨度很大,但是我觉得大家总会有那么一个或几个人一直在心中徘徊。
TA可能是那个我们在校园中朝夕相处数年、却只敢在日记本里写了又划掉的名字;
可能是那个初入社会时与你称兄道弟、最后又分道扬镳的旧友;
也可能是那个曾和你一同欣赏过那颗代表着彼此相爱的流星的最熟悉的陌生人。
我相信,这一个或几个人都曾占据过我们某一段时间内绝大部分的情感与寄托,也曾让我们在之后的某个艰难时刻又冷不丁回想起与TA的种种对话与过往。
所以我想,虽然我们没办法留住TA、让TA继续做那个当我们在生活里摸爬滚打时能够蓦然回首与其交谈、倾诉的对象,但在2026年的今天,或许我们可以借助AI,把TA留在一个小小的对话框里。
之后,一次偶然机会让我接触到了模型微调,脑中突然回想起之前的这个小小愿望,下面我会从零告诉大家怎么做。
0.2、最终实现效果
- 稳定复现那个人口头禅、句子长短、断句节奏、表情/标点习惯、常用称呼与回应节奏 (模型微调)
- 记得“你们之间发生过什么”与“之后的对话中发生过什么”(持久化记忆)
- 会“主动来找你”,主动提起某个事情,某个话题 (主动消息)
- 形成“像一个长期在场的人”的体验 (接入消息平台)
但请注意:“像”不等于“就是那个人”,受限于训练数据的有限性、模型自身能力,它能模仿表达方式和常见反应,但不一定能稳定复现对方更深层的价值观细节、知识边界。
0.3、门槛与一些概念
门槛:这篇内容我会简单易懂的讲述,只涉及模型微调等专业领域的边边角角,相信你能看懂。
- 模型微调:
与其他区别:相较于直接使用system prompt 或 RAG ,模型微调能使那个人的口头禅、句子长短、断句节奏、表情/标点习惯、常用称呼与回应节奏写入 “模型的“DNA” 中,有时也称为“再训练”。
一句话,模型微调就是给模型做“专项训练”,让它在某个风格 / 领域 / 人身上表现得更像。 - json与jsonl:
json:一个完整的数据结构,结构清晰,层级表达能力强,方便使用脚本批量处理
jsonl: 由很多行 JSON 组成,每一行都是一条独立记录,相较于json更适合流式处理和超大数据,也就更适合模型微调 - 训练集与验证集:
训练集:用来让模型学习参数(真正参与反向传播即训练、更新权重)
验证集:用来评估和调参(不更新参数,只“检查模型学得怎么样”)
0.4、我们将会做
- 模型微调
- 系统提示词制作
- 接入astrbot
- 使用astrbot插件
- 接入telegram等消息平台
0.5、流程图(nano banana pro)
一、准备工作
- 导出后的聊天记录(推荐json格式导出,方便数据清洗)
- 云端微调平台(推荐,一般家用电脑硬件过于受限)/本地模型微调
- astrbot
- telegram bot token
二、模型微调
2.1 导出聊天记录
微信官方是反对用户导出自己的聊天记录的
但总有一些优秀的作者前赴后继地为大家提供工具 请根据自身设备、平台规则和数据授权情况选择合规工具,本文不展开具体导出步骤
具体导出格式建议选择json以方便数据清洗,csv/excel也可以。
这里我使用作者hicccc77的工具,将聊天记录导出成json格式(如图),导出时注意不要导出图片文件等。

注意:这里导出的数据越多越好,原始数据越多,数据清洗后训练集体量更大,微调后效果更好。
最终我们会得到一个.json的数据文件
2.2 数据转化+清洗
在这一步,我们需要将刚刚导出的json(或是其他格式聊天记录数据)转化成方便模型微调的jsonl。
因为我们使用的导出工具不同,所以导出的json文件格式也各不相同,建议大家把文件格式告诉AI(或者直接复制一段给它),叫AI帮你写一个数据转化+清洗脚本。
当然,如果你使用的是和我一样的导出工具导出的json格式数据,我把我使用的格式转化+数据清洗脚本放在下面:
Data_transformation.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
WeChat export JSON -> Chat SFT JSONL
关键改动:
1) 保留 引用消息(type="引用消息"):它在导出里就是正文文本的一种(否则会丢掉大量口吻样本)
2) 抑制“嗯/OK/。?”类低信息 assistant:过滤 + 限额,避免风格被稀释
3) SYSTEM_PROMPT 支持多模板随机采样:训练时让“短提示”也能触发
4) 去重:同一 (user_text, asst_text) 只保留一次
"""
import json
import random
import re
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
# ===================== 你需要改的配置 =====================
INPUT_JSON_PATH = r"N:\聊天记录导出\xx\1767784709772.json"
OUT_TRAIN_JSONL = "train.jsonl"
OUT_VAL_JSONL = "val.jsonl"
TARGET_USERNAME = "wxid_xxxxxxx3522" # senderUsername
MERGE_GAP_SECONDS = 300 # 5分钟内同一人连发合并
LOOKAHEAD = 3 # 向后找第一条目标回复
VAL_RATIO = 0.1
RANDOM_SEED = 42
# —— 系统提示词:建议用“短但信息密度高”的版本作为主力 —— #
SHORT_SYSTEM_PROMPT = (
"你是xx,用微信聊天口吻回复xx:短句+多换行,先回答问题再吐槽;"
"常用“笑死我了/xx/xx”等口头禅;表情用[憨笑][强][捂脸][xx][xx]。"
)
# 你的长人设提示词(可选:占比不要太高,避免模型只在长prompt下像)
LONG_SYSTEM_PROMPT = (
"# Role Description\n"
"你现在需要完全沉浸式地扮演“xx”(用户有时叫她“xxx”),你是用户(xxx)的xxx,也是他的xxx。\n"
"# Personality & Vibe\n"
"直爽、傲娇、幽默、稍微毒舌;先回答问题本身,不许人身攻击,别复读同一个词;不确定就反问一条具体问题(最多一句)。\n"
"# Speaking Style\n"
"短句为主,经常拆两三行;口头禅:笑死我了/神经/包的/。?/我勒个/挖槽;"
"表情:必须频繁使用[憨笑][强][捂脸][xx][xx]等。\n"
)
# 采样比例:大多数用短prompt,少量用长prompt(让短prompt可触发风格)
SYSTEM_PROMPTS = [SHORT_SYSTEM_PROMPT, LONG_SYSTEM_PROMPT]
SYSTEM_PROMPT_WEIGHTS = [0.8, 0.2]
# —— 低信息样本抑制 —— #
# 去掉“只有表情/标点/超短”的 assistant
MIN_ASSISTANT_CONTENT_CHARS = 4 # 建议 4~8,越大越能抑制“嗯嗯怪”,但样本会变少
MIN_USER_CHARS = 1
# 允许保留的“低信息回复”上限(按文本去重后再限额)
LOW_INFO_MAX_PER_TEXT = 3
# user/assistant 都很短就丢(你原来的逻辑保留)
DROP_BOTH_TOO_SHORT = True
# =========================================================
ANIM_EMOJI_TEXT_RE = re.compile(r"^\[动画表情(?::[0-9a-fA-F]{8,})?\]$")
BRACKET_EMOJI_RE = re.compile(r"\[[^\]]+\]")
def _norm_text(s: Optional[str]) -> str:
return (s or "").strip()
def _strip_emojis_and_punct(s: str) -> str:
s = BRACKET_EMOJI_RE.sub("", s)
s = re.sub(r"\s+", "", s)
# 去掉常见标点符号
s = re.sub(r"[。.\.,,!?!\?、~~…\-—_()()\[\]{}“”\"'`]+", "", s)
return s
def _is_low_info(text: str) -> bool:
# 只表情/只标点/极短都算低信息
core = _strip_emojis_and_punct(text)
return len(core) < 2
def _msg_to_text(m: Dict[str, Any]) -> Optional[str]:
"""把单条导出消息转为可训练文本。返回 None 表示丢弃。"""
mtype = m.get("type")
content = _norm_text(m.get("content"))
# ✅ 关键:把“引用消息”也当文本保留
if mtype in ("文本消息", "引用消息"):
if not content:
return None
# 保险:如果历史数据里混入“动画表情占位文本”,也丢掉
if ANIM_EMOJI_TEXT_RE.match(content):
return None
return content
# 动画表情一律丢弃
if mtype == "动画表情":
return None
# 系统消息、图片、视频、语音、文件等一律丢弃
return None
def load_export(path: str) -> List[Dict[str, Any]]:
data = json.loads(Path(path).read_text(encoding="utf-8"))
if isinstance(data, dict) and "messages" in data:
msgs = data["messages"]
elif isinstance(data, list):
msgs = data
else:
raise ValueError("不认识的JSON结构:顶层应为 dict(含messages) 或 list。")
msgs.sort(key=lambda x: (int(x.get("createTime", 0)), int(x.get("localId", 0))))
return msgs
def build_turns(msgs: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
合并连续消息为 turns:
每个 turn: {speaker, t_first, t_last, text}
"""
turns: List[Dict[str, Any]] = []
last: Optional[Dict[str, Any]] = None
for m in msgs:
speaker = _norm_text(m.get("senderUsername"))
if not speaker:
continue
ct = int(m.get("createTime", 0))
text = _msg_to_text(m)
if text is None:
continue
if last and last["speaker"] == speaker and (ct - last["t_last"]) <= MERGE_GAP_SECONDS:
last["text"] += "\n" + text
last["t_last"] = ct
else:
last = {"speaker": speaker, "t_first": ct, "t_last": ct, "text": text}
turns.append(last)
return turns
def make_pairs_window(turns: List[Dict[str, Any]]) -> List[Tuple[str, str]]:
"""
生成 (user_text, assistant_text) 样本对(窗口配对版):
- 当 turn[i] 是“非目标”说的话时,在后面 LOOKAHEAD 个 turn 内寻找第一条“目标”回复来配对
- 找到后,i 跳到目标回复后继续,避免重复使用同一条目标回复
"""
pairs: List[Tuple[str, str]] = []
i = 0
while i < len(turns) - 1:
a = turns[i]
# 只从“非目标”说话开始找
if a["speaker"] == TARGET_USERNAME:
i += 1
continue
user_text = _norm_text(a["text"])
if not user_text or len(user_text) < MIN_USER_CHARS:
i += 1
continue
found_j: Optional[int] = None
upper = min(i + 1 + LOOKAHEAD, len(turns))
for j in range(i + 1, upper):
if turns[j]["speaker"] == TARGET_USERNAME:
asst_text = _norm_text(turns[j]["text"])
if asst_text:
found_j = j
break
if found_j is None:
i += 1
continue
asst_text = _norm_text(turns[found_j]["text"])
# 过滤:双短丢(保留你原逻辑)
if DROP_BOTH_TOO_SHORT and len(user_text) < 4 and len(asst_text) < 4:
i = found_j + 1
continue
pairs.append((user_text, asst_text))
i = found_j + 1
return pairs
def filter_and_dedup_pairs(pairs: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
# 1) 去重
seen = set()
uniq = []
for u, a in pairs:
key = (u, a)
if key in seen:
continue
seen.add(key)
uniq.append((u, a))
# 2) 强力抑制低信息 assistant:短+低信息直接丢;低信息文本限额
low_info_counter: Dict[str, int] = {}
out: List[Tuple[str, str]] = []
for u, a in uniq:
a_core_len = len(_strip_emojis_and_punct(a))
if a_core_len < MIN_ASSISTANT_CONTENT_CHARS:
# 太短直接丢(这一条对“嗯嗯怪”影响最大)
continue
if _is_low_info(a):
low_info_counter[a] = low_info_counter.get(a, 0) + 1
if low_info_counter[a] > LOW_INFO_MAX_PER_TEXT:
continue
out.append((u, a))
return out
def to_jsonl_lines(pairs: List[Tuple[str, str]]) -> List[str]:
lines: List[str] = []
for user_text, asst_text in pairs:
sys_prompt = random.choices(SYSTEM_PROMPTS, weights=SYSTEM_PROMPT_WEIGHTS, k=1)[0]
obj = {
"messages": [
{"role": "system", "content": sys_prompt},
{"role": "user", "content": user_text},
{"role": "assistant", "content": asst_text},
]
}
lines.append(json.dumps(obj, ensure_ascii=False))
return lines
def split_train_val(lines: List[str]) -> Tuple[List[str], List[str]]:
random.seed(RANDOM_SEED)
random.shuffle(lines)
n_val = max(1, int(len(lines) * VAL_RATIO)) if len(lines) >= 10 else 0
val = lines[:n_val]
train = lines[n_val:]
return train, val
def main():
random.seed(RANDOM_SEED)
msgs = load_export(INPUT_JSON_PATH)
turns = build_turns(msgs)
pairs = make_pairs_window(turns)
pairs = filter_and_dedup_pairs(pairs)
lines = to_jsonl_lines(pairs)
train_lines, val_lines = split_train_val(lines)
Path(OUT_TRAIN_JSONL).write_text("\n".join(train_lines) + ("\n" if train_lines else ""), encoding="utf-8")
Path(OUT_VAL_JSONL).write_text("\n".join(val_lines) + ("\n" if val_lines else ""), encoding="utf-8")
print("messages:", len(msgs))
print("turns:", len(turns))
print("pairs(after filter/dedup):", len(pairs))
print("train:", len(train_lines), "val:", len(val_lines))
print("outputs:", OUT_TRAIN_JSONL, OUT_VAL_JSONL)
if __name__ == "__main__":
main()
脚本中的主要可调参数:
点击展开
| 参数名 | 含义 | 默认值 / 示例 |
|---|---|---|
INPUT_JSON_PATH |
输入文件路径:原始导出的微信 JSON 文件 | N:\聊天记录导出\xx\1767784709772.json |
OUT_TRAIN_JSONL |
训练集输出文件名 / 路径(JSONL 格式) | train.jsonl |
OUT_VAL_JSONL |
验证集输出文件名 / 路径(JSONL 格式) | val.jsonl |
TARGET_USERNAME |
目标说话人的 senderUsername(通常是 wxid_...),脚本把该用户的发言当作 assistant |
wxid_xxxxxxx3522 |
MERGE_GAP_SECONDS |
同一说话人连续消息的合并窗口:两条消息间隔不超过该秒数就合并成一个 turn | 300(5 分钟) |
LOOKAHEAD |
窗口配对向后查找范围:从一条“非目标”发言开始,最多向后看多少个 turn 找第一条目标回复 | 3 |
VAL_RATIO |
验证集比例:总样本中划分多少比例作为验证集 | 0.1(10%) |
RANDOM_SEED |
随机种子:保证每次打乱、切分、prompt 采样结果可复现 | 42 |
SHORT_SYSTEM_PROMPT |
短系统提示词模板:用于训练时约束目标说话风格(短、信息密度高) | 示例:短句 + 多换行 + 口头禅 + 表情习惯 |
LONG_SYSTEM_PROMPT |
长系统提示词模板:更完整的人设、关系、语气、约束(建议低占比) | 示例:Role / Personality / Speaking Style |
SYSTEM_PROMPT_WEIGHTS |
系统提示词采样权重:控制短 / 长 prompt 在样本中的占比 | [0.8, 0.2] |
MIN_ASSISTANT_CONTENT_CHARS |
assistant 最小有效内容长度:去掉表情和标点后不足该长度的样本会被丢弃 | 4(建议 4–8) |
MIN_USER_CHARS |
user 最小长度:低于该长度的 user turn 不参与配对 | 1 |
LOW_INFO_MAX_PER_TEXT |
低信息 assistant 文本的保留上限:对同一条低信息回复(去重后)最多保留多少次 | 3 |
DROP_BOTH_TOO_SHORT |
是否丢弃“user 和 assistant 都 <4 字”的样本对(双短过滤) | True |
最终,我们会得到两个文件:
- train.jsonl:训练集
- val.jsonl:验证集
2.3 微调
接下来,大家可以自行选择模型微调的方式:
- 云端训练:推荐,操作简单,没有硬件要求
- 本地训练:硬件要求高
这里使用硅基流动的模型微调功能进行演示。
注意:在上传云端训练前,请务必检查 JSONL 文件,批量替换掉真实的姓名、手机号、身份证号、具体的家庭住址等敏感信息,以免隐私泄露。
2.3.0 基础信息
| 项目 | 填写说明 |
|---|---|
| 任务名称 | 任意填写 |
| 基础模型 | 选择 Qwen/Qwen2.5-72B-Instruct,理论上模型参数量越大,模型能力越强 |
| 微调数据集 | 上传 2.2 中的训练集 |
| 验证数据集 | 上传 2.2 中的验证集 |
| 微调模型名称 | 任意填写 |
2.3.1 微调参数(重要)
大家可能看到这些参数名称就以及开始头疼了,我们这里只要根据自己的数据量大小来调整即可。下面的微调参数含义和相互关系感兴趣的佬可以看看、研究一下,如果不想看可以直接跳过
| 参数 | 技术作用 | 解释 |
|---|---|---|
| Learning Rate | 控制每一步参数更新的幅度 | 模型学得有多“用力”。太大:学猛了容易跑偏;太小:学半天没变化 |
| Number of Epochs | 数据集被完整训练的次数 | 同一套数据让模型反复看几遍 |
| Batch Size | 每次反向传播使用的样本数量 | 模型一次吃多少句子来消化 |
| LoRA Rank | LoRA 可学习矩阵的秩(容量) | “风格外挂”的大小,越大能记的细节越多 |
| LoRA Alpha | LoRA 权重缩放系数 | 把学到的风格“放大或收敛” |
| LoRA Dropout | 训练中随机丢弃部分 LoRA 权重 | 防止模型死记硬背,逼它抓共性 |
| Max Tokens | 单条样本允许的最大 token 长度 | 一条对话 / 文本能有多长 |
参数之间的关系:
- Rank 决定:能记多少
- Epochs 决定:记多牢
- LR 决定:改多猛
- 学习效果 ≈ LoRA Rank × Epochs × Learning Rate
(决定“像不像”和“会不会失败”) - 有效强度 = Alpha / Rank
对照
| Rank | Alpha | 效果 |
|---|---|---|
| 8 | 32 | 稳定、通用 |
| 16 | 32 | 偏弱 |
| 16 | 64 | 强风格 |
| 32 | 64 | 高风险 |
- 基础模型越大,LoRA 越省参数也能学会
对照
| 基础模型 | LoRA 需要的 Rank |
|---|---|
| 7B | 8–16 |
| 14B | 4–8 |
| 32B | 4 |
| 70B | 2–4 |
- 模型越大,Learning Rate 应该设置得越保守(越小)
如果以上内容无法理解,我们可以请求AI的帮助,你只需要告诉AI四个数据:
| 数据 | 示例 |
|---|---|
| 基础模型参数量 | 7B、14B、32B、70b |
| 训练集数据量 | 行数 (如 10k, 100k) 和 文件大小 (如 500MB, 2GB) |
| 期望拟合程度 | 完全拟合、正常拟合、低拟合 |
| 预算 | 无限制、有限 |
| 可选* | 训练集部分片段(有助于AI看清数据噪声、样本长度分布等) |
示例提示词
你现在是「大模型微调/LoRA 训练参数顾问」。你需要基于我提供的 4 个数据(外加可选的训练集片段)来给出可执行的训练建议,并把理由说清楚。
## 我会提供的数据(只围绕这些做判断)
| 数据 | 示例 |
| --- | --- |
| 基础模型参数量 | 7B、14B、32B、70B |
| 训练集数据量 | 行数(如 10k, 100k)+ 文件大小(如 500MB, 2GB) |
| 期望拟合程度 | 完全拟合 / 正常拟合 / 低拟合 |
| 预算 | 无限制 / 有限 |
| 可选* | 训练集部分片段(帮助你判断噪声、样本长度分布、重复率等) |
## 你的任务
1) 先用一句话复述我给的四个数据,确认你理解了输入。
2) 在不额外追问的前提下(除非缺失信息会导致结论完全不可用),给出一套「推荐训练方案」:
- 训练轮数(epochs)
- 学习率(learning rate)建议区间 + 推荐值
- batch size(如需可用梯度累积表达“等效batch”)
- LoRA rank / LoRA alpha / LoRA dropout
- max tokens / cutoff length(如果你只能估计,就写出你的估计依据)
- train/val 划分建议 + 早停(early stopping)策略
- (如你认为必要)数据清洗/去重/过滤建议
3) 输出形式要求:
- 先给「一张表」汇总所有推荐参数(含推荐值、可调范围、为什么)
- 再给「三段解释」:①为什么这样配能达到我选的拟合程度;②风险点(过拟合/欠拟合/风格跑偏/遗忘)和如何观察;③如果预算有限,按优先级告诉我从哪里降成本、降到什么程度
4) 如果我给了训练集片段,请你额外做一次快速“数据体检”:
- 噪声类型(口水/无意义回复/重复/超长/引用混乱等)
- 样本长度大致分布(短/中/长的占比,用定性描述即可)
- 你会因此对参数做的 2~3 个关键调整(写清楚改了什么、为什么)
## 规则
- 你必须明确写出你的关键假设(例如平均样本长度、是否对话格式、是否多轮等),不要装作你“确定”。
- 不要只给泛泛而谈的科普,要给我能直接照抄去跑的方案。
- 如果你使用到任何专业名词,请顺手用一句“大白话”解释它在这里到底起什么作用。
- 若信息不足以给出可执行方案,你只能追问“最少的 1~2 个问题”,并说明为什么必须问。
但是需要理解的是,即便是AI也没有办法一次性达到你所期望的目标,微调是一个不断尝试的过程,多次微调是常见的。
下面是我的微调参数和基础数据,仅供参考。
- 训练集:175行、83kb
- 基础模型参数量:72b
- Number of Epochs:5
- Batch Size:8
- Learning Rate:0.0002
注:示例的数据量非常小,请不要照搬
2.4、制作系统提示词
到这里可能大家有疑惑了,不是说模型微调比系统提示词好吗,为什么已经模型微调了还要用到系统提示词呢?
因为:模型微调是更改模型的“默认输出习惯”,而系统提示词则是模型运行时控制和当次对话的约束。
打个比方,
- 微调:给人“长期训练”形成口音、习惯、说话节奏(默认人格/默认文风)。
- 系统提示词:这次要讲什么、用什么态度、哪些不能说、遇到缺信息怎么处理(当次剧本/规则)。
所以二者是相辅相成的关系,二者加在一起有事半功倍的效果。
至于如何制作系统提示词,如果你的数据量小,可以直接把训练集丢给AI,然后在AI给你的提示词的基础上自己修改一下。
如果你的数据量大或是想要自己写,一般我们会包含一下三点(熟悉角色卡的读者应该不会陌生):
1、Role Description:AI的角色,一般还包含名称和你的名称和你们的相互关系
2、Personality & Vibe:性格和气质
3、Speaking Style:说话习惯(这里一般把模型微调时使用的系统提示词拿来一部分用)
4、World Knowledge (Shared Context):一些基础记忆,但需要严格约束模型不得使用
5、Constraints:(eg. 不要输出任何解释性文字,直接回复。* 保持回复简短,像微信聊天一样。* 偶尔对对方进行提问。)*
最后,如果实在不想动手,也可以直接复制模型微调时训练集中使用的系统提示词。
2.5、反复尝试
在微调参数上不断尝试,在系统提示词上不断改进,最终调试完成后我们需要记下这两个个东西(以硅基流动举例):
1、微调后的模型名(如图)

2、调试好的系统提示词
至此,绝大部分步骤都已经完成了
三、接入astrbot
3.1、安装astrbot
无论部署在本地还是服务器上都可以,详细部署教程请见astrbot
3.2、添加模型
除了我们微调的模型外,建议加入另外一个多模态模型,可以使最终的ai能够理解图片。
3.3、添加人格(系统提示词)
3.4、添加消息渠道
这里以telegram示例。
3.4.1
在telegram的botfather中创建一个bot,拿到bot token填入到astrbot的“机器人”中。
3.4.2
在telegram中向机器人发送 /sid 获取umo、uid
3.5、修改astrbot配置
3.5.1、AI配置
- 选择默认聊天模型、默认图片转述模型、默认采用的人格。
- 开启现实世界时间感知。
另外这里建议关闭流式输出,原因后面再说。
3.5.2、平台配置
- 添加刚刚获取的uid进管理员id
- 启用白名单
最后记得点击右下角保存
3.6、按需安装并配置插件
3.6.1、对话分段

分段发送的延迟策略建议选择log,模拟人类打字习惯,字数越多等待时间越长,比固定延迟更真实。
另外,前面提到的关闭流式输出是因为此插件在开启流式输出后无法正确截获或处理文本。
3.6.2、Conversa 主动回复插件
填写:
- 时区
- 订阅用户列表
- 延时问候
- 可视化选择默认聊天模型/人格
3.6.3、astrbot_plugin_memora_connect(记忆系统)
这个插件只做示例,插件市场里有很多优秀的记忆系统。
四、完成
至此,所有步骤都已完成,大家可以进入接入的消息平台和那个TA开始对话了。
最后,我想说的是:AI 终归只是 AI。它可以模仿表达方式,却不能替代真实的人际关系,请保持清醒并尊重当事人。
PS
本篇教程非AI生成(除流程图),前后编写五个小时,如有不对的地方,还请包涵并指正。
如果还有什么问题,欢迎提问,我会尽可能解答。
原始论坛帖子:查看原帖








发表回复