You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

102 lines
2.6 KiB
Rust

//! LLM call logging: tracks every LLM interaction during synthesis generation.
use chrono::{DateTime, Utc};
use serde::Serialize;
use sqlx::PgPool;
use uuid::Uuid;
use crate::errors::AppError;
/// Row returned from llm_call_log queries.
#[derive(Debug, Clone, Serialize, sqlx::FromRow)]
pub struct LlmCallLogRow {
pub id: Uuid,
pub call_type: String,
pub model: String,
pub system_prompt: String,
pub user_prompt: String,
pub response_body: String,
pub duration_ms: i32,
pub article_url: Option<String>,
pub created_at: DateTime<Utc>,
}
/// Insert a single LLM call log entry.
#[allow(clippy::too_many_arguments)]
pub async fn insert(
pool: &PgPool,
user_id: Uuid,
job_id: Uuid,
call_type: &str,
model: &str,
system_prompt: &str,
user_prompt: &str,
response_body: &str,
duration_ms: i32,
article_url: Option<&str>,
) -> Result<(), AppError> {
sqlx::query(
r#"
INSERT INTO llm_call_log (user_id, job_id, call_type, model, system_prompt, user_prompt, response_body, duration_ms, article_url)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
"#,
)
.bind(user_id)
.bind(job_id)
.bind(call_type)
.bind(model)
.bind(system_prompt)
.bind(user_prompt)
.bind(response_body)
.bind(duration_ms)
.bind(article_url)
.execute(pool)
.await?;
Ok(())
}
/// List all LLM call log entries for a generation job.
pub async fn list_by_job_id(
pool: &PgPool,
user_id: Uuid,
job_id: Uuid,
) -> Result<Vec<LlmCallLogRow>, AppError> {
let rows = sqlx::query_as::<_, LlmCallLogRow>(
r#"
SELECT id, call_type, model, system_prompt, user_prompt, response_body, duration_ms, article_url, created_at
FROM llm_call_log
WHERE user_id = $1 AND job_id = $2
ORDER BY created_at ASC
"#,
)
.bind(user_id)
.bind(job_id)
.fetch_all(pool)
.await?;
Ok(rows)
}
/// Truncate old LLM call log entries: replace prompt/response with first 500 chars.
pub async fn truncate_old(
pool: &PgPool,
user_id: Uuid,
days: i32,
) -> Result<u64, AppError> {
let result = sqlx::query(
r#"
UPDATE llm_call_log
SET system_prompt = LEFT(system_prompt, 500) || E'\n[truncated]',
user_prompt = LEFT(user_prompt, 500) || E'\n[truncated]',
response_body = LEFT(response_body, 500) || E'\n[truncated]'
WHERE user_id = $1
AND created_at < now() - make_interval(days => $2)
AND LENGTH(system_prompt) > 510
"#,
)
.bind(user_id)
.bind(days)
.execute(pool)
.await?;
Ok(result.rows_affected())
}