bge_m3_embedding_server/handler/
both.rs1use std::sync::Arc;
18use std::time::Instant;
19
20use axum::{Json, extract::State, http::HeaderMap};
21
22use super::common::{check_ready, collect_x_headers, validate_input};
23use crate::error::AppError;
24use crate::models::{DualEmbeddingData, DualRequest, DualResponse, SparseValues, Usage};
25use crate::state::AppState;
26
27#[allow(clippy::cast_possible_truncation)]
40#[tracing::instrument(
41 skip(state, req, headers),
42 fields(
43 batch_size,
44 prompt_tokens,
45 chunks,
46 max_chunk_seq,
47 tokenize_ms,
48 inference_ms,
49 queue_wait_ms,
50 total_ms,
51 )
52)]
53pub async fn both_embeddings(
54 State(state): State<Arc<AppState>>,
55 headers: HeaderMap,
56 Json(req): Json<DualRequest>,
57) -> Result<Json<DualResponse>, AppError> {
58 check_ready(&state)?;
59 let x_headers = collect_x_headers(&headers);
60 let texts = req.input.0;
61 drop(req.model);
62 validate_input(&texts, state.max_batch)?;
63 let batch_size = texts.len();
64 tracing::Span::current().record("batch_size", batch_size);
65
66 let prompt_tokens: usize = texts.iter().map(|t| t.chars().count() / 4 + 1).sum();
67 tracing::Span::current().record("prompt_tokens", prompt_tokens);
68
69 let t0 = Instant::now();
70
71 let _permit = Arc::clone(&state.request_permits)
72 .acquire_owned()
73 .await
74 .expect("request semaphore is never closed");
75
76 let queue_wait_ms = u64::try_from(t0.elapsed().as_millis()).unwrap_or(u64::MAX);
77
78 let (pairs, embed_stats) = state.pool.both(texts).await?;
79
80 let total_ms = u64::try_from(t0.elapsed().as_millis()).unwrap_or(u64::MAX);
81 tracing::Span::current()
82 .record("chunks", embed_stats.chunks)
83 .record("max_chunk_seq", embed_stats.max_chunk_seq)
84 .record("tokenize_ms", embed_stats.tokenize_ms)
85 .record("inference_ms", embed_stats.inference_ms)
86 .record("queue_wait_ms", queue_wait_ms)
87 .record("total_ms", total_ms);
88 let x_headers_val =
89 (!x_headers.is_empty()).then(|| serde_json::to_string(&x_headers).unwrap_or_default());
90 tracing::info!(
91 route = "both",
92 batch_size,
93 prompt_tokens,
94 chunks = embed_stats.chunks,
95 max_chunk_seq = embed_stats.max_chunk_seq,
96 total_token_positions = embed_stats.total_token_positions,
97 seq_len_min = embed_stats.seq_len_min,
98 seq_len_max = embed_stats.seq_len_max,
99 seq_len_mean = embed_stats.seq_len_mean,
100 seq_len_p95 = embed_stats.seq_len_p95,
101 tokenize_ms = embed_stats.tokenize_ms,
102 inference_ms = embed_stats.inference_ms,
103 queue_wait_ms,
104 total_ms,
105 x_headers = x_headers_val,
106 "embedding request complete"
107 );
108
109 let data = pairs
110 .into_iter()
111 .enumerate()
112 .map(|(index, pair)| DualEmbeddingData {
113 index,
114 embedding: pair.dense,
115 sparse_values: SparseValues {
116 indices: pair.sparse.indices.iter().map(|i| *i as u32).collect(),
117 values: pair.sparse.values,
118 },
119 })
120 .collect();
121
122 Ok(Json(DualResponse {
123 object: "list",
124 model: "bge-m3",
125 data,
126 usage: Usage {
127 prompt_tokens,
128 total_tokens: prompt_tokens,
129 },
130 }))
131}