use super::*;
pub(super) struct CompletedAttempt<'a> {
pub(super) opts: &'a crate::llm::api::LlmCallOptions,
pub(super) result: &'a crate::llm::api::LlmResult,
pub(super) call_id: &'a str,
pub(super) bridge: Option<&'a Arc<crate::bridge::HostBridge>>,
pub(super) iteration: Option<usize>,
pub(super) attempt: usize,
pub(super) duration_ms: u64,
pub(super) user_visible: bool,
pub(super) effective_tool_format: &'a str,
}
#[inline(never)]
pub(super) fn observe_terminal_unproductive_completion(
attempt: CompletedAttempt<'_>,
error: &VmError,
attempt_count: usize,
) {
let CompletedAttempt {
opts,
result,
call_id,
bridge,
iteration,
attempt,
duration_ms,
user_visible,
effective_tool_format,
} = attempt;
let category = crate::value::error_to_category(error);
let message = error.to_string();
let classified = crate::llm::api::classify_vm_llm_error(error);
let status = "retries_exhausted";
let usage = result.usage();
annotate_current_span(&[
("status", serde_json::json!(status)),
("error", serde_json::json!(message.as_str())),
("retryable", serde_json::json!(false)),
("failover_eligible", serde_json::json!(true)),
("attempt", serde_json::json!(attempt)),
]);
annotate_current_span(&usage.metadata_pairs(&result.provider, &result.model));
dump_llm_response(
iteration.unwrap_or(0),
call_id,
result,
duration_ms,
opts.applied_structural_experiment.as_ref(),
opts.call_stage.as_deref(),
);
append_provider_call_error_observability(ProviderCallErrorObservation {
iteration: iteration.unwrap_or(0),
call_id,
attempt: attempt_count,
status,
opts,
category: &category,
classified: &classified,
message: &message,
stream_failure: None,
schema_failure: None,
usage: Some(&usage),
retryable: false,
failover_eligible: true,
attempt_count: Some(attempt_count),
});
dump_resolved_dispatch(
iteration.unwrap_or(0),
call_id,
opts,
effective_tool_format,
&crate::llm::resolved_dispatch::DispatchOutcome::from_error(error),
);
if let Some(b) = bridge {
b.send_call_end(
call_id,
"llm",
"llm_call",
duration_ms,
status,
serde_json::json!({
"error": message,
"retryable": false,
"failover_eligible": true,
"attempt": attempt,
"user_visible": user_visible,
}),
);
}
if let Some(metrics) = crate::active_metrics_registry() {
metrics.record_llm_call(&result.provider, &result.model, status, &usage);
}
trace_llm_call(LlmTraceEntry {
model: result.model.clone(),
provider: result.provider.clone(),
usage,
duration_ms,
});
}
#[inline(never)]
pub(super) fn observe_successful_completion(
attempt: CompletedAttempt<'_>,
empty_completion_retries: usize,
) {
let CompletedAttempt {
opts,
result,
call_id,
bridge,
iteration,
duration_ms,
user_visible,
effective_tool_format,
..
} = attempt;
let usage = result.usage();
annotate_current_span(&[("status", serde_json::json!("ok"))]);
annotate_current_span(&usage.metadata_pairs(&result.provider, &result.model));
dump_llm_response(
iteration.unwrap_or(0),
call_id,
result,
duration_ms,
opts.applied_structural_experiment.as_ref(),
opts.call_stage.as_deref(),
);
dump_resolved_dispatch(
iteration.unwrap_or(0),
call_id,
opts,
effective_tool_format,
&crate::llm::resolved_dispatch::DispatchOutcome::from_result(
result,
empty_completion_retries,
),
);
annotate_current_span(&[(
"structural_experiment",
opts.applied_structural_experiment
.as_ref()
.map(serde_json::to_value)
.transpose()
.unwrap_or(None)
.unwrap_or(serde_json::Value::Null),
)]);
if let Some(b) = bridge {
b.send_call_end(
call_id,
"llm",
"llm_call",
duration_ms,
"ok",
serde_json::json!({
"model": result.model,
"input_tokens": usage.input_tokens,
"output_tokens": usage.output_tokens,
"user_visible": user_visible,
"structural_experiment": opts.applied_structural_experiment.as_ref(),
}),
);
}
trace_llm_call(LlmTraceEntry {
model: result.model.clone(),
provider: result.provider.clone(),
usage: usage.clone(),
duration_ms,
});
if let Some(metrics) = crate::active_metrics_registry() {
metrics.record_llm_call(&result.provider, &result.model, "succeeded", &usage);
if usage.cache_hit {
metrics.record_llm_cache_hit(&result.provider);
}
}
crate::llm::trace::emit_agent_event(crate::llm::trace::AgentTraceEvent::LlmCall {
call_id: call_id.to_string(),
model: result.model.clone(),
usage,
duration_ms,
iteration: iteration.unwrap_or(0),
});
crate::llm::agent_session_host::record_auxiliary_call_usage(opts, result);
if is_retryable_unproductive_completion(result)
&& !crate::llm::providers::is_internal_simulator(&opts.provider)
{
let reason = if is_empty_unproductive_completion(result) {
UnproductiveCompletionReason::EmptyGeneration
} else {
UnproductiveCompletionReason::UnproductiveCompletion
};
crate::llm::rate_limit::observe_unproductive_completion_for_llm_call(opts, reason.as_str());
} else {
crate::llm::rate_limit::observe_network_outcome_for_llm_call(opts, false);
}
}