use crate::events::{Event, EventData, LlmGenerationMetadata};
#[cfg(feature = "otel")]
pub(crate) const LLM_FINISH_REASON: &str = "everruns.llm.finish_reason";
#[cfg(feature = "otel")]
pub(crate) const LLM_PROVIDER_FINISH_REASON: &str = "everruns.llm.provider_finish_reason";
#[cfg(feature = "otel")]
pub(crate) const LLM_TOOL_CALLS_DROPPED: &str = "everruns.llm.tool_calls_dropped";
#[cfg(feature = "otel")]
pub(crate) const LLM_TOOL_CALLS_TRUNCATED_EXECUTED: &str =
"everruns.llm.tool_calls_truncated_executed";
#[cfg(feature = "otel")]
pub(crate) const LLM_TRUNCATION_GATE: &str = "everruns.llm.truncation_gate";
#[cfg(feature = "otel")]
pub(crate) const TURN_STOP_REASON: &str = "everruns.turn.stop_reason";
fn first_finish_reason(meta: &LlmGenerationMetadata) -> Option<&str> {
meta.finish_reasons
.as_ref()
.and_then(|reasons| reasons.first())
.map(String::as_str)
}
#[cfg(feature = "otel")]
pub(crate) fn otel_chat_attributes(meta: &LlmGenerationMetadata) -> Vec<opentelemetry::KeyValue> {
use opentelemetry::KeyValue;
let mut attrs = super::provider_attrs::otel_unknown_cost_attributes(meta);
if let Some(reason) = first_finish_reason(meta) {
attrs.push(KeyValue::new(LLM_FINISH_REASON, reason.to_string()));
}
if let Some(raw) = &meta.provider_finish_reason {
attrs.push(KeyValue::new(LLM_PROVIDER_FINISH_REASON, raw.clone()));
}
if meta.tool_calls_dropped > 0 {
attrs.push(KeyValue::new(
LLM_TOOL_CALLS_DROPPED,
i64::from(meta.tool_calls_dropped),
));
}
if meta.tool_calls_truncated_executed > 0 {
attrs.push(KeyValue::new(
LLM_TOOL_CALLS_TRUNCATED_EXECUTED,
i64::from(meta.tool_calls_truncated_executed),
));
}
if let Some(action) = &meta.truncation_gate {
attrs.push(KeyValue::new(LLM_TRUNCATION_GATE, action.clone()));
}
attrs
}
#[cfg(feature = "otel")]
pub(crate) fn otel_turn_attributes(
data: &crate::events::TurnCompletedData,
) -> Vec<opentelemetry::KeyValue> {
data.stop_reason
.iter()
.map(|reason| opentelemetry::KeyValue::new(TURN_STOP_REASON, reason.clone()))
.collect()
}
#[cfg_attr(not(feature = "braintrust"), allow(dead_code))]
pub(crate) fn annotate_braintrust(event: &Event, metadata: &mut serde_json::Value) {
match &event.data {
EventData::LlmGeneration(data) => {
let meta = &data.metadata;
if let Some(reason) = first_finish_reason(meta) {
metadata["finish_reason"] = serde_json::json!(reason);
}
if let Some(raw) = &meta.provider_finish_reason {
metadata["provider_finish_reason"] = serde_json::json!(raw);
}
if let Some(usage) = &meta.usage {
metadata["output_tokens"] = serde_json::json!(usage.output_tokens);
}
if meta.tool_calls_dropped > 0 {
metadata["tool_calls_dropped"] = serde_json::json!(meta.tool_calls_dropped);
}
if meta.tool_calls_truncated_executed > 0 {
metadata["tool_calls_truncated_executed"] =
serde_json::json!(meta.tool_calls_truncated_executed);
}
if let Some(action) = &meta.truncation_gate {
metadata["truncation_gate"] = serde_json::json!(action);
}
}
EventData::TurnCompleted(data) => {
if let Some(reason) = &data.stop_reason {
metadata["stop_reason"] = serde_json::json!(reason);
}
}
_ => {}
}
}