@@ -1077,3 +1077,189 @@ def __init__(self):
10771077 assert props ["$ai_web_search_count" ] == 3
10781078 assert props ["$ai_input_tokens" ] == 100
10791079 assert props ["$ai_output_tokens" ] == 50
1080+
1081+
1082+ @pytest .fixture
1083+ def mock_anthropic_stream_with_web_search ():
1084+ """Mock stream events for web search."""
1085+
1086+ class MockServerToolUse :
1087+ def __init__ (self ):
1088+ self .web_search_requests = 2
1089+
1090+ class MockMessage :
1091+ def __init__ (self ):
1092+ self .usage = MockUsage (
1093+ input_tokens = 50 ,
1094+ cache_creation_input_tokens = 0 ,
1095+ cache_read_input_tokens = 5 ,
1096+ )
1097+
1098+ def stream_generator ():
1099+ # Message start with usage
1100+ event = MockStreamEvent ("message_start" )
1101+ event .message = MockMessage ()
1102+ yield event
1103+
1104+ # Text block start
1105+ event = MockStreamEvent ("content_block_start" )
1106+ event .content_block = MockContentBlock ("text" )
1107+ event .index = 0
1108+ yield event
1109+
1110+ # Text delta
1111+ event = MockStreamEvent ("content_block_delta" )
1112+ event .delta = MockDelta (text = "Here are the search results..." )
1113+ event .index = 0
1114+ yield event
1115+
1116+ # Text block stop
1117+ event = MockStreamEvent ("content_block_stop" )
1118+ event .index = 0
1119+ yield event
1120+
1121+ # Message delta with final usage including web search
1122+ event = MockStreamEvent ("message_delta" )
1123+ usage = MockUsage (output_tokens = 25 )
1124+ usage .server_tool_use = MockServerToolUse ()
1125+ event .usage = usage
1126+ yield event
1127+
1128+ # Message stop
1129+ event = MockStreamEvent ("message_stop" )
1130+ yield event
1131+
1132+ return stream_generator ()
1133+
1134+
1135+ def test_streaming_with_web_search (mock_client , mock_anthropic_stream_with_web_search ):
1136+ """Test that web search count is properly captured in streaming mode."""
1137+ with patch (
1138+ "anthropic.resources.Messages.create" ,
1139+ return_value = mock_anthropic_stream_with_web_search ,
1140+ ):
1141+ client = Anthropic (api_key = "test-key" , posthog_client = mock_client )
1142+ response = client .messages .create (
1143+ model = "claude-3-opus-20240229" ,
1144+ messages = [{"role" : "user" , "content" : "Search for recent news" }],
1145+ stream = True ,
1146+ posthog_distinct_id = "test-id" ,
1147+ )
1148+
1149+ # Consume the stream - this triggers the finally block synchronously
1150+ list (response )
1151+
1152+ # Capture happens synchronously when generator is exhausted
1153+ assert mock_client .capture .call_count == 1
1154+
1155+ call_args = mock_client .capture .call_args [1 ]
1156+ props = call_args ["properties" ]
1157+
1158+ # Verify web search count is captured
1159+ assert props ["$ai_web_search_count" ] == 2
1160+ assert props ["$ai_input_tokens" ] == 50
1161+ assert props ["$ai_output_tokens" ] == 25
1162+
1163+
1164+ def test_async_with_web_search (mock_client ):
1165+ """Test that web search count is properly tracked in async non-streaming mode."""
1166+ import asyncio
1167+
1168+ # Create a mock usage with web search
1169+ class MockServerToolUse :
1170+ def __init__ (self ):
1171+ self .web_search_requests = 3
1172+
1173+ class MockUsageWithWebSearch :
1174+ def __init__ (self ):
1175+ self .input_tokens = 100
1176+ self .output_tokens = 50
1177+ self .cache_read_input_tokens = 0
1178+ self .cache_creation_input_tokens = 0
1179+ self .server_tool_use = MockServerToolUse ()
1180+
1181+ class MockResponseWithWebSearch :
1182+ def __init__ (self ):
1183+ self .content = [MockContent (text = "Search results show..." )]
1184+ self .model = "claude-3-opus-20240229"
1185+ self .usage = MockUsageWithWebSearch ()
1186+
1187+ mock_response = MockResponseWithWebSearch ()
1188+
1189+ async def mock_async_create (** kwargs ):
1190+ return mock_response
1191+
1192+ with patch (
1193+ "anthropic.resources.AsyncMessages.create" ,
1194+ side_effect = mock_async_create ,
1195+ ):
1196+ async_client = AsyncAnthropic (api_key = "test-key" , posthog_client = mock_client )
1197+
1198+ async def run_test ():
1199+ response = await async_client .messages .create (
1200+ model = "claude-3-opus-20240229" ,
1201+ messages = [{"role" : "user" , "content" : "Search for recent news" }],
1202+ posthog_distinct_id = "test-id" ,
1203+ )
1204+ return response
1205+
1206+ # asyncio.run() waits for all async operations to complete
1207+ response = asyncio .run (run_test ())
1208+
1209+ assert response == mock_response
1210+ assert mock_client .capture .call_count == 1
1211+
1212+ call_args = mock_client .capture .call_args [1 ]
1213+ props = call_args ["properties" ]
1214+
1215+ # Verify web search count is captured
1216+ assert props ["$ai_web_search_count" ] == 3
1217+ assert props ["$ai_input_tokens" ] == 100
1218+ assert props ["$ai_output_tokens" ] == 50
1219+
1220+
1221+ def test_async_streaming_with_web_search (
1222+ mock_client , mock_anthropic_stream_with_web_search
1223+ ):
1224+ """Test that web search count is properly captured in async streaming mode."""
1225+ import asyncio
1226+
1227+ async def mock_async_generator ():
1228+ # Convert regular generator to async generator
1229+ for event in mock_anthropic_stream_with_web_search :
1230+ yield event
1231+
1232+ async def mock_async_create (** kwargs ):
1233+ # Return the async generator (to be awaited by the implementation)
1234+ return mock_async_generator ()
1235+
1236+ with patch (
1237+ "anthropic.resources.AsyncMessages.create" ,
1238+ side_effect = mock_async_create ,
1239+ ):
1240+ async_client = AsyncAnthropic (api_key = "test-key" , posthog_client = mock_client )
1241+
1242+ async def run_test ():
1243+ response = await async_client .messages .create (
1244+ model = "claude-3-opus-20240229" ,
1245+ messages = [{"role" : "user" , "content" : "Search for recent news" }],
1246+ stream = True ,
1247+ posthog_distinct_id = "test-id" ,
1248+ )
1249+
1250+ # Consume the async stream
1251+ [event async for event in response ]
1252+
1253+ # asyncio.run() waits for all async operations to complete
1254+ asyncio .run (run_test ())
1255+
1256+ # Capture completes before asyncio.run() returns
1257+ assert mock_client .capture .call_count == 1
1258+
1259+ call_args = mock_client .capture .call_args [1 ]
1260+ props = call_args ["properties" ]
1261+
1262+ # Verify web search count is captured
1263+ assert props ["$ai_web_search_count" ] == 2
1264+ assert props ["$ai_input_tokens" ] == 50
1265+ assert props ["$ai_output_tokens" ] == 25
0 commit comments