Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/terraphim_rlm/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ pub enum RlmError {
#[error("LLM call failed: {message}")]
LlmCallFailed { message: String },

/// No LLM client configured. Enable the `llm` feature and set an API key
/// or run a local Ollama instance.
#[error(
"No LLM client configured. Enable the `llm` feature (--features llm) and set OPENROUTER_API_KEY or run Ollama on localhost:11434."
)]
LlmNotConfigured,

/// LLM bridge authentication failed.
#[error("LLM bridge authentication failed: invalid session token")]
LlmBridgeAuthFailed,
Expand Down
60 changes: 50 additions & 10 deletions crates/terraphim_rlm/src/llm_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,37 @@ pub struct LlmBridge {
session_manager: Arc<SessionManager>,
/// Budget trackers per session.
budget_trackers: dashmap::DashMap<SessionId, Arc<BudgetTracker>>,
/// Optional real LLM client. When `None`, `query()` returns
/// `LlmNotConfigured` instead of a silent stub.
#[cfg(feature = "llm")]
llm_client: Option<Arc<dyn terraphim_service::llm::LlmClient>>,
}

impl LlmBridge {
/// Create a new LLM bridge.
/// Create a new LLM bridge without a real LLM client.
/// Queries will return `LlmNotConfigured`.
pub fn new(config: LlmBridgeConfig, session_manager: Arc<SessionManager>) -> Self {
Self {
config,
session_manager,
budget_trackers: dashmap::DashMap::new(),
#[cfg(feature = "llm")]
llm_client: None,
}
}

/// Create a new LLM bridge with a configured LLM client.
#[cfg(feature = "llm")]
pub fn with_llm_client(
config: LlmBridgeConfig,
session_manager: Arc<SessionManager>,
client: Arc<dyn terraphim_service::llm::LlmClient>,
) -> Self {
Self {
config,
session_manager,
budget_trackers: dashmap::DashMap::new(),
llm_client: Some(client),
}
}

Expand Down Expand Up @@ -189,16 +211,34 @@ impl LlmBridge {

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

// TODO: Actually call the LLM service
// For now, return a stub response
let response_text = format!(
"[LLM Bridge stub] Query: {}...",
if request.prompt.len() > 50 {
&request.prompt[..50]
} else {
&request.prompt
#[cfg(feature = "llm")]
let response_text = match &self.llm_client {
Some(client) => {
let chat_opts = terraphim_service::llm::ChatOptions {
max_tokens: request.max_tokens.map(|t| t as u32),
temperature: request.temperature,
};
let messages = vec![serde_json::json!({
"role": "user",
"content": request.prompt
})];
client
.chat_completion(messages, chat_opts)
.await
.map_err(|e| RlmError::LlmCallFailed {
message: e.to_string(),
})?
}
None => {
return Err(RlmError::LlmNotConfigured);
}
);
};

#[cfg(not(feature = "llm"))]
{
let _request = request;
return Err(RlmError::LlmNotConfigured);
}

// Estimate tokens (1 token ~= 4 chars for English text)
let estimated_tokens = (request.prompt.len() / 4 + response_text.len() / 4) as u64;
Expand Down
21 changes: 20 additions & 1 deletion crates/terraphim_rlm/src/rlm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl TerraphimRlm {
// Create session manager
let session_manager = Arc::new(SessionManager::new(config.clone()));

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

Expand Down Expand Up @@ -794,6 +794,25 @@ impl TerraphimRlm {
// Command history would be added here if tracking is enabled
})
}

/// Inject an LLM client from the orchestrator's routing pipeline.
///
/// The orchestrator owns provider health, budget tracking, and
/// fallback routing. Call this after construction to wire RLM
/// into the existing cost-optimisation stack instead of building
/// a standalone client.
///
/// Requires the `llm` feature.
#[cfg(feature = "llm")]
pub fn set_llm_client(&mut self, client: Arc<dyn terraphim_service::llm::LlmClient>) {
log::info!("RLM LLM bridge configured with provider: {}", client.name());
let bridge_config = LlmBridgeConfig::default();
self.llm_bridge = Arc::new(LlmBridge::with_llm_client(
bridge_config,
self.session_manager.clone(),
client,
));
}
}

/// Result from a direct LLM query.
Expand Down
Loading