@@ -113,6 +113,7 @@ def test_metadata_capture(mock_client):
113113 base_url = "https://us.posthog.com" ,
114114 name = "test" ,
115115 end_time = None ,
116+ metadata = {"ls_model_name" : "hog-mini" , "ls_provider" : "posthog" },
116117 )
117118 assert callbacks ._runs [run_id ] == expected
118119 with patch ("time.time" , return_value = 1234567891 ):
@@ -1269,6 +1270,7 @@ def test_metadata_tools(mock_client):
12691270 name = "test" ,
12701271 tools = tools ,
12711272 end_time = None ,
1273+ metadata = {"ls_model_name" : "hog-mini" , "ls_provider" : "posthog" },
12721274 )
12731275 assert callbacks ._runs [run_id ] == expected
12741276 with patch ("time.time" , return_value = 1234567891 ):
@@ -1839,6 +1841,7 @@ def test_tool_definition(mock_client):
18391841 name = "test" ,
18401842 tools = tools ,
18411843 end_time = None ,
1844+ metadata = {"ls_model_name" : "gpt-4o-mini" , "ls_provider" : "openai" },
18421845 )
18431846 assert callbacks ._runs [run_id ] == expected
18441847
@@ -2126,3 +2129,88 @@ def test_agent_action_and_finish_imports():
21262129 assert mock_client .capture .call_count == 1
21272130 call_args = mock_client .capture .call_args [1 ]
21282131 assert call_args ["event" ] == "$ai_span"
2132+
2133+
2134+ def test_ai_billable_metadata (mock_client ):
2135+ """Test that $ai_billable metadata is properly captured in generation events."""
2136+ prompt = ChatPromptTemplate .from_messages (
2137+ [
2138+ ("user" , "What is the weather?" ),
2139+ ]
2140+ )
2141+ model = FakeMessagesListChatModel (
2142+ responses = [
2143+ AIMessage (
2144+ content = "It's sunny!" ,
2145+ usage_metadata = {
2146+ "input_tokens" : 10 ,
2147+ "output_tokens" : 5 ,
2148+ "total_tokens" : 15 ,
2149+ },
2150+ )
2151+ ]
2152+ )
2153+
2154+ callbacks = [CallbackHandler (mock_client )]
2155+ chain = prompt | model
2156+
2157+ # Invoke with $ai_billable metadata set to False
2158+ result = chain .invoke (
2159+ {}, config = {"callbacks" : callbacks , "metadata" : {"$ai_billable" : False }}
2160+ )
2161+
2162+ assert result .content == "It's sunny!"
2163+ assert mock_client .capture .call_count == 3
2164+
2165+ generation_args = mock_client .capture .call_args_list [1 ][1 ]
2166+ generation_props = generation_args ["properties" ]
2167+
2168+ assert generation_args ["event" ] == "$ai_generation"
2169+ assert generation_props ["$ai_billable" ] is False
2170+
2171+ # Test with $ai_billable set to True
2172+ mock_client .reset_mock ()
2173+
2174+ result = chain .invoke (
2175+ {}, config = {"callbacks" : callbacks , "metadata" : {"$ai_billable" : True }}
2176+ )
2177+
2178+ assert mock_client .capture .call_count == 3
2179+ generation_args = mock_client .capture .call_args_list [1 ][1 ]
2180+ generation_props = generation_args ["properties" ]
2181+
2182+ assert generation_args ["event" ] == "$ai_generation"
2183+ assert generation_props ["$ai_billable" ] is True
2184+
2185+
2186+ def test_ai_billable_metadata_not_present_when_not_set (mock_client ):
2187+ """Test that $ai_billable is not in event properties when not set in metadata."""
2188+ prompt = ChatPromptTemplate .from_messages ([("user" , "Hello" )])
2189+ model = FakeMessagesListChatModel (
2190+ responses = [
2191+ AIMessage (
2192+ content = "Hi!" ,
2193+ usage_metadata = {
2194+ "input_tokens" : 5 ,
2195+ "output_tokens" : 2 ,
2196+ "total_tokens" : 7 ,
2197+ },
2198+ )
2199+ ]
2200+ )
2201+
2202+ callbacks = [CallbackHandler (mock_client )]
2203+ chain = prompt | model
2204+
2205+ # Invoke without $ai_billable metadata
2206+ result = chain .invoke ({}, config = {"callbacks" : callbacks })
2207+
2208+ assert result .content == "Hi!"
2209+ assert mock_client .capture .call_count == 3
2210+
2211+ generation_args = mock_client .capture .call_args_list [1 ][1 ]
2212+ generation_props = generation_args ["properties" ]
2213+
2214+ assert generation_args ["event" ] == "$ai_generation"
2215+ # $ai_billable should not be present at all
2216+ assert "$ai_billable" not in generation_props
0 commit comments