[model-gateway] Tighten visibility in modules and remove unused re-exports (#16524)
This commit is contained in:
@@ -21,8 +21,8 @@ use crate::{
|
|||||||
tokenizer::{
|
tokenizer::{
|
||||||
cache::{CacheConfig, CachedTokenizer},
|
cache::{CacheConfig, CachedTokenizer},
|
||||||
factory as tokenizer_factory,
|
factory as tokenizer_factory,
|
||||||
|
registry::TokenizerRegistry,
|
||||||
traits::Tokenizer,
|
traits::Tokenizer,
|
||||||
TokenizerRegistry,
|
|
||||||
},
|
},
|
||||||
tool_parser::ParserFactory as ToolParserFactory,
|
tool_parser::ParserFactory as ToolParserFactory,
|
||||||
wasm::{config::WasmRuntimeConfig, module_manager::WasmModuleManager},
|
wasm::{config::WasmRuntimeConfig, module_manager::WasmModuleManager},
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ fn is_link_local_v6(ip: &Ipv6Addr) -> bool {
|
|||||||
///
|
///
|
||||||
/// For testing purposes, HTTP is allowed for localhost/127.0.0.1 only.
|
/// For testing purposes, HTTP is allowed for localhost/127.0.0.1 only.
|
||||||
/// In production, only HTTPS should be used.
|
/// In production, only HTTPS should be used.
|
||||||
pub fn validate_url(url_str: &str) -> Result<Url, JwksError> {
|
pub(crate) fn validate_url(url_str: &str) -> Result<Url, JwksError> {
|
||||||
let url = Url::parse(url_str)
|
let url = Url::parse(url_str)
|
||||||
.map_err(|e| JwksError::InvalidUrl(format!("Failed to parse URL: {}", e)))?;
|
.map_err(|e| JwksError::InvalidUrl(format!("Failed to parse URL: {}", e)))?;
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ impl CachedJwks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// JWKS provider with caching and automatic refresh.
|
/// JWKS provider with caching and automatic refresh.
|
||||||
pub struct JwksProvider {
|
pub(crate) struct JwksProvider {
|
||||||
/// HTTP client for fetching JWKS
|
/// HTTP client for fetching JWKS
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
/// JWKS endpoint URL (validated)
|
/// JWKS endpoint URL (validated)
|
||||||
@@ -304,6 +304,7 @@ impl JwksProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the JWKS URI.
|
/// Get the JWKS URI.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn jwks_uri(&self) -> &str {
|
pub fn jwks_uri(&self) -> &str {
|
||||||
&self.jwks_uri
|
&self.jwks_uri
|
||||||
}
|
}
|
||||||
@@ -411,6 +412,7 @@ impl JwksProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Force refresh the JWKS cache.
|
/// Force refresh the JWKS cache.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn refresh(&self) -> Result<(), JwksError> {
|
pub async fn refresh(&self) -> Result<(), JwksError> {
|
||||||
let jwks = self.fetch_jwks().await?;
|
let jwks = self.fetch_jwks().await?;
|
||||||
let mut cache = self.cache.write();
|
let mut cache = self.cache.write();
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ pub enum JwtValidatorError {
|
|||||||
|
|
||||||
/// Standard JWT claims we extract.
|
/// Standard JWT claims we extract.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct StandardClaims {
|
pub(crate) struct StandardClaims {
|
||||||
/// Subject (user ID)
|
/// Subject (user ID)
|
||||||
pub sub: Option<String>,
|
pub sub: Option<String>,
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ pub struct StandardClaims {
|
|||||||
/// Audience claim can be a single string or an array.
|
/// Audience claim can be a single string or an array.
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Audience {
|
pub(crate) enum Audience {
|
||||||
Single(String),
|
Single(String),
|
||||||
Multiple(Vec<String>),
|
Multiple(Vec<String>),
|
||||||
#[default]
|
#[default]
|
||||||
@@ -123,6 +123,7 @@ pub enum Audience {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Audience {
|
impl Audience {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn contains(&self, aud: &str) -> bool {
|
pub fn contains(&self, aud: &str) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Audience::Single(s) => s == aud,
|
Audience::Single(s) => s == aud,
|
||||||
@@ -151,7 +152,8 @@ pub struct ValidatedToken {
|
|||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
|
||||||
/// Full claims for additional processing
|
/// Full claims for additional processing
|
||||||
pub claims: StandardClaims,
|
#[allow(dead_code)]
|
||||||
|
pub(crate) claims: StandardClaims,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JTI (JWT ID) cache entry with expiration tracking.
|
/// JTI (JWT ID) cache entry with expiration tracking.
|
||||||
@@ -181,12 +183,13 @@ pub struct JwtValidator {
|
|||||||
|
|
||||||
impl JwtValidator {
|
impl JwtValidator {
|
||||||
/// Create a new JWT validator with explicit JWKS URI.
|
/// Create a new JWT validator with explicit JWKS URI.
|
||||||
pub fn new(config: JwtConfig, jwks_provider: Arc<JwksProvider>) -> Self {
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn new(config: JwtConfig, jwks_provider: Arc<JwksProvider>) -> Self {
|
||||||
Self::new_with_options(config, jwks_provider, false)
|
Self::new_with_options(config, jwks_provider, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new JWT validator with optional JTI replay protection.
|
/// Create a new JWT validator with optional JTI replay protection.
|
||||||
pub fn new_with_options(
|
pub(crate) fn new_with_options(
|
||||||
config: JwtConfig,
|
config: JwtConfig,
|
||||||
jwks_provider: Arc<JwksProvider>,
|
jwks_provider: Arc<JwksProvider>,
|
||||||
enable_jti_check: bool,
|
enable_jti_check: bool,
|
||||||
@@ -500,7 +503,8 @@ impl JwtValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the JWKS provider.
|
/// Get a reference to the JWKS provider.
|
||||||
pub fn jwks_provider(&self) -> &Arc<JwksProvider> {
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn jwks_provider(&self) -> &Arc<JwksProvider> {
|
||||||
&self.jwks_provider
|
&self.jwks_provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
pub mod builder;
|
pub mod builder;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod validation;
|
pub(crate) mod validation;
|
||||||
|
|
||||||
pub use builder::*;
|
pub use builder::*;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
pub use validation::*;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Configuration validator
|
/// Configuration validator
|
||||||
pub struct ConfigValidator;
|
pub(crate) struct ConfigValidator;
|
||||||
|
|
||||||
impl ConfigValidator {
|
impl ConfigValidator {
|
||||||
pub fn validate(config: &RouterConfig) -> ConfigResult<()> {
|
pub(crate) fn validate(config: &RouterConfig) -> ConfigResult<()> {
|
||||||
Self::validate_mode(&config.mode)?;
|
Self::validate_mode(&config.mode)?;
|
||||||
Self::validate_policy(&config.policy)?;
|
Self::validate_policy(&config.policy)?;
|
||||||
Self::validate_server_settings(config)?;
|
Self::validate_server_settings(config)?;
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ fn merge_family(a: PrometheusFamily, b: PrometheusFamily) -> anyhow::Result<Prom
|
|||||||
.map_err(|e| anyhow::anyhow!("failed to merge samples: {e:?}"))
|
.map_err(|e| anyhow::anyhow!("failed to merge samples: {e:?}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_reduce<I, T, E, F>(iterable: I, f: F) -> Result<Option<T>, E>
|
fn try_reduce<I, T, E, F>(iterable: I, f: F) -> Result<Option<T>, E>
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = T>,
|
I: IntoIterator<Item = T>,
|
||||||
F: FnMut(T, T) -> Result<T, E>,
|
F: FnMut(T, T) -> Result<T, E>,
|
||||||
|
|||||||
@@ -27,19 +27,17 @@ pub mod worker_manager;
|
|||||||
pub mod worker_registry;
|
pub mod worker_registry;
|
||||||
pub mod worker_service;
|
pub mod worker_service;
|
||||||
|
|
||||||
pub use circuit_breaker::{
|
// Re-export commonly used types for convenience
|
||||||
CircuitBreaker, CircuitBreakerConfig, CircuitBreakerStats, CircuitState,
|
pub use circuit_breaker::{CircuitBreaker, CircuitBreakerConfig};
|
||||||
};
|
|
||||||
pub use error::{WorkerError, WorkerResult};
|
pub use error::{WorkerError, WorkerResult};
|
||||||
pub use job_queue::{Job, JobQueue, JobQueueConfig};
|
pub use job_queue::{Job, JobQueue, JobQueueConfig};
|
||||||
pub use model_card::{ModelCard, ProviderType};
|
pub use model_card::{ModelCard, ProviderType};
|
||||||
pub use model_type::{Endpoint, ModelType};
|
pub use retry::{is_retryable_status, RetryExecutor};
|
||||||
pub use retry::{is_retryable_status, BackoffCalculator, RetryError, RetryExecutor};
|
|
||||||
pub use worker::{
|
pub use worker::{
|
||||||
attach_guards_to_response, worker_to_info, BasicWorker, ConnectionMode, DPAwareWorker,
|
attach_guards_to_response, BasicWorker, ConnectionMode, HealthConfig, RuntimeType, Worker,
|
||||||
HealthChecker, HealthConfig, RuntimeType, Worker, WorkerFactory, WorkerLoadGuard, WorkerType,
|
WorkerLoadGuard, WorkerType,
|
||||||
};
|
};
|
||||||
pub use worker_builder::{BasicWorkerBuilder, DPAwareWorkerBuilder};
|
pub use worker_builder::{BasicWorkerBuilder, DPAwareWorkerBuilder};
|
||||||
pub use worker_manager::{LoadMonitor, WorkerManager};
|
pub use worker_manager::{LoadMonitor, WorkerManager};
|
||||||
pub use worker_registry::{HashRing, WorkerId, WorkerRegistry, WorkerRegistryStats};
|
pub use worker_registry::{HashRing, WorkerRegistry};
|
||||||
pub use worker_service::{WorkerService, WorkerServiceError};
|
pub use worker_service::WorkerService;
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ impl std::fmt::Display for ProviderType {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use smg::core::{ModelCard, ModelType, ProviderType};
|
/// use smg::core::{model_type::ModelType, ModelCard, ProviderType};
|
||||||
///
|
///
|
||||||
/// let card = ModelCard::new("meta-llama/Llama-3.1-8B-Instruct")
|
/// let card = ModelCard::new("meta-llama/Llama-3.1-8B-Instruct")
|
||||||
/// .with_display_name("Llama 3.1 8B Instruct")
|
/// .with_display_name("Llama 3.1 8B Instruct")
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ use tracing::{debug, info};
|
|||||||
use crate::{
|
use crate::{
|
||||||
app_context::AppContext,
|
app_context::AppContext,
|
||||||
core::{
|
core::{
|
||||||
model_card::ModelCard, BasicWorkerBuilder, CircuitBreakerConfig, ConnectionMode,
|
circuit_breaker::CircuitBreakerConfig,
|
||||||
HealthConfig, RuntimeType, Worker, WorkerType,
|
model_card::ModelCard,
|
||||||
|
worker::{HealthConfig, RuntimeType, WorkerType},
|
||||||
|
BasicWorkerBuilder, ConnectionMode, Worker,
|
||||||
},
|
},
|
||||||
protocols::worker_spec::WorkerConfigRequest,
|
protocols::worker_spec::WorkerConfigRequest,
|
||||||
workflow::{StepExecutor, StepResult, WorkflowContext, WorkflowError, WorkflowResult},
|
workflow::{StepExecutor, StepResult, WorkflowContext, WorkflowError, WorkflowResult},
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ use super::discover_dp::DpInfo;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app_context::AppContext,
|
app_context::AppContext,
|
||||||
core::{
|
core::{
|
||||||
BasicWorkerBuilder, CircuitBreakerConfig, ConnectionMode, DPAwareWorkerBuilder,
|
circuit_breaker::CircuitBreakerConfig,
|
||||||
HealthConfig, ModelCard, RuntimeType, Worker, WorkerType, UNKNOWN_MODEL_ID,
|
model_card::ModelCard,
|
||||||
|
worker::{HealthConfig, RuntimeType, WorkerType},
|
||||||
|
BasicWorkerBuilder, ConnectionMode, DPAwareWorkerBuilder, Worker, UNKNOWN_MODEL_ID,
|
||||||
},
|
},
|
||||||
protocols::worker_spec::WorkerConfigRequest,
|
protocols::worker_spec::WorkerConfigRequest,
|
||||||
workflow::{StepExecutor, StepId, StepResult, WorkflowContext, WorkflowError, WorkflowResult},
|
workflow::{StepExecutor, StepId, StepResult, WorkflowContext, WorkflowError, WorkflowResult},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::{
|
|||||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||||
Arc, LazyLock, RwLock as StdRwLock,
|
Arc, LazyLock, RwLock as StdRwLock,
|
||||||
},
|
},
|
||||||
time::{Duration, Instant},
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -13,11 +13,11 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tokio::{sync::OnceCell, time};
|
use tokio::{sync::OnceCell, time};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
CircuitBreaker, Endpoint, ModelCard, ModelType, ProviderType, WorkerError, WorkerResult,
|
model_card::{ModelCard, ProviderType},
|
||||||
UNKNOWN_MODEL_ID,
|
model_type::{Endpoint, ModelType},
|
||||||
|
CircuitBreaker, WorkerError, WorkerResult, UNKNOWN_MODEL_ID,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{BasicWorkerBuilder, DPAwareWorkerBuilder},
|
|
||||||
observability::metrics::{metrics_labels, Metrics},
|
observability::metrics::{metrics_labels, Metrics},
|
||||||
protocols::worker_spec::WorkerInfo,
|
protocols::worker_spec::WorkerInfo,
|
||||||
routers::grpc::client::GrpcClient,
|
routers::grpc::client::GrpcClient,
|
||||||
@@ -1000,94 +1000,6 @@ impl Worker for DPAwareWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Worker factory for creating workers of different types
|
|
||||||
pub struct WorkerFactory;
|
|
||||||
|
|
||||||
impl WorkerFactory {
|
|
||||||
/// Create a DP-aware worker of specified type
|
|
||||||
pub fn create_dp_aware(
|
|
||||||
base_url: String,
|
|
||||||
dp_rank: usize,
|
|
||||||
dp_size: usize,
|
|
||||||
worker_type: WorkerType,
|
|
||||||
api_key: Option<String>,
|
|
||||||
) -> Box<dyn Worker> {
|
|
||||||
let mut builder =
|
|
||||||
DPAwareWorkerBuilder::new(base_url, dp_rank, dp_size).worker_type(worker_type);
|
|
||||||
if let Some(api_key) = api_key {
|
|
||||||
builder = builder.api_key(api_key);
|
|
||||||
}
|
|
||||||
Box::new(builder.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Static health validation before creating a worker
|
|
||||||
/// This replaces wait_for_worker_health in handlers
|
|
||||||
pub async fn validate_health(url: &str, timeout_secs: u64) -> WorkerResult<()> {
|
|
||||||
let start_time = Instant::now();
|
|
||||||
let timeout = Duration::from_secs(timeout_secs);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if start_time.elapsed() > timeout {
|
|
||||||
return Err(WorkerError::HealthCheckFailed {
|
|
||||||
url: url.to_string(),
|
|
||||||
reason: format!(
|
|
||||||
"Timeout {}s waiting for worker to become healthy",
|
|
||||||
timeout_secs
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: This static function doesn't have access to worker's API key
|
|
||||||
// API key authentication is handled in the worker instance's check_health_async method
|
|
||||||
match WORKER_CLIENT
|
|
||||||
.get(format!("{}/health", url))
|
|
||||||
.timeout(Duration::from_secs(5))
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(res) if res.status().is_success() => {
|
|
||||||
tracing::info!("Worker {} is healthy", url);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Ok(res) => {
|
|
||||||
tracing::warn!(
|
|
||||||
"Worker {} health check failed with status: {}",
|
|
||||||
url,
|
|
||||||
res.status()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("Failed to contact worker {}: {}", url, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time::sleep(Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a list of worker URLs to worker trait objects
|
|
||||||
pub fn urls_to_workers(urls: Vec<String>, api_key: Option<String>) -> Vec<Box<dyn Worker>> {
|
|
||||||
urls.into_iter()
|
|
||||||
.map(|url| {
|
|
||||||
let worker_builder = BasicWorkerBuilder::new(url).worker_type(WorkerType::Regular);
|
|
||||||
|
|
||||||
let worker = if let Some(ref api_key) = api_key {
|
|
||||||
worker_builder.api_key(api_key.clone()).build()
|
|
||||||
} else {
|
|
||||||
worker_builder.build()
|
|
||||||
};
|
|
||||||
|
|
||||||
Box::new(worker) as Box<dyn Worker>
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert worker trait objects back to URLs
|
|
||||||
pub fn workers_to_urls(workers: &[Box<dyn Worker>]) -> Vec<String> {
|
|
||||||
workers.iter().map(|w| w.url().to_string()).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RAII guard for worker load management
|
/// RAII guard for worker load management
|
||||||
///
|
///
|
||||||
/// Automatically decrements worker load when dropped. Can be attached to
|
/// Automatically decrements worker load when dropped. Can be attached to
|
||||||
@@ -1204,7 +1116,8 @@ impl http_body::Body for MultiGuardedBody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Health checker handle with graceful shutdown
|
/// Health checker handle with graceful shutdown
|
||||||
pub struct HealthChecker {
|
pub(crate) struct HealthChecker {
|
||||||
|
#[allow(dead_code)]
|
||||||
handle: tokio::task::JoinHandle<()>,
|
handle: tokio::task::JoinHandle<()>,
|
||||||
shutdown: Arc<AtomicBool>,
|
shutdown: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
@@ -1224,6 +1137,7 @@ impl HealthChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shutdown the health checker gracefully
|
/// Shutdown the health checker gracefully
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn shutdown(self) {
|
pub async fn shutdown(self) {
|
||||||
self.shutdown.store(true, Ordering::Release);
|
self.shutdown.store(true, Ordering::Release);
|
||||||
let _ = self.handle.await;
|
let _ = self.handle.await;
|
||||||
@@ -1280,7 +1194,10 @@ mod tests {
|
|||||||
use std::{thread, time::Duration};
|
use std::{thread, time::Duration};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::core::{CircuitBreakerConfig, CircuitState};
|
use crate::core::{
|
||||||
|
circuit_breaker::{CircuitBreakerConfig, CircuitState},
|
||||||
|
DPAwareWorkerBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_worker_type_display() {
|
fn test_worker_type_display() {
|
||||||
@@ -1585,6 +1502,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_regular_worker() {
|
fn test_create_regular_worker() {
|
||||||
|
use crate::core::BasicWorkerBuilder;
|
||||||
let worker: Box<dyn Worker> = Box::new(
|
let worker: Box<dyn Worker> = Box::new(
|
||||||
BasicWorkerBuilder::new("http://regular:8080")
|
BasicWorkerBuilder::new("http://regular:8080")
|
||||||
.worker_type(WorkerType::Regular)
|
.worker_type(WorkerType::Regular)
|
||||||
@@ -1596,6 +1514,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_prefill_worker() {
|
fn test_create_prefill_worker() {
|
||||||
|
use crate::core::BasicWorkerBuilder;
|
||||||
let worker1: Box<dyn Worker> = Box::new(
|
let worker1: Box<dyn Worker> = Box::new(
|
||||||
BasicWorkerBuilder::new("http://prefill:8080")
|
BasicWorkerBuilder::new("http://prefill:8080")
|
||||||
.worker_type(WorkerType::Prefill {
|
.worker_type(WorkerType::Prefill {
|
||||||
@@ -1628,6 +1547,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_decode_worker() {
|
fn test_create_decode_worker() {
|
||||||
|
use crate::core::BasicWorkerBuilder;
|
||||||
let worker: Box<dyn Worker> = Box::new(
|
let worker: Box<dyn Worker> = Box::new(
|
||||||
BasicWorkerBuilder::new("http://decode:8080")
|
BasicWorkerBuilder::new("http://decode:8080")
|
||||||
.worker_type(WorkerType::Decode)
|
.worker_type(WorkerType::Decode)
|
||||||
@@ -1637,36 +1557,6 @@ mod tests {
|
|||||||
assert_eq!(worker.worker_type(), &WorkerType::Decode);
|
assert_eq!(worker.worker_type(), &WorkerType::Decode);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_urls_to_workers() {
|
|
||||||
let urls = vec!["http://w1:8080".to_string(), "http://w2:8080".to_string()];
|
|
||||||
|
|
||||||
let workers = urls_to_workers(urls, Some("test_api_key".to_string()));
|
|
||||||
assert_eq!(workers.len(), 2);
|
|
||||||
assert_eq!(workers[0].url(), "http://w1:8080");
|
|
||||||
assert_eq!(workers[1].url(), "http://w2:8080");
|
|
||||||
assert_eq!(workers[0].worker_type(), &WorkerType::Regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_workers_to_urls() {
|
|
||||||
let workers: Vec<Box<dyn Worker>> = vec![
|
|
||||||
Box::new(
|
|
||||||
BasicWorkerBuilder::new("http://w1:8080")
|
|
||||||
.worker_type(WorkerType::Regular)
|
|
||||||
.build(),
|
|
||||||
),
|
|
||||||
Box::new(
|
|
||||||
BasicWorkerBuilder::new("http://w2:8080")
|
|
||||||
.worker_type(WorkerType::Regular)
|
|
||||||
.build(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
let urls = workers_to_urls(&workers);
|
|
||||||
assert_eq!(urls, vec!["http://w1:8080", "http://w2:8080"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_check_health_async() {
|
async fn test_check_health_async() {
|
||||||
use crate::core::BasicWorkerBuilder;
|
use crate::core::BasicWorkerBuilder;
|
||||||
@@ -1819,45 +1709,6 @@ mod tests {
|
|||||||
assert_eq!(dp_worker.processed_requests(), 1);
|
assert_eq!(dp_worker.processed_requests(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_factory_create_dp_aware() {
|
|
||||||
let worker = WorkerFactory::create_dp_aware(
|
|
||||||
"http://worker1:8080".to_string(),
|
|
||||||
1,
|
|
||||||
4,
|
|
||||||
WorkerType::Regular,
|
|
||||||
Some("test_api_key".to_string()),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(worker.url(), "http://worker1:8080@1");
|
|
||||||
assert!(worker.is_dp_aware());
|
|
||||||
assert_eq!(worker.dp_rank(), Some(1));
|
|
||||||
assert_eq!(worker.dp_size(), Some(4));
|
|
||||||
assert_eq!(worker.worker_type(), &WorkerType::Regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_factory_create_dp_aware_prefill() {
|
|
||||||
let worker = WorkerFactory::create_dp_aware(
|
|
||||||
"http://worker1:8080".to_string(),
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
WorkerType::Prefill {
|
|
||||||
bootstrap_port: Some(8090),
|
|
||||||
},
|
|
||||||
Some("test_api_key".to_string()),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(worker.url(), "http://worker1:8080@0");
|
|
||||||
assert!(worker.is_dp_aware());
|
|
||||||
assert_eq!(
|
|
||||||
worker.worker_type(),
|
|
||||||
&WorkerType::Prefill {
|
|
||||||
bootstrap_port: Some(8090)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_worker_circuit_breaker() {
|
fn test_worker_circuit_breaker() {
|
||||||
use crate::core::BasicWorkerBuilder;
|
use crate::core::BasicWorkerBuilder;
|
||||||
@@ -1929,6 +1780,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_mixed_worker_types() {
|
async fn test_mixed_worker_types() {
|
||||||
|
use crate::core::BasicWorkerBuilder;
|
||||||
let regular: Box<dyn Worker> = Box::new(
|
let regular: Box<dyn Worker> = Box::new(
|
||||||
BasicWorkerBuilder::new("http://regular:8080")
|
BasicWorkerBuilder::new("http://regular:8080")
|
||||||
.worker_type(WorkerType::Regular)
|
.worker_type(WorkerType::Regular)
|
||||||
@@ -1946,28 +1798,25 @@ mod tests {
|
|||||||
.worker_type(WorkerType::Decode)
|
.worker_type(WorkerType::Decode)
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
let dp_aware_regular = WorkerFactory::create_dp_aware(
|
let dp_aware_regular: Box<dyn Worker> = Box::new(
|
||||||
"http://dp:8080".to_string(),
|
DPAwareWorkerBuilder::new("http://dp:8080", 0, 2)
|
||||||
0,
|
.worker_type(WorkerType::Regular)
|
||||||
2,
|
.api_key("test_api_key")
|
||||||
WorkerType::Regular,
|
.build(),
|
||||||
Some("test_api_key".to_string()),
|
|
||||||
);
|
);
|
||||||
let dp_aware_prefill = WorkerFactory::create_dp_aware(
|
let dp_aware_prefill: Box<dyn Worker> = Box::new(
|
||||||
"http://dp-prefill:8080".to_string(),
|
DPAwareWorkerBuilder::new("http://dp-prefill:8080", 1, 2)
|
||||||
1,
|
.worker_type(WorkerType::Prefill {
|
||||||
2,
|
|
||||||
WorkerType::Prefill {
|
|
||||||
bootstrap_port: None,
|
bootstrap_port: None,
|
||||||
},
|
})
|
||||||
Some("test_api_key".to_string()),
|
.api_key("test_api_key")
|
||||||
|
.build(),
|
||||||
);
|
);
|
||||||
let dp_aware_decode = WorkerFactory::create_dp_aware(
|
let dp_aware_decode: Box<dyn Worker> = Box::new(
|
||||||
"http://dp-decode:8080".to_string(),
|
DPAwareWorkerBuilder::new("http://dp-decode:8080", 0, 4)
|
||||||
0,
|
.worker_type(WorkerType::Decode)
|
||||||
4,
|
.api_key("test_api_key")
|
||||||
WorkerType::Decode,
|
.build(),
|
||||||
Some("test_api_key".to_string()),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let workers: Vec<Box<dyn Worker>> = vec![
|
let workers: Vec<Box<dyn Worker>> = vec![
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ use dashmap::DashMap;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{CircuitState, ConnectionMode, RuntimeType, Worker, WorkerType},
|
core::{
|
||||||
|
circuit_breaker::CircuitState,
|
||||||
|
worker::{HealthChecker, RuntimeType, WorkerType},
|
||||||
|
ConnectionMode, Worker,
|
||||||
|
},
|
||||||
observability::metrics::Metrics,
|
observability::metrics::Metrics,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -588,7 +592,7 @@ impl WorkerRegistry {
|
|||||||
|
|
||||||
/// Start a health checker for all workers in the registry
|
/// Start a health checker for all workers in the registry
|
||||||
/// This should be called once after the registry is populated with workers
|
/// This should be called once after the registry is populated with workers
|
||||||
pub fn start_health_checker(&self, check_interval_secs: u64) -> crate::core::HealthChecker {
|
pub(crate) fn start_health_checker(&self, check_interval_secs: u64) -> HealthChecker {
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
@@ -632,7 +636,7 @@ impl WorkerRegistry {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
crate::core::HealthChecker::new(handle, shutdown)
|
HealthChecker::new(handle, shutdown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +680,7 @@ mod tests {
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::core::{BasicWorkerBuilder, CircuitBreakerConfig};
|
use crate::core::{circuit_breaker::CircuitBreakerConfig, BasicWorkerBuilder};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_worker_registry() {
|
fn test_worker_registry() {
|
||||||
@@ -697,7 +701,7 @@ mod tests {
|
|||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Register worker (WorkerFactory returns Box<dyn Worker>, convert to Arc)
|
// Register worker
|
||||||
let worker_id = registry.register(Arc::from(worker));
|
let worker_id = registry.register(Arc::from(worker));
|
||||||
|
|
||||||
assert!(registry.get(&worker_id).is_some());
|
assert!(registry.get(&worker_id).is_some());
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use tracing::warn;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::RouterConfig,
|
config::RouterConfig,
|
||||||
core::{worker_to_info, Job, JobQueue, WorkerId, WorkerRegistry},
|
core::{worker::worker_to_info, worker_registry::WorkerId, Job, JobQueue, WorkerRegistry},
|
||||||
protocols::worker_spec::{
|
protocols::worker_spec::{
|
||||||
WorkerConfigRequest, WorkerErrorResponse, WorkerInfo, WorkerUpdateRequest,
|
WorkerConfigRequest, WorkerErrorResponse, WorkerInfo, WorkerUpdateRequest,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ type EvictionCallback = Arc<dyn Fn(&str) + Send + Sync>;
|
|||||||
|
|
||||||
/// Cached MCP connection with metadata
|
/// Cached MCP connection with metadata
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CachedConnection {
|
pub(crate) struct CachedConnection {
|
||||||
/// The MCP client instance
|
/// The MCP client instance
|
||||||
pub client: Arc<McpClient>,
|
pub client: Arc<McpClient>,
|
||||||
/// Server configuration used to create this connection
|
/// Server configuration used to create this connection
|
||||||
|
#[allow(dead_code)]
|
||||||
pub config: McpServerConfig,
|
pub config: McpServerConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +221,7 @@ pub struct PoolStats {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::mcp::McpTransport;
|
use crate::mcp::config::McpTransport;
|
||||||
|
|
||||||
// Helper to create test server config
|
// Helper to create test server config
|
||||||
fn create_test_config(url: &str) -> McpServerConfig {
|
fn create_test_config(url: &str) -> McpServerConfig {
|
||||||
@@ -277,7 +278,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pool_with_global_proxy() {
|
fn test_pool_with_global_proxy() {
|
||||||
use crate::mcp::McpProxyConfig;
|
use crate::mcp::config::McpProxyConfig;
|
||||||
|
|
||||||
// Create proxy config
|
// Create proxy config
|
||||||
let proxy = McpProxyConfig {
|
let proxy = McpProxyConfig {
|
||||||
|
|||||||
@@ -8,21 +8,21 @@ use crate::mcp::config::{Prompt, RawResource, Tool};
|
|||||||
|
|
||||||
/// Cached tool with metadata
|
/// Cached tool with metadata
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CachedTool {
|
pub(crate) struct CachedTool {
|
||||||
pub server_name: String,
|
pub server_name: String,
|
||||||
pub tool: Tool,
|
pub tool: Tool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cached prompt with metadata
|
/// Cached prompt with metadata
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CachedPrompt {
|
pub(crate) struct CachedPrompt {
|
||||||
pub server_name: String,
|
pub server_name: String,
|
||||||
pub prompt: Prompt,
|
pub prompt: Prompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cached resource with metadata
|
/// Cached resource with metadata
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CachedResource {
|
pub(crate) struct CachedResource {
|
||||||
pub server_name: String,
|
pub server_name: String,
|
||||||
pub resource: RawResource,
|
pub resource: RawResource,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,6 @@ pub mod oauth;
|
|||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
pub mod tool_args;
|
pub mod tool_args;
|
||||||
|
|
||||||
// Re-export the main types for convenience
|
// Re-export types used outside this module
|
||||||
pub use config::{
|
pub use config::{McpConfig, McpServerConfig, McpTransport, Tool};
|
||||||
InventoryConfig, McpConfig, McpPoolConfig, McpProxyConfig, McpServerConfig, McpTransport,
|
pub use manager::McpManager;
|
||||||
Prompt, RawResource, Tool, WarmupServer,
|
|
||||||
};
|
|
||||||
pub use connection_pool::{CachedConnection, McpConnectionPool, PoolStats};
|
|
||||||
pub use error::{McpError, McpResult};
|
|
||||||
pub use inventory::ToolInventory;
|
|
||||||
pub use manager::{McpManager, McpManagerStats};
|
|
||||||
pub use proxy::{create_http_client, resolve_proxy_config};
|
|
||||||
pub use tool_args::ToolArgs;
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const CALLBACK_HTML: &str = r#"
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
/// OAuth authentication helper for MCP servers
|
/// OAuth authentication helper for MCP servers
|
||||||
pub struct OAuthHelper {
|
pub(crate) struct OAuthHelper {
|
||||||
server_url: String,
|
server_url: String,
|
||||||
redirect_uri: String,
|
redirect_uri: String,
|
||||||
callback_port: u16,
|
callback_port: u16,
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::mcp::{McpError, McpProxyConfig, McpResult, McpServerConfig};
|
use crate::mcp::{
|
||||||
|
config::{McpProxyConfig, McpServerConfig},
|
||||||
|
error::{McpError, McpResult},
|
||||||
|
};
|
||||||
|
|
||||||
/// Resolve proxy configuration for a server
|
/// Resolve proxy configuration for a server
|
||||||
/// Priority: server.proxy > global.proxy > None
|
/// Priority: server.proxy > global.proxy > None
|
||||||
@@ -15,7 +18,7 @@ use crate::mcp::{McpError, McpProxyConfig, McpResult, McpServerConfig};
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// The resolved proxy configuration, or None for direct connection
|
/// The resolved proxy configuration, or None for direct connection
|
||||||
pub fn resolve_proxy_config<'a>(
|
pub(crate) fn resolve_proxy_config<'a>(
|
||||||
server_config: &'a McpServerConfig,
|
server_config: &'a McpServerConfig,
|
||||||
global_proxy: Option<&'a McpProxyConfig>,
|
global_proxy: Option<&'a McpProxyConfig>,
|
||||||
) -> Option<&'a McpProxyConfig> {
|
) -> Option<&'a McpProxyConfig> {
|
||||||
@@ -42,7 +45,7 @@ pub fn resolve_proxy_config<'a>(
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// The configured builder or error
|
/// The configured builder or error
|
||||||
pub fn apply_proxy_to_builder(
|
pub(super) fn apply_proxy_to_builder(
|
||||||
mut builder: reqwest::ClientBuilder,
|
mut builder: reqwest::ClientBuilder,
|
||||||
proxy_cfg: &McpProxyConfig,
|
proxy_cfg: &McpProxyConfig,
|
||||||
) -> McpResult<reqwest::ClientBuilder> {
|
) -> McpResult<reqwest::ClientBuilder> {
|
||||||
@@ -94,7 +97,9 @@ pub fn apply_proxy_to_builder(
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A configured reqwest::Client or error
|
/// A configured reqwest::Client or error
|
||||||
pub fn create_http_client(proxy_config: Option<&McpProxyConfig>) -> McpResult<reqwest::Client> {
|
pub(crate) fn create_http_client(
|
||||||
|
proxy_config: Option<&McpProxyConfig>,
|
||||||
|
) -> McpResult<reqwest::Client> {
|
||||||
let mut builder = reqwest::Client::builder().connect_timeout(Duration::from_secs(10));
|
let mut builder = reqwest::Client::builder().connect_timeout(Duration::from_secs(10));
|
||||||
|
|
||||||
// Apply MCP-specific proxy if configured
|
// Apply MCP-specific proxy if configured
|
||||||
@@ -110,7 +115,7 @@ pub fn create_http_client(proxy_config: Option<&McpProxyConfig>) -> McpResult<re
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::mcp::McpTransport;
|
use crate::mcp::config::McpTransport;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resolve_proxy_no_config() {
|
fn test_resolve_proxy_no_config() {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ static STRING_INTERNER: Lazy<DashMap<String, Arc<str>>> = Lazy::new(DashMap::new
|
|||||||
/// This function is designed for high-throughput scenarios where the same
|
/// This function is designed for high-throughput scenarios where the same
|
||||||
/// strings (model IDs, worker URLs) appear repeatedly. The first call allocates,
|
/// strings (model IDs, worker URLs) appear repeatedly. The first call allocates,
|
||||||
/// subsequent calls just clone the Arc (very cheap - just a ref count increment).
|
/// subsequent calls just clone the Arc (very cheap - just a ref count increment).
|
||||||
pub fn intern_string(s: &str) -> Arc<str> {
|
pub(crate) fn intern_string(s: &str) -> Arc<str> {
|
||||||
// Fast path: check if already interned
|
// Fast path: check if already interned
|
||||||
if let Some(entry) = STRING_INTERNER.get(s) {
|
if let Some(entry) = STRING_INTERNER.get(s) {
|
||||||
return Arc::clone(entry.value());
|
return Arc::clone(entry.value());
|
||||||
@@ -46,7 +46,8 @@ pub fn intern_string(s: &str) -> Arc<str> {
|
|||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interner_size() -> usize {
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn interner_size() -> usize {
|
||||||
STRING_INTERNER.len()
|
STRING_INTERNER.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ pub fn status_code_to_static_str(code: u16) -> Option<&'static str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Static HTTP method strings to avoid allocations on every request.
|
/// Static HTTP method strings to avoid allocations on every request.
|
||||||
pub mod http_methods {
|
pub(crate) mod http_methods {
|
||||||
pub const GET: &str = "GET";
|
pub const GET: &str = "GET";
|
||||||
pub const POST: &str = "POST";
|
pub const POST: &str = "POST";
|
||||||
pub const PUT: &str = "PUT";
|
pub const PUT: &str = "PUT";
|
||||||
@@ -144,7 +145,7 @@ impl Default for PrometheusConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_metrics() {
|
pub(crate) fn init_metrics() {
|
||||||
// Layer 1: HTTP metrics
|
// Layer 1: HTTP metrics
|
||||||
describe_counter!(
|
describe_counter!(
|
||||||
"smg_http_requests_total",
|
"smg_http_requests_total",
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ fn get_allowed_targets() -> &'static [&'static str; 3] {
|
|||||||
|
|
||||||
/// Filter that only allows specific module targets to be exported to OTEL.
|
/// Filter that only allows specific module targets to be exported to OTEL.
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
pub struct CustomOtelFilter;
|
pub(crate) struct CustomOtelFilter;
|
||||||
|
|
||||||
impl CustomOtelFilter {
|
impl CustomOtelFilter {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -23,13 +23,16 @@ use crate::{
|
|||||||
app_context::AppContext,
|
app_context::AppContext,
|
||||||
config::{RouterConfig, RoutingMode},
|
config::{RouterConfig, RoutingMode},
|
||||||
core::{
|
core::{
|
||||||
|
job_queue::{JobQueue, JobQueueConfig},
|
||||||
steps::{
|
steps::{
|
||||||
create_external_worker_registration_workflow, create_mcp_registration_workflow,
|
create_external_worker_registration_workflow, create_mcp_registration_workflow,
|
||||||
create_tokenizer_registration_workflow, create_wasm_module_registration_workflow,
|
create_tokenizer_registration_workflow, create_wasm_module_registration_workflow,
|
||||||
create_wasm_module_removal_workflow, create_worker_registration_workflow,
|
create_wasm_module_removal_workflow, create_worker_registration_workflow,
|
||||||
create_worker_removal_workflow, create_worker_update_workflow,
|
create_worker_removal_workflow, create_worker_update_workflow,
|
||||||
},
|
},
|
||||||
Job, JobQueue, JobQueueConfig, WorkerManager, WorkerType,
|
worker::WorkerType,
|
||||||
|
worker_manager::WorkerManager,
|
||||||
|
Job,
|
||||||
},
|
},
|
||||||
middleware::{self, AuthConfig, QueuedRequest},
|
middleware::{self, AuthConfig, QueuedRequest},
|
||||||
observability::{
|
observability::{
|
||||||
|
|||||||
@@ -23,20 +23,14 @@ pub mod tiktoken;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
// Re-exports
|
// Internal imports for Tokenizer struct
|
||||||
pub use cache::{CacheConfig, CacheStats, CachedTokenizer, TokenizerFingerprint};
|
use factory::{create_tokenizer_from_file, create_tokenizer_with_chat_template};
|
||||||
pub use factory::{
|
// Re-export types used outside this module
|
||||||
create_tokenizer, create_tokenizer_async, create_tokenizer_async_with_chat_template,
|
|
||||||
create_tokenizer_from_file, create_tokenizer_with_chat_template,
|
|
||||||
create_tokenizer_with_chat_template_blocking, TokenizerType,
|
|
||||||
};
|
|
||||||
pub use huggingface::HuggingFaceTokenizer;
|
pub use huggingface::HuggingFaceTokenizer;
|
||||||
pub use registry::TokenizerRegistry;
|
pub use registry::TokenizerRegistry;
|
||||||
pub use sequence::Sequence;
|
pub use stop::StopSequenceDecoder;
|
||||||
pub use stop::{SequenceDecoderOutput, StopSequenceConfig, StopSequenceDecoder};
|
|
||||||
pub use stream::DecodeStream;
|
pub use stream::DecodeStream;
|
||||||
pub use tiktoken::{TiktokenModel, TiktokenTokenizer};
|
pub use traits::{Decoder, Encoder, Encoding, SpecialTokens};
|
||||||
pub use traits::{Decoder, Encoder, Encoding, SpecialTokens, Tokenizer as TokenizerTrait};
|
|
||||||
|
|
||||||
/// Main tokenizer wrapper that provides a unified interface for different tokenizer implementations
|
/// Main tokenizer wrapper that provides a unified interface for different tokenizer implementations
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use super::traits::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Tiktoken tokenizer wrapper for OpenAI GPT models
|
/// Tiktoken tokenizer wrapper for OpenAI GPT models
|
||||||
pub struct TiktokenTokenizer {
|
pub(crate) struct TiktokenTokenizer {
|
||||||
tokenizer: CoreBPE,
|
tokenizer: CoreBPE,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
model: TiktokenModel,
|
model: TiktokenModel,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod factory;
|
pub mod factory;
|
||||||
pub mod partial_json;
|
pub mod partial_json;
|
||||||
pub mod state;
|
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
@@ -15,13 +14,11 @@ pub mod parsers;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
// Re-export commonly used types
|
// Re-export types used outside this module
|
||||||
pub use errors::{ParserError, ParserResult};
|
pub use factory::{ParserFactory, PooledParser};
|
||||||
pub use factory::{ParserFactory, ParserRegistry, PooledParser};
|
|
||||||
// Re-export parsers for convenience
|
|
||||||
pub use parsers::{
|
pub use parsers::{
|
||||||
DeepSeekParser, Glm4MoeParser, JsonParser, KimiK2Parser, LlamaParser, MinimaxM2Parser,
|
DeepSeekParser, Glm4MoeParser, JsonParser, KimiK2Parser, LlamaParser, MinimaxM2Parser,
|
||||||
MistralParser, PythonicParser, QwenParser, Step3Parser,
|
MistralParser, PythonicParser, QwenParser, Step3Parser,
|
||||||
};
|
};
|
||||||
pub use traits::{PartialJsonParser, ToolParser};
|
pub use traits::ToolParser;
|
||||||
pub use types::{FunctionCall, PartialToolCall, StreamingParseResult, ToolCall};
|
pub use types::{FunctionCall, PartialToolCall, StreamingParseResult, ToolCall};
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ pub fn normalize_arguments_field(mut obj: Value) -> Value {
|
|||||||
/// - `Ok(StreamingParseResult)` with any tool call items to stream
|
/// - `Ok(StreamingParseResult)` with any tool call items to stream
|
||||||
/// - `Err(ParserError)` if JSON parsing or serialization fails
|
/// - `Err(ParserError)` if JSON parsing or serialization fails
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn handle_json_tool_streaming(
|
pub(crate) fn handle_json_tool_streaming(
|
||||||
current_text: &str,
|
current_text: &str,
|
||||||
start_idx: usize,
|
start_idx: usize,
|
||||||
partial_json: &mut crate::tool_parser::partial_json::PartialJson,
|
partial_json: &mut crate::tool_parser::partial_json::PartialJson,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub use kimik2::KimiK2Parser;
|
|||||||
pub use llama::LlamaParser;
|
pub use llama::LlamaParser;
|
||||||
pub use minimax_m2::MinimaxM2Parser;
|
pub use minimax_m2::MinimaxM2Parser;
|
||||||
pub use mistral::MistralParser;
|
pub use mistral::MistralParser;
|
||||||
pub use passthrough::PassthroughParser;
|
pub(crate) use passthrough::PassthroughParser;
|
||||||
pub use pythonic::PythonicParser;
|
pub use pythonic::PythonicParser;
|
||||||
pub use qwen::QwenParser;
|
pub use qwen::QwenParser;
|
||||||
pub use step3::Step3Parser;
|
pub use step3::Step3Parser;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use crate::{
|
|||||||
|
|
||||||
/// Passthrough parser that returns text unchanged with no tool calls
|
/// Passthrough parser that returns text unchanged with no tool calls
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PassthroughParser;
|
pub(crate) struct PassthroughParser;
|
||||||
|
|
||||||
impl PassthroughParser {
|
impl PassthroughParser {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
|||||||
@@ -531,23 +531,3 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Utility function to check if a string contains complete JSON
|
|
||||||
pub fn is_complete_json(input: &str) -> bool {
|
|
||||||
serde_json::from_str::<Value>(input).is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility function to find common prefix between two strings
|
|
||||||
pub fn find_common_prefix(s1: &str, s2: &str) -> usize {
|
|
||||||
s1.chars()
|
|
||||||
.zip(s2.chars())
|
|
||||||
.take_while(|(a, b)| a == b)
|
|
||||||
.count()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility function to compute diff between old and new strings
|
|
||||||
pub fn compute_diff(old: &str, new: &str) -> String {
|
|
||||||
let common_len = find_common_prefix(old, new);
|
|
||||||
// Convert character count to byte offset
|
|
||||||
new.chars().skip(common_len).collect()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
/// Placeholder for Harmony streaming metadata captured during token-aware parsing.
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct HarmonyStreamState {
|
|
||||||
/// All tokens observed so far for the current assistant response.
|
|
||||||
pub tokens: Vec<u32>,
|
|
||||||
/// Number of tokens that have already been processed by the Harmony parser.
|
|
||||||
pub processed_tokens: usize,
|
|
||||||
/// Number of tool calls emitted downstream.
|
|
||||||
pub emitted_calls: usize,
|
|
||||||
/// Pending analysis-channel content awaiting flush into normal text output.
|
|
||||||
pub analysis_buffer: String,
|
|
||||||
/// Whether the tool name has been surfaced for the current call.
|
|
||||||
pub emitted_name: bool,
|
|
||||||
/// Whether arguments have been surfaced for the current call.
|
|
||||||
pub emitted_args: bool,
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::tool_parser::{
|
use crate::tool_parser::{parsers::JsonParser, partial_json::PartialJson, traits::ToolParser};
|
||||||
parsers::JsonParser,
|
|
||||||
partial_json::{compute_diff, find_common_prefix, is_complete_json, PartialJson},
|
|
||||||
traits::ToolParser,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_tool_parser_factory() {
|
async fn test_tool_parser_factory() {
|
||||||
@@ -97,37 +93,6 @@ fn test_partial_json_depth_limit() {
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_complete_json() {
|
|
||||||
assert!(is_complete_json(r#"{"name": "test"}"#));
|
|
||||||
assert!(is_complete_json(r#"[1, 2, 3]"#));
|
|
||||||
assert!(is_complete_json(r#""string""#));
|
|
||||||
assert!(is_complete_json("42"));
|
|
||||||
assert!(is_complete_json("true"));
|
|
||||||
assert!(is_complete_json("null"));
|
|
||||||
|
|
||||||
assert!(!is_complete_json(r#"{"name": "#));
|
|
||||||
assert!(!is_complete_json(r#"[1, 2, "#));
|
|
||||||
assert!(!is_complete_json(r#""unclosed"#));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_find_common_prefix() {
|
|
||||||
assert_eq!(find_common_prefix("hello", "hello"), 5);
|
|
||||||
assert_eq!(find_common_prefix("hello", "help"), 3);
|
|
||||||
assert_eq!(find_common_prefix("hello", "world"), 0);
|
|
||||||
assert_eq!(find_common_prefix("", "hello"), 0);
|
|
||||||
assert_eq!(find_common_prefix("hello", ""), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_compute_diff() {
|
|
||||||
assert_eq!(compute_diff("hello", "hello world"), " world");
|
|
||||||
assert_eq!(compute_diff("", "hello"), "hello");
|
|
||||||
assert_eq!(compute_diff("hello", "hello"), "");
|
|
||||||
assert_eq!(compute_diff("test", "hello"), "hello");
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: test_stream_result_variants removed - StreamResult enum replaced by StreamingParseResult
|
// NOTE: test_stream_result_variants removed - StreamResult enum replaced by StreamingParseResult
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -16,42 +16,6 @@ pub struct FunctionCall {
|
|||||||
pub arguments: String,
|
pub arguments: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Streaming parse result
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum StreamResult {
|
|
||||||
/// Need more data to continue parsing
|
|
||||||
Incomplete,
|
|
||||||
/// Found a tool name (for streaming)
|
|
||||||
ToolName { index: usize, name: String },
|
|
||||||
/// Found incremental arguments (for streaming)
|
|
||||||
ToolArguments { index: usize, arguments: String },
|
|
||||||
/// Completed parsing a tool
|
|
||||||
ToolComplete(ToolCall),
|
|
||||||
/// Normal text (not part of tool call)
|
|
||||||
NormalText(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Token configuration for parsing
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TokenConfig {
|
|
||||||
/// Start tokens for tool calls
|
|
||||||
pub start_tokens: Vec<String>,
|
|
||||||
/// End tokens for tool calls
|
|
||||||
pub end_tokens: Vec<String>,
|
|
||||||
/// Separator between multiple tool calls
|
|
||||||
pub separator: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TokenConfig {
|
|
||||||
/// Iterate over start/end token pairs
|
|
||||||
pub fn iter_pairs(&self) -> impl Iterator<Item = (&str, &str)> {
|
|
||||||
self.start_tokens
|
|
||||||
.iter()
|
|
||||||
.zip(self.end_tokens.iter())
|
|
||||||
.map(|(s, e)| (s.as_str(), e.as_str()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simple partial tool call for streaming
|
/// Simple partial tool call for streaming
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PartialToolCall {
|
pub struct PartialToolCall {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use common::mock_mcp_server::MockMCPServer;
|
use common::mock_mcp_server::MockMCPServer;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use smg::mcp::{McpConfig, McpError, McpManager, McpServerConfig, McpTransport};
|
use smg::mcp::{error::McpError, McpConfig, McpManager, McpServerConfig, McpTransport};
|
||||||
|
|
||||||
/// Create a new mock server for testing (each test gets its own)
|
/// Create a new mock server for testing (each test gets its own)
|
||||||
async fn create_mock_server() -> MockMCPServer {
|
async fn create_mock_server() -> MockMCPServer {
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use smg::{
|
use smg::{
|
||||||
config::{
|
config::{ConfigError, HistoryBackend, OracleConfig, RouterConfig, RoutingMode},
|
||||||
ConfigError, ConfigValidator, HistoryBackend, OracleConfig, RouterConfig, RoutingMode,
|
|
||||||
},
|
|
||||||
data_connector::{ResponseId, StoredResponse},
|
data_connector::{ResponseId, StoredResponse},
|
||||||
protocols::{
|
protocols::{
|
||||||
chat::{ChatCompletionRequest, ChatMessage, MessageContent},
|
chat::{ChatCompletionRequest, ChatMessage, MessageContent},
|
||||||
@@ -857,8 +855,9 @@ fn oracle_config_validation_requires_config_when_enabled() {
|
|||||||
.history_backend(HistoryBackend::Oracle)
|
.history_backend(HistoryBackend::Oracle)
|
||||||
.build_unchecked();
|
.build_unchecked();
|
||||||
|
|
||||||
let err =
|
let err = config
|
||||||
ConfigValidator::validate(&config).expect_err("config should fail without oracle details");
|
.validate()
|
||||||
|
.expect_err("config should fail without oracle details");
|
||||||
|
|
||||||
match err {
|
match err {
|
||||||
ConfigError::MissingRequired { field } => {
|
ConfigError::MissingRequired { field } => {
|
||||||
@@ -883,7 +882,7 @@ fn oracle_config_validation_accepts_dsn_only() {
|
|||||||
})
|
})
|
||||||
.build_unchecked();
|
.build_unchecked();
|
||||||
|
|
||||||
ConfigValidator::validate(&config).expect("dsn-based config should validate");
|
config.validate().expect("dsn-based config should validate");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -901,5 +900,7 @@ fn oracle_config_validation_accepts_wallet_alias() {
|
|||||||
})
|
})
|
||||||
.build_unchecked();
|
.build_unchecked();
|
||||||
|
|
||||||
ConfigValidator::validate(&config).expect("wallet-based config should validate");
|
config
|
||||||
|
.validate()
|
||||||
|
.expect("wallet-based config should validate");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user