use crate::{
providers::{LLMRequest, TokenUsage},
shared::{ContentBlock, Message},
};
use serde_json::Value;
use tracing::Span;
fn blocks_json(blocks: &[ContentBlock]) -> String {
serde_json::to_string(blocks).unwrap_or_else(|_| "[]".to_string())
}
fn messages_json(messages: &[Message]) -> String {
serde_json::to_string(messages).unwrap_or_else(|_| "[]".to_string())
}
pub(crate) fn user_message(session_id: &str, text: &str) {
tracing::info!(
target: "tiny_agent",
event_type = "user_message",
"gen_ai.conversation.id" = %session_id,
"tiny_agent.user.input_bytes" = text.len(),
"tiny_agent.session.id" = %session_id,
"user message submitted"
);
}
pub(crate) fn reasoning_started(session_id: &str, req: &LLMRequest, provider: &str) {
tracing::info!(
target: "tiny_agent",
event_type = "reasoning_started",
"gen_ai.operation.name" = "chat",
"gen_ai.conversation.id" = %session_id,
"gen_ai.provider.name" = %provider,
"gen_ai.request.model" = %req.model,
"gen_ai.request.temperature" = ?req.temperature,
"gen_ai.request.max_tokens" = ?req.max_tokens,
"gen_ai.request.stream" = true,
"tiny_agent.request.has_system" = req.system.is_some(),
"tiny_agent.request.message_count" = req.messages.len(),
"tiny_agent.request.tool_count" = req.tools.len(),
"tiny_agent.session.id" = %session_id,
"reasoning started"
);
}
pub(crate) fn thinking(session_id: &str, text: &str) {
tracing::debug!(
target: "tiny_agent",
event_type = "thinking",
"gen_ai.conversation.id" = %session_id,
"tiny_agent.thinking.bytes" = text.len(),
"tiny_agent.session.id" = %session_id,
"thinking content received"
);
}
pub(crate) fn assistant_message(session_id: &str, blocks: &[ContentBlock]) {
tracing::info!(
target: "tiny_agent",
event_type = "assistant_message",
"gen_ai.conversation.id" = %session_id,
"tiny_agent.response.block_count" = blocks.len(),
"tiny_agent.session.id" = %session_id,
"assistant message received"
);
}
pub(crate) fn tool_started(session_id: &str, call_id: &str, name: &str, arguments: &Value) {
tracing::info!(
target: "tiny_agent",
event_type = "tool_started",
"gen_ai.operation.name" = "execute_tool",
"gen_ai.conversation.id" = %session_id,
"gen_ai.tool.call.id" = %call_id,
"gen_ai.tool.name" = %name,
"gen_ai.tool.type" = "function",
"tiny_agent.tool.argument_kind" = %value_kind(arguments),
"tiny_agent.session.id" = %session_id,
"tool started"
);
}
pub(crate) fn tool_completed(
session_id: &str,
call_id: &str,
name: &str,
output: &[ContentBlock],
is_error: bool,
) {
tracing::info!(
target: "tiny_agent",
event_type = "tool_completed",
"gen_ai.operation.name" = "execute_tool",
"gen_ai.conversation.id" = %session_id,
"gen_ai.tool.call.id" = %call_id,
"gen_ai.tool.name" = %name,
"gen_ai.tool.type" = "function",
"tiny_agent.tool.result_block_count" = output.len(),
"tiny_agent.session.id" = %session_id,
error = is_error,
"tool completed"
);
}
pub(crate) fn interrupted(session_id: &str) {
tracing::info!(
target: "tiny_agent",
event_type = "interrupted",
"gen_ai.conversation.id" = %session_id,
"tiny_agent.session.id" = %session_id,
"agent interrupted"
);
}
pub(crate) fn waiting_for_user(session_id: &str, kind: &str) {
tracing::info!(
target: "tiny_agent",
event_type = "waiting_for_user",
"gen_ai.conversation.id" = %session_id,
"tiny_agent.user_interaction.kind" = %kind,
"tiny_agent.session.id" = %session_id,
"waiting for user"
);
}
pub(crate) fn failed(session_id: &str, kind: &str, message: &str) {
tracing::warn!(
target: "tiny_agent",
event_type = "failed",
"gen_ai.conversation.id" = %session_id,
"error.type" = %kind,
"error.message" = %message,
"tiny_agent.session.id" = %session_id,
"agent failure"
);
}
pub(crate) fn succeeded(session_id: &str, final_resp: Option<&str>) {
tracing::info!(
target: "tiny_agent",
event_type = "succeeded",
"gen_ai.conversation.id" = %session_id,
"tiny_agent.final_response.bytes" = final_resp.map(str::len).unwrap_or(0),
"tiny_agent.session.id" = %session_id,
"agent succeeded"
);
}
pub(crate) fn record_chat_input(req: &LLMRequest) {
let span = Span::current();
if let Some(temperature) = req.temperature {
span.record("gen_ai.request.temperature", temperature as f64);
}
if let Some(max_tokens) = req.max_tokens {
span.record("gen_ai.request.max_tokens", max_tokens as i64);
}
if let Some(system) = req.system.as_deref() {
span.record("gen_ai.request.system", system);
}
span.record(
"gen_ai.input.messages",
messages_json(&req.messages).as_str(),
);
}
pub(crate) fn record_chat_output(
blocks: &[ContentBlock],
finish_reason: &str,
usage: Option<&TokenUsage>,
) {
let span = Span::current();
span.record("gen_ai.output.messages", blocks_json(blocks).as_str());
span.record("gen_ai.response.finish_reasons", finish_reason);
if let Some(usage) = usage {
if let Some(input_tokens) = usage.input_tokens {
span.record("gen_ai.usage.input_tokens", input_tokens as i64);
}
if let Some(output_tokens) = usage.output_tokens {
span.record("gen_ai.usage.output_tokens", output_tokens as i64);
}
if let Some(total_tokens) = usage.total_tokens {
span.record("gen_ai.usage.total_tokens", total_tokens as i64);
}
}
}
pub(crate) fn record_tool_input(arguments: &Value) {
Span::current().record("gen_ai.tool.call.arguments", arguments.to_string().as_str());
}
pub(crate) fn record_tool_output(output: &[ContentBlock], is_error: bool) {
let span = Span::current();
span.record("gen_ai.tool.call.result", blocks_json(output).as_str());
span.record("error", is_error);
}
fn value_kind(value: &Value) -> &'static str {
match value {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}