ceylon_runtime/lib.rs
1//! # Ceylon Runtime
2//!
3//! A Rust-based agent mesh framework for building local and distributed AI agent systems.
4//!
5//! ## Architecture
6//!
7//! Ceylon Runtime is modular, split into focused sub-crates that are re-exported here:
8//!
9//! | Crate | Description |
10//! |-------|-------------|
11//! | [`core`] | Core abstractions: Agent, Memory, Mesh, Message |
12//! | [`llm`] | LLM integration with 13+ providers |
13//! | [`local`] | Local mesh implementation |
14//! | [`memory`] | Memory backend implementations |
15//! | [`logging`]/[`metrics`] | Observability |
16//! | [`mcp`] | Model Context Protocol (feature: `mcp`) |
17//!
18//! ## Features
19//!
20//! - **Agent Framework**: Build autonomous AI agents with a simple trait-based API
21//! - **Local Mesh**: Connect multiple agents for inter-agent communication
22//! - **LLM Integration**: Built-in support for OpenAI, Anthropic, Ollama, Google, and 10+ more
23//! - **Memory Backends**: Pluggable memory (in-memory, SQLite, Redis)
24//! - **TOML Configuration**: Load agents from config files
25//! - **Async-first**: Built on Tokio for high performance
26//! - **Observability**: Structured logging and metrics
27//!
28//! ## Quick Start
29//!
30//! ### Custom Agent
31//!
32//! ```rust,no_run
33//! use ceylon_runtime::{Agent, AgentContext, LocalMesh, Message};
34//! use ceylon_runtime::core::error::Result;
35//! use ceylon_runtime::core::mesh::Mesh;
36//! use async_trait::async_trait;
37//!
38//! struct MyAgent { name: String }
39//!
40//! #[async_trait]
41//! impl Agent for MyAgent {
42//! fn name(&self) -> String { self.name.clone() }
43//!
44//! async fn on_message(&mut self, msg: Message, _ctx: &mut AgentContext) -> Result<()> {
45//! println!("Received: {:?}", msg.topic);
46//! Ok(())
47//! }
48//! }
49//!
50//! #[tokio::main]
51//! async fn main() -> anyhow::Result<()> {
52//! let mesh = LocalMesh::new("my-mesh");
53//! mesh.add_agent(Box::new(MyAgent { name: "agent".into() })).await?;
54//! mesh.start().await?;
55//! Ok(())
56//! }
57//! ```
58//!
59//! ### LLM Agent
60//!
61//! ```rust,no_run
62//! use ceylon_runtime::{LlmAgent, AgentContext};
63//!
64//! #[tokio::main]
65//! async fn main() -> anyhow::Result<()> {
66//! let mut agent = LlmAgent::builder("assistant", "ollama::gemma3:latest")
67//! .with_system_prompt("You are helpful.")
68//! .with_temperature(0.7)
69//! .build()?;
70//!
71//! let mut ctx = AgentContext::new("test".into(), None);
72//! let response = agent.send_message_and_get_response("Hello!", &mut ctx).await?;
73//! println!("{}", response);
74//! Ok(())
75//! }
76//! ```
77//!
78//! ### TOML Configuration
79//!
80//! ```rust,no_run
81//! use ceylon_runtime::{LlmAgent, LlmAgentFromConfig};
82//! use ceylon_runtime::config::AgentConfig;
83//!
84//! let config = AgentConfig::from_file("agent.toml").unwrap();
85//! let agent = LlmAgent::from_config(config).unwrap();
86//! ```
87//!
88//! ## Feature Flags
89//!
90//! - `sqlite` - SQLite memory backend
91//! - `redis` - Redis memory backend
92//! - `mcp` - Model Context Protocol support
93//! - `full` - All optional features
94
95// Configuration module
96pub mod config;
97
98// Re-export sub-crates as modules
99pub use ceylon_core as core;
100pub use ceylon_llm as llm;
101pub use ceylon_local as local;
102pub use ceylon_memory as memory;
103pub use ceylon_observability::logging;
104pub use ceylon_observability::metrics;
105
106#[cfg(feature = "mcp")]
107pub use ceylon_mcp as mcp;
108
109// Convenience re-exports at crate root
110pub use ceylon_core::{
111 Agent, AgentContext, Memory, MemoryEntry, MemoryQuery, Mesh, Message, VectorMemory,
112};
113pub use ceylon_llm::{LLMConfig, LlmAgent, LlmAgentBuilder, UniversalLLMClient};
114pub use ceylon_local::LocalMesh;
115pub use ceylon_memory::InMemoryBackend;
116pub use config::{AgentConfig, MemoryBackendType, MemoryConfig, MeshConfig};
117
118#[cfg(feature = "sqlite")]
119pub use ceylon_memory::SqliteBackend;
120
121#[cfg(feature = "redis")]
122pub use ceylon_memory::RedisBackend;
123
124#[cfg(feature = "qdrant")]
125pub use ceylon_memory::QdrantBackend;
126
127#[cfg(feature = "chromadb")]
128pub use ceylon_memory::ChromaDBBackend;
129
130#[cfg(feature = "pinecone")]
131pub use ceylon_memory::PineconeBackend;
132
133#[cfg(feature = "mcp")]
134pub use ceylon_mcp::{McpAgentServer, McpClient, McpTransport};
135
136/// Extension trait for creating [`LlmAgent`] from TOML configuration.
137///
138/// Import this trait to use [`LlmAgent::from_config`]:
139///
140/// ```rust,no_run
141/// use ceylon_runtime::{LlmAgent, LlmAgentFromConfig};
142/// use ceylon_runtime::config::AgentConfig;
143///
144/// let config = AgentConfig::from_file("agent.toml").unwrap();
145/// let agent = LlmAgent::from_config(config).unwrap();
146/// ```
147pub trait LlmAgentFromConfig {
148 /// Create an LlmAgent from an [`AgentConfig`].
149 fn from_config(config: AgentConfig) -> core::error::Result<LlmAgent>;
150}
151
152impl LlmAgentFromConfig for LlmAgent {
153 fn from_config(config: AgentConfig) -> core::error::Result<LlmAgent> {
154 let mut builder = LlmAgentBuilder::new(&config.name, &config.model)
155 .with_system_prompt(&config.system_prompt);
156
157 if let Some(ref api_key) = config.api_key {
158 builder = builder.with_api_key(api_key);
159 }
160
161 if let Some(temperature) = config.temperature {
162 builder = builder.with_temperature(temperature);
163 }
164
165 if let Some(max_tokens) = config.max_tokens {
166 builder = builder.with_max_tokens(max_tokens);
167 }
168
169 builder.build()
170 }
171}
172
173/// Extension trait for creating memory backends from TOML configuration.
174///
175/// # Example
176///
177/// ```rust,no_run
178/// use ceylon_runtime::{InMemoryBackend, MemoryFromConfig};
179/// use ceylon_runtime::config::MemoryConfig;
180///
181/// # async fn example() -> anyhow::Result<()> {
182/// let config = MemoryConfig::in_memory().with_max_entries(1000);
183/// let memory = InMemoryBackend::from_config(&config);
184/// # Ok(())
185/// # }
186/// ```
187pub trait MemoryFromConfig {
188 /// Create a memory backend from a [`MemoryConfig`].
189 fn from_config(config: &MemoryConfig) -> Self;
190}
191
192impl MemoryFromConfig for InMemoryBackend {
193 fn from_config(config: &MemoryConfig) -> Self {
194 let mut backend = InMemoryBackend::new();
195
196 if let Some(max_entries) = config.max_entries {
197 backend = backend.with_max_entries(max_entries);
198 }
199
200 if let Some(ttl_seconds) = config.ttl_seconds {
201 backend = backend.with_ttl_seconds(ttl_seconds);
202 }
203
204 backend
205 }
206}
207
208/// Async trait for creating memory backends that require async initialization.
209///
210/// Used for SQLite, Redis, and vector database backends that need async connection setup.
211#[cfg(any(
212 feature = "sqlite",
213 feature = "redis",
214 feature = "qdrant",
215 feature = "chromadb",
216 feature = "pinecone"
217))]
218#[async_trait::async_trait]
219pub trait MemoryFromConfigAsync {
220 /// Create a memory backend from a [`MemoryConfig`] asynchronously.
221 async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self>
222 where
223 Self: Sized;
224}
225
226#[cfg(feature = "sqlite")]
227#[async_trait::async_trait]
228impl MemoryFromConfigAsync for SqliteBackend {
229 async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self> {
230 let path = config.path.as_deref().unwrap_or(":memory:");
231 SqliteBackend::new(path).await
232 }
233}
234
235#[cfg(feature = "redis")]
236#[async_trait::async_trait]
237impl MemoryFromConfigAsync for RedisBackend {
238 async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self> {
239 let url = config.url.as_deref().unwrap_or("redis://localhost:6379");
240 let mut backend = RedisBackend::new(url).await?;
241
242 if let Some(ref prefix) = config.prefix {
243 backend = backend.with_prefix(prefix);
244 }
245
246 if let Some(ttl_seconds) = config.ttl_seconds {
247 backend = backend.with_ttl_seconds(ttl_seconds);
248 }
249
250 Ok(backend)
251 }
252}
253
254#[cfg(feature = "qdrant")]
255#[async_trait::async_trait]
256impl MemoryFromConfigAsync for QdrantBackend {
257 async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self> {
258 let grpc_url = config
259 .grpc_url
260 .as_deref()
261 .unwrap_or("http://localhost:6334");
262 let collection = config.collection.as_deref().unwrap_or("ceylon_memory");
263 let dimensions = config.dimensions.unwrap_or(384);
264
265 QdrantBackend::new(grpc_url, collection, dimensions).await
266 }
267}
268
269#[cfg(feature = "chromadb")]
270#[async_trait::async_trait]
271impl MemoryFromConfigAsync for ChromaDBBackend {
272 async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self> {
273 let url = config.url.as_deref().unwrap_or("http://localhost:8000");
274 let collection = config.collection.as_deref().unwrap_or("ceylon_memory");
275
276 ChromaDBBackend::new(url, collection).await
277 }
278}
279
280#[cfg(feature = "pinecone")]
281#[async_trait::async_trait]
282impl MemoryFromConfigAsync for PineconeBackend {
283 async fn from_config_async(config: &MemoryConfig) -> core::error::Result<Self> {
284 let api_key = config
285 .api_key
286 .as_deref()
287 .ok_or_else(|| core::error::Error::MemoryError("Pinecone requires api_key".into()))?;
288 let collection = config.collection.as_deref().unwrap_or("ceylon-memory");
289 let dimensions = config.dimensions.unwrap_or(1536);
290
291 PineconeBackend::new(api_key, collection, dimensions).await
292 }
293}