对消息和内存使用生存时间
消息和记忆可以在创建它们的对话结束后保持可搜索。通过上线时间,应用可以在定义的时段后自动使记录失效,例如满足合规性要求、删除可能已过时的信息或节省存储空间。
本指南说明如何为 Oracle DB 支持的消息和内存配置记录失效。它还解释了清除作业和按记录的生存时间覆盖。
Oracle Agent Memory 在两个层中应用生存时间:
- 方案级保留默认值来自
MemoryRetentionConfig。在新写入时省略了ttl_days值,请使用MemoryRetentionConfig.default_ttl_days。 - 每条记录
ttl_days和ttl_anchor值允许一条消息或内存比模式默认值更快、更晚或从不同的锚点时间到期。
当您选择每条记录锚点时,请使用 TimeToLiveAnchor.CREATED_AT 从 Oracle 存储行的时间开始计数,或者使用 TimeToLiveAnchor.TIMESTAMP 从记录的事件时间戳中计数。
注:当记录应在定义的期间内保持可用,然后自动到期时,请使用生存时间。例如,应用程序可能会将支持详细信息保留 30 天,或将临时任务信息保留一周。
适用以下保留规则:
- 在新写入时省略
ttl_days将使用模式默认值。 - 在配置最大值时传递
ttl_days=None使用max_ttl_days,在未配置最大值时存储非失效行。 max_ttl_days以上的值将夹到该最大值并显示警告。- 更新 API 将保留当前到期时间,除非提供
ttl_days或ttl_anchor。
提示:有关软件包设置,请参见 Get Started with Agent Memory 。如果此示例需要本地 Oracle 数据库,请按照 Run Oracle AI Database Locally 操作。
配置方案级保留默认值
如果希望 SDK 创建或升级托管 Oracle 方案并与其一起保留保留策略,请使用 schema_policy=SchemaPolicy.CREATE_IF_NECESSARY 创建 OracleAgentMemory 客户端。
import oracledb
from oracleagentmemory.apis import TimeToLiveAnchor
from oracleagentmemory.core import (
MemoryExtractionConfig,
MemoryRetentionConfig,
OracleAgentMemory,
SchemaPolicy,
)
from oracleagentmemory.core.embedders.embedder import Embedder
embedder = Embedder(model="YOUR_EMBEDDING_MODEL")
db_pool = oracledb.SessionPool(
user="YOUR DB USER",
password="YOUR DB PASSWORD",
dsn="YOUR DB CONNECT STRING",
)
memory = OracleAgentMemory(
connection=db_pool,
embedder=embedder,
schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
memory_retention_config=MemoryRetentionConfig(
default_ttl_days=30,
max_ttl_days=90,
),
)
在此配置中:
- 忽略
ttl_days的新写入默认为30天 90天以上的显式ttl_days值将受到警告的限制- 显式
ttl_days=None解析为90天,因为已设置max_ttl_days
API 参考:OracleAgentMemory
使用实时添加消息和内存
在为其他记录设置不同的 TTL 时,使用线程写入来依赖某些记录的模式默认 TTL。
from datetime import datetime, timezone
ttl_thread = memory.create_thread(
thread_id="ttl_demo_thread",
user_id="user_123",
)
message_ids = ttl_thread.add_messages(
[
{
"id": "msg-ttl-1",
"role": "user",
"content": (
"I opened ticket 1042 yesterday because the laptop battery failed."
),
"timestamp": "2026-04-01T09:00:00Z",
},
{
"id": "msg-ttl-2",
"role": "assistant",
"content": (
"I will keep ticket 1042 active and send a replacement checklist."
),
"timestamp": "2026-04-01T09:01:00Z",
},
]
)
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
#for instance, 2026-04-01T09:00:00Z
backfilled_memory_id = ttl_thread.add_memory(
"Ticket 1042 battery-failure report was filed on 2026-04-01.",
memory_id="mem-ttl-backfill",
timestamp=timestamp,
ttl_days=90,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)
short_lived_memory_id = ttl_thread.add_memory(
"Replacement checklist should be revisited within one week.",
memory_id="mem-ttl-short",
ttl_days=7,
)
print(message_ids)
print(backfilled_memory_id)
print(short_lived_memory_id)
#['msg-ttl-1', 'msg-ttl-2']
#mem-ttl-backfill
#mem-ttl-short
在本例中:
add_messages()省略ttl_days,因此两条消息都使用模式默认值30天mem-ttl-backfill在存储的事件时间戳后90天到期,因为它使用TimeToLiveAnchor.TIMESTAMPmem-ttl-short覆盖默认值,并在7天后过期
API 参考:OracleThread
刷新现有记录上的生存时间
使用 update_message() 或 update_memory() 刷新现有到期,而不替换存储的内容。
ttl_thread.update_message("msg-ttl-1", ttl_days=14)
ttl_thread.update_memory(
"mem-ttl-backfill",
ttl_days=60,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)
这些调用将保留存储的内容和元数据,同时重新计算到期窗口。请记住,除非您传递 ttl_days 或 ttl_anchor,否则更新将保留当前到期。
API 参考:OracleThread
确保存在托管清除作业
当 Oracle Agent Memory 创建或升级其托管 Oracle 方案时,它还会创建一个每日 DBMS_SCHEDULER 作业,用于删除过期消息和内存行及其检索块。作业将按批清除行,而不是发出一个大型删除、在批之间提交,并提前捕获一个参考时间戳,因此单个运行使用一个一致的失效截止。
调度程序定义还将 schedule_limit 设置为一天,以便可以跳过启动太晚的运行,而不是任意延迟运行。作业正文单独停止从运行开始时间起一天后获取新批。在批之间检查该运行时限制,以便允许正在进行的批完成并提交。在运行结束时,作业将写入一个简短的 DBMS_OUTPUT 摘要,其中包括是否已达到运行时限制以及按类别删除了多少行。
当 SchemaPolicy.CREATE_IF_NECESSARY 需要创建该作业但方案用户缺少 CREATE JOB 时,设置将完成并显示警告。过期的记录不会被读取和搜索隐藏,但是在特权用户创建作业之前,它们不会被物理清除。
如果使用 table_name_prefix,则对下面的作业和表名应用相同的前缀。默认托管作业名称为 PURGE_EXPIRED_RECORDS_J。
提示:方案所有者没有 CREATE JOB 时,请使用以下 DBA 选项。
授予调度程序作业权限
GRANT CREATE JOB TO app_schema;
-- Run OracleAgentMemory schema setup as app_schema.
REVOKE CREATE JOB FROM app_schema;
手动创建清除作业
将 APP_SCHEMA 替换为托管方案所有者,并调整对象名称(如果配置了表名前缀)。下面的 PL/SQL 块是有意压缩的,因此即使前缀对象名称很长,它仍会保留在 Oracle Scheduler 的 job_action 长度限制内:
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
job_type => 'PLSQL_BLOCK',
job_action => q'[
DECLARE
bs CONSTANT PLS_INTEGER := 1000;
n PLS_INTEGER;
mc PLS_INTEGER := 0;
xc PLS_INTEGER := 0;
mr PLS_INTEGER := 0;
xr PLS_INTEGER := 0;
tl PLS_INTEGER := 0;
rt TIMESTAMP(6) WITH TIME ZONE := SYSTIMESTAMP;
dl TIMESTAMP(6) WITH TIME ZONE := rt + INTERVAL '1 00:00:00' DAY TO SECOND;
BEGIN
LOOP
IF SYSTIMESTAMP >= dl THEN
tl := 1;
EXIT;
END IF;
DELETE FROM APP_SCHEMA.RECORD_CHUNKS c
WHERE c.rowid IN (
SELECT rid
FROM (
SELECT c.rowid AS rid
FROM APP_SCHEMA.RECORD_CHUNKS c
WHERE c.source_record_type = 'message'
AND EXISTS (
SELECT 1
FROM APP_SCHEMA.MESSAGE m
WHERE m.record_id = c.source_id
AND m.expires_at IS NOT NULL
AND m.expires_at <= rt
)
FETCH FIRST bs ROWS ONLY
)
);
n := SQL%ROWCOUNT;
mc := mc + n;
EXIT WHEN n = 0;
COMMIT;
END LOOP;
LOOP
IF SYSTIMESTAMP >= dl THEN
tl := 1;
EXIT;
END IF;
DELETE FROM APP_SCHEMA.RECORD_CHUNKS c
WHERE c.rowid IN (
SELECT rid
FROM (
SELECT c.rowid AS rid
FROM APP_SCHEMA.RECORD_CHUNKS c
WHERE c.source_record_type IN ('fact', 'guideline', 'memory', 'preference')
AND EXISTS (
SELECT 1
FROM APP_SCHEMA.MEMORY m
WHERE m.record_id = c.source_id
AND m.memory_type = c.source_record_type
AND m.expires_at IS NOT NULL
AND m.expires_at <= rt
)
FETCH FIRST bs ROWS ONLY
)
);
n := SQL%ROWCOUNT;
xc := xc + n;
EXIT WHEN n = 0;
COMMIT;
END LOOP;
LOOP
IF SYSTIMESTAMP >= dl THEN
tl := 1;
EXIT;
END IF;
DELETE FROM APP_SCHEMA.MESSAGE
WHERE rowid IN (
SELECT rid
FROM (
SELECT rowid AS rid
FROM APP_SCHEMA.MESSAGE
WHERE expires_at IS NOT NULL
AND expires_at <= rt
FETCH FIRST bs ROWS ONLY
)
);
n := SQL%ROWCOUNT;
mr := mr + n;
EXIT WHEN n = 0;
COMMIT;
END LOOP;
LOOP
IF SYSTIMESTAMP >= dl THEN
tl := 1;
EXIT;
END IF;
DELETE FROM APP_SCHEMA.MEMORY
WHERE rowid IN (
SELECT rid
FROM (
SELECT rowid AS rid
FROM APP_SCHEMA.MEMORY
WHERE expires_at IS NOT NULL
AND expires_at <= rt
FETCH FIRST bs ROWS ONLY
)
);
n := SQL%ROWCOUNT;
xr := xr + n;
EXIT WHEN n = 0;
COMMIT;
END LOOP;
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge reference_time='
|| TO_CHAR(rt, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZH:TZM')
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge run_deadline='
|| TO_CHAR(dl, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZH:TZM')
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge runtime_limit_reached='
|| CASE WHEN tl = 1 THEN 'true' ELSE 'false' END
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge deleted message chunk rows='
|| TO_CHAR(mc)
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge deleted fact/guideline/memory/preference chunk rows='
|| TO_CHAR(xc)
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge deleted message rows='
|| TO_CHAR(mr)
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge deleted fact/guideline/memory/preference rows='
|| TO_CHAR(xr)
);
END;
]',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=DAILY;INTERVAL=1',
enabled => FALSE,
auto_drop => FALSE,
comments => 'OracleAgentMemory expired-record purge'
);
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
attribute => 'logging_level',
value => DBMS_SCHEDULER.LOGGING_RUNS
);
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
attribute => 'store_output',
value => TRUE
);
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
attribute => 'schedule_limit',
value => INTERVAL '1 00:00:00' DAY TO SECOND
);
DBMS_SCHEDULER.ENABLE('APP_SCHEMA.PURGE_EXPIRED_RECORDS_J');
END;
/
确认排程引擎定义并检查最近的运行历史记录非常有用。如果需要,请替换 APP_SCHEMA,并在部署使用表名前缀时将配置的表名前缀应用于作业名。
检查托管清除作业是否存在并保留预期的检验设置:
SELECT owner,
job_name,
enabled,
state,
repeat_interval,
schedule_limit,
logging_level,
store_output,
start_date,
last_start_date,
next_run_date
FROM all_scheduler_jobs
WHERE owner = 'APP_SCHEMA'
AND job_name = 'PURGE_EXPIRED_RECORDS_J';
在需要确认正在执行清除并成功完成时,检查最近的运行:
SELECT log_date,
status,
run_duration,
output,
additional_info
FROM all_scheduler_job_run_details
WHERE owner = 'APP_SCHEMA'
AND job_name = 'PURGE_EXPIRED_RECORDS_J'
ORDER BY log_date DESC;
如果要立即验证安装,请手动运行一次作业:
BEGIN
DBMS_SCHEDULER.RUN_JOB(
job_name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
use_current_session => TRUE
);
END;
/
小结
在本指南中,我们了解了模式级保留默认值、每条记录的 ttl_days 值和 TimeToLiveAnchor 如何协同工作、托管清除作业如何删除过期的行以及如何从 Python 刷新现有过期。
有关按元数据筛选记录的更多信息,请参见 Use Metadata and Metadata Filtering 。
完整代码
以下代码将本指南中的示例组合在一起。
#Copyright © 2026 Oracle and/or its affiliates.
#This software is under the Apache License 2.0
#(LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License
#(UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option.
#Oracle Agent Memory Code Example - Use Time-to-Live for Messages and Memories
#-----------------------------------------------------------------------------
##Create a retention aware client
from datetime import datetime, timezone
import oracledb
from oracleagentmemory.apis import TimeToLiveAnchor
from oracleagentmemory.core import (
MemoryExtractionConfig,
MemoryRetentionConfig,
OracleAgentMemory,
SchemaPolicy,
)
from oracleagentmemory.core.embedders.embedder import Embedder
embedder = Embedder(model="YOUR_EMBEDDING_MODEL")
db_pool = oracledb.SessionPool(
user="YOUR DB USER",
password="YOUR DB PASSWORD",
dsn="YOUR DB CONNECT STRING",
)
memory = OracleAgentMemory(
connection=db_pool,
embedder=embedder,
schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
memory_retention_config=MemoryRetentionConfig(
default_ttl_days=30,
max_ttl_days=90,
),
)
##Add messages and memories with time to live
ttl_thread = memory.create_thread(
thread_id="ttl_demo_thread",
user_id="user_123",
)
message_ids = ttl_thread.add_messages(
[
{
"id": "msg-ttl-1",
"role": "user",
"content": (
"I opened ticket 1042 yesterday because the laptop battery failed."
),
"timestamp": "2026-04-01T09:00:00Z",
},
{
"id": "msg-ttl-2",
"role": "assistant",
"content": (
"I will keep ticket 1042 active and send a replacement checklist."
),
"timestamp": "2026-04-01T09:01:00Z",
},
]
)
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
#for instance, 2026-04-01T09:00:00Z
backfilled_memory_id = ttl_thread.add_memory(
"Ticket 1042 battery-failure report was filed on 2026-04-01.",
memory_id="mem-ttl-backfill",
timestamp=timestamp,
ttl_days=90,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)
short_lived_memory_id = ttl_thread.add_memory(
"Replacement checklist should be revisited within one week.",
memory_id="mem-ttl-short",
ttl_days=7,
)
print(message_ids)
print(backfilled_memory_id)
print(short_lived_memory_id)
#['msg-ttl-1', 'msg-ttl-2']
#mem-ttl-backfill
#mem-ttl-short
##Refresh time to live on existing records
ttl_thread.update_message("msg-ttl-1", ttl_days=14)
ttl_thread.update_memory(
"mem-ttl-backfill",
ttl_days=60,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)