Skip to content

Commit 3f12333

Browse files
authored
fix(rlm): wire LlmClient through LlmBridge, replace silent stub Refs #1744
1 parent e56a587 commit 3f12333

3 files changed

Lines changed: 77 additions & 11 deletions

File tree

crates/terraphim_rlm/src/error.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ pub enum RlmError {
132132
#[error("LLM call failed: {message}")]
133133
LlmCallFailed { message: String },
134134

135+
/// No LLM client configured. Enable the `llm` feature and set an API key
136+
/// or run a local Ollama instance.
137+
#[error(
138+
"No LLM client configured. Enable the `llm` feature (--features llm) and set OPENROUTER_API_KEY or run Ollama on localhost:11434."
139+
)]
140+
LlmNotConfigured,
141+
135142
/// LLM bridge authentication failed.
136143
#[error("LLM bridge authentication failed: invalid session token")]
137144
LlmBridgeAuthFailed,

crates/terraphim_rlm/src/llm_bridge.rs

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,37 @@ pub struct LlmBridge {
123123
session_manager: Arc<SessionManager>,
124124
/// Budget trackers per session.
125125
budget_trackers: dashmap::DashMap<SessionId, Arc<BudgetTracker>>,
126+
/// Optional real LLM client. When `None`, `query()` returns
127+
/// `LlmNotConfigured` instead of a silent stub.
128+
#[cfg(feature = "llm")]
129+
llm_client: Option<Arc<dyn terraphim_service::llm::LlmClient>>,
126130
}
127131

128132
impl LlmBridge {
129-
/// Create a new LLM bridge.
133+
/// Create a new LLM bridge without a real LLM client.
134+
/// Queries will return `LlmNotConfigured`.
130135
pub fn new(config: LlmBridgeConfig, session_manager: Arc<SessionManager>) -> Self {
131136
Self {
132137
config,
133138
session_manager,
134139
budget_trackers: dashmap::DashMap::new(),
140+
#[cfg(feature = "llm")]
141+
llm_client: None,
142+
}
143+
}
144+
145+
/// Create a new LLM bridge with a configured LLM client.
146+
#[cfg(feature = "llm")]
147+
pub fn with_llm_client(
148+
config: LlmBridgeConfig,
149+
session_manager: Arc<SessionManager>,
150+
client: Arc<dyn terraphim_service::llm::LlmClient>,
151+
) -> Self {
152+
Self {
153+
config,
154+
session_manager,
155+
budget_trackers: dashmap::DashMap::new(),
156+
llm_client: Some(client),
135157
}
136158
}
137159

@@ -189,16 +211,34 @@ impl LlmBridge {
189211

190212
let start = std::time::Instant::now();
191213

192-
// TODO: Actually call the LLM service
193-
// For now, return a stub response
194-
let response_text = format!(
195-
"[LLM Bridge stub] Query: {}...",
196-
if request.prompt.len() > 50 {
197-
&request.prompt[..50]
198-
} else {
199-
&request.prompt
214+
#[cfg(feature = "llm")]
215+
let response_text = match &self.llm_client {
216+
Some(client) => {
217+
let chat_opts = terraphim_service::llm::ChatOptions {
218+
max_tokens: request.max_tokens.map(|t| t as u32),
219+
temperature: request.temperature,
220+
};
221+
let messages = vec![serde_json::json!({
222+
"role": "user",
223+
"content": request.prompt
224+
})];
225+
client
226+
.chat_completion(messages, chat_opts)
227+
.await
228+
.map_err(|e| RlmError::LlmCallFailed {
229+
message: e.to_string(),
230+
})?
231+
}
232+
None => {
233+
return Err(RlmError::LlmNotConfigured);
200234
}
201-
);
235+
};
236+
237+
#[cfg(not(feature = "llm"))]
238+
{
239+
let _request = request;
240+
return Err(RlmError::LlmNotConfigured);
241+
}
202242

203243
// Estimate tokens (1 token ~= 4 chars for English text)
204244
let estimated_tokens = (request.prompt.len() / 4 + response_text.len() / 4) as u64;

crates/terraphim_rlm/src/rlm.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ impl TerraphimRlm {
106106
// Create session manager
107107
let session_manager = Arc::new(SessionManager::new(config.clone()));
108108

109-
// Create LLM bridge
109+
// Create LLM bridge (bare — caller wires a client via set_llm_client()).
110110
let llm_bridge_config = LlmBridgeConfig::default();
111111
let llm_bridge = Arc::new(LlmBridge::new(llm_bridge_config, session_manager.clone()));
112112

@@ -794,6 +794,25 @@ impl TerraphimRlm {
794794
// Command history would be added here if tracking is enabled
795795
})
796796
}
797+
798+
/// Inject an LLM client from the orchestrator's routing pipeline.
799+
///
800+
/// The orchestrator owns provider health, budget tracking, and
801+
/// fallback routing. Call this after construction to wire RLM
802+
/// into the existing cost-optimisation stack instead of building
803+
/// a standalone client.
804+
///
805+
/// Requires the `llm` feature.
806+
#[cfg(feature = "llm")]
807+
pub fn set_llm_client(&mut self, client: Arc<dyn terraphim_service::llm::LlmClient>) {
808+
log::info!("RLM LLM bridge configured with provider: {}", client.name());
809+
let bridge_config = LlmBridgeConfig::default();
810+
self.llm_bridge = Arc::new(LlmBridge::with_llm_client(
811+
bridge_config,
812+
self.session_manager.clone(),
813+
client,
814+
));
815+
}
797816
}
798817

799818
/// Result from a direct LLM query.

0 commit comments

Comments
 (0)