From 5007af866efe5dfd320a468baa2f300f084041cd Mon Sep 17 00:00:00 2001 From: lorenzejay Date: Tue, 4 Aug 2026 09:52:05 -0700 Subject: [PATCH] refactor(llm): remove dead provider helpers --- lib/crewai/src/crewai/llms/base_llm.py | 11 - .../llms/providers/anthropic/completion.py | 198 ------------------ .../llms/providers/bedrock/completion.py | 54 ----- .../tests/llms/anthropic/test_anthropic.py | 24 --- 4 files changed, 287 deletions(-) diff --git a/lib/crewai/src/crewai/llms/base_llm.py b/lib/crewai/src/crewai/llms/base_llm.py index a71126f58..b75cf2c8a 100644 --- a/lib/crewai/src/crewai/llms/base_llm.py +++ b/lib/crewai/src/crewai/llms/base_llm.py @@ -438,17 +438,6 @@ class BaseLLM(BaseModel, ABC): """ return DEFAULT_SUPPORTS_STOP_WORDS - def _supports_stop_words_implementation(self) -> bool: - """Check if stop words are configured for this LLM instance. - - Native providers can override supports_stop_words() to return this value - to ensure consistent behavior based on whether stop words are actually configured. - - Returns: - True if stop words are configured and can be applied - """ - return bool(self.stop_sequences) - def _apply_stop_words(self, content: str) -> str: """Apply stop words to truncate response content. diff --git a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py index 7deecbf60..ec4a019f5 100644 --- a/lib/crewai/src/crewai/llms/providers/anthropic/completion.py +++ b/lib/crewai/src/crewai/llms/providers/anthropic/completion.py @@ -1385,120 +1385,6 @@ class AnthropicCompletion(BaseLLM): from_agent=from_agent, ) - # TODO: we drop this - def _handle_tool_use_conversation( - self, - initial_response: Message | BetaMessage, - tool_uses: list[_AnthropicToolUseBlock], - params: dict[str, Any], - available_functions: dict[str, Any], - from_task: Any | None = None, - from_agent: Any | None = None, - ) -> str: - """Handle the complete tool use conversation flow. - - This implements the proper Anthropic tool use pattern: - 1. Claude requests tool use - 2. We execute the tools - 3. We send tool results back to Claude - 4. Claude processes results and generates final response - """ - tool_results = self._execute_tools_and_collect_results( - tool_uses, available_functions, from_task, from_agent - ) - - follow_up_params = params.copy() - - assistant_content: list[ - ThinkingBlock | ToolUseBlock | TextBlock | dict[str, Any] - ] = [] - for block in initial_response.content: - thinking_block = self._extract_thinking_block(block) - if thinking_block: - assistant_content.append(thinking_block) - elif _is_tool_use_block(block): - assistant_content.append( - { - "type": "tool_use", - "id": _tool_use_id(block), - "name": _tool_use_name(block), - "input": _tool_use_input(block), - } - ) - elif hasattr(block, "text"): - assistant_content.append({"type": "text", "text": block.text}) - - assistant_message = {"role": "assistant", "content": assistant_content} - - user_message = {"role": "user", "content": tool_results} - - follow_up_params["messages"] = params["messages"] + [ - assistant_message, - user_message, - ] - - try: - final_response: Message = self._get_sync_client().messages.create( - **follow_up_params - ) - - follow_up_usage = self._extract_anthropic_token_usage(final_response) - self._track_token_usage_internal(follow_up_usage) - - final_content = "" - thinking_blocks: list[ThinkingBlock] = [] - - if final_response.content: - for content_block in final_response.content: - if hasattr(content_block, "text"): - final_content += content_block.text - else: - thinking_block = self._extract_thinking_block(content_block) - if thinking_block: - thinking_blocks.append(cast(ThinkingBlock, thinking_block)) - - if thinking_blocks: - self._previous_thinking_blocks = thinking_blocks - - final_content = self._apply_stop_words(final_content) - - finish_reason, final_response_id = self._extract_finish_reason_and_id( - final_response - ) - - self._emit_call_completed_event( - response=final_content, - call_type=LLMCallType.LLM_CALL, - from_task=from_task, - from_agent=from_agent, - messages=follow_up_params["messages"], - usage=follow_up_usage, - finish_reason=finish_reason, - response_id=final_response_id, - ) - - total_usage = { - "input_tokens": follow_up_usage.get("input_tokens", 0), - "output_tokens": follow_up_usage.get("output_tokens", 0), - "total_tokens": follow_up_usage.get("total_tokens", 0), - } - - if total_usage.get("total_tokens", 0) > 0: - logging.info(f"Anthropic API tool conversation usage: {total_usage}") - - return final_content - - except Exception as e: - if is_context_length_exceeded(e): - logging.error(f"Context window exceeded in tool follow-up: {e}") - raise LLMContextLengthExceededError(str(e)) from e - - logging.error(f"Tool follow-up conversation failed: {e}") - # Fallback to first tool result when follow-up fails - if tool_results: - return cast(str, tool_results[0]["content"]) - raise e - async def _ahandle_completion( self, params: dict[str, Any], @@ -1830,90 +1716,6 @@ class AnthropicCompletion(BaseLLM): return full_response - async def _ahandle_tool_use_conversation( - self, - initial_response: Message | BetaMessage, - tool_uses: list[_AnthropicToolUseBlock], - params: dict[str, Any], - available_functions: dict[str, Any], - from_task: Any | None = None, - from_agent: Any | None = None, - ) -> str: - """Handle the complete async tool use conversation flow. - - This implements the proper Anthropic tool use pattern: - 1. Claude requests tool use - 2. We execute the tools - 3. We send tool results back to Claude - 4. Claude processes results and generates final response - """ - tool_results = self._execute_tools_and_collect_results( - tool_uses, available_functions, from_task, from_agent - ) - - follow_up_params = params.copy() - - assistant_message = {"role": "assistant", "content": initial_response.content} - - user_message = {"role": "user", "content": tool_results} - - follow_up_params["messages"] = params["messages"] + [ - assistant_message, - user_message, - ] - - try: - final_response: Message = await self._get_async_client().messages.create( - **follow_up_params - ) - - follow_up_usage = self._extract_anthropic_token_usage(final_response) - self._track_token_usage_internal(follow_up_usage) - - final_content = "" - if final_response.content: - for content_block in final_response.content: - if hasattr(content_block, "text"): - final_content += content_block.text - - final_content = self._apply_stop_words(final_content) - - finish_reason, final_response_id = self._extract_finish_reason_and_id( - final_response - ) - - self._emit_call_completed_event( - response=final_content, - call_type=LLMCallType.LLM_CALL, - from_task=from_task, - from_agent=from_agent, - messages=follow_up_params["messages"], - usage=follow_up_usage, - finish_reason=finish_reason, - response_id=final_response_id, - ) - - total_usage = { - "input_tokens": follow_up_usage.get("input_tokens", 0), - "output_tokens": follow_up_usage.get("output_tokens", 0), - "total_tokens": follow_up_usage.get("total_tokens", 0), - } - - if total_usage.get("total_tokens", 0) > 0: - logging.info(f"Anthropic API tool conversation usage: {total_usage}") - - return final_content - - except Exception as e: - if is_context_length_exceeded(e): - logging.error(f"Context window exceeded in tool follow-up: {e}") - raise LLMContextLengthExceededError(str(e)) from e - - logging.error(f"Tool follow-up conversation failed: {e}") - if tool_results: - return cast(str, tool_results[0]["content"]) - raise e - def supports_function_calling(self) -> bool: """Check if the model supports function calling.""" return self.supports_tools diff --git a/lib/crewai/src/crewai/llms/providers/bedrock/completion.py b/lib/crewai/src/crewai/llms/providers/bedrock/completion.py index d10fc2ba8..c2938b017 100644 --- a/lib/crewai/src/crewai/llms/providers/bedrock/completion.py +++ b/lib/crewai/src/crewai/llms/providers/bedrock/completion.py @@ -2146,17 +2146,6 @@ class BedrockCompletion(BaseLLM): ) return any(model_lower.startswith(m) for m in vision_models) - def _is_nova_model(self) -> bool: - """Check if the model is an Amazon Nova model. - - Only Nova models support S3 links for multimedia. - - Returns: - True if the model is a Nova model. - """ - model_lower = self.model.lower() - return "amazon.nova-" in model_lower - def get_file_uploader(self) -> Any: """Get a Bedrock S3 file uploader using this LLM's AWS credentials. @@ -2185,49 +2174,6 @@ class BedrockCompletion(BaseLLM): except ImportError: return None - def _get_document_format(self, content_type: str) -> str | None: - """Map content type to Bedrock document format. - - Args: - content_type: MIME type of the document. - - Returns: - Bedrock format string or None if unsupported. - """ - format_map = { - "application/pdf": "pdf", - "text/csv": "csv", - "text/plain": "txt", - "text/markdown": "md", - "text/html": "html", - "application/msword": "doc", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx", - "application/vnd.ms-excel": "xls", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx", - } - return format_map.get(content_type) - - def _get_video_format(self, content_type: str) -> str | None: - """Map content type to Bedrock video format. - - Args: - content_type: MIME type of the video. - - Returns: - Bedrock format string or None if unsupported. - """ - format_map = { - "video/mp4": "mp4", - "video/quicktime": "mov", - "video/x-matroska": "mkv", - "video/webm": "webm", - "video/x-flv": "flv", - "video/mpeg": "mpeg", - "video/x-ms-wmv": "wmv", - "video/3gpp": "three_gp", - } - return format_map.get(content_type) - def format_text_content(self, text: str) -> dict[str, Any]: """Format text as a Bedrock content block. diff --git a/lib/crewai/tests/llms/anthropic/test_anthropic.py b/lib/crewai/tests/llms/anthropic/test_anthropic.py index fd21d3b8a..36dfacbdd 100644 --- a/lib/crewai/tests/llms/anthropic/test_anthropic.py +++ b/lib/crewai/tests/llms/anthropic/test_anthropic.py @@ -1576,30 +1576,6 @@ def test_anthropic_dict_tool_use_blocks_execute_available_function(): assert result == "found CrewAI" -def test_anthropic_dict_tool_use_blocks_work_in_follow_up_conversation(): - from crewai.llms.providers.anthropic.completion import AnthropicCompletion - - llm = AnthropicCompletion(model="claude-fable-5") - initial_response = _dict_tool_use_response() - final_response = MagicMock() - final_response.content = [types.SimpleNamespace(text="Final answer")] - final_response.usage = MagicMock(input_tokens=4, output_tokens=3) - final_response.stop_reason = "end_turn" - final_response.id = "msg_final" - mock_client = MagicMock() - mock_client.messages.create.return_value = final_response - llm._client = mock_client - - result = llm._handle_tool_use_conversation( - initial_response, - initial_response.content, - params={"messages": []}, - available_functions={"search_web": lambda query: f"found {query}"}, - ) - - assert result == "Final answer" - - @pytest.mark.vcr() def test_tool_search_discovers_and_calls_tool(): """Tool search should discover the right tool and return a tool_use block."""