1
- from collections . abc import Iterable
1
+ import json
2
2
from typing import cast
3
3
4
4
from verifiers .types import ChatMessage , Messages
5
5
6
6
7
- def sanitize_object (obj : object ):
8
- """
9
- Recursively convert Pydantic/OpenAI SDK objects to plain Python types
10
- (dict/list/str/bool/number). Leaves primitives unchanged.
11
- """
12
- if isinstance (obj , (str , bytes , bytearray , int , float , bool )) or obj is None :
13
- return obj
14
- dump = getattr (obj , "model_dump" , None )
15
- if callable (dump ):
16
- obj = dump ()
17
- if isinstance (obj , dict ):
18
- return {k : sanitize_object (v ) for k , v in obj .items ()}
19
- # check if obj is iterable
20
- if isinstance (obj , Iterable ):
21
- return [sanitize_object (x ) for x in obj ]
22
- return obj
23
-
24
-
25
- def sanitize_chat_message (message : ChatMessage ):
26
- """
27
- input: chat message (dict or object)
28
- output: chat message (dict)
29
- """
30
- # TODO: debug for multimodal messages; content can get consumed as an iterator
31
- new_message = {}
32
- dump = getattr (message , "model_dump" , None )
33
- if callable (dump ):
34
- new_message = dump ()
35
- return new_message
36
- assert isinstance (message , dict )
37
- assert isinstance (new_message , dict )
38
- new_message ["role" ] = message ["role" ]
39
- if "content" in message and message ["content" ]:
40
- content = message ["content" ]
41
- if isinstance (content , str ):
42
- new_message ["content" ] = content
43
- else :
44
- new_message ["content" ] = []
45
- parts = list (content ) if not isinstance (content , str ) else content
46
- for c in parts :
47
- if isinstance (c , str ):
48
- new_message ["content" ].append (c )
49
- else :
50
- new_message ["content" ].append (sanitize_object (c ))
51
- if "tool_calls" in message and message ["tool_calls" ]:
52
- tool_calls = list (message ["tool_calls" ])
53
- new_message ["tool_calls" ] = [
54
- sanitize_object (tool_call ) for tool_call in tool_calls
55
- ]
56
- return new_message
57
-
58
-
59
- def sanitize_messages (messages : Messages ) -> str | list :
60
- """
61
- input: list of dicts or Pydantic models, or str
62
- output: list of dicts, or str
63
- """
64
- if isinstance (messages , str ):
65
- return messages
66
- sanitized_list = [sanitize_chat_message (m ) for m in list (messages )]
67
- return sanitized_list
68
-
69
-
70
- def content_to_printable (content : object ) -> str :
7
+ def message_to_printable (message : ChatMessage ) -> ChatMessage :
71
8
"""
72
- Render content to readable text, handling multimodal lists.
73
- - Text parts: return their text
74
- - Image-like parts: return "[image]"
75
- Falls back to str(content).
9
+ Removes image_url objects from message content.
76
10
"""
77
- print (str (content )[:100 ])
78
- if isinstance (content , str ):
79
- return content
80
- if isinstance (content , dict ):
81
- if "type" in content and content ["type" ] == "text" :
82
- return content ["text" ]
83
- if "type" in content and content ["type" ] in {
84
- "image_url" ,
85
- "input_image" ,
86
- "image" ,
87
- }:
88
- return "[image]"
89
- if isinstance (content , (list , tuple )):
90
- out = []
91
- for x in content :
92
- out .append (content_to_printable (x ))
93
- return "\n \n " .join (out )
94
- return str (content )
95
-
96
-
97
- def message_to_printable (message : ChatMessage ) -> ChatMessage :
98
11
new_message = {}
99
12
new_message ["role" ] = message ["role" ]
100
13
new_message ["content" ] = []
101
- if "tool_calls" in message and message [ "tool_calls" ] :
14
+ if "tool_calls" in message :
102
15
new_message ["tool_calls" ] = message ["tool_calls" ]
103
16
content = message .get ("content" )
104
17
if content is None :
@@ -121,6 +34,9 @@ def message_to_printable(message: ChatMessage) -> ChatMessage:
121
34
122
35
123
36
def messages_to_printable (messages : Messages ) -> Messages :
37
+ """
38
+ Removes image_url objects from messages.
39
+ """
124
40
if isinstance (messages , str ):
125
41
return messages
126
42
return [message_to_printable (m ) for m in messages ]
@@ -129,6 +45,8 @@ def messages_to_printable(messages: Messages) -> Messages:
129
45
def cleanup_message (message : ChatMessage ) -> ChatMessage :
130
46
new_message = {}
131
47
new_message ["role" ] = message ["role" ]
48
+ if "tool_calls" in message :
49
+ new_message ["tool_calls" ] = message ["tool_calls" ]
132
50
new_message ["content" ] = []
133
51
content = message .get ("content" )
134
52
if content is None :
@@ -161,3 +79,26 @@ def cleanup_messages(messages: Messages) -> Messages:
161
79
for m in messages :
162
80
new_messages .append (cleanup_message (m ))
163
81
return new_messages
82
+
83
+
84
+ def sanitize_tool_calls (messages : Messages ):
85
+ """
86
+ Sanitize tool calls from messages.
87
+ """
88
+ if not isinstance (messages , list ):
89
+ return messages
90
+ sanitized_messages = []
91
+ for m in messages :
92
+ if "tool_calls" in m :
93
+ new_m = {
94
+ "role" : m ["role" ],
95
+ "content" : m .get ("content" , "" ),
96
+ "tool_calls" : [
97
+ json .dumps (tc .model_dump ()) # type: ignore
98
+ for tc in m .get ("tool_calls" , [])
99
+ ],
100
+ }
101
+ sanitized_messages .append (new_m )
102
+ else :
103
+ sanitized_messages .append (m )
104
+ return sanitized_messages
0 commit comments