Compare commits

...

3 Commits

Author SHA1 Message Date
Devin AI
36e064b240 Address PR feedback: Enhance type safety and add tests for multimodal validation
Co-Authored-By: Joe Moura <joao@crewai.com>
2025-03-11 07:33:05 +00:00
Devin AI
942d65b1de Fix import sorting in test file
Co-Authored-By: Joe Moura <joao@crewai.com>
2025-03-11 07:30:51 +00:00
Devin AI
447a994ef8 Fix #2327: Update LLMCallStartedEvent to support multimodal content
Co-Authored-By: Joe Moura <joao@crewai.com>
2025-03-11 07:26:48 +00:00
2 changed files with 126 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
from enum import Enum
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Literal, Optional, Union
from crewai.utilities.events.base_events import CrewEvent
@@ -11,11 +11,47 @@ class LLMCallType(Enum):
LLM_CALL = "llm_call"
class ContentType(str, Enum):
"""Types of content in multimodal messages"""
TEXT = "text"
IMAGE_URL = "image_url"
class LLMCallStartedEvent(CrewEvent):
"""Event emitted when a LLM call starts"""
type: str = "llm_call_started"
messages: Union[str, List[Dict[str, str]]]
messages: Union[
str,
List[Union[
str,
Dict[str, Union[
str,
List[Dict[str, Union[
str,
Dict[Literal["url"], str]
]]]
]]
]]
]
"""
Supports both string messages and structured messages including multimodal content.
Formats supported:
1. Simple string: "This is a message"
2. List of message objects: [{"role": "user", "content": "Hello"}]
3. Mixed list with strings and objects: ["Simple message", {"role": "user", "content": "Hello"}]
4. Multimodal format:
{
'role': str,
'content': List[
Union[
Dict[Literal["type", "text"], str],
Dict[Literal["type", "image_url"], Dict[str, str]]
]
]
}
"""
tools: Optional[List[dict]] = None
callbacks: Optional[List[Any]] = None
available_functions: Optional[Dict[str, Any]] = None

View File

@@ -0,0 +1,88 @@
from typing import Any, Dict, List, Union
import pytest
from pydantic import ValidationError
from crewai.utilities.events.llm_events import LLMCallStartedEvent
def test_llm_call_started_event_with_multimodal_content():
"""Test that LLMCallStartedEvent properly handles multimodal content."""
# Create a multimodal message structure
multimodal_message = {
'role': 'user',
'content': [
{'type': 'text', 'text': 'Please analyze this image'},
{
'type': 'image_url',
'image_url': {
'url': 'https://example.com/test-image.jpg',
},
},
],
}
# This should not raise a ValidationError
event = LLMCallStartedEvent(messages=[multimodal_message])
# Verify the event was created correctly
assert event.messages[0]['role'] == 'user'
assert isinstance(event.messages[0]['content'], list)
assert len(event.messages[0]['content']) == 2
assert event.messages[0]['content'][0]['type'] == 'text'
assert event.messages[0]['content'][1]['type'] == 'image_url'
def test_llm_call_started_event_with_string_message():
"""Test that LLMCallStartedEvent still works with string messages."""
# Create a simple string message
message = "This is a test message"
# This should not raise a ValidationError
event = LLMCallStartedEvent(messages=message)
# Verify the event was created correctly
assert event.messages == message
def test_llm_call_started_event_with_standard_messages():
"""Test that LLMCallStartedEvent still works with standard message format."""
# Create standard messages
messages = [
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello, how are you?"}
]
# This should not raise a ValidationError
event = LLMCallStartedEvent(messages=messages)
# Verify the event was created correctly
assert len(event.messages) == 2
assert event.messages[0]['role'] == 'system'
assert event.messages[0]['content'] == 'You are a helpful assistant'
assert event.messages[1]['role'] == 'user'
assert event.messages[1]['content'] == 'Hello, how are you?'
def test_llm_call_started_event_with_mixed_content():
"""Test that LLMCallStartedEvent handles mixed content types."""
mixed_messages = [
"Simple string message",
{
'role': 'user',
'content': [
{'type': 'text', 'text': 'With image'},
{'type': 'image_url', 'image_url': {'url': 'https://example.com/image.jpg'}},
],
}
]
# This should not raise a ValidationError
event = LLMCallStartedEvent(messages=mixed_messages)
# Verify the event was created correctly
assert isinstance(event.messages, list)
assert isinstance(event.messages[0], str)
assert isinstance(event.messages[1], dict)
assert event.messages[1]['role'] == 'user'
assert isinstance(event.messages[1]['content'], list)