Skip to content

Commit 61f7df1

Browse files
feat: add terraphim_router and terraphim_spawner crates for unified LLM/agent routing
* feat: Add terraphim_router crate for unified LLM and Agent routing This commit adds a new terraphim_router crate that provides capability-based routing for both LLM models and spawned agents using the same routing logic. Features: - KeywordRouter: Extracts capabilities from prompts using keyword matching - RoutingEngine: Main routing logic with fallback handling - 4 Routing Strategies: CostOptimized, LatencyOptimized, CapabilityFirst, RoundRobin - ProviderRegistry: Loads providers from markdown files with YAML frontmatter - Capability Types: DeepThinking, CodeGeneration, SecurityAudit, etc. Also adds capability module to terraphim_types with: - Capability enum for capability types - Provider struct for unified LLM/Agent providers - ProviderType enum distinguishing LLM vs Agent - CostLevel and Latency enums for optimization All code is edition2021 compatible and includes comprehensive unit tests. * feat: Add terraphim_spawner crate for agent process management This crate provides functionality to spawn external AI agents as non-interactive processes with health checking, output capture, and @mention detection. Features: - AgentSpawner: Spawn agents from Provider configuration - AgentValidator: Validate CLI exists, API keys set, working directory valid - HealthChecker: 30s interval health checks with status tracking - OutputCapture: Capture stdout/stderr with @mention detection - Auto-restart on failure (configurable) Supports agents: - Claude Code (claude) - OpenCode (opencode) - Codex (codex) - Custom agents via CLI command All code includes comprehensive unit tests. * feat: Add unified routing example and complete integration - Add unified_routing.rs example showing LLM + Agent routing - Demonstrates capability-based routing with different strategies - Shows how Router and AgentSpawner work together - Update Cargo.toml with dev-dependencies for examples This completes Phase 3: Integration between terraphim_router and terraphim_spawner. * docs: Add comprehensive README for unified routing - Architecture overview - Quick start guide - Provider configuration examples - Routing strategies documentation - Testing instructions * feat: Add @mention routing and integration tests - Add MentionRouter for decentralized agent coordination - Agents can route messages via @mentions in output - Add integration tests for unified routing - Test LLM vs Agent provider selection - Test capability-based routing with cost optimization * feat: Add fallback, metrics, and knowledge graph integration - FallbackRouter with multiple fallback strategies - NextBestProvider, LlmFallback, Retry, FailFast - Configurable max fallback attempts - Automatic retry with provider exclusion - RouterMetrics for observability - Routing request/success/failure counts - Spawn attempt/success/failure counts - Health failure tracking - Structured logging with targets - KnowledgeGraphRouter for smart routing - Term expansion using thesauri - Role-based concept lookup - Provider relevance scoring - Related concept discovery All modules include comprehensive unit tests. * feat: Add advanced routing example Demonstrates: - Fallback routing with NextBestProvider strategy - Metrics collection and reporting - Knowledge graph term expansion - LLM fallback when agent fails - Complete metrics summary output
1 parent fa49546 commit 61f7df1

22 files changed

Lines changed: 3596 additions & 0 deletions

crates/terraphim_router/Cargo.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "terraphim_router"
3+
version = "1.8.0"
4+
edition = "2021"
5+
authors = ["Terraphim Team"]
6+
description = "Unified routing engine for LLM and Agent providers"
7+
license = "Apache-2.0"
8+
repository = "https://github.com/terraphim/terraphim-ai"
9+
10+
[dependencies]
11+
# Terraphim internal crates
12+
terraphim_types = { path = "../terraphim_types", version = "1.8.0" }
13+
14+
# Core dependencies
15+
serde = { version = "1.0", features = ["derive"] }
16+
serde_yaml = "0.9"
17+
serde_json = "1.0"
18+
regex = "1.10"
19+
thiserror = "1.0"
20+
anyhow = "1.0"
21+
log = "0.4"
22+
chrono = { version = "0.4", features = ["serde"] }
23+
tokio = { version = "1.0", features = ["full"] }
24+
async-trait = "0.1"
25+
uuid = { version = "1.0", features = ["v4", "serde"] }
26+
dirs = "5.0"
27+
28+
[dev-dependencies]
29+
tokio-test = "0.4"
30+
tempfile = "3.8"
31+
terraphim_spawner = { path = "../terraphim_spawner", version = "1.8.0" }

crates/terraphim_router/README.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Unified Routing for Terraphim
2+
3+
This directory contains the unified routing system for Terraphim AI, providing capability-based routing for both LLM providers and spawned agents.
4+
5+
## Crates
6+
7+
### terraphim_router
8+
Capability-based routing engine that routes tasks to the best provider based on:
9+
- Keyword extraction from prompts
10+
- Provider capabilities
11+
- Routing strategies (cost, latency, capability match)
12+
13+
### terraphim_spawner
14+
Agent process spawner with:
15+
- CLI validation
16+
- API key checking
17+
- Health monitoring (30s heartbeat)
18+
- Output capture with @mention detection
19+
- Auto-restart on failure
20+
21+
### terraphim_types (updated)
22+
Added capability types:
23+
- `Capability` enum (DeepThinking, CodeGeneration, etc.)
24+
- `Provider` struct (unified LLM/Agent)
25+
- `ProviderType` enum (Llm vs Agent)
26+
- `CostLevel` and `Latency` enums
27+
28+
## Quick Start
29+
30+
```rust
31+
use terraphim_router::{Router, RoutingContext};
32+
use terraphim_types::capability::{
33+
Capability, Provider, ProviderType, CostLevel, Latency
34+
};
35+
use std::path::PathBuf;
36+
37+
// Create router
38+
let mut router = Router::new();
39+
40+
// Register LLM provider
41+
router.add_provider(Provider::new(
42+
"claude-opus",
43+
"Claude Opus",
44+
ProviderType::Llm {
45+
model_id: "claude-3-opus-20240229".to_string(),
46+
api_endpoint: "https://api.anthropic.com/v1".to_string(),
47+
},
48+
vec![Capability::DeepThinking, Capability::CodeGeneration],
49+
));
50+
51+
// Register agent provider
52+
router.add_provider(Provider::new(
53+
"@codex",
54+
"Codex Agent",
55+
ProviderType::Agent {
56+
agent_id: "@codex".to_string(),
57+
cli_command: "opencode".to_string(),
58+
working_dir: PathBuf::from("/workspace"),
59+
},
60+
vec![Capability::CodeGeneration],
61+
));
62+
63+
// Route a task
64+
let decision = router.route(
65+
"Implement a function to parse JSON",
66+
&RoutingContext::default(),
67+
)?;
68+
69+
println!("Routed to: {}", decision.provider.name);
70+
```
71+
72+
## Provider Configuration
73+
74+
Providers can be configured via markdown files with YAML frontmatter:
75+
76+
```markdown
77+
---
78+
id: "claude-opus"
79+
name: "Claude Opus"
80+
type: "llm"
81+
model_id: "claude-3-opus-20240229"
82+
api_endpoint: "https://api.anthropic.com/v1"
83+
capabilities:
84+
- deep-thinking
85+
- code-generation
86+
cost: expensive
87+
latency: slow
88+
keywords:
89+
- think
90+
- reason
91+
---
92+
93+
# Claude Opus
94+
95+
Anthropic's most capable model.
96+
```
97+
98+
## Routing Strategies
99+
100+
- **CostOptimized**: Select cheapest provider
101+
- **LatencyOptimized**: Select fastest provider
102+
- **CapabilityFirst**: Select provider with most capabilities
103+
- **RoundRobin**: Distribute load evenly
104+
105+
## Architecture
106+
107+
```
108+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
109+
│ User Prompt │────▶│ KeywordRouter │────▶│ Capabilities │
110+
└─────────────────┘ └──────────────────┘ └─────────────────┘
111+
112+
113+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
114+
│ LLM Provider │◀────│ RoutingEngine │◀────│ ProviderRegistry│
115+
│ (API call) │ │ (Strategy) │ │ (Filtered) │
116+
└─────────────────┘ └──────────────────┘ └─────────────────┘
117+
118+
119+
┌─────────────────┐ ┌──────────────────┐
120+
│ Agent Process │◀────│ AgentSpawner │
121+
│ (Spawned) │ │ (Health + I/O) │
122+
└─────────────────┘ └──────────────────┘
123+
```
124+
125+
## Testing
126+
127+
```bash
128+
cargo test -p terraphim_router
129+
cargo test -p terraphim_spawner
130+
```
131+
132+
## Examples
133+
134+
See `terraphim_router/examples/unified_routing.rs` for a complete example.
135+
136+
## License
137+
138+
Apache-2.0
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//! Example: Advanced routing with fallback, metrics, and knowledge graph
2+
//!
3+
//! This example demonstrates:
4+
//! - Fallback routing when providers fail
5+
//! - Metrics collection
6+
//! - Knowledge graph integration for term expansion
7+
8+
use std::path::PathBuf;
9+
use terraphim_router::{
10+
FallbackRouter, FallbackStrategy, KnowledgeGraphRouter,
11+
Router, RouterMetrics, RoutingContext, Timer,
12+
};
13+
use terraphim_types::capability::{
14+
Capability, CostLevel, Latency, Provider, ProviderType,
15+
};
16+
use terraphim_types::{NormalizedTerm, NormalizedTermValue, RoleName, Thesaurus};
17+
18+
#[tokio::main]
19+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
20+
println!("=== Advanced Unified Routing Example ===\n");
21+
22+
// Create metrics collector
23+
let metrics = RouterMetrics::new();
24+
25+
// Create base router
26+
let mut router = Router::new();
27+
28+
// Register providers
29+
router.add_provider(Provider::new(
30+
"gpt-4",
31+
"GPT-4",
32+
ProviderType::Llm {
33+
model_id: "gpt-4".to_string(),
34+
api_endpoint: "https://api.openai.com".to_string(),
35+
},
36+
vec![Capability::DeepThinking, Capability::CodeGeneration],
37+
).with_cost(CostLevel::Expensive));
38+
39+
router.add_provider(Provider::new(
40+
"@codex",
41+
"Codex Agent",
42+
ProviderType::Agent {
43+
agent_id: "@codex".to_string(),
44+
cli_command: "opencode".to_string(),
45+
working_dir: PathBuf::from("/workspace"),
46+
},
47+
vec![Capability::CodeGeneration],
48+
).with_cost(CostLevel::Cheap));
49+
50+
// Example 1: Fallback routing
51+
println!("=== Example 1: Fallback Routing ===");
52+
let fallback_router = FallbackRouter::new(router.clone())
53+
.with_strategy(FallbackStrategy::NextBestProvider)
54+
.with_max_fallbacks(3);
55+
56+
let timer = Timer::start();
57+
let result = fallback_router
58+
.route_with_fallback(
59+
"Implement a function",
60+
&RoutingContext::default(),
61+
|provider| async move {
62+
println!(" Trying provider: {}", provider.id);
63+
// Simulate failure for first provider
64+
if provider.id == "gpt-4" {
65+
Err("API rate limited".to_string())
66+
} else {
67+
Ok(())
68+
}
69+
},
70+
)
71+
.await;
72+
73+
match result {
74+
Ok(decision) => {
75+
println!(" ✓ Successfully routed to: {}", decision.provider.id);
76+
metrics.record_routing_request(&decision.provider,
77+
timer.elapsed_ms()
78+
);
79+
}
80+
Err(e) => {
81+
println!(" ✗ Routing failed: {}", e);
82+
metrics.record_routing_failure("all_providers_failed");
83+
}
84+
}
85+
86+
// Example 2: Knowledge graph term expansion
87+
println!("\n=== Example 2: Knowledge Graph Integration ===");
88+
let mut kg_router = KnowledgeGraphRouter::new()
89+
.with_default_role(RoleName::new("engineer"));
90+
91+
// Create a thesaurus with synonyms
92+
let mut thesaurus = Thesaurus::new("programming".to_string());
93+
let mut term = NormalizedTerm::new(
94+
1,
95+
NormalizedTermValue::from("rust"),
96+
);
97+
term.synonyms.push(NormalizedTermValue::from("rustlang"));
98+
term.synonyms.push(NormalizedTermValue::from("rust-lang"));
99+
thesaurus.insert(
100+
NormalizedTermValue::from("rust"),
101+
term,
102+
);
103+
104+
kg_router.add_thesaurus(RoleName::new("engineer"), thesaurus);
105+
106+
let terms = vec![NormalizedTermValue::from("rust")];
107+
let expanded = kg_router.expand_terms(
108+
&terms,
109+
Some(&RoleName::new("engineer")),
110+
);
111+
112+
println!(" Original terms: {:?}", terms);
113+
println!(" Expanded terms: {:?}", expanded);
114+
115+
// Example 3: LLM fallback when agent fails
116+
println!("\n=== Example 3: LLM Fallback Strategy ===");
117+
let llm_fallback_router = FallbackRouter::new(router.clone())
118+
.with_strategy(FallbackStrategy::LlmFallback);
119+
120+
let result = llm_fallback_router
121+
.route_with_fallback(
122+
"Think deeply about system design",
123+
&RoutingContext::default(),
124+
|provider| async move {
125+
match &provider.provider_type {
126+
ProviderType::Agent { .. } => {
127+
println!(" Agent {} failed to spawn", provider.id);
128+
Err("Spawn failed".to_string())
129+
}
130+
ProviderType::Llm { model_id, .. } => {
131+
println!(" LLM {} succeeded", model_id);
132+
Ok(())
133+
}
134+
}
135+
},
136+
)
137+
.await;
138+
139+
if let Ok(decision) = result {
140+
println!(" ✓ Fallback to LLM: {}", decision.provider.id);
141+
}
142+
143+
// Print metrics summary
144+
println!("\n=== Metrics Summary ===");
145+
println!("{}", metrics);
146+
147+
println!("\n=== All Examples Complete ===");
148+
Ok(())
149+
}

0 commit comments

Comments
 (0)