Files
windmill/backend/windmill-worker/src/memory_common.rs
centdix 27acbbf3d5 refactor: move ai sse plumbing to windmill-ai (#9059)
* docs: refine windmill ai refactor plan

* refactor: move ai sse plumbing to windmill-ai

* refactor: remove ai re-export shims

* fix: update ee ai memory ref

* chore: update ee-repo-ref to d3bc7fa85195b46b7a38d43c2f806520bf8b5454

This commit updates the EE repository reference after PR #560 was merged in windmill-ee-private.

Previous ee-repo-ref: ff35bf7cc198e13884b33654e1d6dbd8a8b314d3

New ee-repo-ref: d3bc7fa85195b46b7a38d43c2f806520bf8b5454

Automated by sync-ee-ref workflow.

---------

Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-05-11 10:01:45 +00:00

124 lines
3.4 KiB
Rust

use uuid::Uuid;
use windmill_ai::types::OpenAIMessage;
use windmill_common::{db::DB, error::Error};
pub const MAX_MEMORY_SIZE_BYTES: usize = 100_000; // 100KB per memory entry in database
/// Read AI agent memory from database
pub async fn read_from_db(
db: &DB,
workspace_id: &str,
conversation_id: Uuid,
step_id: &str,
) -> Result<Option<Vec<OpenAIMessage>>, Error> {
let result = sqlx::query!(
"SELECT messages FROM ai_agent_memory
WHERE workspace_id = $1 AND conversation_id = $2 AND step_id = $3",
workspace_id,
conversation_id,
step_id
)
.fetch_optional(db)
.await?;
match result {
Some(row) => {
let messages: Vec<OpenAIMessage> = serde_json::from_value(row.messages)?;
Ok(Some(messages))
}
None => Ok(None),
}
}
/// Write AI agent memory to database with size checking and truncation
pub async fn write_to_db(
db: &DB,
workspace_id: &str,
conversation_id: Uuid,
step_id: &str,
messages: &[OpenAIMessage],
) -> Result<(), Error> {
if messages.is_empty() {
return Ok(());
}
// Serialize messages and check size
let mut messages_to_store = messages.to_vec();
let mut json_value = serde_json::to_value(&messages_to_store)?;
let size_bytes = json_value.to_string().len();
// Truncate if necessary
if size_bytes > MAX_MEMORY_SIZE_BYTES {
tracing::warn!(
"Memory size ({} bytes) exceeds limit ({} bytes) for workspace={} conversation={} step={}. Truncating messages. Use S3 storage in workspace settings to store full conversation history.",
size_bytes,
MAX_MEMORY_SIZE_BYTES,
workspace_id,
conversation_id,
step_id
);
messages_to_store = truncate_messages(messages, MAX_MEMORY_SIZE_BYTES)?;
json_value = serde_json::to_value(&messages_to_store)?;
}
// Insert or update using UPSERT
sqlx::query!(
"INSERT INTO ai_agent_memory (workspace_id, conversation_id, step_id, messages, created_at, updated_at)
VALUES ($1, $2, $3, $4, NOW(), NOW())
ON CONFLICT (workspace_id, conversation_id, step_id)
DO UPDATE SET
messages = EXCLUDED.messages,
updated_at = NOW()",
workspace_id,
conversation_id,
step_id,
json_value
)
.execute(db)
.await?;
Ok(())
}
/// Delete all memory for a conversation from database
pub async fn delete_conversation_from_db(
db: &DB,
workspace_id: &str,
conversation_id: Uuid,
) -> Result<(), Error> {
sqlx::query!(
"DELETE FROM ai_agent_memory
WHERE workspace_id = $1 AND conversation_id = $2",
workspace_id,
conversation_id
)
.execute(db)
.await?;
Ok(())
}
/// Truncate messages to fit within the size limit
fn truncate_messages(
messages: &[OpenAIMessage],
max_size_bytes: usize,
) -> Result<Vec<OpenAIMessage>, Error> {
let mut result = messages.to_vec();
// Keep removing oldest messages until we're under the threshold
while !result.is_empty() {
let test_json = serde_json::to_value(&result)?;
let test_size = test_json.to_string().len();
if test_size <= max_size_bytes {
break;
}
// Remove the first (oldest) message
result.remove(0);
}
Ok(result)
}