1pub const SEMCONV_VERSION: &str = "1.42.0";
10
11pub const CAPTURE_CONTENT_ENV: &str = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT";
17
18pub const RATEL_SEARCH: &str = "ratel.search";
24pub const EXECUTE_TOOL: &str = "execute_tool";
30pub const RATEL_SKILL_LOAD: &str = "ratel.skill.load";
32pub const RATEL_UPSTREAM_REGISTER: &str = "ratel.upstream.register";
34pub const RATEL_AUTH_FLOW: &str = "ratel.auth.flow";
36
37pub const RATEL_SEARCH_RESULTS: &str = "ratel.search.results";
44pub const GEN_AI_INFERENCE_DETAILS: &str = "gen_ai.client.inference.operation.details";
47
48pub const RATEL_ORIGIN: &str = "ratel.origin";
54pub const RATEL_SEARCH_TARGET: &str = "ratel.search.target";
56pub const RATEL_SEARCH_TOP_K: &str = "ratel.search.top_k";
58pub const RATEL_SEARCH_HIT_COUNT: &str = "ratel.search.hit_count";
60pub const RATEL_SEARCH_QUERY: &str = "ratel.search.query";
62pub const RATEL_TOOL_ARGS_SIZE_BYTES: &str = "ratel.tool.args_size_bytes";
64pub const RATEL_UPSTREAM_SERVER: &str = "ratel.upstream.server";
66pub const RATEL_UPSTREAM_TRANSPORT: &str = "ratel.upstream.transport";
68pub const RATEL_UPSTREAM_TOOL_COUNT: &str = "ratel.upstream.tool_count";
70pub const RATEL_SKILL_ID: &str = "ratel.skill.id";
72pub const RATEL_AUTH_OUTCOME: &str = "ratel.auth.outcome";
74
75pub const GEN_AI_OPERATION_NAME: &str = "gen_ai.operation.name";
85pub const GEN_AI_TOOL_NAME: &str = "gen_ai.tool.name";
87pub const GEN_AI_TOOL_CALL_ID: &str = "gen_ai.tool.call.id";
89pub const GEN_AI_TOOL_CALL_ARGUMENTS: &str = "gen_ai.tool.call.arguments";
91pub const GEN_AI_TOOL_CALL_RESULT: &str = "gen_ai.tool.call.result";
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum Origin {
99 Direct,
100 Agent,
101}
102
103impl Origin {
104 pub fn as_str(self) -> &'static str {
106 match self {
107 Origin::Direct => "direct",
108 Origin::Agent => "agent",
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum SearchTarget {
117 Tool,
118 Skill,
119}
120
121impl SearchTarget {
122 pub fn as_str(self) -> &'static str {
124 match self {
125 SearchTarget::Tool => "tool",
126 SearchTarget::Skill => "skill",
127 }
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum AuthOutcome {
135 Ok,
136 Refreshed,
137 NeedsAuth,
138 Failed,
139}
140
141impl AuthOutcome {
142 pub fn as_str(self) -> &'static str {
144 match self {
145 AuthOutcome::Ok => "ok",
146 AuthOutcome::Refreshed => "refreshed",
147 AuthOutcome::NeedsAuth => "needs_auth",
148 AuthOutcome::Failed => "failed",
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn origin_maps_to_wire_strings() {
159 assert_eq!(Origin::Direct.as_str(), "direct");
160 assert_eq!(Origin::Agent.as_str(), "agent");
161 }
162
163 #[test]
164 fn search_target_maps_to_wire_strings() {
165 assert_eq!(SearchTarget::Tool.as_str(), "tool");
166 assert_eq!(SearchTarget::Skill.as_str(), "skill");
167 }
168
169 #[test]
170 fn auth_outcome_maps_to_wire_strings() {
171 assert_eq!(AuthOutcome::Ok.as_str(), "ok");
172 assert_eq!(AuthOutcome::Refreshed.as_str(), "refreshed");
173 assert_eq!(AuthOutcome::NeedsAuth.as_str(), "needs_auth");
174 assert_eq!(AuthOutcome::Failed.as_str(), "failed");
175 }
176
177 #[test]
178 fn ratel_attribute_keys_match_the_pin() {
179 assert_eq!(RATEL_ORIGIN, "ratel.origin");
180 assert_eq!(RATEL_SEARCH_TARGET, "ratel.search.target");
181 assert_eq!(RATEL_SEARCH_TOP_K, "ratel.search.top_k");
182 assert_eq!(RATEL_SEARCH_HIT_COUNT, "ratel.search.hit_count");
183 assert_eq!(RATEL_SEARCH_QUERY, "ratel.search.query");
184 assert_eq!(RATEL_TOOL_ARGS_SIZE_BYTES, "ratel.tool.args_size_bytes");
185 assert_eq!(RATEL_UPSTREAM_SERVER, "ratel.upstream.server");
186 assert_eq!(RATEL_UPSTREAM_TRANSPORT, "ratel.upstream.transport");
187 assert_eq!(RATEL_UPSTREAM_TOOL_COUNT, "ratel.upstream.tool_count");
188 assert_eq!(RATEL_SKILL_ID, "ratel.skill.id");
189 assert_eq!(RATEL_AUTH_OUTCOME, "ratel.auth.outcome");
190 }
191
192 #[test]
193 fn semconv_pin_is_explicit() {
194 assert_eq!(SEMCONV_VERSION, "1.42.0");
197 }
198
199 #[test]
200 fn content_capture_gate_is_the_ecosystem_env_var() {
201 assert_eq!(
202 CAPTURE_CONTENT_ENV,
203 "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"
204 );
205 }
206
207 #[test]
208 fn span_event_names_match_the_pin() {
209 assert_eq!(RATEL_SEARCH_RESULTS, "ratel.search.results");
210 assert_eq!(
211 GEN_AI_INFERENCE_DETAILS,
212 "gen_ai.client.inference.operation.details"
213 );
214 }
215
216 #[test]
217 fn span_names_match_the_pin() {
218 assert_eq!(RATEL_SEARCH, "ratel.search");
219 assert_eq!(RATEL_SKILL_LOAD, "ratel.skill.load");
220 assert_eq!(RATEL_UPSTREAM_REGISTER, "ratel.upstream.register");
221 assert_eq!(RATEL_AUTH_FLOW, "ratel.auth.flow");
222 }
223
224 #[test]
225 fn tool_invocation_is_the_gen_ai_execute_tool_operation_not_ratel_invoke() {
226 assert_eq!(EXECUTE_TOOL, "execute_tool");
229 assert_ne!(EXECUTE_TOOL, "ratel.invoke");
230 }
231
232 #[test]
233 fn gen_ai_interop_keys_match_the_pin() {
234 assert_eq!(GEN_AI_OPERATION_NAME, "gen_ai.operation.name");
237 assert_eq!(GEN_AI_TOOL_NAME, "gen_ai.tool.name");
238 assert_eq!(GEN_AI_TOOL_CALL_ID, "gen_ai.tool.call.id");
239 assert_eq!(GEN_AI_TOOL_CALL_ARGUMENTS, "gen_ai.tool.call.arguments");
240 assert_eq!(GEN_AI_TOOL_CALL_RESULT, "gen_ai.tool.call.result");
241 for key in [
242 GEN_AI_OPERATION_NAME,
243 GEN_AI_TOOL_NAME,
244 GEN_AI_TOOL_CALL_ID,
245 GEN_AI_TOOL_CALL_ARGUMENTS,
246 GEN_AI_TOOL_CALL_RESULT,
247 ] {
248 assert!(key.starts_with("gen_ai."), "{key} is not under gen_ai.*");
249 assert!(
250 !key.starts_with("ratel."),
251 "{key} must not be renamed into ratel.*"
252 );
253 }
254 }
255
256 #[test]
257 fn every_ratel_attribute_key_is_namespaced() {
258 for key in [
259 RATEL_ORIGIN,
260 RATEL_SEARCH_TARGET,
261 RATEL_SEARCH_TOP_K,
262 RATEL_SEARCH_HIT_COUNT,
263 RATEL_SEARCH_QUERY,
264 RATEL_TOOL_ARGS_SIZE_BYTES,
265 RATEL_UPSTREAM_SERVER,
266 RATEL_UPSTREAM_TRANSPORT,
267 RATEL_UPSTREAM_TOOL_COUNT,
268 RATEL_SKILL_ID,
269 RATEL_AUTH_OUTCOME,
270 ] {
271 assert!(key.starts_with("ratel."), "{key} is not under ratel.*");
272 }
273 }
274
275 #[test]
276 fn attribute_keys_are_unique() {
277 let keys = [
280 RATEL_ORIGIN,
281 RATEL_SEARCH_TARGET,
282 RATEL_SEARCH_TOP_K,
283 RATEL_SEARCH_HIT_COUNT,
284 RATEL_SEARCH_QUERY,
285 RATEL_TOOL_ARGS_SIZE_BYTES,
286 RATEL_UPSTREAM_SERVER,
287 RATEL_UPSTREAM_TRANSPORT,
288 RATEL_UPSTREAM_TOOL_COUNT,
289 RATEL_SKILL_ID,
290 RATEL_AUTH_OUTCOME,
291 GEN_AI_OPERATION_NAME,
292 GEN_AI_TOOL_NAME,
293 GEN_AI_TOOL_CALL_ID,
294 GEN_AI_TOOL_CALL_ARGUMENTS,
295 GEN_AI_TOOL_CALL_RESULT,
296 ];
297 let unique: std::collections::HashSet<&str> = keys.iter().copied().collect();
298 assert_eq!(unique.len(), keys.len(), "duplicate attribute key");
299 }
300
301 #[test]
302 fn span_names_are_unique() {
303 let names = [
305 RATEL_SEARCH,
306 EXECUTE_TOOL,
307 RATEL_SKILL_LOAD,
308 RATEL_UPSTREAM_REGISTER,
309 RATEL_AUTH_FLOW,
310 ];
311 let unique: std::collections::HashSet<&str> = names.iter().copied().collect();
312 assert_eq!(unique.len(), names.len(), "duplicate span name");
313 }
314
315 #[test]
316 fn event_names_are_unique() {
317 let names = [RATEL_SEARCH_RESULTS, GEN_AI_INFERENCE_DETAILS];
319 let unique: std::collections::HashSet<&str> = names.iter().copied().collect();
320 assert_eq!(unique.len(), names.len(), "duplicate event name");
321 }
322}