在调用多模型时,需要使用ThreadPoolExecutor显式开启并发执行,不能采用串行invoke;配置Hermes时,应设置HERMES_MAX_CONCURRENT并启用asyncio.Semaphore;为避免返回结果因顺序不同而被覆盖,响应中必须携带task_id键值。

如果你希望多个大模型(如百川、Hermes Agent、Qwen等)同时发起请求,避免串行调用造成整体耗时明显增加,就必须主动开启并发机制,而不能依赖默认的单线程执行方式。
确认模型调用是否原生支持并发
并不是所有封装好的 SDK 都会默认开启并发能力。例如,requests 库本质上是同步阻塞的,如果直接放在 for 循环里调用,就会按照顺序逐个执行;而某些高级封装,比如 langchain 的 AsyncLLMChain,虽然内部支持异步调用,但也需要你主动使用 ainvoke,而不是继续用 invoke。
如果你当前代码里的每个模型调用都写成 result = model.invoke(prompt),那么实际执行方式就是串行调用——这一点必须优先调整。
Python 中用 ThreadPoolExecutor 开启多模型并发
这是最通用、兼容性也最强的方案,适合各种基于 requests 或 httpx 封装的模型 API 调用场景。
方法一:为每个模型分别定义独立调用函数
第一步:分别编写三个函数,各自封装一个模型的完整请求流程(包括 headers、data、URL、异常处理),确保函数之间相互独立,避免共享状态。
第二步:将三个函数名与对应 prompt 组成元组列表,例如:[(call_baichuan, "解释量子计算"), (call_hermes, "生成测试用例"), (call_qwen, "写一封辞职信")]。
第三步:使用 ThreadPoolExecutor(max_workers=3) 提交任务,通过 executor.submit(func, *args) 分别触发调用,不建议使用 map——因为各函数的参数签名可能并不一致。
【max_workers 必须与模型数量保持一致,否则部分模型请求会进入排队状态,导致启动延迟】
方法二:统一接口 + 模型标识参数
也可以把所有模型请求逻辑统一收敛到一个函数中,通过 model_name 参数区分不同模型行为,再传入 prompts 列表和对应模型名列表,借助 executor.map 实现并发执行。这种写法更适合模型结构高度相似、调用参数一致的场景。
控制 Hermes Agent 的并发安全边界
如果你调用的是 Hermes Agent,它内部虽然预置了并发控制层,但仍然需要手动开启,否则依旧会走串行的沙箱初始化流程。
打开 environments/agent_loop.py,找到 _creation_locks_lock = threading.Lock() 所在的位置。
确认该文件中已经存在 asyncio.Semaphore(max_concurrent_requests) 的初始化语句,并且它的值来自配置项,而不是被硬编码为 1。
在执行批量请求前,务必显式设置环境变量 HERMES_MAX_CONCURRENT=5,或者在配置文件中修改 max_concurrent_requests: 5,否则信号量配置不会真正生效。
如果这一步遗漏,所有 Agent 请求都可能被强制串行执行,即使外层已经使用线程池并发,也无法提升实际请求效率。
避免结果乱序与数据污染
并发请求的返回顺序并不等于提交顺序,尤其是在不同模型响应时延差异较大时(例如百川较快、Hermes 较慢、Qwen 处于中间)。
不要默认 results[0] 一定对应第一个 prompt——正确做法是在每个调用函数内部,把 prompt 或 task_id 一并写入返回值,例如 {"task_id": "qwen_1", "response": "..."} 。
建议使用字典而不是列表收集结果,并以 task_id 作为 key,这样可以从根本上避免结果顺序错位的问题。
如果多个并发任务共用全局变量(例如一个 shared_dict)来接收结果,却没有加锁或使用原子操作,【很容易发生键值覆盖,最终造成部分模型输出丢失】。
