mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-26 17:25:10 +00:00
* fix: bump litellm to >=1.83.0 to address CVE-2026-35030 Bump litellm from <=1.82.6 to >=1.83.0 to fix JWT auth bypass via OIDC cache key collision (CVE-2026-35030). Also widen devtools openai pin from ~=1.83.0 to >=1.83.0,<3 to resolve the version conflict (litellm 1.83.0 requires openai>=2.8.0). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve mypy errors from litellm bump - Remove unused type: ignore[import-untyped] on instructor import - Remove all unused type: ignore[union-attr] comments (litellm types fixed) - Add hasattr guard for tool_call.function — new litellm adds ChatCompletionMessageCustomToolCall to the union which lacks .function * fix: tighten litellm pin to ~=1.83.0 (patch-only bumps) >=1.83.0,<2 is too wide — litellm has had breaking changes between minors. ~=1.83.0 means >=1.83.0,<1.84.0 — gets CVE patches but won't pull in breaking minor releases. * ci: bump uv from 0.8.4 to 0.11.3 * fix: resolve mypy errors in openai completion from 2.x type changes Use isinstance checks with concrete openai response types instead of string comparisons for proper type narrowing. Update code interpreter handling for outputs/OutputImage API changes in openai 2.x. * fix: pre-cache tiktoken encoding before VCR intercepts requests --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Alex <alex@crewai.com> Co-authored-by: Greyson LaLonde <greyson@crewai.com>
204 lines
5.8 KiB
Python
204 lines
5.8 KiB
Python
"""Tests for Anthropic async completion functionality."""
|
|
import json
|
|
import logging
|
|
|
|
import pytest
|
|
import tiktoken
|
|
from pydantic import BaseModel
|
|
|
|
from crewai.llm import LLM
|
|
|
|
# Pre-cache tiktoken encoding so VCR doesn't intercept the download request
|
|
tiktoken.get_encoding("cl100k_base")
|
|
from crewai.llms.providers.anthropic.completion import AnthropicCompletion
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_basic_call():
|
|
"""Test basic async call with Anthropic."""
|
|
llm = LLM(model="anthropic/claude-sonnet-4-0")
|
|
|
|
result = await llm.acall("Say hello")
|
|
|
|
assert result is not None
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_with_temperature():
|
|
"""Test async call with temperature parameter."""
|
|
llm = LLM(model="anthropic/claude-sonnet-4-0", temperature=0.1)
|
|
|
|
result = await llm.acall("Say the word 'test' once")
|
|
|
|
assert result is not None
|
|
assert isinstance(result, str)
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_with_max_tokens():
|
|
"""Test async call with max_tokens parameter."""
|
|
llm = LLM(model="anthropic/claude-sonnet-4-0", max_tokens=10)
|
|
|
|
result = await llm.acall("Write a very long story about a dragon.")
|
|
|
|
assert result is not None
|
|
assert isinstance(result, str)
|
|
encoder = tiktoken.get_encoding("cl100k_base")
|
|
token_count = len(encoder.encode(result))
|
|
assert token_count <= 10
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_with_system_message():
|
|
"""Test async call with system message."""
|
|
llm = LLM(model="anthropic/claude-sonnet-4-0")
|
|
|
|
messages = [
|
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
{"role": "user", "content": "What is 2+2?"}
|
|
]
|
|
|
|
result = await llm.acall(messages)
|
|
|
|
assert result is not None
|
|
assert isinstance(result, str)
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_conversation():
|
|
"""Test async call with conversation history."""
|
|
llm = LLM(model="anthropic/claude-sonnet-4-0")
|
|
|
|
messages = [
|
|
{"role": "user", "content": "My name is Alice."},
|
|
{"role": "assistant", "content": "Hello Alice! Nice to meet you."},
|
|
{"role": "user", "content": "What is my name?"}
|
|
]
|
|
|
|
result = await llm.acall(messages)
|
|
|
|
assert result is not None
|
|
assert isinstance(result, str)
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_stop_sequences():
|
|
"""Test async call with stop sequences."""
|
|
llm = LLM(
|
|
model="anthropic/claude-sonnet-4-0",
|
|
stop_sequences=["END", "STOP"]
|
|
)
|
|
|
|
result = await llm.acall("Count from 1 to 10")
|
|
|
|
assert result is not None
|
|
assert isinstance(result, str)
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_multiple_calls():
|
|
"""Test making multiple async calls in sequence."""
|
|
llm = LLM(model="anthropic/claude-sonnet-4-0")
|
|
|
|
result1 = await llm.acall("What is 1+1?")
|
|
result2 = await llm.acall("What is 2+2?")
|
|
|
|
assert result1 is not None
|
|
assert result2 is not None
|
|
assert isinstance(result1, str)
|
|
assert isinstance(result2, str)
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_with_response_format_none():
|
|
"""Test async call with response_format set to None."""
|
|
llm = LLM(model="anthropic/claude-sonnet-4-0", response_format=None)
|
|
|
|
result = await llm.acall("Tell me a short fact")
|
|
|
|
assert result is not None
|
|
assert isinstance(result, str)
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_with_response_format_json():
|
|
"""Test async call with JSON response format."""
|
|
llm = LLM(model="anthropic/claude-sonnet-4-0", response_format={"type": "json_object"})
|
|
|
|
result = await llm.acall("Return a JSON object devoid of ```json{x}```, where x is the json object, with a 'greeting' field")
|
|
assert isinstance(result, str)
|
|
deserialized_result = json.loads(result)
|
|
assert isinstance(deserialized_result, dict)
|
|
assert isinstance(deserialized_result["greeting"], str)
|
|
|
|
|
|
class GreetingResponse(BaseModel):
|
|
"""Response model for greeting test."""
|
|
|
|
greeting: str
|
|
language: str
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_with_response_model():
|
|
"""Test async call with Pydantic response_model for structured output."""
|
|
llm = LLM(model="anthropic/claude-sonnet-4-0")
|
|
|
|
result = await llm.acall(
|
|
"Say hello in French",
|
|
response_model=GreetingResponse
|
|
)
|
|
# When response_model is provided, the result is already a parsed Pydantic model instance
|
|
assert isinstance(result, GreetingResponse)
|
|
assert isinstance(result.greeting, str)
|
|
assert isinstance(result.language, str)
|
|
|
|
|
|
@pytest.mark.vcr()
|
|
@pytest.mark.asyncio
|
|
async def test_anthropic_async_with_tools():
|
|
"""Test async call with tools."""
|
|
llm = AnthropicCompletion(model="claude-sonnet-4-0")
|
|
|
|
tools = [
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_weather",
|
|
"description": "Get the current weather for a location",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city and state, e.g. San Francisco, CA"
|
|
}
|
|
},
|
|
"required": ["location"]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
result = await llm.acall(
|
|
"What's the weather in San Francisco?",
|
|
tools=tools
|
|
)
|
|
logging.debug("result: %s", result)
|
|
|
|
assert result is not None
|
|
# Result can be either a string or a list of tool calls (native tool calling)
|
|
assert isinstance(result, (str, list))
|