Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions crates/keplor-server/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,16 @@ pub struct EventResponse {
pub streaming: bool,
pub error: Option<String>,
pub metadata: Option<serde_json::Value>,
/// Client source IP as a string. Surfaced for SDK / geographic
/// breakdowns; never None in practice — events without a recorded
/// IP show as `null`. Consumers should bucket to /24 for IPv4
/// before display rather than render raw addresses.
#[serde(skip_serializing_if = "Option::is_none")]
pub client_ip: Option<String>,
/// Caller-supplied User-Agent string. Used by clients to derive
/// coarse SDK identification (e.g. `openai-python/1.42`).
#[serde(skip_serializing_if = "Option::is_none")]
pub user_agent: Option<String>,
}

/// Token usage in query response.
Expand Down Expand Up @@ -317,6 +327,8 @@ pub async fn query_events(
endpoint: e.endpoint,
streaming: e.streaming,
error: e.error_type,
client_ip: e.client_ip,
user_agent: e.user_agent,
metadata,
}
})
Expand Down Expand Up @@ -506,6 +518,8 @@ fn llm_event_to_response(ev: keplor_core::LlmEvent) -> EventResponse {
.to_owned()
}),
metadata: ev.metadata,
client_ip: ev.client_ip.map(|ip| ip.to_string()),
user_agent: ev.user_agent.map(|ua| ua.to_string()),
}
}

Expand Down Expand Up @@ -1024,6 +1038,8 @@ pub async fn export_events(
.metadata_json
.as_deref()
.and_then(|s| serde_json::from_str(s).ok()),
client_ip: event.client_ip,
user_agent: event.user_agent,
};
if let Ok(json) = serde_json::to_string(&resp) {
lines.push(json);
Expand Down
2 changes: 2 additions & 0 deletions crates/keplor-store/src/kdb_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,8 @@ fn llm_to_summary(ev: LlmEvent) -> EventSummary {
source: ev.source.map(|s| s.to_string()),
error_type: ev.error.as_ref().map(|e| provider_error_type_key(e).to_owned()),
metadata_json: ev.metadata.as_ref().map(|v| v.to_string()),
client_ip: ev.client_ip.map(|ip| ip.to_string()),
user_agent: ev.user_agent.map(|ua| ua.to_string()),
}
}

Expand Down
8 changes: 8 additions & 0 deletions crates/keplor-store/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ pub struct EventSummary {
pub error_type: Option<String>,
/// Arbitrary metadata as JSON text.
pub metadata_json: Option<String>,
/// Client source IP as a string ("203.0.113.1" or "2001:db8::1").
/// Already accepted on ingest and stored in the label table —
/// surfaced here so consumers can derive geographic / network
/// breakdowns without re-querying the raw event row.
pub client_ip: Option<String>,
/// Caller-supplied User-Agent header. Used to derive coarse SDK
/// identification (e.g. "openai-python", "anthropic-sdk-typescript").
pub user_agent: Option<String>,
}

/// Cost + event count from a quota query.
Expand Down
Loading