Files
doctate/server/src/main.rs
T
Brummel 70eed20909 Add LLM system prompt override
Allow overriding the LLM system prompt via the `LLM_SYSTEM_PROMPT`
environment variable. This provides flexibility for customizing LLM
behavior without code changes. A default prompt is used if the
environment variable is unset or empty.
2026-04-15 21:31:21 +02:00

114 lines
3.5 KiB
Rust

use std::sync::Arc;
use tokio::net::TcpListener;
use tower_http::limit::RequestBodyLimitLayer;
use tower_http::trace::TraceLayer;
use tracing::{info, warn};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use doctate_server::analyze;
use doctate_server::config::Config;
use doctate_server::transcribe;
use doctate_server::AppState;
#[tokio::main]
async fn main() {
dotenvy::dotenv().ok();
let config = Arc::new(Config::from_env());
// Logging: stdout + daily rotating log files.
let env_filter = EnvFilter::try_new(&config.log_level)
.unwrap_or_else(|_| EnvFilter::new("info"));
let file_appender = tracing_appender::rolling::daily(&config.log_path, "recorder.log");
let (file_writer, _guard) = tracing_appender::non_blocking(file_appender);
tracing_subscriber::registry()
.with(env_filter)
.with(
tracing_subscriber::fmt::layer()
.with_target(false)
.with_writer(std::io::stdout),
)
.with(
tracing_subscriber::fmt::layer()
.with_target(false)
.with_ansi(false)
.with_writer(file_writer),
)
.init();
// Surface configuration gaps that silently disable features.
if !config.llm_configured() {
warn!(
"LLM not configured — 'Fall abschließen' is disabled. \
Set LLM_URL, LLM_API_KEY and LLM_MODEL in .env to enable."
);
}
{
let custom = std::env::var("LLM_SYSTEM_PROMPT")
.map(|v| !v.is_empty())
.unwrap_or(false);
info!(
prompt_source = if custom { "env" } else { "default" },
prompt_chars = config.llm_system_prompt.chars().count(),
"system prompt loaded"
);
}
// Shared HTTP client for both downstream pipelines.
let http_client = reqwest::Client::builder()
.build()
.expect("reqwest client build failed");
// Transcription pipeline: channel + worker + recovery scan.
let (transcribe_tx, transcribe_rx) = transcribe::channel();
tokio::spawn(transcribe::worker::run(
transcribe_rx,
config.clone(),
http_client.clone(),
));
{
let tx = transcribe_tx.clone();
let data_path = config.data_path.clone();
tokio::spawn(async move {
transcribe::recovery::scan_and_enqueue(&data_path, &tx).await;
});
}
// Analyze pipeline: channel + worker + recovery scan.
let (analyze_tx, analyze_rx) = analyze::channel();
tokio::spawn(analyze::worker::run(
analyze_rx,
config.clone(),
http_client.clone(),
));
{
let tx = analyze_tx.clone();
let data_path = config.data_path.clone();
tokio::spawn(async move {
analyze::recovery::scan_and_enqueue(&data_path, &tx).await;
});
}
let state = AppState {
config: config.clone(),
transcribe_tx,
analyze_tx,
session_store: doctate_server::web_session::new_store(),
};
let addr = format!("0.0.0.0:{}", config.server_port);
info!(port = config.server_port, "Server starting");
let app = doctate_server::create_router_with_state(state)
.layer(TraceLayer::new_for_http())
.layer(RequestBodyLimitLayer::new(50 * 1024 * 1024)); // 50 MB
let listener = TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}