use crate::chat::ToolCall;
use crate::otel::conventions as c;
use crate::otel::{attrs, error as otel_error};
use tracing::Span;
use tracing::field::Empty;
pub fn set_error(span: &Span, error_type: impl AsRef<str>) {
span.record(c::OTEL_STATUS_CODE, c::STATUS_ERROR);
span.record(c::ERROR_TYPE, error_type.as_ref());
}
pub fn set_genai_error(span: &Span, error: &crate::Error) {
set_error(span, otel_error::error_type(error));
}
#[derive(Debug, Clone, Default)]
pub struct ExecuteToolSpan {
name: String,
call_id: Option<String>,
description: Option<String>,
tool_type: Option<String>,
arguments: Option<String>,
}
impl ExecuteToolSpan {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Default::default()
}
}
pub fn with_call_id(mut self, call_id: impl Into<String>) -> Self {
self.call_id = Some(call_id.into());
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_tool_type(mut self, tool_type: impl Into<String>) -> Self {
self.tool_type = Some(tool_type.into());
self
}
pub fn with_arguments(mut self, arguments: impl Into<String>) -> Self {
self.arguments = Some(arguments.into());
self
}
pub fn start(&self) -> Span {
let span_name = format!("{} {}", c::OP_EXECUTE_TOOL, self.name);
let span = tracing::info_span!(
"genai.execute_tool",
"otel.name" = span_name.as_str(),
"otel.kind" = c::KIND_INTERNAL,
"otel.status_code" = Empty,
"error.type" = Empty,
"gen_ai.operation.name" = c::OP_EXECUTE_TOOL,
"gen_ai.tool.name" = self.name.as_str(),
"gen_ai.tool.call.id" = Empty,
"gen_ai.tool.description" = Empty,
"gen_ai.tool.type" = Empty,
"gen_ai.tool.call.arguments" = Empty,
"gen_ai.tool.call.result" = Empty,
);
if let Some(call_id) = &self.call_id {
span.record(c::TOOL_CALL_ID, call_id.as_str());
}
if let Some(description) = &self.description {
span.record(c::TOOL_DESCRIPTION, description.as_str());
}
if let Some(tool_type) = &self.tool_type {
span.record(c::TOOL_TYPE, tool_type.as_str());
}
if attrs::capture_content()
&& let Some(arguments) = &self.arguments
{
span.record(c::TOOL_CALL_ARGUMENTS, arguments.as_str());
}
span
}
pub fn record_result(span: &Span, result: impl AsRef<str>) {
if attrs::capture_content() {
span.record(c::TOOL_CALL_RESULT, result.as_ref());
}
}
}
pub fn execute_tool_span(tool_call: &ToolCall) -> Span {
let mut builder = ExecuteToolSpan::new(tool_call.fn_name.clone())
.with_call_id(tool_call.call_id.clone())
.with_tool_type("function");
if let Ok(arguments) = serde_json::to_string(&tool_call.fn_arguments) {
builder = builder.with_arguments(arguments);
}
builder.start()
}
#[derive(Debug, Clone)]
pub struct AgentSpan {
operation: &'static str,
kind: &'static str,
name: Option<String>,
id: Option<String>,
description: Option<String>,
provider: Option<String>,
model: Option<String>,
}
impl AgentSpan {
pub fn create_agent() -> Self {
Self::new(c::OP_CREATE_AGENT, c::KIND_CLIENT)
}
pub fn invoke_agent() -> Self {
Self::new(c::OP_INVOKE_AGENT, c::KIND_CLIENT)
}
fn new(operation: &'static str, kind: &'static str) -> Self {
Self {
operation,
kind,
name: None,
id: None,
description: None,
provider: None,
model: None,
}
}
pub fn internal(mut self) -> Self {
self.kind = c::KIND_INTERNAL;
self
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_provider(mut self, provider: impl Into<String>) -> Self {
self.provider = Some(provider.into());
self
}
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
pub fn start(&self) -> Span {
let span_name = match &self.name {
Some(name) => format!("{} {name}", self.operation),
None => self.operation.to_string(),
};
let span = tracing::info_span!(
"genai.agent",
"otel.name" = span_name.as_str(),
"otel.kind" = self.kind,
"otel.status_code" = Empty,
"error.type" = Empty,
"gen_ai.operation.name" = self.operation,
"gen_ai.agent.name" = Empty,
"gen_ai.agent.id" = Empty,
"gen_ai.agent.description" = Empty,
"gen_ai.provider.name" = Empty,
"gen_ai.request.model" = Empty,
);
if let Some(name) = &self.name {
span.record(c::AGENT_NAME, name.as_str());
}
if let Some(id) = &self.id {
span.record(c::AGENT_ID, id.as_str());
}
if let Some(description) = &self.description {
span.record(c::AGENT_DESCRIPTION, description.as_str());
}
if let Some(provider) = &self.provider {
span.record(c::PROVIDER_NAME, provider.as_str());
}
if let Some(model) = &self.model {
span.record(c::REQUEST_MODEL, model.as_str());
}
span
}
}
pub fn invoke_workflow_span(workflow_name: impl AsRef<str>) -> Span {
let workflow_name = workflow_name.as_ref();
let span_name = format!("{} {workflow_name}", c::OP_INVOKE_WORKFLOW);
let span = tracing::info_span!(
"genai.invoke_workflow",
"otel.name" = span_name.as_str(),
"otel.kind" = c::KIND_INTERNAL,
"otel.status_code" = Empty,
"error.type" = Empty,
"gen_ai.operation.name" = c::OP_INVOKE_WORKFLOW,
"gen_ai.workflow.name" = workflow_name,
);
span
}
pub fn plan_span(agent_name: Option<&str>) -> Span {
let span_name = match agent_name {
Some(name) => format!("{} {name}", c::OP_PLAN),
None => c::OP_PLAN.to_string(),
};
let span = tracing::info_span!(
"genai.plan",
"otel.name" = span_name.as_str(),
"otel.kind" = c::KIND_INTERNAL,
"otel.status_code" = Empty,
"error.type" = Empty,
"gen_ai.operation.name" = c::OP_PLAN,
"gen_ai.agent.name" = Empty,
);
if let Some(name) = agent_name {
span.record(c::AGENT_NAME, name);
}
span
}