开发文档
后端要求 Python 3.12.x。语言默认 XUWEN_LANGUAGE=en,可设置 zh-CN;CLI --language 与 HTTP Accept-Language 覆盖各自作用域,主动消息固定用后端语言。
本文面向想参与 Afterglow 开发、添加导入格式、调试后端能力或提交 PR 的贡献者。
环境准备
后端:
cd backend
uv sync --extra dev
cp .env.example.zh-CN .env前端:
cd frontend
pnpm install启动后端:
cd backend
XUWEN_LANGUAGE=zh-CN uv run uvicorn xuwen.chat_api.app:create_app --factory --reload启动前端:
cd frontend
pnpm dev后端结构
backend/xuwen/
├── chat_api/ FastAPI 服务、路由、LLM/VLM/联网客户端、调试指标
├── companion/ AI 生活状态、关系记忆
├── core/ 通用模型、错误、时间、指标
├── ingestion/ 导入 JSON、清洗、切分、向量化、打标
├── memory/ LanceDB schema、读写、混合检索、回写队列
└── persona/ persona 分析、prompt 模板、语义打标核心请求链路:
POST /v1/chat/completions接收 OpenAI 兼容请求。- 如果消息带图片,根据视觉配置处理图片。
HybridRetriever用当前文本执行五路向量召回,并读取当前会话 Recent Live。LifeStateManager更新或读取 AI 当前生活状态。RelationshipMemoryManager提供关系记忆摘要。- 可选
WebSearchClient/WebFetchClient注入公开网页上下文。 build_chat_messages()组装 system prompt。LLMClient调用上游模型。- 如果有
conversation_id,把这一轮写入 live memory。
导入插件开发
导入系统已经按插件拆开。主流程只需要统一的 NormalizedMessage,不关心消息来自 QQ、微信还是其它平台。
目前内置 plugin:
| name | display_name | 输入格式 | 识别特征 |
|---|---|---|---|
afterglow_v1 | Afterglow Chat v1 | Afterglow 专用私聊 JSON/JSONL | JSON metadata 或 typed/bare JSONL records |
qqexporter_v5 | QQChatExporter | QQChatExporter JSON/JSONL | metadata/chatInfo 或 JSONL message records |
wechat_weflow | WeChat (WeFlow) | WeFlow 导出 JSON/JSONL | arkme-json 或 ChatLab typed JSONL records |
douyin_chat_export | Douyin Chat Export | 抖音 ChatLab JSON/JSONL | generator=douyin-chat-export 且 platform=douyin |
| whatsapp | WhatsApp | TXT / Markdown | 日期消息行或 WhatsApp Markdown 标题 | | telegram_html | Telegram Desktop HTML | HTML | history/message HTML 结构 | | discord_chat_exporter | DiscordChatExporter | JSON | guild/channel/exportedAt/messages 字段 |
微信导入提醒:使用 WeFlow 导出微信记录进行开发和测试时,请注意不要使用未脱敏的真实聊天数据。
CLI 在导入时按注册顺序遍历 match(),第一个命中的负责 parse(); 也可以用 --plugin <name> 强制指定。
通用 loader 把 TXT / Markdown / HTML 解码为带后缀的中性文本 payload(TEXT_CONTENT_KEY / TEXT_SUFFIX_KEY),把 JSONL 解码为中性 records,不包含任何平台字段判断。plugin 使用 jsonl_records(payload) 取得逐行对象,并在自身模块内完成格式识别与规范化。
插件接口在 backend/xuwen/ingestion/plugins/__init__.py:
class ImportPlugin(Protocol):
name: str
display_name: str
def match(self, payload: dict[str, Any]) -> bool:
...
def parse(
self,
payload: dict[str, Any],
settings: Settings,
) -> list[NormalizedMessage]:
...配置向导身份识别和历史图片引用是独立可选能力。需要这些功能的 plugin 还应实现:
class InspectableImportPlugin(Protocol):
def inspect(self, payload: dict[str, Any]) -> ImportInspection: ...
class ImageReferenceImportPlugin(Protocol):
def extract_image_refs(self, payload: dict[str, Any]) -> list[ImportImageRef]: ...格式名称、候选身份和图片字段解释都应留在 plugin 内;不要在 parser.py、WebUI 或 image_importer.py 添加平台专用分支。
新增一个导入格式的步骤:
- 在
backend/xuwen/ingestion/plugins/下新增模块,例如wechat_xxx.py。 - 实现
name、display_name、match()、parse()。 - 需要向导自动识别身份时实现
inspect();需要历史图片导入时实现extract_image_refs()。 - 在
backend/xuwen/ingestion/parser.py注册插件。 - 添加单元测试,覆盖自动识别、强制指定和关键消息类型。
- 用真实脱敏样例跑一次导入。
最小示例:
from typing import Any
from xuwen.config import Settings
from xuwen.core.models import MessageKind, NormalizedMessage, SenderRole
class ExamplePlugin:
name = "example"
display_name = "Example Export"
def match(self, payload: dict[str, Any]) -> bool:
return payload.get("format") == "example"
def parse(
self,
payload: dict[str, Any],
settings: Settings,
) -> list[NormalizedMessage]:
messages: list[NormalizedMessage] = []
for idx, raw in enumerate(payload.get("messages") or []):
sender_uid = str(raw.get("sender_id") or "")
if sender_uid in settings.all_self_uids:
role: SenderRole = "self"
elif sender_uid in settings.all_friend_uids:
role = "friend"
else:
role = "other"
messages.append(
NormalizedMessage(
message_id=str(raw.get("id") or idx),
seq=idx,
timestamp_ms=int(raw.get("timestamp_ms") or 0),
sender_uid=sender_uid,
sender_name=str(raw.get("sender_name") or ""),
sender_role=role,
kind=MessageKind.TEXT,
raw_type=str(raw.get("type") or "text"),
text=str(raw.get("text") or ""),
raw=raw,
)
)
return messages注意事项:
match()必须轻量,不做文件 IO,不发网络请求。parse()不要直接写库、不要调用 embedding、不要改全局状态。- 解析失败的单条消息可以跳过,但不要吞掉整体格式错误。
timestamp_ms必须是毫秒时间戳。sender_role必须正确区分SELF和FRIEND,否则检索会把用户自己的话当成对方风格。- 图片、表情、语音等无法转文字的内容应放入
placeholders,正文可保留[图片]等占位。
查看当前插件:
cd backend
uv run python -m xuwen.ingestion.cli plugins --language zh-CN强制使用某插件导入:
uv run python -m xuwen.ingestion.cli import export.json --plugin qqexporter_v5 --language zh-CN
uv run python -m xuwen.ingestion.cli import export.json --plugin wechat_weflow --language zh-CN
uv run python -m xuwen.ingestion.cli import export.jsonl --plugin douyin_chat_export --language zh-CN
uv run python -m xuwen.ingestion.cli import export.jsonl --plugin afterglow_v1 --language zh-CNAfterglow Chat v1
Afterglow 专用 JSON / JSONL 中间格式已经独立为 Afterglow Chat v1 格式。本页只保留 ingestion plugin 的开发约定。
测试与质量检查
后端常用命令:
cd backend
uv run ruff check xuwen
uv run mypy xuwen多语言检查可在仓库根运行 node scripts/check_locales.cjs;修改两套前端源文件后分别构建,配置 UI 的构建产物需同步提交。
前端构建:
cd frontend
pnpm build发布版本同步
根目录的 VERSION 是版本号的唯一人工来源。不要分别手改 Python 项目、锁文件或 Docker 示例;在仓库根目录运行:
# 指定版本
uv run --project backend python scripts/set_version.py 0.4.0
# 或按语义版本递增
uv run --project backend python scripts/set_version.py --bump patch
uv run --project backend python scripts/set_version.py --bump minor
uv run --project backend python scripts/set_version.py --bump major脚本会调用 uv version 同步 backend/pyproject.toml 与 backend/uv.lock,并更新 .env.docker.example。只检查、不修改文件时运行:
uv run --project backend python scripts/set_version.py --check版本号变更由仓库维护者在发布时执行,贡献者不需要也不应该在普通 PR 中 bump 版本。 PR 工作流仍会执行只读一致性检查,用于阻止意外修改或只改其中一处的版本漂移。
调试建议
调试端点默认关闭。需要运行时诊断时,在 backend/.env 里临时设置 DEBUG_ENDPOINTS_ENABLED=true 并重启后端;排查完成后建议关回 false。
运行时诊断:
curl -H "Accept-Language: zh-CN" http://127.0.0.1:8000/debug/stats \
-H "Authorization: Bearer $XUWEN_API_KEY"
curl -H "Accept-Language: zh-CN" http://127.0.0.1:8000/debug/config \
-H "Authorization: Bearer $XUWEN_API_KEY"重点看:
model_chain:模型请求完整链路。life:AI 当前生活状态和最近决策。database:LanceDB 读写耗时。calls:LLM、retrieval、web search、web fetch 等调用统计。
检索调试:
curl -H "Accept-Language: zh-CN" -X POST http://127.0.0.1:8000/memory/search \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $XUWEN_API_KEY" \
-d '{"query":"你在干嘛","top_k":12}'配置开发原则
- 新增运行时配置应放进
Settings,不要在业务代码硬编码。 .env.example必须同步更新。- 默认值必须保守,尤其是联网、视觉、写回、调试端点等涉及隐私或外部请求的能力。
- API key 使用
SecretStr,日志和调试接口不能输出明文。 - 任何“读取 URL”的能力都必须做 SSRF 防护:拒绝本机/内网/特殊地址,限制跳转、超时、响应大小和 prompt 注入长度。
文档与注释风格
用户文档中英文同等维护:docs/wiki/ 为中文,docs/en/ 为英文,对应页面在 docs/pages.json 登记;网站固定文案按语言放在 docs/.vitepress/locales/。
标识符使用英文。已有解释性注释和 docstring 可保留中文;新增用户可见提示应使用语言文件和可读固定兜底。原文证据、持久化枚举、协议标记和用户自定义提示词不机械翻译。
GitHub Pages 与 Wiki 都是生成结果,不直接编辑。具体规则见文档维护。
提交前检查
提交前确认:
- 没有提交
backend/.env、backend/.data/、聊天导出 JSON、真实 API key。 - 新配置已写入
.env.example。 - 新接口已同步更新两种语言 API 文档。
- 新页面已创建中英文文件并加入
docs/pages.json和两种语言导航。 - 新导入格式已写测试和插件说明。
- 相关测试、ruff、mypy 已通过。