use std::collections::HashMap;
use std::sync::Arc;
use serde::Deserialize;
use serde_json::Value;
use crate::{
ClientPool, ConfigBasedLLMFactory, Llm, ModelConfig, NvidiaCatalogCache, NvidiaConfig,
ProviderConfig, ProviderRegistry,
};
fn block_on_plugin<S: cordis::Service + 'static>(
ctx: &std::sync::Arc<cordis::Context>,
svc: S,
) -> Result<cordis::FiberId, cordis::CordisError> {
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(ctx.plugin(svc)))
}
#[derive(Debug, Default, Deserialize)]
struct LlmPluginConfig {
#[serde(default)]
providers: HashMap<String, ProviderConfig>,
#[serde(default)]
models: HashMap<String, ModelConfig>,
#[serde(default)]
nvidia: Option<NvidiaConfig>,
}
fn parse_config(config: &Value) -> LlmPluginConfig {
match serde_json::from_value::<LlmPluginConfig>(config.clone()) {
Ok(parsed) => parsed,
Err(err) => {
tracing::warn!("Llm loader config deserialize failed ({err}); using empty defaults");
LlmPluginConfig::default()
}
}
}
fn factory_llm(
ctx: &Arc<cordis::Context>,
config: &Value,
) -> Result<cordis::FiberId, cordis::CordisError> {
let parsed = parse_config(config);
let nvidia = parsed.nvidia;
let nvidia_cfg = nvidia.clone().unwrap_or_default();
let catalog = Arc::new(NvidiaCatalogCache::new(nvidia_cfg.clone()));
match tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(catalog.refresh())
}) {
Ok(count) => tracing::info!("NVIDIA catalog refreshed with {} models", count),
Err(err) => tracing::warn!("NVIDIA catalog initial refresh failed: {err}"),
}
catalog.clone().start_background_refresh();
tracing::info!(
"[nvidia] api_base={} default_model={}",
nvidia_cfg.api_base,
nvidia_cfg.default_model,
);
let registry = ProviderRegistry::from_config(
parsed.providers.clone(),
parsed.models.clone(),
nvidia.as_ref(),
)
.with_catalog(catalog.clone());
let factory =
ConfigBasedLLMFactory::from_config(parsed.providers, parsed.models, nvidia.as_ref())
.map_err(|err| cordis::CordisError::Configuration(format!("LLM factory: {err}")))?;
tracing::info!(
"LLM factory initialized with default model: {}",
factory.default_model()
);
let pool = Arc::new(ClientPool::with_defaults());
let llm = Llm::new(Arc::new(registry), pool, Some(catalog)).with_factory(Arc::new(factory));
block_on_plugin(ctx, llm)
}
pub fn register_plugins(reg: &cordis::PluginRegistry) {
reg.register("Llm", Arc::new(factory_llm));
}
pub fn install_tracing_router() -> std::sync::Arc<crate::ExporterRouter> {
let mut router = crate::ExporterRouter::with_capacity(1);
if let Err(err) = router.register(std::sync::Arc::new(crate::TracingExporter)) {
tracing::warn!("TracingExporter registration failed: {err}");
}
Arc::new(router)
}
#[cfg(feature = "inventory")]
inventory::submit! {
cordis::CordisPluginFactory { name: "Llm", make: factory_llm }
}
#[cfg(test)]
mod tests {
use super::register_plugins;
use cordis::PluginRegistry;
#[test]
fn register_plugins_registers_only_llm() {
let reg = PluginRegistry::new();
register_plugins(®);
let mut names = reg.names();
names.sort();
assert_eq!(names, vec!["Llm".to_string()]);
}
}