use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
fn default_datetime() -> DateTime<Utc> {
Utc::now()
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct ChatRequest {
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_type: Option<AgentType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct ChatResponse {
pub response: String,
pub agent: String,
pub context_id: String,
pub sources: Option<Vec<Source>>,
}
#[derive(Debug, Serialize, Deserialize, ToSchema, Clone)]
pub struct Source {
pub title: String,
pub url: Option<String>,
pub relevance_score: f32,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct ResearchRequest {
pub query: String,
pub depth: Option<u8>,
pub max_iterations: Option<u8>,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct ResearchResponse {
pub findings: String,
pub sources: Vec<Source>,
pub duration_ms: u64,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RagIngestRequest {
pub collection: String,
pub content: String,
pub title: Option<String>,
pub source: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub chunking_strategy: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RagIngestResponse {
pub chunks_created: usize,
pub document_ids: Vec<String>,
pub collection: String,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RagSearchRequest {
pub collection: String,
pub query: String,
#[serde(default = "default_search_limit")]
pub limit: usize,
#[serde(default)]
pub strategy: Option<String>,
#[serde(default = "default_search_threshold")]
pub threshold: f32,
#[serde(default)]
pub rerank: bool,
#[serde(default)]
pub reranker_model: Option<String>,
}
fn default_search_limit() -> usize {
10
}
fn default_search_threshold() -> f32 {
0.0
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RagSearchResult {
pub id: String,
pub content: String,
pub score: f32,
pub metadata: DocumentMetadata,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RagSearchResponse {
pub results: Vec<RagSearchResult>,
pub total: usize,
pub strategy: String,
pub reranked: bool,
pub duration_ms: u64,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RagDeleteCollectionRequest {
pub collection: String,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RagDeleteCollectionResponse {
pub success: bool,
pub collection: String,
pub documents_deleted: usize,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct WorkflowRequest {
pub query: String,
#[serde(default)]
pub context: std::collections::HashMap<String, serde_json::Value>,
}
#[derive(Debug, Serialize, Deserialize, ToSchema, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum AgentType {
Router,
Orchestrator,
Product,
Invoice,
Sales,
Finance,
#[serde(rename = "hr")]
HR,
#[serde(untagged)]
Custom(String),
}
impl AgentType {
pub fn as_str(&self) -> &str {
match self {
AgentType::Router => "router",
AgentType::Orchestrator => "orchestrator",
AgentType::Product => "product",
AgentType::Invoice => "invoice",
AgentType::Sales => "sales",
AgentType::Finance => "finance",
AgentType::HR => "hr",
AgentType::Custom(name) => name,
}
}
pub fn from_string(s: &str) -> Self {
match s.to_lowercase().as_str() {
"router" => AgentType::Router,
"orchestrator" => AgentType::Orchestrator,
"product" => AgentType::Product,
"invoice" => AgentType::Invoice,
"sales" => AgentType::Sales,
"finance" => AgentType::Finance,
"hr" => AgentType::HR,
_ => AgentType::Custom(s.to_string()),
}
}
pub fn is_builtin(&self) -> bool {
!matches!(self, AgentType::Custom(_))
}
}
impl std::fmt::Display for AgentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct AgentContext {
pub user_id: String,
pub session_id: String,
pub conversation_history: Vec<Message>,
pub user_memory: Option<UserMemory>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub role: MessageRole,
pub content: String,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MessageRole {
System,
User,
Assistant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserMemory {
pub user_id: String,
pub preferences: Vec<Preference>,
pub facts: Vec<MemoryFact>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Preference {
pub category: String,
pub key: String,
pub value: String,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryFact {
pub id: String,
pub user_id: String,
pub category: String,
pub fact_key: String,
pub fact_value: String,
pub confidence: f32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: serde_json::Value,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ToolResult {
pub tool_call_id: String,
pub result: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Document {
pub id: String,
pub content: String,
pub metadata: DocumentMetadata,
pub embedding: Option<Vec<f32>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
pub struct DocumentMetadata {
#[serde(default)]
pub title: String,
#[serde(default)]
pub source: String,
#[serde(default = "default_datetime")]
pub created_at: DateTime<Utc>,
#[serde(default)]
pub tags: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct SearchQuery {
pub query: String,
pub limit: usize,
pub threshold: f32,
pub filters: Option<Vec<SearchFilter>>,
}
#[derive(Debug, Clone)]
pub struct SearchFilter {
pub field: String,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct SearchResult {
pub document: Document,
pub score: f32,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct LoginRequest {
pub email: String,
pub password: String,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct RegisterRequest {
pub email: String,
pub password: String,
pub name: String,
}
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct TokenResponse {
pub access_token: String,
pub refresh_token: String,
pub expires_in: i64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
pub sub: String,
pub email: String,
pub exp: usize,
pub iat: usize,
}
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ErrorCode {
DatabaseError,
LlmError,
AuthenticationFailed,
AuthorizationFailed,
NotFound,
InvalidInput,
ConfigurationError,
ExternalServiceError,
InternalError,
}
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("Database error: {0}")]
Database(String),
#[error("LLM error: {0}")]
LLM(String),
#[error("Authentication error: {0}")]
Auth(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Configuration error: {0}")]
Configuration(String),
#[error("External service error: {0}")]
External(String),
#[error("Internal error: {0}")]
Internal(String),
#[error("Service unavailable: {0}")]
Unavailable(String),
#[error("Rate limited: {0}")]
RateLimited(String),
}
impl AppError {
pub fn code(&self) -> ErrorCode {
match self {
AppError::Database(_) => ErrorCode::DatabaseError,
AppError::LLM(_) => ErrorCode::LlmError,
AppError::Auth(_) => ErrorCode::AuthenticationFailed,
AppError::NotFound(_) => ErrorCode::NotFound,
AppError::InvalidInput(_) => ErrorCode::InvalidInput,
AppError::Configuration(_) => ErrorCode::ConfigurationError,
AppError::External(_) => ErrorCode::ExternalServiceError,
AppError::Internal(_) => ErrorCode::InternalError,
AppError::Unavailable(_) => ErrorCode::InternalError,
AppError::RateLimited(_) => ErrorCode::InternalError,
}
}
fn is_internal(&self) -> bool {
matches!(
self,
AppError::Database(_)
| AppError::LLM(_)
| AppError::Configuration(_)
| AppError::Internal(_)
)
}
}
impl From<std::io::Error> for AppError {
fn from(err: std::io::Error) -> Self {
AppError::Internal(format!("IO error: {}", err))
}
}
impl From<serde_json::Error> for AppError {
fn from(err: serde_json::Error) -> Self {
AppError::InvalidInput(format!("JSON error: {}", err))
}
}
impl axum::response::IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
if self.is_internal() {
tracing::error!(error = %self, code = ?self.code(), "Internal error occurred");
}
let (status, message) = match &self {
AppError::Database(msg) => (axum::http::StatusCode::INTERNAL_SERVER_ERROR, msg.clone()),
AppError::LLM(msg) => (axum::http::StatusCode::INTERNAL_SERVER_ERROR, msg.clone()),
AppError::Auth(msg) => (axum::http::StatusCode::UNAUTHORIZED, msg.clone()),
AppError::NotFound(msg) => (axum::http::StatusCode::NOT_FOUND, msg.clone()),
AppError::InvalidInput(msg) => (axum::http::StatusCode::BAD_REQUEST, msg.clone()),
AppError::Configuration(msg) => {
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, msg.clone())
}
AppError::External(msg) => (axum::http::StatusCode::BAD_GATEWAY, msg.clone()),
AppError::Internal(msg) => (axum::http::StatusCode::INTERNAL_SERVER_ERROR, msg.clone()),
AppError::Unavailable(msg) => (axum::http::StatusCode::SERVICE_UNAVAILABLE, msg.clone()),
AppError::RateLimited(msg) => (axum::http::StatusCode::TOO_MANY_REQUESTS, msg.clone()),
};
let body = serde_json::json!({
"error": message,
"code": self.code()
});
(status, axum::Json(body)).into_response()
}
}
pub type Result<T> = std::result::Result<T, AppError>;