terraphim_service - Main Service Layer
Overview
terraphim_service is the primary service layer for the Terraphim AI system. It orchestrates search operations, manages LLM interactions, and provides the main API functionality. This crate coordinates between configuration, middleware, persistence, and external services.
Domain Model
Core Concepts
TerraphimService
Main service coordinator that manages configuration and orchestrates operations.
pub struct TerraphimService {
config_state: ConfigState,
}
Key Responsibilities:
- Manage role and haystack configuration
- Coordinate search operations
- Handle LLM integration
- Provide service lifecycle management
ConfigState
Shared state containing all configuration and runtime data.
pub struct ConfigState {
pub config: Arc<Mutex<TerraphimConfig>>,
pub roles: AHashMap<RoleName, RoleGraphSync>,
pub llm_client: Arc<Mutex<Option<LlmClient>>>,
}
Key Responsibilities:
- Store global configuration
- Cache role graphs
- Manage LLM client lifecycle
- Thread-safe access to shared state
Search Operations
Query
Search query structure with scoring capabilities.
pub struct Query {
pub search_term: NormalisedTermValue,
pub search_terms: Option<Vec<NormalisedTermValue>>,
pub operator: Option<LogicalOperator>,
pub skip: Option<u64>,
pub limit: Option<u64>,
pub role: Option<RoleName>,
pub layer: Layer,
}
Key Responsibilities:
- Define search parameters
- Support complex queries
- Enable pagination
- Role-scoped searches
LLM Integration
LlmClient
Generic LLM client supporting multiple providers.
pub struct LlmClient {
pub provider: LlmProvider,
pub api_key: Option<String>,
pub base_url: Option<String>,
pub model: String,
}
Key Responsibilities:
- Unified interface for multiple LLM providers
- Manage API authentication
- Handle provider-specific configuration
- Standardise request/response formats
LlmProvider
Supported LLM providers.
pub enum LlmProvider {
OpenRouter,
OpenAI,
Anthropic,
Ollama,
Custom(String),
}
Key Responsibilities:
- Identify provider type
- Enable provider-specific logic
- Support custom providers
Context Management
ContextItem
Fragment of context for LLM requests.
pub struct ContextItem {
pub content: String,
pub source: String,
pub relevance: f64,
}
Key Responsibilities:
- Store context fragments
- Track source attribution
- Maintain relevance scores
ConversationManager
Manage chat conversations and context.
pub struct ConversationManager {
pub conversations: AHashMap<String, Conversation>,
}
Key Responsibilities:
- Store conversation history
- Manage context windows
- Handle conversation lifecycle
Summarisation
SummarisationRequest
Request for document summarisation.
pub struct SummarisationRequest {
pub documents: Vec<Document>,
pub role: RoleName,
pub max_length: Option<usize>,
}
Key Responsibilities:
- Specify summarisation parameters
- Provide documents to summarise
- Set output constraints
SummarisationResult
Result of summarisation operation.
pub struct SummarisationResult {
pub summary: String,
pub documents_processed: usize,
pub model_used: String,
}
Key Responsibilities:
- Store generated summary
- Track processing statistics
- Identify model used
Data Models
Service Types
RelevanceFunction
Algorithm for ranking search results.
pub enum RelevanceFunction {
TitleScorer,
BM25,
BM25F,
BM25Plus,
TerraphimGraph,
}
Use Cases:
TitleScorer: Simple title matching
BM25: Okapi BM25 algorithm
BM25F: Field-length normalised BM25
BM25Plus: BM25 with additional features
TerraphimGraph: Knowledge graph-based ranking
SearchQuery
Search request structure.
pub struct SearchQuery {
pub search_term: NormalisedTermValue,
pub search_terms: Option<Vec<NormalisedTermValue>>,
pub operator: Option<LogicalOperator>,
pub skip: Option<u64>,
pub limit: Option<u64>,
pub role: Option<RoleName>,
pub layer: Layer,
pub include_pinned: bool,
}
Use Cases:
- Single-term search
- Multi-term boolean search
- Role-scoped queries
- Layer-aware search
- Pagination
LogicalOperator
Boolean operators for combining search terms.
pub enum LogicalOperator {
And,
Or,
Not,
}
Use Cases:
- Combining search criteria
- Excluding terms
- Building complex queries
LLM Data Models
LlmMessage
Message in LLM conversation.
pub struct LlmMessage {
pub role: String,
pub content: String,
}
Use Cases:
- User messages
- Assistant messages
- System prompts
LlmResponse
Response from LLM provider.
pub struct LlmResponse {
pub content: String,
pub model: String,
pub tokens_used: Option<u32>,
}
Use Cases:
- Store response content
- Track model used
- Monitor token usage
RoutingDecision
Decision on which LLM provider to use.
pub struct RoutingDecision {
pub provider: LlmProvider,
pub model: String,
pub confidence: f64,
}
Use Cases:
- Provider selection
- Model choice
- Confidence tracking
Thesaurus Management
ThesaurusBuildRequest
Request to build thesaurus from haystack.
pub struct ThesaurusBuildRequest {
pub search_query: SearchQuery,
pub role: RoleName,
}
Use Cases:
- Trigger thesaurus build
- Specify role context
- Define build parameters
ThesaurusBuildResult
Result of thesaurus build operation.
pub struct ThesaurusBuildResult {
pub thesaurus: Thesaurus,
pub documents_processed: usize,
pub build_time_ms: u64,
}
Use Cases:
- Return built thesaurus
- Report statistics
- Performance metrics
Implementation Patterns
Service Lifecycle
Initialisation
impl TerraphimService {
pub fn new(config_state: ConfigState) -> Self {
Self { config_state }
}
}
Pattern:
- Accept pre-initialised config state
- Store reference for lifecycle
- Minimal construction logic
Configuration Loading
impl TerraphimService {
pub async fn ensure_thesaurus_loaded(
&mut self,
role_name: &RoleName
) -> Result<Thesaurus> {
}
}
Pattern:
- Multi-source loading with fallback
- Persistence caching
- Graceful degradation
Search Operations
Query Execution
impl TerraphimService {
pub async fn search(&self, query: &SearchQuery) -> Result<Vec<Document>> {
}
}
Pattern:
- Async/await throughout
- Multi-step pipeline
- Error propagation
- Result aggregation
Thesaurus Building
impl TerraphimService {
pub async fn build_thesaurus(
&mut self,
search_query: &SearchQuery
) -> Result<()> {
Ok(build_thesaurus_from_haystack(
&mut self.config_state,
search_query
).await?)
}
}
Pattern:
- Delegate to middleware
- Shared config state mutation
- Error wrapper for consistent API
LLM Integration
Client Management
impl TerraphimService {
pub async fn get_llm_client(&self) -> Result<LlmClient> {
let config_guard = self.config_state.config.lock().await;
let role = config_guard.get_role(&query.role)?;
}
}
Pattern:
- Lazy initialisation
- Caching for performance
- Configuration-driven creation
Summarisation
impl TerraphimService {
pub async fn summarise_documents(
&self,
request: SummarisationRequest
) -> Result<SummarisationResult> {
let client = self.get_llm_client().await?;
}
}
Pattern:
- Client retrieval
- Context building
- Provider-agnostic operation
- Error handling
Error Handling
ServiceError
Comprehensive error type for service operations.
pub enum ServiceError {
#[error("Middleware error: {0}")]
Middleware(#[from] terraphim_middleware::Error),
#[error("OpenDal error: {0}")]
OpenDal(Box<opendal::Error>),
#[error("Persistence error: {0}")]
Persistence(#[from] terraphim_persistence::Error),
#[error("Config error: {0}")]
Config(String),
#[cfg(feature = "openrouter")]
#[error("OpenRouter error: {0}")]
OpenRouter(#[from] crate::openrouter::OpenRouterError),
#[error("Common error: {0}")]
Common(#[from] crate::error::CommonError),
}
Categories:
- Integration: Middleware, OpenRouter errors
- Storage: OpenDal, Persistence errors
- Configuration: Config errors
- Common: Shared error types
Error Handling Patterns
pub async fn example_operation(&self) -> Result<()> {
let config = self.config_state.config.lock().await;
let role = config.get_role(&role_name)
.ok_or_else(|| ServiceError::Config(...))?;
let result = risky_operation().await?;
let result = another_operation()
.await
.map_err(|e| ServiceError::Middleware(...))?;
Ok(())
}
Concurrency Patterns
Shared State
Mutex Protection
pub struct ConfigState {
pub config: Arc<Mutex<TerraphimConfig>>,
pub roles: AHashMap<RoleName, RoleGraphSync>,
pub llm_client: Arc<Mutex<Option<LlmClient>>>,
}
Pattern:
Arc for shared ownership
Mutex for exclusive access
- Minimal lock duration
- Lock order consistency
Async Locking
pub async fn get_config(&self) -> Result<Role> {
let config_guard = self.config_state.config.lock().await;
config_guard.get_role(&role_name)
.cloned()
.ok_or_else(|| ServiceError::Config(...))
}
Pattern:
lock().await for async mutex
- Minimise lock duration
- Clone before releasing
- Avoid deadlock with consistent ordering
Performance Optimisations
Caching
Role Graph Caching
impl ConfigState {
pub async fn get_or_build_role_graph(
&self,
role_name: &RoleName
) -> Result<RoleGraphSync> {
if let Some(cached) = self.roles.get(role_name) {
return Ok(cached.clone());
}
let role_graph = self.build_role_graph(role_name).await?;
self.roles.insert(role_name.clone(), role_graph.clone());
Ok(role_graph)
}
}
Pattern:
- Check cache before building
- Clone for thread safety
- Populate cache on miss
- Return cached value
LLM Client Caching
impl TerraphimService {
pub async fn get_llm_client(&self) -> Result<LlmClient> {
let client_guard = self.config_state.llm_client.lock().await;
if let Some(client) = client_guard.as_ref() {
return Ok(client.clone());
}
let client = LlmClient::new(...)?;
*client_guard = Some(client.clone());
Ok(client)
}
}
Pattern:
- Check initialised state
- Initialise on first use
- Cache for subsequent calls
- Clone for thread safety
Batch Operations
Bulk Document Processing
impl TerraphimService {
pub async fn summarise_documents_batch(
&self,
documents: Vec<Document>
) -> Result<Vec<SummarisationResult>> {
let results = tokio::try_join_all(
documents.into_iter()
.map(|doc| self.summarise_document(doc))
.collect()
).await;
results.into_iter().collect()
}
}
Pattern:
try_join_all for concurrent execution
- Preserve error information
- Collect all results
- Handle partial failures
Testing Patterns
Unit Tests
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_search_simple() {
let service = create_test_service().await;
let query = create_test_query();
let results = service.search(&query).await.unwrap();
assert!(!results.is_empty());
}
#[tokio::test]
async fn test_thesaurus_building() {
let mut service = create_test_service().await;
let query = create_test_query();
service.build_thesaurus(&query).await.unwrap();
let role = service.get_role("test").await.unwrap();
assert!(!role.thesaurus.is_empty());
}
}
Integration Tests
#[tokio::test]
async fn test_end_to_end_search() {
}
Future Enhancements
Planned Features
Streaming Responses
impl TerraphimService {
pub async fn stream_llm_response(
&self,
messages: Vec<LlmMessage>
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>> {
}
}
Caching Layer
impl TerraphimService {
pub async fn search_with_cache(
&self,
query: &SearchQuery,
ttl: Duration
) -> Result<Vec<Document>> {
}
}
Rate Limiting
impl TerraphimService {
pub async fn search_rate_limited(
&self,
query: &SearchQuery
) -> Result<Vec<Document>> {
}
}
Best Practices
Service Design
- Keep services thin and focused
- Delegate to specialised crates
- Use async throughout
- Handle errors gracefully
Configuration
- Load configuration once
- Cache initialised objects
- Support runtime updates
- Validate at load time
Error Handling
- Use
Result<T> consistently
- Provide context in errors
- Categorise error types
- Support graceful degradation
Concurrency
- Minimise lock duration
- Use
Arc for sharing
- Avoid nested locks
- Respect async boundaries
References