nanobot 我关心的 bug 死活不修,影响正常使用了,而且 token 耗的也快……我改成维护更久的 astrbot,这玩意儿 22 年就开始开发了,不过 LLM,主动推送等能力也是最近才开发的所以仍旧 buggy,但仍旧有如下的优点让我用它:
有一个完善的前端页面可用,可以直接拿来看日志,十分舒适
Skill,MCP,定时任务等功能都有(定时任务比 nanobot 好用,但对 QQ 适配器需要做一些处理)
支持插件系统来帮助我热修改它的代码或增加功能(这让我把它某种程度上可以 openclaw 化)
对 QQ 适配器的支持好,支持流式输出,文件、图像的上传下载,主动推送(定时任务等)……
得益于轻量的系统提示词,astrbot 对 Token 的用量似乎很少
我做了一些处理把 astrbot 去 openclaw 化,这里记一下。
我是用官方的 docker compose 部署的(以安全为考虑),本来是打算同时部署 shipyard,但发现目前的版本对沙箱的配置不太好,对临时文件如上传的图像的 mount 没有处理好。我决定就走 local 了,反正是在 docker 容器里,按天备份就行了,掀不起大风浪。
工作区 执行astrbot run时,默认是把数据存在当前目录的 data 目录下,并在当前目录存一个.astrbot空文件作为标识。在 Docker 容器中,容器在/Astrbot目录下执行,因此 Docker 容器下,astrbot 自己的数据目录存在/Astrbot/data。
但这个数据目录东西比较多,如果都让 AI 访问的话我觉得有些噪音了 。我的选择是:
创建一个 workspace 目录,在提示词中强调 AI 以/Astrbot/data/workspace作为工作目录。提示词见下。
(可选?我只是确保一致性,欣慰的是 astrbot 它能正确处理软链接)把/Astrbot/data/skills挪到工作区中,然后在/Astrbot/data中安排一个相对软链接供索引 ln -r -s workspace/skills skills
增加的人格系统提示词如下:
<工作区设定 > 你的默认的工作区在 `/Astrbot/data/workspace`,如果你看到相对路径,以该绝对路径为基本路径。你尽量不要污染 workspace 以外的路径。</工作区设定 >
Git 备份工作区 为/Astrbot/data创建一个私有仓库,创建 deploy key,然后在 config 中增加这些配置,mount 到 docker 容器中:
Host github-astrbot-workspace HostName ssh.github.com User git IdentityFile ~/.ssh/astrbot-workspace IdentitiesOnly yes
然后远程仓库就设置成下面这样,就能直接对单独这个仓库用上这个密钥了:
git remote set-url origin github-astrbot-workspace:USERNAME/ REPO .git
然后给 AI 一个定时任务,让她每天自己做备份,就这样。
可以配置.gitignore减少仓库大小:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 .clawhub/ plugins/* !plugins/astrbot_plugin_naer_* plugins.json dashboard.zip dist/ site-packages/ temp/.DS_Store *.db-shm *.db-wal *.db-journal __pycache__
迁移 openclaw 的记忆能力 我直接拷贝了 nanobot 的 memory 技能 到工作区中。
但这里有一个问题——Openclaw 中的长期记忆,它是直接将 MEMORY.md 的内容构造到 System Prompt 的,而 astrbot 并没有提供这样的能力,它只能自己读取 MEMORY skill 内容然后自己读 MEMORY.md 。
但这并不保险,即使严肃提示,AI 还是会偷懒不读 MEMORY.md,所以能做到的话还是尽量希望能动态构造系统提示词。
然而,you guess what?通过插件可以做到,参考文档 接受消息事件时 ,在这时直接操作系统提示词即可,我选择使用内置的string.Template进行变量插值。
在/Astrbot目录下执行astrbot plug new去创建新插件,然后修改 main.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import osfrom pathlib import Pathfrom astrbot.api.event import filter , AstrMessageEvent, MessageEventResultfrom astrbot.api.star import Context, Star, registerfrom astrbot.api import loggerfrom astrbot.core.utils.astrbot_path import get_astrbot_data_pathfrom astrbot.api.provider import ProviderRequestfrom string import Template@register("helloworld" , "YourName" , "一个简单的 Hello World 插件" , "1.0.0" ) class MyPlugin (Star ): def __init__ (self, context: Context ): super ().__init__(context) async def initialize (self ): """可选择实现异步的插件初始化方法,当实例化该插件类之后会自动调用该方法。""" async def terminate (self ): """可选择实现异步的插件销毁方法,当插件被卸载/停用时会调用。""" @filter.on_llm_request(desc='读取 workspace/memory/MEMORY.md,作为 $MEMORY 或 ${MEMORY} 插值到系统提示词' ) async def my_custom_hook_1 (self, event: AstrMessageEvent, req: ProviderRequest ): template = Template(req.system_prompt) memory_file_path = Path(get_astrbot_data_path())/'workspace' /'memory' /'MEMORY.md' if not memory_file_path.exists(): logger.info(f'文件 {memory_file_path} 不存在,未插值 $MEMORY' ) return result = template.safe_substitute(MEMORY=memory_file_path.read_text('utf-8' )) req.system_prompt = result logger.debug(f'REPLACED SYSTEM_PROMPT: {req.system_prompt} ' )
然后,人格系统提示词中增加下面的内容:
<记忆设定 > 积极地使用memory skill以记忆和回忆重要信息,保证跨会话上下文一致。但**你无需自己读取长期记忆(MEMORY.md)内容,下面的`<长期记忆 > `会自动注入最新的MEMORY.md内容**。 <长期记忆 > $ {MEMORY} </长期记忆 > </记忆设定 >
一个 QQ 主动推送的 bug 参照我提的 issue 和 pr ,因为这个改动可能会有未预期的影响,所以一时半会估计合并不进去,我自己做处理了。
这里最整蛊的地方是,QQ官方解除了主动推送的限制,但被动回复的时间限制还在,结果导致居然移除msg_id反而就能让代码通过了,搞笑啊这。
反正我就直接用插件打猴子补丁了,我自己先解决问题,官方想怎么干就怎么干吧。
在插件初始化时执行下面的函数即可。
def override_qqofficial_post_c2c_message (): """重写一个方法,永远地移除掉msg_id参数""" from astrbot.core.platform.sources.qqofficial.qqofficial_message_event import QQOfficialMessageEvent old_method = QQOfficialMessageEvent.post_c2c_message async def new_method (self, *args, **kwargs ): kwargs.pop('msg_id' , None ) return await old_method(self, *args, **kwargs) QQOfficialMessageEvent.post_c2c_message = new_method
SubAgent超时bug SubAgent没有走平台中配置的工具调用超时,而是默认的60秒超时,这处理deep research就非常勉强。
关于DeepResearch,我发现还是专门整一个独立的MCP服务去处理好使,我用的是 https://github.com/u14app/deep-research 。
这个还是打一个猴子补丁……我的issue在这里 https://github.com/AstrBotDevs/AstrBot/issues/6671 。
def override_context_tool_loop_agent (): old_tool_loop_agent = Context.tool_loop_agent async def new_tool_loop_agent (self: Context, *args, **kwargs ): if 'tool_call_timeout' in kwargs: return await old_tool_loop_agent(self, *args, **kwargs) prov_settings: dict = self.get_config().get("provider_settings" , {}) tool_call_timeout = int (prov_settings.get("tool_call_timeout" , 60 )) kwargs['tool_call_timeout' ] = tool_call_timeout return await old_tool_loop_agent(self, *args, **kwargs) Context.tool_loop_agent = new_tool_loop_agent