Skip to content

Commit 5214907

Browse files
authored
Merge pull request #268 from demml/trace-filters
Trace filters
2 parents a2ee088 + 28214ed commit 5214907

32 files changed

Lines changed: 2240 additions & 194 deletions

File tree

.github/workflows/publish-docs.yml

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
1-
name: Publish Docs
1+
name: Publish-Github-Pages
22

33
on:
44
push:
55
branches:
66
- main
7+
78
release:
89
types: [published]
9-
workflow_dispatch:
1010

1111
permissions:
12-
contents: read
13-
14-
concurrency:
15-
group: "github-pages"
16-
cancel-in-progress: false
12+
contents: write
1713

1814
jobs:
1915
build:
2016
runs-on: ubuntu-latest
21-
permissions:
22-
contents: read
23-
pages: write
2417
steps:
2518
- name: Checkout
2619
uses: actions/checkout@v5
@@ -36,14 +29,15 @@ jobs:
3629

3730
deploy:
3831
needs: build
39-
runs-on: ubuntu-latest
4032
permissions:
41-
contents: read
4233
pages: write
4334
id-token: write
35+
4436
environment:
4537
name: github-pages
4638
url: ${{ steps.deployment.outputs.page_url }}
39+
40+
runs-on: ubuntu-latest
4741
steps:
4842
- name: Deploy to GitHub Pages
4943
id: deployment

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/scouter_client/src/lib.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,16 @@ pub use scouter_types::{
6060
ScouterResponse, ScouterServerError, ServerRecord, ServerRecords, SlackDispatchConfig,
6161
SpanEvent, SpanLink, SpcRecord, SquareRoot, Sturges, TagRecord, TagsResponse, TerrellScott,
6262
TimeInterval, ToolDashboardRequest, ToolDashboardResponse, ToolTimeBucket, TraceBaggageRecord,
63-
TraceBaggageResponse, TraceMetricsRequest, TraceMetricsResponse, TracePaginationResponse,
64-
TraceRecord, TraceRequest, TraceSpanRecord, TraceSpansResponse, UpdateAlertResponse,
65-
UpdateAlertStatus, VersionRequest, GEN_AI_AGENT_ID, GEN_AI_AGENT_NAME, GEN_AI_CONVERSATION_ID,
66-
GEN_AI_ERROR_TYPE, GEN_AI_OPERATION_NAME, GEN_AI_OUTPUT_TYPE, GEN_AI_PROVIDER_NAME,
67-
GEN_AI_REQUEST_MAX_TOKENS, GEN_AI_REQUEST_MODEL, GEN_AI_REQUEST_TEMPERATURE,
68-
GEN_AI_REQUEST_TOP_P, GEN_AI_RESPONSE_FINISH_REASONS, GEN_AI_RESPONSE_ID,
69-
GEN_AI_RESPONSE_MODEL, GEN_AI_TOOL_CALL_ID, GEN_AI_TOOL_NAME, GEN_AI_TOOL_TYPE,
70-
GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
71-
GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, OPENAI_API_TYPE, OPENAI_SERVICE_TIER,
72-
SCOUTER_ENTITY, SCOUTER_TAG_PREFIX,
63+
TraceBaggageResponse, TraceFacetDimension, TraceFacetsResponse, TraceMetricsRequest,
64+
TraceMetricsResponse, TracePaginationResponse, TraceRecord, TraceRequest, TraceSpanRecord,
65+
TraceSpansResponse, UpdateAlertResponse, UpdateAlertStatus, VersionRequest, GEN_AI_AGENT_ID,
66+
GEN_AI_AGENT_NAME, GEN_AI_CONVERSATION_ID, GEN_AI_ERROR_TYPE, GEN_AI_OPERATION_NAME,
67+
GEN_AI_OUTPUT_TYPE, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MAX_TOKENS, GEN_AI_REQUEST_MODEL,
68+
GEN_AI_REQUEST_TEMPERATURE, GEN_AI_REQUEST_TOP_P, GEN_AI_RESPONSE_FINISH_REASONS,
69+
GEN_AI_RESPONSE_ID, GEN_AI_RESPONSE_MODEL, GEN_AI_TOOL_CALL_ID, GEN_AI_TOOL_NAME,
70+
GEN_AI_TOOL_TYPE, GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
71+
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS,
72+
OPENAI_API_TYPE, OPENAI_SERVICE_TIER, SCOUTER_ENTITY, SCOUTER_TAG_PREFIX,
7373
};
7474

7575
pub use crate::http::{PyScouterClient, ScouterClient};

crates/scouter_dataframe/src/parquet/control/engine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ async fn build_or_create_control_table(
524524
schema: SchemaRef,
525525
) -> Result<DeltaTable, TraceEngineError> {
526526
let object_store = object_store.clone();
527-
run_delta_init(build_or_create_control_table_inner(object_store, schema)).await
527+
run_delta_init(build_or_create_control_table_inner(object_store, schema)).await?
528528
}
529529

530530
async fn build_or_create_control_table_inner(

crates/scouter_dataframe/src/parquet/tracing/dispatch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ async fn build_or_create_dispatch_table(
197197
schema: Arc<Schema>,
198198
) -> Result<DeltaTable, TraceEngineError> {
199199
let object_store = object_store.clone();
200-
run_delta_init(build_or_create_dispatch_table_inner(object_store, schema)).await
200+
run_delta_init(build_or_create_dispatch_table_inner(object_store, schema)).await?
201201
}
202202

203203
async fn build_or_create_dispatch_table_inner(

crates/scouter_dataframe/src/parquet/tracing/engine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ async fn build_or_create_table(
115115
schema: SchemaRef,
116116
) -> Result<DeltaTable, TraceEngineError> {
117117
let object_store = object_store.clone();
118-
run_delta_init(build_or_create_table_inner(object_store, schema)).await
118+
run_delta_init(build_or_create_table_inner(object_store, schema)).await?
119119
}
120120

121121
async fn build_or_create_table_inner(

crates/scouter_dataframe/src/parquet/tracing/genai.rs

Lines changed: 164 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ use arrow_array::Array;
1313
use arrow_array::RecordBatch;
1414
use chrono::{DateTime, Datelike, Utc};
1515
use datafusion::functions_aggregate::expr_fn::{avg, count, sum};
16-
use datafusion::logical_expr::{col, lit};
16+
use datafusion::logical_expr::{col, lit, Expr};
1717
use datafusion::prelude::*;
18+
use datafusion::scalar::ScalarValue;
1819
use deltalake::datafusion::parquet::basic::{Compression, Encoding, ZstdLevel};
1920
use deltalake::datafusion::parquet::file::properties::WriterProperties;
2021
use 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

607608
async 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

Comments
 (0)