Skip to content

[Bug] 分析社交关系报错“筛选模型调用失败: Timeout context manager should be used inside a task” #82

@jackson7789

Description

@jackson7789

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

  1. 关系正确更新(如果有)
  2. 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 插件系统中,你的程序实际上运行在两个平行的“世界”里:

  1. Bot 主线程(A 世界): 负责连接 QQ/微信,这里运行着一个主异步事件循环。框架的 LLM Provider(模型客户端)就是在这个世界里出生并绑定它的。
  2. 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 线程会稳稳地开始跑大模型进度,不会再有任何报错。

根据以上回复进行相应的代码修改后,确认问题解决:没有报错,且分析关系正常更新。
尚不确定该修改会对其他模块造成什么影响,所以放在这里仅供开发人员参考。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions