@@ -2032,20 +2032,24 @@ def invoke_model_with_response_stream_tool_call(
20322032 expect_content ,
20332033):
20342034 # pylint:disable=too-many-locals,too-many-statements,too-many-branches
2035- messages = [
2036- {
2037- "role" : "user" ,
2038- "content" : [
2039- {
2040- "text" : "What is the weather in Seattle and San Francisco today? Please expect one tool call for Seattle and one for San Francisco" ,
2041- "type" : "text" ,
2042- }
2043- ],
2035+ user_prompt = "What is the weather in Seattle and San Francisco today? Please give one tool call for Seattle and one for San Francisco"
2036+ if "anthropic.claude" in llm_model_value :
2037+ user_msg_content = {
2038+ "text" : user_prompt ,
2039+ "type" : "text" ,
20442040 }
2045- ]
2041+ else :
2042+ user_msg_content = {
2043+ "text" : user_prompt ,
2044+ }
2045+ messages = [{"role" : "user" , "content" : [user_msg_content ]}]
20462046
20472047 max_tokens = 1000
2048- tool_config = get_anthropic_tool_config ()
2048+ if "anthropic.claude" in llm_model_value :
2049+ tool_config = get_anthropic_tool_config ()
2050+ else :
2051+ tool_config = get_tool_config ()
2052+
20492053 body = get_invoke_model_body (
20502054 llm_model_value ,
20512055 messages = messages ,
@@ -2059,12 +2063,16 @@ def invoke_model_with_response_stream_tool_call(
20592063
20602064 content = []
20612065 content_block = {}
2066+ # used only by anthropic claude
20622067 input_json_buf = ""
2068+ # used only by amazon nova
2069+ tool_use = None
20632070 for event in response_0 ["body" ]:
20642071 json_bytes = event ["chunk" ].get ("bytes" , b"" )
20652072 decoded = json_bytes .decode ("utf-8" )
20662073 chunk = json .loads (decoded )
20672074
2075+ # anthropic claude
20682076 if (message_type := chunk .get ("type" )) is not None :
20692077 if message_type == "content_block_start" :
20702078 content_block = chunk ["content_block" ]
@@ -2079,28 +2087,81 @@ def invoke_model_with_response_stream_tool_call(
20792087 content .append (content_block )
20802088 content_block = None
20812089 input_json_buf = ""
2090+ else :
2091+ if "contentBlockDelta" in chunk :
2092+ delta = chunk ["contentBlockDelta" ]["delta" ]
2093+ if "text" in delta :
2094+ content_block .setdefault ("text" , "" )
2095+ content_block ["text" ] += delta ["text" ]
2096+ elif "toolUse" in delta :
2097+ tool_use ["toolUse" ]["input" ] = json .loads (
2098+ delta ["toolUse" ]["input" ]
2099+ )
2100+ elif "contentBlockStart" in chunk :
2101+ if content_block :
2102+ content .append (content_block )
2103+ content_block = {}
2104+ start = chunk ["contentBlockStart" ]["start" ]
2105+ if "toolUse" in start :
2106+ tool_use = start
2107+ elif "contentBlockStop" in chunk :
2108+ if tool_use :
2109+ content .append (tool_use )
2110+ tool_use = {}
20822111
20832112 assert content
20842113
2085- tool_requests_ids = [
2086- item ["id" ] for item in content if item ["type" ] == "tool_use"
2087- ]
2114+ if "anthropic.claude" in llm_model_value :
2115+ tool_requests_ids = [
2116+ item ["id" ] for item in content if item ["type" ] == "tool_use"
2117+ ]
2118+ else :
2119+ tool_requests_ids = [
2120+ item ["toolUse" ]["toolUseId" ]
2121+ for item in content
2122+ if "toolUse" in item
2123+ ]
2124+
20882125 assert len (tool_requests_ids ) == 2
2089- tool_call_result = {
2090- "role" : "user" ,
2091- "content" : [
2092- {
2093- "type" : "tool_result" ,
2094- "tool_use_id" : tool_requests_ids [0 ],
2095- "content" : "50 degrees and raining" ,
2096- },
2097- {
2098- "type" : "tool_result" ,
2099- "tool_use_id" : tool_requests_ids [1 ],
2100- "content" : "70 degrees and sunny" ,
2101- },
2102- ],
2103- }
2126+
2127+ if "anthropic.claude" in llm_model_value :
2128+ tool_call_result = {
2129+ "role" : "user" ,
2130+ "content" : [
2131+ {
2132+ "type" : "tool_result" ,
2133+ "tool_use_id" : tool_requests_ids [0 ],
2134+ "content" : "50 degrees and raining" ,
2135+ },
2136+ {
2137+ "type" : "tool_result" ,
2138+ "tool_use_id" : tool_requests_ids [1 ],
2139+ "content" : "70 degrees and sunny" ,
2140+ },
2141+ ],
2142+ }
2143+ else :
2144+ tool_call_result = {
2145+ "role" : "user" ,
2146+ "content" : [
2147+ {
2148+ "toolResult" : {
2149+ "toolUseId" : tool_requests_ids [0 ],
2150+ "content" : [
2151+ {"json" : {"weather" : "50 degrees and raining" }}
2152+ ],
2153+ }
2154+ },
2155+ {
2156+ "toolResult" : {
2157+ "toolUseId" : tool_requests_ids [1 ],
2158+ "content" : [
2159+ {"json" : {"weather" : "70 degrees and sunny" }}
2160+ ],
2161+ }
2162+ },
2163+ ],
2164+ }
21042165
21052166 # remove extra attributes from response
21062167 messages .append ({"role" : "assistant" , "content" : content })
@@ -2112,14 +2173,43 @@ def invoke_model_with_response_stream_tool_call(
21122173 max_tokens = max_tokens ,
21132174 tools = tool_config ,
21142175 )
2176+ import pprint
2177+
2178+ pprint .pprint (messages [1 ])
21152179 response_1 = bedrock_runtime_client .invoke_model_with_response_stream (
21162180 body = body ,
21172181 modelId = llm_model_value ,
21182182 )
21192183
2120- # consume the body to have it traced
2121- for _ in response_1 ["body" ]:
2122- pass
2184+ content_block = {}
2185+ response_1_content = []
2186+ for event in response_1 ["body" ]:
2187+ json_bytes = event ["chunk" ].get ("bytes" , b"" )
2188+ decoded = json_bytes .decode ("utf-8" )
2189+ chunk = json .loads (decoded )
2190+
2191+ # anthropic claude
2192+ if (message_type := chunk .get ("type" )) is not None :
2193+ if message_type == "content_block_start" :
2194+ content_block = chunk ["content_block" ]
2195+ elif message_type == "content_block_delta" :
2196+ if chunk ["delta" ]["type" ] == "text_delta" :
2197+ content_block ["text" ] += chunk ["delta" ]["text" ]
2198+ elif message_type == "content_block_stop" :
2199+ response_1_content .append (content_block )
2200+ content_block = None
2201+ else :
2202+ if "contentBlockDelta" in chunk :
2203+ delta = chunk ["contentBlockDelta" ]["delta" ]
2204+ if "text" in delta :
2205+ content_block .setdefault ("text" , "" )
2206+ content_block ["text" ] += delta ["text" ]
2207+ elif "messageStop" in chunk :
2208+ if content_block :
2209+ response_1_content .append (content_block )
2210+ content_block = {}
2211+
2212+ assert response_1_content
21232213
21242214 (span_0 , span_1 ) = span_exporter .get_finished_spans ()
21252215 assert_stream_completion_attributes (
@@ -2194,21 +2284,38 @@ def invoke_model_with_response_stream_tool_call(
21942284 assistant_body ,
21952285 span_1 ,
21962286 )
2197- tool_message_0 = {
2198- "id" : tool_requests_ids [0 ],
2199- "content" : tool_call_result ["content" ][0 ]["content" ]
2200- if expect_content
2201- else None ,
2202- }
2287+
2288+ if "anthropic.claude" in llm_model_value :
2289+ tool_message_0 = {
2290+ "id" : tool_requests_ids [0 ],
2291+ "content" : tool_call_result ["content" ][0 ]["content" ]
2292+ if expect_content
2293+ else None ,
2294+ }
2295+ tool_message_1 = {
2296+ "id" : tool_requests_ids [1 ],
2297+ "content" : tool_call_result ["content" ][1 ]["content" ]
2298+ if expect_content
2299+ else None ,
2300+ }
2301+ else :
2302+ tool_message_0 = {
2303+ "id" : tool_requests_ids [0 ],
2304+ "content" : tool_call_result ["content" ][0 ]["toolResult" ]["content" ]
2305+ if expect_content
2306+ else None ,
2307+ }
2308+ tool_message_1 = {
2309+ "id" : tool_requests_ids [1 ],
2310+ "content" : tool_call_result ["content" ][1 ]["toolResult" ]["content" ]
2311+ if expect_content
2312+ else None ,
2313+ }
2314+
22032315 assert_message_in_logs (
22042316 logs [4 ], "gen_ai.tool.message" , tool_message_0 , span_1
22052317 )
2206- tool_message_1 = {
2207- "id" : tool_requests_ids [1 ],
2208- "content" : tool_call_result ["content" ][1 ]["content" ]
2209- if expect_content
2210- else None ,
2211- }
2318+
22122319 assert_message_in_logs (
22132320 logs [5 ], "gen_ai.tool.message" , tool_message_1 , span_1
22142321 )
@@ -2225,27 +2332,31 @@ def invoke_model_with_response_stream_tool_call(
22252332 "finish_reason" : "end_turn" ,
22262333 "message" : {
22272334 "role" : "assistant" ,
2228- "content" : [
2229- {
2230- "type" : "text" ,
2231- "text" : "\n \n Great! I have the current weather information for both cities. Here's the weather in Seattle and San Francisco today:\n \n Seattle: 50 degrees and raining\n San Francisco: 70 degrees and sunny\n \n As you can see, the weather is quite different in these two cities today. Seattle is experiencing cooler temperatures with rain, which is fairly typical for the city. On the other hand, San Francisco is enjoying a warm and sunny day. If you're planning any activities, you might want to consider indoor options for Seattle, while it's a great day for outdoor activities in San Francisco.\n \n Is there anything else you'd like to know about the weather in these cities or any other locations?" ,
2232- }
2233- ],
2335+ "content" : response_1_content ,
22342336 },
22352337 }
22362338 if not expect_content :
22372339 choice_body ["message" ].pop ("content" )
22382340 assert_message_in_logs (logs [7 ], "gen_ai.choice" , choice_body , span_1 )
22392341
22402342
2343+ @pytest .mark .parametrize (
2344+ "model_family" ,
2345+ ["amazon.nova" , "anthropic.claude" ],
2346+ )
22412347@pytest .mark .vcr ()
22422348def test_invoke_model_with_response_stream_with_content_tool_call (
22432349 span_exporter ,
22442350 log_exporter ,
22452351 bedrock_runtime_client ,
22462352 instrument_with_content ,
2353+ model_family ,
22472354):
2248- llm_model_value = "us.anthropic.claude-3-5-sonnet-20240620-v1:0"
2355+ if model_family == "amazon.nova" :
2356+ llm_model_value = "amazon.nova-micro-v1:0"
2357+ elif model_family == "anthropic.claude" :
2358+ llm_model_value = "us.anthropic.claude-3-5-sonnet-20240620-v1:0"
2359+
22492360 invoke_model_with_response_stream_tool_call (
22502361 span_exporter ,
22512362 log_exporter ,
@@ -2412,13 +2523,23 @@ def test_invoke_model_with_response_stream_no_content_different_events(
24122523 assert_message_in_logs (logs [4 ], "gen_ai.choice" , choice_body , span )
24132524
24142525
2526+ @pytest .mark .parametrize (
2527+ "model_family" ,
2528+ ["amazon.nova" , "anthropic.claude" ],
2529+ )
24152530@pytest .mark .vcr ()
24162531def test_invoke_model_with_response_stream_no_content_tool_call (
24172532 span_exporter ,
24182533 log_exporter ,
24192534 bedrock_runtime_client ,
24202535 instrument_no_content ,
2536+ model_family ,
24212537):
2538+ if model_family == "amazon.nova" :
2539+ llm_model_value = "amazon.nova-micro-v1:0"
2540+ elif model_family == "anthropic.claude" :
2541+ llm_model_value = "us.anthropic.claude-3-5-sonnet-20240620-v1:0"
2542+
24222543 llm_model_value = "us.anthropic.claude-3-5-sonnet-20240620-v1:0"
24232544 invoke_model_with_response_stream_tool_call (
24242545 span_exporter ,
0 commit comments