Skip to content

Commit c4e85ba

Browse files
committed
feat: inject model alias for LLM routing and enhance alias mapping strategy
1 parent 71b671b commit c4e85ba

File tree

4 files changed

+107
-16
lines changed

4 files changed

+107
-16
lines changed

assets/bootstrap.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,14 @@ def clean_str(s):
9797
final_prompt = f"{history_context}{rendered_prompt}"
9898

9999
# 4. Call LLM Proxy
100+
# AEGIS_MODEL_ALIAS is injected by the orchestrator from spec.runtime.model.
101+
# It routes this execution to the correct provider alias (e.g. "judge",
102+
# "smart", "default"). The key must match InnerLoopRequest.model_alias.
100103
payload = {
101104
"prompt": final_prompt,
102105
"execution_id": execution_id,
103106
"iteration_number": iteration_number,
104-
"model": "default"
107+
"model_alias": os.environ.get("AEGIS_MODEL_ALIAS", "default")
105108
}
106109

107110
debug_print(f"Preparing LLM request - prompt length: {len(final_prompt)} chars")

orchestrator/core/src/application/execution.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,14 @@ impl ExecutionService for StandardExecutionService {
583583
llm_timeout_seconds.to_string(),
584584
);
585585

586+
// Inject model alias so bootstrap.py routes this agent's LLM calls to the
587+
// correct provider (e.g. "judge" → anthropic/claude-haiku, "smart" → local).
588+
// Falls back to "default" when spec.runtime.model is not set in the manifest.
589+
env.insert(
590+
"AEGIS_MODEL_ALIAS".to_string(),
591+
agent.manifest.spec.runtime.model.clone(),
592+
);
593+
586594
// Convert resource limits from domain format to runtime format
587595
let resources = if let Some(security) = &agent.manifest.spec.security {
588596
crate::domain::runtime::ResourceLimits {

orchestrator/core/src/domain/node_config.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,17 @@ pub struct LLMProviderConfig {
218218
pub models: Vec<ModelConfig>,
219219
}
220220

221+
impl LLMProviderConfig {
222+
/// Returns `true` when this provider runs inference locally (no external API call).
223+
///
224+
/// Local provider types: `"ollama"`, `"openai-compatible"` (e.g. LM Studio, vLLM).
225+
/// Cloud provider types: `"openai"`, `"anthropic"`.
226+
/// Used by `ProviderRegistry::build_alias_map` to implement `LLMSelectionStrategy`.
227+
pub fn is_local(&self) -> bool {
228+
matches!(self.provider_type.as_str(), "ollama" | "openai-compatible")
229+
}
230+
}
231+
221232
#[derive(Debug, Clone, Serialize, Deserialize)]
222233
pub struct ModelConfig {
223234
/// Model alias used in agent manifests (e.g., "default", "fast", "smart")

orchestrator/core/src/infrastructure/llm/registry.rs

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,89 @@ use super::anthropic::AnthropicAdapter;
2222
use super::ollama::OllamaAdapter;
2323
use super::openai::OpenAIAdapter;
2424

25+
/// Builds the alias → (provider_name, ModelConfig) map respecting the configured
26+
/// `LLMSelectionStrategy`.
27+
///
28+
/// When multiple enabled providers define the same alias, the strategy decides which
29+
/// entry wins rather than the last-write-wins behaviour of a plain sequential insert:
30+
///
31+
/// | Strategy | Wins when … |
32+
/// |--------------------|---------------------------------------------------------------------|
33+
/// | `PreferLocal` | New provider `is_local()` and existing entry is not local |
34+
/// | `PreferCloud` | New provider is not local and existing entry is local |
35+
/// | `CostOptimized` | New model's `cost_per_1k_tokens` < existing model's cost |
36+
/// | `LatencyOptimized` | Same as `PreferLocal` — local inference always has lower latency |
37+
///
38+
/// Providers that have been disabled via `enabled: false` are skipped entirely.
39+
fn build_alias_map(
40+
provider_configs: &[LLMProviderConfig],
41+
strategy: &LLMSelectionStrategy,
42+
) -> HashMap<String, (String, ModelConfig)> {
43+
// Intermediate map also tracks is_local for the current winner so we can
44+
// apply the strategy without re-looking up the provider config later.
45+
// (alias -> (provider_name, is_local, model_config))
46+
let mut working: HashMap<String, (String, bool, ModelConfig)> = HashMap::new();
47+
48+
for provider_config in provider_configs {
49+
if !provider_config.enabled {
50+
continue;
51+
}
52+
53+
let new_is_local = provider_config.is_local();
54+
55+
for model_config in &provider_config.models {
56+
let alias = &model_config.alias;
57+
58+
let should_insert = match working.get(alias) {
59+
None => true,
60+
Some((_, existing_is_local, existing_model)) => match strategy {
61+
LLMSelectionStrategy::PreferLocal | LLMSelectionStrategy::LatencyOptimized => {
62+
// Overwrite only if the new provider is local and the existing is not.
63+
new_is_local && !existing_is_local
64+
}
65+
LLMSelectionStrategy::PreferCloud => {
66+
// Overwrite only if the new provider is cloud and the existing is local.
67+
!new_is_local && *existing_is_local
68+
}
69+
LLMSelectionStrategy::CostOptimized => {
70+
model_config.cost_per_1k_tokens < existing_model.cost_per_1k_tokens
71+
}
72+
},
73+
};
74+
75+
if should_insert {
76+
info!(
77+
"Mapping alias '{}' -> {} ({}) [strategy={:?}]",
78+
alias, model_config.model, provider_config.name, strategy
79+
);
80+
working.insert(
81+
alias.clone(),
82+
(
83+
provider_config.name.clone(),
84+
new_is_local,
85+
model_config.clone(),
86+
),
87+
);
88+
} else {
89+
info!(
90+
"Alias '{}' already mapped to a preferred provider, skipping {} ({}) [strategy={:?}]",
91+
alias, model_config.model, provider_config.name, strategy
92+
);
93+
}
94+
}
95+
}
96+
97+
// Strip the is_local bookkeeping field — callers only need (provider_name, ModelConfig).
98+
working
99+
.into_iter()
100+
.map(|(alias, (provider_name, _, model_config))| (alias, (provider_name, model_config)))
101+
.collect()
102+
}
103+
25104
/// Registry for managing LLM providers and resolving model aliases
26105
pub struct ProviderRegistry {
27106
providers: HashMap<String, Arc<dyn LLMProvider>>,
28107
alias_map: HashMap<String, (String, ModelConfig)>, // alias -> (provider_name, model_config)
29-
_selection_strategy: LLMSelectionStrategy,
30108
fallback_provider: Option<String>,
31109
max_retries: u32,
32110
retry_delay_ms: u64,
@@ -36,7 +114,6 @@ impl ProviderRegistry {
36114
/// Create provider registry from node configuration
37115
pub fn from_config(config: &NodeConfigManifest) -> anyhow::Result<Self> {
38116
let mut providers = HashMap::new();
39-
let mut alias_map = HashMap::new();
40117

41118
info!("Initializing LLM provider registry");
42119

@@ -52,18 +129,6 @@ impl ProviderRegistry {
52129
match Self::create_provider(provider_config) {
53130
Ok(provider) => {
54131
providers.insert(provider_config.name.clone(), provider);
55-
56-
// Build alias mapping
57-
for model_config in &provider_config.models {
58-
info!(
59-
"Mapping alias '{}' -> {} ({})",
60-
model_config.alias, model_config.model, provider_config.name
61-
);
62-
alias_map.insert(
63-
model_config.alias.clone(),
64-
(provider_config.name.clone(), model_config.clone()),
65-
);
66-
}
67132
}
68133
Err(e) => {
69134
warn!(
@@ -79,10 +144,14 @@ impl ProviderRegistry {
79144
warn!("No LLM providers configured - semantic validation will not be available");
80145
}
81146

147+
let alias_map = build_alias_map(
148+
&config.spec.llm_providers,
149+
&config.spec.llm_selection.strategy,
150+
);
151+
82152
Ok(Self {
83153
providers,
84154
alias_map,
85-
_selection_strategy: config.spec.llm_selection.strategy.clone(),
86155
fallback_provider: config.spec.llm_selection.fallback_provider.clone(),
87156
max_retries: config.spec.llm_selection.max_retries,
88157
retry_delay_ms: config.spec.llm_selection.retry_delay_ms,

0 commit comments

Comments
 (0)