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
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())
|
|
}
|