Added config file

This commit is contained in:
2026-07-24 20:42:25 +03:00
parent 5cdf47fca8
commit d909f00f64
5 changed files with 191 additions and 52 deletions
+42
View File
@@ -0,0 +1,42 @@
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AppConfig {
pub api_url: String,
pub model: String,
pub max_history_messages: usize, // сколько последних сообщений подтягивать из БД
pub system_prompt: Option<String>, // задел на системный промпт
}
impl Default for AppConfig {
fn default() -> Self {
Self {
api_url: "http://192.168.0.50:6969/v1/chat/completions".to_string(),
model: "qwen2.5-coder".to_string(),
max_history_messages: 10,
system_prompt: Some("You are a helpful programming assistant.".to_string()),
}
}
}
impl AppConfig {
pub fn load_or_create<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
let path = path.as_ref();
if path.exists() {
// Если файл есть — читаем
let content = fs::read_to_string(path)?;
let config: AppConfig = toml::from_str(&content)?;
Ok(config)
} else {
// Если файла нет — создаём дефолтный
let default_config = AppConfig::default();
let toml_string = toml::to_string_pretty(&default_config)?;
fs::write(path, toml_string)?;
println!("[Config]: Создан стандартный файл конфигурации {:?}", path);
Ok(default_config)
}
}
}
+84 -52
View File
@@ -1,3 +1,6 @@
mod config;
use config::AppConfig;
use clap::Parser;
use serde::{Deserialize, Serialize};
use serde_json::json;
@@ -10,20 +13,21 @@ use tokio::io::{AsyncBufReadExt, BufReader};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long, default_value = "default_session")]
session_id: String,
#[arg(short, long)]
session_id: Option<String>,
#[arg(
short,
long,
default_value = "http://192.168.0.50:6969/v1/chat/completions"
)]
api_url: String,
#[arg(long)]
api_url: Option<String>,
#[arg(short, long, default_value = "qwen2.5-coder")]
model: String,
#[arg(short, long)]
model: Option<String>,
#[arg(long)]
limit: Option<usize>,
#[arg(short, long, default_value = "config.toml")]
config: String,
/// Путь к файлу базы данных SQLite
#[arg(long, default_value = "sqlite:chat_history.db?mode=rwc")]
db_url: String,
}
@@ -34,18 +38,17 @@ struct ChatMessage {
content: String,
}
/// Инициализация БД: создание таблицы, индексов и включение режима WAL
/// Инициализация БД (режим WAL)
async fn init_db(db_url: &str) -> Result<Pool<Sqlite>, Box<dyn std::error::Error>> {
let options = SqliteConnectOptions::from_str(db_url)?
.create_if_missing(true)
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal); // WAL режим для высокой параллельности
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal);
let pool = SqlitePoolOptions::new()
.max_connections(10) // До 10 параллельных соединений к БД
.max_connections(10)
.connect_with(options)
.await?;
// Создаем таблицу и индекс, если их нет
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS messages (
@@ -65,7 +68,7 @@ async fn init_db(db_url: &str) -> Result<Pool<Sqlite>, Box<dyn std::error::Error
Ok(pool)
}
/// Сохранение сообщения в историю
/// Сохранение реплики
async fn save_message(
pool: &Pool<Sqlite>,
session_id: &str,
@@ -82,11 +85,11 @@ async fn save_message(
Ok(())
}
/// Выборка последних N (5) сообщений контекста в хронологическом порядке
/// Выборка последних N сообщений
async fn get_recent_history(
pool: &Pool<Sqlite>,
session_id: &str,
limit: i64,
limit: usize,
) -> Result<Vec<ChatMessage>, sqlx::Error> {
let rows = sqlx::query_as::<_, (String, String)>(
r#"
@@ -98,11 +101,11 @@ async fn get_recent_history(
ORDER BY id DESC
LIMIT ?
)
ORDER BY id ASC
ORDER BY id ASC;
"#,
)
.bind(session_id)
.bind(limit * 2)
.bind(limit as i64)
.fetch_all(pool)
.await?;
@@ -114,16 +117,34 @@ async fn get_recent_history(
Ok(history)
}
/// Отправка запроса с учетом накопленного контекста
/// Отправка запроса с контекстом в LLM
async fn send_llm_request_with_history(
client: &reqwest::Client,
api_url: &str,
model: &str,
system_prompt: Option<&str>, // <-- Добавили системный промпт
history: &[ChatMessage],
) -> Result<String, Box<dyn std::error::Error>> {
// Собираем полный вектор сообщений
let mut full_messages = Vec::new();
// 1. Если system_prompt задан и не пустой, ставим его САМЫМ ПЕРВЫМ
if let Some(prompt) = system_prompt {
if !prompt.trim().is_empty() {
full_messages.push(ChatMessage {
role: "system".to_string(),
content: prompt.to_string(),
});
}
}
// 2. Добавляем историю из базы данных (user / assistant)
full_messages.extend_from_slice(history);
// 3. Отправляем в payload уже полный вектор
let payload = json!({
"model": model,
"messages": history, // Передаем весь сохраненный контекст
"messages": full_messages,
"temperature": 0.7
});
@@ -145,25 +166,31 @@ async fn send_llm_request_with_history(
}
#[tokio::main]
async fn main() {
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
// 1. Загружаем или создаём config.toml
let file_config = AppConfig::load_or_create(&args.config)?;
// 2. Объединяем аргументы CLI и значения из конфига
let api_url = args.api_url.unwrap_or(file_config.api_url);
let model = args.model.unwrap_or(file_config.model);
let history_limit = args.limit.unwrap_or(file_config.max_history_messages);
let session_id = args
.session_id
.unwrap_or_else(|| "default_session".to_string());
// 3. Клиент HTTP и подключение к БД
let client = reqwest::Client::new();
// 1. Инициализируем БД
let db_pool = match init_db(&args.db_url).await {
Ok(pool) => pool,
Err(e) => {
eprintln!("Ошибка инициализации БД: {}", e);
return;
}
};
let db_pool = init_db(&args.db_url).await?;
let system_prompt = file_config.system_prompt;
println!("==================================================");
println!(" LLM CLI Agent with Memory (Stage 2)");
println!(" Session ID: {}", args.session_id);
println!(" API URL: {}", args.api_url);
println!(" Model: {}", args.model);
println!(" Database: SQLite (WAL Mode)");
println!(" LLM CLI Agent with Memory & Config");
println!(" Session ID: {}", session_id);
println!(" API URL: {}", api_url);
println!(" Model: {}", model);
println!(" History: последние {} сообщений", history_limit);
println!(" Config: {}", args.config);
println!("==================================================");
println!("Введите сообщение. Для выхода наберите 'exit' или 'quit'.\n");
@@ -171,7 +198,7 @@ async fn main() {
let mut reader = BufReader::new(stdin);
loop {
print!("[Session: {}] > ", args.session_id);
print!("[Session: {}] > ", session_id);
io::stdout().flush().unwrap();
let mut input = String::new();
@@ -193,14 +220,14 @@ async fn main() {
break;
}
// 2. Сохраняем сообщение пользователя в БД
if let Err(e) = save_message(&db_pool, &args.session_id, "user", trimmed_input).await {
// Сохраняем запрос юзера
if let Err(e) = save_message(&db_pool, &session_id, "user", trimmed_input).await {
eprintln!("[Ошибка записи в БД]: {}", e);
continue;
}
// 3. Достаем последние 5 сообщений из БД (включая только что сохраненное)
let history = match get_recent_history(&db_pool, &args.session_id, 5).await {
// Вычитываем контекст (N сообщений)
let history = match get_recent_history(&db_pool, &session_id, history_limit).await {
Ok(h) => h,
Err(e) => {
eprintln!("[Ошибка чтения из БД]: {}", e);
@@ -208,21 +235,24 @@ async fn main() {
}
};
print!(
"[Запрос к LLM с контекстом ({} сообщ.)...]\r",
history.len()
);
print!("[Запрос к LLM (контекст {} сообщ.)...]\r", history.len());
io::stdout().flush().unwrap();
// 4. Отправляем контекст в LLM
match send_llm_request_with_history(&client, &args.api_url, &args.model, &history).await {
// Отправка в LLM
match send_llm_request_with_history(
&client,
&api_url,
&model,
system_prompt.as_deref(), // Преобразуем Option<String> в Option<&str>
&history,
)
.await
{
Ok(reply) => {
// \r - в начало, \x1B[2K - очистить всю текущую строку терминала
print!("\r\x1B[2K");
println!("[LLM]: {}\n", reply);
if let Err(e) = save_message(&db_pool, &args.session_id, "assistant", &reply).await
{
if let Err(e) = save_message(&db_pool, &session_id, "assistant", &reply).await {
eprintln!("[Ошибка записи ответа в БД]: {}", e);
}
}
@@ -232,4 +262,6 @@ async fn main() {
}
}
}
Ok(())
}