@@ -13,8 +13,9 @@ use arrow_array::Array;
1313use arrow_array:: RecordBatch ;
1414use chrono:: { DateTime , Datelike , Utc } ;
1515use datafusion:: functions_aggregate:: expr_fn:: { avg, count, sum} ;
16- use datafusion:: logical_expr:: { col, lit} ;
16+ use datafusion:: logical_expr:: { col, lit, Expr } ;
1717use datafusion:: prelude:: * ;
18+ use datafusion:: scalar:: ScalarValue ;
1819use deltalake:: datafusion:: parquet:: basic:: { Compression , Encoding , ZstdLevel } ;
1920use deltalake:: datafusion:: parquet:: file:: properties:: WriterProperties ;
2021use deltalake:: datafusion:: parquet:: schema:: types:: ColumnPath ;
@@ -601,7 +602,7 @@ async fn build_or_create_genai_table(
601602 schema : SchemaRef ,
602603) -> Result < DeltaTable , TraceEngineError > {
603604 let object_store = object_store. clone ( ) ;
604- run_delta_init ( build_or_create_genai_table_inner ( object_store, schema) ) . await
605+ run_delta_init ( build_or_create_genai_table_inner ( object_store, schema) ) . await ?
605606}
606607
607608async fn build_or_create_genai_table_inner (
@@ -1135,6 +1136,71 @@ impl GenAiQueries {
11351136 }
11361137 }
11371138
1139+ fn trace_span_projection ( include_sensitive_content : bool ) -> Vec < Expr > {
1140+ let mut projection = vec ! [
1141+ col( TRACE_ID_COL ) ,
1142+ col( SPAN_ID_COL ) ,
1143+ col( SERVICE_NAME_COL ) ,
1144+ col( START_TIME_COL ) ,
1145+ col( END_TIME_COL ) ,
1146+ col( DURATION_MS_COL ) ,
1147+ col( STATUS_CODE_COL ) ,
1148+ col( OPERATION_NAME_COL ) ,
1149+ col( PROVIDER_NAME_COL ) ,
1150+ col( REQUEST_MODEL_COL ) ,
1151+ col( RESPONSE_MODEL_COL ) ,
1152+ col( RESPONSE_ID_COL ) ,
1153+ col( INPUT_TOKENS_COL ) ,
1154+ col( OUTPUT_TOKENS_COL ) ,
1155+ col( CACHE_CREATION_INPUT_TOKENS_COL ) ,
1156+ col( CACHE_READ_INPUT_TOKENS_COL ) ,
1157+ col( FINISH_REASONS_COL ) ,
1158+ col( OUTPUT_TYPE_COL ) ,
1159+ col( CONVERSATION_ID_COL ) ,
1160+ col( AGENT_NAME_COL ) ,
1161+ col( AGENT_ID_COL ) ,
1162+ col( TOOL_NAME_COL ) ,
1163+ col( TOOL_TYPE_COL ) ,
1164+ col( TOOL_CALL_ID_COL ) ,
1165+ col( REQUEST_TEMPERATURE_COL ) ,
1166+ col( REQUEST_MAX_TOKENS_COL ) ,
1167+ col( REQUEST_TOP_P_COL ) ,
1168+ col( ERROR_TYPE_COL ) ,
1169+ col( OPENAI_API_TYPE_COL ) ,
1170+ col( OPENAI_SERVICE_TIER_COL ) ,
1171+ col( LABEL_COL ) ,
1172+ col( AGENT_DESCRIPTION_COL ) ,
1173+ col( AGENT_VERSION_COL ) ,
1174+ col( DATA_SOURCE_ID_COL ) ,
1175+ col( REQUEST_CHOICE_COUNT_COL ) ,
1176+ col( REQUEST_SEED_COL ) ,
1177+ col( REQUEST_FREQUENCY_PENALTY_COL ) ,
1178+ col( REQUEST_PRESENCE_PENALTY_COL ) ,
1179+ col( REQUEST_STOP_SEQUENCES_COL ) ,
1180+ col( SERVER_ADDRESS_COL ) ,
1181+ col( SERVER_PORT_COL ) ,
1182+ ] ;
1183+
1184+ if include_sensitive_content {
1185+ projection. extend ( [
1186+ col ( INPUT_MESSAGES_COL ) ,
1187+ col ( OUTPUT_MESSAGES_COL ) ,
1188+ col ( SYSTEM_INSTRUCTIONS_COL ) ,
1189+ col ( TOOL_DEFINITIONS_COL ) ,
1190+ ] ) ;
1191+ } else {
1192+ projection. extend ( [
1193+ lit ( ScalarValue :: Utf8 ( None ) ) . alias ( INPUT_MESSAGES_COL ) ,
1194+ lit ( ScalarValue :: Utf8 ( None ) ) . alias ( OUTPUT_MESSAGES_COL ) ,
1195+ lit ( ScalarValue :: Utf8 ( None ) ) . alias ( SYSTEM_INSTRUCTIONS_COL ) ,
1196+ lit ( ScalarValue :: Utf8 ( None ) ) . alias ( TOOL_DEFINITIONS_COL ) ,
1197+ ] ) ;
1198+ }
1199+
1200+ projection. push ( col ( EVAL_RESULTS_COL ) ) ;
1201+ projection
1202+ }
1203+
11381204 pub async fn get_token_metrics (
11391205 & self ,
11401206 service_name : Option < & str > ,
@@ -1931,9 +1997,10 @@ impl GenAiQueries {
19311997 Ok ( results)
19321998 }
19331999
1934- pub async fn get_genai_spans (
2000+ async fn get_genai_spans_inner (
19352001 & self ,
19362002 filters : & GenAiSpanFilters ,
2003+ include_sensitive_content : bool ,
19372004 ) -> Result < Vec < GenAiSpanRecord > , TraceEngineError > {
19382005 let df = self . ctx . table ( GEN_AI_TABLE_NAME ) . await ?;
19392006
@@ -1977,42 +2044,92 @@ impl GenAiQueries {
19772044
19782045 let df = df. sort ( vec ! [ col( START_TIME_COL ) . sort( false , false ) ] ) ?;
19792046 let df = df. limit ( 0 , Some ( filters. limit . unwrap_or ( 100 ) . min ( 10_000 ) ) ) ?;
2047+ let df = df. select ( Self :: trace_span_projection ( include_sensitive_content) ) ?;
19802048
19812049 let batches = df. collect ( ) . await ?;
19822050 batches_to_genai_records ( batches)
19832051 }
19842052
2053+ pub async fn get_genai_spans (
2054+ & self ,
2055+ filters : & GenAiSpanFilters ,
2056+ ) -> Result < Vec < GenAiSpanRecord > , TraceEngineError > {
2057+ self . get_genai_spans_inner ( filters, true ) . await
2058+ }
2059+
2060+ pub async fn get_genai_spans_with_projection (
2061+ & self ,
2062+ filters : & GenAiSpanFilters ,
2063+ include_sensitive_content : bool ,
2064+ ) -> Result < Vec < GenAiSpanRecord > , TraceEngineError > {
2065+ self . get_genai_spans_inner ( filters, include_sensitive_content)
2066+ . await
2067+ }
2068+
19852069 pub async fn get_genai_spans_by_trace_id (
19862070 & self ,
19872071 trace_id : & TraceId ,
19882072 start : DateTime < Utc > ,
19892073 end : DateTime < Utc > ,
19902074 limit : usize ,
19912075 ) -> Result < TraceSpanQueryResult , TraceEngineError > {
1992- let clamped_limit = limit. clamp ( 1 , 10_000 ) ;
2076+ self . query_genai_spans_by_trace_id ( trace_id, start, end, Some ( limit) , true )
2077+ . await
2078+ }
2079+
2080+ pub async fn get_genai_spans_for_trace_metrics (
2081+ & self ,
2082+ trace_id : & TraceId ,
2083+ start : DateTime < Utc > ,
2084+ end : DateTime < Utc > ,
2085+ ) -> Result < Vec < GenAiSpanRecord > , TraceEngineError > {
2086+ self . query_genai_spans_by_trace_id ( trace_id, start, end, None , false )
2087+ . await
2088+ . map ( |query_result| query_result. spans )
2089+ }
2090+
2091+ async fn query_genai_spans_by_trace_id (
2092+ & self ,
2093+ trace_id : & TraceId ,
2094+ start : DateTime < Utc > ,
2095+ end : DateTime < Utc > ,
2096+ limit : Option < usize > ,
2097+ include_sensitive_content : bool ,
2098+ ) -> Result < TraceSpanQueryResult , TraceEngineError > {
19932099 let df = self . ctx . table ( GEN_AI_TABLE_NAME ) . await ?;
19942100 let df = Self :: apply_time_filters ( df, & start, & end) ?;
19952101 let df = df. filter ( col ( TRACE_ID_COL ) . eq ( lit ( trace_id. as_bytes ( ) . as_slice ( ) ) ) ) ?;
19962102 let df = df. sort ( vec ! [ col( START_TIME_COL ) . sort( true , true ) ] ) ?;
1997- let df = df. limit ( 0 , Some ( clamped_limit + 1 ) ) ?;
2103+ let df = df. select ( Self :: trace_span_projection ( include_sensitive_content) ) ?;
2104+
2105+ let clamped_limit = limit. map ( |value| value. clamp ( 1 , 10_000 ) ) ;
2106+ let df = match clamped_limit {
2107+ Some ( value) => df. limit ( 0 , Some ( value + 1 ) ) ?,
2108+ None => df,
2109+ } ;
19982110
19992111 let batches = df. collect ( ) . await ?;
20002112 let mut spans = batches_to_genai_records ( batches) ?;
2001- let spans_truncated = spans. len ( ) > clamped_limit;
2002- if spans_truncated {
2003- spans. truncate ( clamped_limit) ;
2113+ let mut spans_truncated = false ;
2114+ if let Some ( value) = clamped_limit {
2115+ spans_truncated = spans. len ( ) > value;
2116+ if spans_truncated {
2117+ spans. truncate ( value) ;
2118+ }
20042119 }
2120+
20052121 Ok ( TraceSpanQueryResult {
20062122 spans,
20072123 spans_truncated,
20082124 } )
20092125 }
20102126
2011- pub async fn get_conversation_spans (
2127+ async fn get_conversation_spans_inner (
20122128 & self ,
20132129 conversation_id : & str ,
20142130 start : Option < DateTime < Utc > > ,
20152131 end : Option < DateTime < Utc > > ,
2132+ include_sensitive_content : bool ,
20162133 ) -> Result < Vec < GenAiSpanRecord > , TraceEngineError > {
20172134 let df = self . ctx . table ( GEN_AI_TABLE_NAME ) . await ?;
20182135
@@ -2031,11 +2148,33 @@ impl GenAiQueries {
20312148 let df = df. filter ( col ( CONVERSATION_ID_COL ) . eq ( lit ( conversation_id) ) ) ?;
20322149 let df = df. sort ( vec ! [ col( START_TIME_COL ) . sort( true , true ) ] ) ?;
20332150 let df = df. limit ( 0 , Some ( 1_000 ) ) ?;
2151+ let df = df. select ( Self :: trace_span_projection ( include_sensitive_content) ) ?;
20342152
20352153 let batches = df. collect ( ) . await ?;
20362154 batches_to_genai_records ( batches)
20372155 }
20382156
2157+ pub async fn get_conversation_spans (
2158+ & self ,
2159+ conversation_id : & str ,
2160+ start : Option < DateTime < Utc > > ,
2161+ end : Option < DateTime < Utc > > ,
2162+ ) -> Result < Vec < GenAiSpanRecord > , TraceEngineError > {
2163+ self . get_conversation_spans_inner ( conversation_id, start, end, true )
2164+ . await
2165+ }
2166+
2167+ pub async fn get_conversation_spans_with_projection (
2168+ & self ,
2169+ conversation_id : & str ,
2170+ start : Option < DateTime < Utc > > ,
2171+ end : Option < DateTime < Utc > > ,
2172+ include_sensitive_content : bool ,
2173+ ) -> Result < Vec < GenAiSpanRecord > , TraceEngineError > {
2174+ self . get_conversation_spans_inner ( conversation_id, start, end, include_sensitive_content)
2175+ . await
2176+ }
2177+
20392178 pub async fn get_agent_metrics_by_bucket (
20402179 & self ,
20412180 service_name : Option < & str > ,
@@ -3822,6 +3961,22 @@ mod tests {
38223961 ) ;
38233962 assert_eq ! ( span. eval_results. len( ) , 1 ) ;
38243963
3964+ let aggregate_spans = service
3965+ . query_service
3966+ . get_genai_spans_for_trace_metrics (
3967+ & trace_id,
3968+ now - chrono:: Duration :: hours ( 1 ) ,
3969+ now + chrono:: Duration :: hours ( 1 ) ,
3970+ )
3971+ . await ?;
3972+ assert_eq ! ( aggregate_spans. len( ) , 1 ) ;
3973+ let aggregate_span = & aggregate_spans[ 0 ] ;
3974+ assert ! ( aggregate_span. input_messages. is_none( ) ) ;
3975+ assert ! ( aggregate_span. output_messages. is_none( ) ) ;
3976+ assert ! ( aggregate_span. system_instructions. is_none( ) ) ;
3977+ assert ! ( aggregate_span. tool_definitions. is_none( ) ) ;
3978+ assert_eq ! ( aggregate_span. response_model. as_deref( ) , Some ( "gpt-4o" ) ) ;
3979+
38253980 service. shutdown ( ) . await ?;
38263981 Ok ( ( ) )
38273982 }
0 commit comments