#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod parser;
pub mod prompt;
pub mod providers;
pub mod util;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use thiserror::Error;
pub use tdln_ast::SemanticUnit;
#[derive(Debug, Error)]
pub enum BrainError {
#[error("provider error: {0}")]
Provider(String),
#[error("hallucination: invalid TDLN JSON: {0}")]
Hallucination(String),
#[error("context window exceeded")]
ContextOverflow,
#[error("parsing error: {0}")]
Parsing(String),
#[error("render error: {0}")]
Render(String),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Message {
pub role: String,
pub content: String,
}
impl Message {
#[must_use]
pub fn system(s: impl Into<String>) -> Self {
Self {
role: "system".into(),
content: s.into(),
}
}
#[must_use]
pub fn user(s: impl Into<String>) -> Self {
Self {
role: "user".into(),
content: s.into(),
}
}
#[must_use]
pub fn assistant(s: impl Into<String>) -> Self {
Self {
role: "assistant".into(),
content: s.into(),
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct CognitiveContext {
pub system_directive: String,
pub recall: Vec<String>,
pub history: Vec<Message>,
pub constraints: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GenerationConfig {
pub temperature: f32,
pub max_tokens: Option<u32>,
pub require_reasoning: bool,
}
impl Default for GenerationConfig {
fn default() -> Self {
Self {
temperature: 0.0,
max_tokens: Some(1024),
require_reasoning: false,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct UsageMeta {
pub input_tokens: u32,
pub output_tokens: u32,
pub model_id: String,
}
#[derive(Clone, Debug)]
pub struct RawOutput {
pub content: String,
pub meta: UsageMeta,
}
#[derive(Debug)]
pub struct Decision {
pub reasoning: Option<String>,
pub intent: SemanticUnit,
pub meta: UsageMeta,
}
#[async_trait]
pub trait NeuralBackend: Send + Sync {
fn model_id(&self) -> &str;
async fn generate(
&self,
messages: &[Message],
config: &GenerationConfig,
) -> Result<RawOutput, BrainError>;
}
pub struct Brain<B: NeuralBackend> {
backend: B,
}
impl<B: NeuralBackend> Brain<B> {
#[must_use]
pub fn new(backend: B) -> Self {
Self { backend }
}
#[must_use]
pub fn backend(&self) -> &B {
&self.backend
}
pub async fn reason(
&self,
ctx: &CognitiveContext,
cfg: &GenerationConfig,
) -> Result<Decision, BrainError> {
let msgs = prompt::render(ctx).map_err(BrainError::Render)?;
let raw = self.backend.generate(&msgs, cfg).await?;
parser::parse_decision(&raw.content, raw.meta)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn message_builders() {
let sys = Message::system("hello");
assert_eq!(sys.role, "system");
assert_eq!(sys.content, "hello");
let usr = Message::user("hi");
assert_eq!(usr.role, "user");
let ast = Message::assistant("ok");
assert_eq!(ast.role, "assistant");
}
#[test]
fn default_config() {
let cfg = GenerationConfig::default();
assert_eq!(cfg.temperature, 0.0);
assert_eq!(cfg.max_tokens, Some(1024));
assert!(!cfg.require_reasoning);
}
#[test]
fn cognitive_context_default() {
let ctx = CognitiveContext::default();
assert!(ctx.system_directive.is_empty());
assert!(ctx.recall.is_empty());
assert!(ctx.history.is_empty());
assert!(ctx.constraints.is_empty());
}
}