mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-08-10 08:21:54 +00:00
refactor(llm): remove dead provider helpers
This commit is contained in:
@@ -438,17 +438,6 @@ class BaseLLM(BaseModel, ABC):
|
|||||||
"""
|
"""
|
||||||
return DEFAULT_SUPPORTS_STOP_WORDS
|
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:
|
def _apply_stop_words(self, content: str) -> str:
|
||||||
"""Apply stop words to truncate response content.
|
"""Apply stop words to truncate response content.
|
||||||
|
|
||||||
|
|||||||
@@ -1385,120 +1385,6 @@ class AnthropicCompletion(BaseLLM):
|
|||||||
from_agent=from_agent,
|
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(
|
async def _ahandle_completion(
|
||||||
self,
|
self,
|
||||||
params: dict[str, Any],
|
params: dict[str, Any],
|
||||||
@@ -1830,90 +1716,6 @@ class AnthropicCompletion(BaseLLM):
|
|||||||
|
|
||||||
return full_response
|
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:
|
def supports_function_calling(self) -> bool:
|
||||||
"""Check if the model supports function calling."""
|
"""Check if the model supports function calling."""
|
||||||
return self.supports_tools
|
return self.supports_tools
|
||||||
|
|||||||
@@ -2146,17 +2146,6 @@ class BedrockCompletion(BaseLLM):
|
|||||||
)
|
)
|
||||||
return any(model_lower.startswith(m) for m in vision_models)
|
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:
|
def get_file_uploader(self) -> Any:
|
||||||
"""Get a Bedrock S3 file uploader using this LLM's AWS credentials.
|
"""Get a Bedrock S3 file uploader using this LLM's AWS credentials.
|
||||||
|
|
||||||
@@ -2185,49 +2174,6 @@ class BedrockCompletion(BaseLLM):
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
return None
|
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]:
|
def format_text_content(self, text: str) -> dict[str, Any]:
|
||||||
"""Format text as a Bedrock content block.
|
"""Format text as a Bedrock content block.
|
||||||
|
|
||||||
|
|||||||
@@ -1576,30 +1576,6 @@ def test_anthropic_dict_tool_use_blocks_execute_available_function():
|
|||||||
assert result == "found CrewAI"
|
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()
|
@pytest.mark.vcr()
|
||||||
def test_tool_search_discovers_and_calls_tool():
|
def test_tool_search_discovers_and_calls_tool():
|
||||||
"""Tool search should discover the right tool and return a tool_use block."""
|
"""Tool search should discover the right tool and return a tool_use block."""
|
||||||
|
|||||||
Reference in New Issue
Block a user