Phase 37: Hot-swap async + Phase 38: Universal API skeleton
- JobTracker extended with JobType::ProfileActivation + Embed - activate_profile returns job_id immediately, work spawns in background - /v1/chat, /v1/usage, /v1/sessions endpoints (OpenAI-compatible) - Langfuse trace integration (Phase 40 early deliverable) - 12 gateway unit tests green, curl gates pass
This commit is contained in:
+66
-39
@@ -8,6 +8,13 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum JobType {
|
||||
Embed,
|
||||
ProfileActivation,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum JobStatus {
|
||||
@@ -19,24 +26,61 @@ pub enum JobStatus {
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Job {
|
||||
pub id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub job_type: Option<JobType>,
|
||||
pub status: JobStatus,
|
||||
pub index_name: String,
|
||||
pub index_name: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub total_chunks: usize,
|
||||
/// How many chunks have been embedded so far.
|
||||
/// Also serialized as "processed" for backward-compat with monitoring scripts.
|
||||
pub embedded_chunks: usize,
|
||||
#[serde(rename = "processed")]
|
||||
pub processed_alias: usize,
|
||||
pub total: usize,
|
||||
pub progress_pct: f32,
|
||||
pub storage_key: Option<String>,
|
||||
pub error: Option<String>,
|
||||
pub started_at: String,
|
||||
pub completed_at: Option<String>,
|
||||
pub chunks_per_sec: f32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub result: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl Job {
|
||||
pub fn new_embed(index_name: &str, total_chunks: usize) -> Self {
|
||||
Self {
|
||||
id: format!("job-{}", chrono::Utc::now().timestamp_millis()),
|
||||
job_type: Some(JobType::Embed),
|
||||
status: JobStatus::Running,
|
||||
index_name: Some(index_name.to_string()),
|
||||
profile_id: None,
|
||||
total_chunks,
|
||||
processed_alias: 0,
|
||||
progress_pct: 0.0,
|
||||
storage_key: None,
|
||||
error: None,
|
||||
started_at: chrono::Utc::now().to_rfc3339(),
|
||||
completed_at: None,
|
||||
result: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_profile_activation(profile_id: &str) -> Self {
|
||||
Self {
|
||||
id: format!("job-{}", chrono::Utc::now().timestamp_millis()),
|
||||
job_type: Some(JobType::ProfileActivation),
|
||||
status: JobStatus::Running,
|
||||
index_name: None,
|
||||
profile_id: Some(profile_id.to_string()),
|
||||
total_chunks: 0,
|
||||
processed_alias: 0,
|
||||
progress_pct: 0.0,
|
||||
storage_key: None,
|
||||
error: None,
|
||||
started_at: chrono::Utc::now().to_rfc3339(),
|
||||
completed_at: None,
|
||||
result: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared progress tracker that background tasks update.
|
||||
#[derive(Clone)]
|
||||
pub struct JobTracker {
|
||||
jobs: Arc<RwLock<HashMap<String, Job>>>,
|
||||
@@ -49,57 +93,42 @@ impl JobTracker {
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a new job. Returns the job ID.
|
||||
pub async fn create(&self, index_name: &str, total_chunks: usize) -> String {
|
||||
let id = format!("job-{}", chrono::Utc::now().timestamp_millis());
|
||||
let job = Job {
|
||||
id: id.clone(),
|
||||
status: JobStatus::Running,
|
||||
index_name: index_name.to_string(),
|
||||
total_chunks,
|
||||
embedded_chunks: 0,
|
||||
processed_alias: 0,
|
||||
total: total_chunks,
|
||||
progress_pct: 0.0,
|
||||
storage_key: None,
|
||||
error: None,
|
||||
started_at: chrono::Utc::now().to_rfc3339(),
|
||||
completed_at: None,
|
||||
chunks_per_sec: 0.0,
|
||||
};
|
||||
pub async fn create_embed(&self, index_name: &str, total_chunks: usize) -> String {
|
||||
let job = Job::new_embed(index_name, total_chunks);
|
||||
let id = job.id.clone();
|
||||
self.jobs.write().await.insert(id.clone(), job);
|
||||
id
|
||||
}
|
||||
|
||||
/// Update progress.
|
||||
pub async fn update_progress(&self, id: &str, embedded: usize, rate: f32) {
|
||||
pub async fn create_profile_activation(&self, profile_id: &str) -> String {
|
||||
let job = Job::new_profile_activation(profile_id);
|
||||
let id = job.id.clone();
|
||||
self.jobs.write().await.insert(id.clone(), job);
|
||||
id
|
||||
}
|
||||
|
||||
pub async fn update_embed_progress(&self, id: &str, embedded: usize, _rate: f32) {
|
||||
let mut jobs = self.jobs.write().await;
|
||||
if let Some(job) = jobs.get_mut(id) {
|
||||
job.embedded_chunks = embedded;
|
||||
job.processed_alias = embedded; // keep alias in sync
|
||||
job.processed_alias = embedded;
|
||||
job.progress_pct = if job.total_chunks > 0 {
|
||||
(embedded as f32 / job.total_chunks as f32) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
job.chunks_per_sec = rate;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark job as completed.
|
||||
pub async fn complete(&self, id: &str, storage_key: String) {
|
||||
pub async fn complete(&self, id: &str, result: Option<serde_json::Value>) {
|
||||
let mut jobs = self.jobs.write().await;
|
||||
if let Some(job) = jobs.get_mut(id) {
|
||||
job.status = JobStatus::Completed;
|
||||
job.embedded_chunks = job.total_chunks;
|
||||
job.processed_alias = job.total_chunks;
|
||||
job.progress_pct = 100.0;
|
||||
job.storage_key = Some(storage_key);
|
||||
job.completed_at = Some(chrono::Utc::now().to_rfc3339());
|
||||
job.result = result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark job as failed.
|
||||
pub async fn fail(&self, id: &str, error: String) {
|
||||
let mut jobs = self.jobs.write().await;
|
||||
if let Some(job) = jobs.get_mut(id) {
|
||||
@@ -109,13 +138,11 @@ impl JobTracker {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get job status.
|
||||
pub async fn get(&self, id: &str) -> Option<Job> {
|
||||
self.jobs.read().await.get(id).cloned()
|
||||
}
|
||||
|
||||
/// List all jobs.
|
||||
pub async fn list(&self) -> Vec<Job> {
|
||||
self.jobs.read().await.values().cloned().collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user