Plugin Version
Next-2.0.6
AstrBot Version
4.22.2
Database Type
SQLite (default)
Operating System
Linux
Bug Description
在webui手动执行社交关系分析时在Astrbot后台看到标题报错。
模型配置为筛选模型gemini-3.1-flash-lite-preview,其余两个均为gemini-3-flash--preview,Astrbot后台确认两个模型均正常。
Steps to Reproduce
在webui手动执行社交关系分析->(选择群组)->分析关系,结果关系没有更新,且Astrbot后台看到标题报错
Expected Behavior
- 关系正确更新(如果有)
- Astrbot后台没有报错
Error Logs
[2026-04-02 14:33:45.815] [Plug] [INFO] [social.social_relation_analyzer:203]: 调用LLM分析社交关系 (消息数: 52, 用户数: 8)
[2026-04-02 14:33:45.883] [Plug] [ERRO] [v4.22.2] [core.framework_llm_adapter:294]: 筛选模型调用失败: Timeout context manager should be used inside a task
[2026-04-02 14:33:45.888] [Plug] [WARN] [v4.22.2] [social.social_relation_analyzer:211]: LLM返回空响应
[2026-04-02 14:33:45.893] [Plug] [WARN] [v4.22.2] [social.social_relation_analyzer:144]: 未能分析出有效的社交关系
Relevant Configuration
Additional Context
在和gemini进行探讨之后,得到以下解决建议:
==============================================
看到日志里成功打印了 new触发,说明你确实已经用上了最新的代码。但报错依然一模一样,这其实是一个天大的好消息! 为什么这么说?因为它彻底帮我们排除了“语法错误”或“代码逻辑错误”的可能性,直接将“真凶”锁定在了 Python 异步编程中最隐蔽、也最让人抓狂的终极 Boss:跨线程/跨事件循环(Cross-Loop)环境隔离。
🕵️ 根本原因:被隔离的“两个世界”
在搭载了 WebUI 的 AstrBot 插件系统中,你的程序实际上运行在两个平行的“世界”里:
- Bot 主线程(A 世界): 负责连接 QQ/微信,这里运行着一个主异步事件循环。框架的 LLM Provider(模型客户端)就是在这个世界里出生并绑定它的。
- Quart Web 线程(B 世界): 为了不让网页请求卡住机器人回复,框架通常会将 Web 引擎(Quart/Uvicorn)跑在一个完全独立的后台线程中,并拥有属于它自己的异步事件循环。
案发过程:
当你在 Web 页面点击“触发分析”时,请求进入了 B 世界。我们在上一轮修改中用 asyncio.create_task 在 B 世界里创建了一个任务。但是,当执行到 await llm_adapter.generate_response 时,代码试图唤醒诞生在 A 世界 的 LLM 客户端发起网络请求。
底层的 anyio(网络通信库)具有严格的“防窜台”机制。它低头一看:“你当前所在的世界 B 没有我的 Task 上下文啊!” 于是立刻翻脸,抛出了这个 Timeout context manager should be used inside a task。
🛠️ 终极修复方案:跨界任务投递 (run_coroutine_threadsafe)
要彻底解决这个问题,我们不能在 Quart 的路由(B 世界)里直接运行分析任务,而是必须将任务打包,像寄快递一样扔回 Bot 的主线程(A 世界)去执行。
请按照以下两步修改,即可完美破解:
第一步:捕获机器人的主循环 (Main Loop)
我们需要找个地方把 Bot 的主循环存下来。为了避免循环导入,建议在你的插件核心目录新建一个非常简单的变量文件,或者直接挂载在 container 上。
最稳妥的做法是修改你的 插件入口文件(通常是 main.py 或 __init__.py,里面定义了插件的主 Class):
# 在你的插件入口文件 (例如 main.py) 中
import asyncio
# 1. 声明一个全局变量用来保存主循环
BOT_MAIN_LOOP = None
# 2. 在插件初始化时捕获它
class YourPlugin(...): # 你的插件主类
def __init__(self, ...):
# ... 原有的初始化代码 ...
global BOT_MAIN_LOOP
try:
# 这里的 get_event_loop 会获取到机器人的主循环
BOT_MAIN_LOOP = asyncio.get_event_loop()
except RuntimeError:
pass
第二步:在 Quart 路由中进行跨线程投递
回到你刚才修改的 blueprints.social 路由文件,使用 Python 官方专门用于跨线程异步通信的终极武器 run_coroutine_threadsafe:
import asyncio
from quart import Blueprint, request, jsonify
from astrbot.api import logger
# ... 其他原有的 imports ...
# 注意:把下面的包路径改成你实际的 main.py 路径
from ..main import BOT_MAIN_LOOP
@social_bp.route("/social_relations/<group_id>/analyze", methods=["POST"])
@require_auth
async def trigger_social_relation_analysis(group_id: str):
"""触发群组社交关系分析"""
try:
container = get_container()
social_service = SocialService(container)
if BOT_MAIN_LOOP is None:
return jsonify({
"success": False,
"error": "Bot主循环未捕获,无法执行跨线程任务"
}), 500
# 🌟 终极核心修复 🌟
# 不使用 await,也不使用 create_task
# 将耗时的 LLM 分析任务强行“投递”回机器人的主循环(BOT_MAIN_LOOP)去运行!
asyncio.run_coroutine_threadsafe(
social_service.trigger_analysis(group_id),
BOT_MAIN_LOOP
)
return jsonify({
"success": True,
"message": "分析任务已成功投递至 Bot 主线程,请稍后刷新页面查看结果!"
}), 200
except Exception as e:
logger.error(f"触发社交关系分析失败: {e}", exc_info=True)
return jsonify({
"success": False,
"error": str(e)
}), 500
💡 为什么这招一定管用?
run_coroutine_threadsafe 的神奇之处在于,它跨越了线程的边界。当它执行时,social_service.trigger_analysis 的所有代码(包括调用大模型)都会被转移到诞生大模型的那个原生态主循环中去跑。anyio 在里面找上下文时,就能完美找到它需要的环境。
改完这两处后重启,你会看到网页瞬间返回成功,而后台的 Bot 线程会稳稳地开始跑大模型进度,不会再有任何报错。
根据以上回复进行相应的代码修改后,确认问题解决:没有报错,且分析关系正常更新。
尚不确定该修改会对其他模块造成什么影响,所以放在这里仅供开发人员参考。
Plugin Version
Next-2.0.6
AstrBot Version
4.22.2
Database Type
SQLite (default)
Operating System
Linux
Bug Description
在webui手动执行社交关系分析时在Astrbot后台看到标题报错。
模型配置为筛选模型gemini-3.1-flash-lite-preview,其余两个均为gemini-3-flash--preview,Astrbot后台确认两个模型均正常。
Steps to Reproduce
在webui手动执行社交关系分析->(选择群组)->分析关系,结果关系没有更新,且Astrbot后台看到标题报错
Expected Behavior
Error Logs
Relevant Configuration
Additional Context
在和gemini进行探讨之后,得到以下解决建议:
==============================================
看到日志里成功打印了
new触发,说明你确实已经用上了最新的代码。但报错依然一模一样,这其实是一个天大的好消息! 为什么这么说?因为它彻底帮我们排除了“语法错误”或“代码逻辑错误”的可能性,直接将“真凶”锁定在了 Python 异步编程中最隐蔽、也最让人抓狂的终极 Boss:跨线程/跨事件循环(Cross-Loop)环境隔离。🕵️ 根本原因:被隔离的“两个世界”
在搭载了 WebUI 的 AstrBot 插件系统中,你的程序实际上运行在两个平行的“世界”里:
案发过程:
当你在 Web 页面点击“触发分析”时,请求进入了 B 世界。我们在上一轮修改中用
asyncio.create_task在 B 世界里创建了一个任务。但是,当执行到await llm_adapter.generate_response时,代码试图唤醒诞生在 A 世界 的 LLM 客户端发起网络请求。底层的
anyio(网络通信库)具有严格的“防窜台”机制。它低头一看:“你当前所在的世界 B 没有我的 Task 上下文啊!” 于是立刻翻脸,抛出了这个Timeout context manager should be used inside a task。🛠️ 终极修复方案:跨界任务投递 (
run_coroutine_threadsafe)要彻底解决这个问题,我们不能在 Quart 的路由(B 世界)里直接运行分析任务,而是必须将任务打包,像寄快递一样扔回 Bot 的主线程(A 世界)去执行。
请按照以下两步修改,即可完美破解:
第一步:捕获机器人的主循环 (Main Loop)
我们需要找个地方把 Bot 的主循环存下来。为了避免循环导入,建议在你的插件核心目录新建一个非常简单的变量文件,或者直接挂载在
container上。最稳妥的做法是修改你的 插件入口文件(通常是
main.py或__init__.py,里面定义了插件的主 Class):第二步:在 Quart 路由中进行跨线程投递
回到你刚才修改的
blueprints.social路由文件,使用 Python 官方专门用于跨线程异步通信的终极武器run_coroutine_threadsafe:💡 为什么这招一定管用?
run_coroutine_threadsafe的神奇之处在于,它跨越了线程的边界。当它执行时,social_service.trigger_analysis的所有代码(包括调用大模型)都会被转移到诞生大模型的那个原生态主循环中去跑。anyio在里面找上下文时,就能完美找到它需要的环境。改完这两处后重启,你会看到网页瞬间返回成功,而后台的 Bot 线程会稳稳地开始跑大模型进度,不会再有任何报错。
根据以上回复进行相应的代码修改后,确认问题解决:没有报错,且分析关系正常更新。
尚不确定该修改会对其他模块造成什么影响,所以放在这里仅供开发人员参考。