Repository navigation
fix: fall back to sync call when summarizing with an LLM without acall - #7860
jeon-jihyeon wants to merge 2 commits into
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (2)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughMessage summarization uses ChangesMessage summarization
Suggested reviewers: Priority: ➖ Normal Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to Sync-only custom LLMs can use message summarization without an identified merge-blocking risk. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change restores compatibility while preserving existing asynchronous behavior. However, synchronous calls now run concurrently against the same client and callbacks, which may affect extensions with mutable state. No concrete security exploit was established. Retained concerns
Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
VANDRANKI
left a comment
There was a problem hiding this comment.
Community review. This does not clear the merge gate.
I checked out the branch and ran tests/utilities/test_agent_utils.py -k summar with --noconftest -o addopts="". On the PR: 30 passed, 2 failed (TestParallelSummarizationVCR::test_parallel_summarize_openai and ..._preserves_files). On base, the same 2 fail (29 passed), so those are environment or cassette issues, not this change. I also put the PR's new test on the base source: test_falls_back_to_call_when_acall_not_implemented fails there and passes on the PR, so it is a real regression test.
I confirmed the cause: BaseLLM.acall in src/crewai/llms/base_llm.py is a bare raise NotImplementedError, so a custom LLM that only implements call fails in _summarize_one. The fallback via asyncio.to_thread(llm.call, ...) is the right shape, and to_thread copies the context vars, so callbacks that read them still work.
Things to consider:
-
The same gap exists in other places that call
acalldirectly. From a grep:agent_utils.pyline 633 (aget_llm_response) andutilities/converter.pylines 128 and 133 both callself.llm.acall(...)with no fallback. A custom sync-only LLM will still fail there. If the intent is "sync-only custom LLMs work", a small shared helper (tryacall, onNotImplementedErrorfall back to a thread) would cover all three. -
except NotImplementedErroris broad. If a provider's realacallraisesNotImplementedErrorfrom somewhere deeper (an unsupported feature in a subclass, for example) after it has already sent the request, the fallback sends a second request. That doubles cost and could repeat side effects. I did not find an instance of that in the repo, so it is a hypothetical, but checking thattype(llm).acall is BaseLLM.acallbefore falling back would make the intent exact. -
Chunks are summarized concurrently via
asyncio.gather, so now N threads call the samellm.callinstance at once. Many sync LLM wrappers keep per-instance state (token counters, last-response fields). I did not check which ones do. A short comment, or a bound on parallelism, would help. -
In the test,
SyncOnlyLLM.callignorescallbacks. Passing a callback and asserting it is invoked once per chunk would pin thecallbacks=self.callbacksargument on the fallback path.
|
Thanks for the thorough review, and for checking the new test against base!
|
Related issue
Fixes #7859
Summary
#7815 made summarization always go through
await llm.acall(...).BaseLLM.acallraisesNotImplementedErrorby default, so custom LLMs that only implementcallnow crash when the context window overflows.This catches
NotImplementedErrorfromacallin_summarize_oneand runsllm.callviaasyncio.to_threadinstead. The fallback sits inside the existingtry, so the context-length retry still works. The parallel summarization from #7815 is unchanged, and the Bedrock provider already uses the sameto_threadpattern.Verification
Added
TestParallelSummarization::test_falls_back_to_call_when_acall_not_implemented, which summarizes two chunks with acall-onlyBaseLLM.The 3 failures are the Anthropic, Gemini, and Azure integration tests. They fail the same way without this change, because I don't have those optional extras installed locally.
ruff check,ruff format --check, andmypyonagent_utils.pyall pass.Repro script from #7859:
Additional context
This PR should get the
llm-generatedlabel per CONTRIBUTING, but I can't add labels as an outside contributor. I used Claude Code to help find the bug and draft the fix. I reviewed the diff and ran the repro and tests myself.Note
Low Risk
Localized change to context-overflow summarization with class-level detection to avoid double-calling LLMs that implement async explicitly.
Overview
Fixes context-window summarization crashing for custom
BaseLLMimplementations that only overridecall, after parallel summarization started always usingacall.In
_summarize_one, the code now checks whether the LLM class still uses the defaultBaseLLM.acall. If so, it runsllm.callviaasyncio.to_thread(with the same callbacks and summary prompt); otherwise it keepsawait llm.acall. Parallel chunk summarization and context-length retry behavior are unchanged.Tests cover the sync-only path (two chunks, callbacks forwarded) and confirm that an LLM with a real overridden
acallthat raises does not silently fall back tocall.Reviewed by Cursor Bugbot for commit 5f932b9. Bugbot is set up for automated code reviews on this repo. Configure here.