快速结论:当你在 CrewAI 中使用原生 provider(openai / anthropic / bedrock / gemini / azure)或 LiteLLM fallback 的异步路径 acall()/kickoff_async,并注册了 before_llm_call 拦截钩子时,钩子不会被调用,导致本应被阻断的请求仍然发出并被计费。优先排查是否走了异步调用路径,以及钩子是否在同步 call() 路径上正常生效。
适用环境:Issue 在 main 分支提交 ebe0082 上复现,涉及 CrewAI 原生 provider(openai、anthropic、bedrock、gemini、azure)以及 crewai/llm.py 中的 LiteLLM fallback(需 LLM(model=..., is_litellm=True) 才能进入)。复现脚本使用 stub client,无需真实网络。Issue 未确认具体的 Python、CUDA、PyTorch、显卡或依赖版本。
最快修复方案:暂无确认的一步修复方案。Issue 中对应的修复 PR(#6740 覆盖五个原生 provider 的 before 路径,#6737 覆盖 after 路径)在关闭时仍在评审中,LiteLLM fallback 的漏洞另需单独修复。
注意事项:该问题只影响异步 acall() 路径,同步 call() 的拦截仍正常。仅断言返回值的测试无法区分“钩子运行并放行”和“钩子从未运行”,因此验证时应观察请求是否真正发出。LiteLLM fallback 并不像早期描述的那样“两条路径都有保护”,它同样存在漏洞。
问题场景
在 CrewAI 中通过 register_before_llm_call_hook 注册拦截钩子(如 PII 门禁、预算上限、策略检查、提示注入过滤器),钩子返回 False 或通过 reducer 抛出 HookAborted,期望阻止 LLM 请求发出。在同步 call() 路径下拦截正常,但改用异步 acall() 或 kickoff_async 时,请求仍然被发送到 provider 并产生费用。该问题影响五个原生 provider(openai、anthropic、bedrock、gemini、azure),以及使用 is_litellm=True 的 LiteLLM fallback。
报错原文
sync -> LLM call blocked by before_llm_call hook
sync request issued: no
async -> LEAKED
async request issued: ['async']
ValueError: LLM call blocked by before_llm_call hook
原因分析
最可能的原因是:五个原生 provider 只在同步 call() 路径调用了 _invoke_before_llm_call_hooks,而在异步 acall() 路径中完全没有调用点。Issue 中给出的覆盖表显示:openai completion :465、anthropic completion :377、bedrock completion :380、gemini completion :316、azure completion :524 均在同步路径有钩子调用,异步路径缺失。
此外,LiteLLM fallback 位于 crewai/llm.py(而非仅定义 helper 的 llms/base_llm.py):其 call() 在 :1876、:1904 对两个钩子家族都有保护,但 acall() 在 :1959 对两者都没有保护。因此 fallback 并非安全路径。
由于 _invoke_before_llm_call_hooks 在 from_agent is not None 时会立即返回 True,修复时从各 provider 的 acall() 调用其自身同步路径的 guard 不会造成重复派发。
环境排查
- 确认调用的入口:是同步
call()还是异步acall()/kickoff_async,异步路径是问题触发条件。 - 确认使用的 provider:是否为 openai、anthropic、bedrock、gemini、azure 之一,或是否通过
LLM(model=..., is_litellm=True)走 LiteLLM fallback。 - 确认 CrewAI 代码版本是否包含对应修复(Issue 基于
main提交ebe0082复现)。 - 确认钩子类型:是
before_llm_call(本 Issue 主体)还是after_llm_call(相关的 #6736/#6737)。 - Issue 未确认 Python、CUDA、PyTorch、显卡及依赖版本,这些不作为必要排查项。
解决步骤
- 先区分同步与异步路径。用最小复现脚本(见 Issue 正文,替换 provider client 为记录请求是否发出的 stub),分别执行
llm.call(msgs)和asyncio.run(llm.acall(msgs)),观察ISSUED记录与返回内容。 - 确认同步路径已被拦截:
call()应抛出ValueError: LLM call blocked by before_llm_call hook,且request issued: no。 - 确认异步路径是否泄漏:
acall()返回了本应被拦截的响应,且request issued: ['async'],说明钩子未运行、请求已发出。 - 若使用原生 provider,等待并合入 #6740 类修复:在各 provider 的
acall()中补上与同步路径相同的_invoke_before_llm_call_hooks调用。Issue 指出 gemini 需特别处理,其格式化 payload 是Content对象而非 dict,需保留同步路径的_convert_contents_to_dict行,否则钩子收到的 payload 结构与同步路径不一致。 - 若使用 LiteLLM fallback(
is_litellm=True),需单独修复crewai/llm.py中acall()缺失的两处钩子调用;该改动与 provider 侧修复属不同文件、不同故障,不会随 #6737/#6740 一起被覆盖。 - 在修复合入前,如果拦截是安全关键(如 PII 门禁、预算上限),可优先尝试在异步流程外层自行增加请求前校验,避免依赖异步路径上的钩子。
验证方法
修复后,同步与异步路径都应拒绝发出请求:
sync -> LLM call blocked by before_llm_call hook
sync request issued: no
async -> LLM call blocked by before_llm_call hook
async request issued: no
验证时应以“请求是否离开进程”为观测点(stub 的 create/acreate 是否被调用),而不是仅断言返回字符串;否则无法区分“钩子运行并放行”与“钩子从未运行”。如需在真实环境验证,可同时注册一个只观察的 before 钩子与一个重写响应的 after 钩子,确认异步路径的计数由 before:0 变为 before:1。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[Question]: Docs Generator download returns code 102 “Document not found” for DOCX, PDF and HTML](https://www.chat-gpts.plus/wp-content/uploads/2026/10/16506-539c2ddf-768x403.jpg)
![[Bug]: After receiving mineru, the error "FAILED TO PARSE TABLE" occurred during the parsing process.](https://www.chat-gpts.plus/wp-content/uploads/2026/10/16114-b8de03ab-768x403.jpg)
