TencentDB-Agent-Memory Opik 历史数据导入:将 Opik Trace 一键迁移至 Memory Core L0
【免费下载链接】TencentDB-Agent-MemoryTencentDB Agent Memory is a team-level memory hub for AI Agents — turning conversations, docs, and code into four reusable memory assets (Chat Memory, Skill, LLM-Wiki, Code-Graph) that are governed, shared, and equipped across agents and frameworks.项目地址: https://gitcode.com/GitHub_Trending/te/TencentDB-Agent-Memory
导读
本指南围绕 TencentDB-Agent-Memory 仓库中MemoryCore/scripts/import-opik-to-memory-core工具,完整讲解如何把 Opik 平台上已有的 Project Trace 批量转换为对话消息,并通过 Memory Core Gateway 的POST /v3/conversation/add写入 L0 层。读完本文,你将掌握:从环境变量安全配置 Opik 与 Memory Core 地址、Dry Run 预演、断点续传、Pipeline 节流到回查校验的一整套可落地的历史数据迁移方案,同时了解工具底层如何解析常见 Trace 结构、合并 input/output 消息并稳定生成 session_id。
工具定位:把可观测平台沉淀的对话搬进记忆系统
Opik(Comet 开源的 LLM 可观测平台)会以 Trace 形式记录每次 LLM 调用的input/output,其中往往完整保存了用户与模型的对话。当团队从其他框架或自建链路迁移到 TencentDB-Agent-Memory 时,这些历史对话正是最有价值的记忆资产——它们对应 Memory Core 四层记忆体系中的 L0 原始对话流水(Raw Conversation)。
本工具(index.ts)扮演“数据搬运工”角色:
- 从 Opik 私有 API(
/api/v1/private)分页读取全部 Project 与指定 Project 的全部 Trace; - 从 Trace 的
input/output中自动识别并抽取对话消息,规范化为{ role, content, timestamp }结构; - 通过 Memory Core Gateway 的
POST /v3/conversation/add写入 L0,并带上team_id/agent_id/user_id/service_id(x-tdai-service-id)等隔离字段,使历史对话进入与线上链路一致的租户隔离与后续 L1 抽取流程。
从工具入口看,命令注册在 package.json 的import:opikscript,实际执行体是tsx scripts/import-opik-to-memory-core/index.ts,要求 Node.js>= 22.16.0。
前置条件与快速查看帮助
开始前需要满足以下前提:
- Node.js
>= 22.16.0,且已安装当前项目依赖(在 MemoryCore/package.json 中声明); - Opik REST API 可访问;
- 远端 Memory Core Gateway 可访问
/health、/v3/conversation/add和/v3/conversation/query; - 已确定目标
service_id、team_id、agent_id和user_id(如需要还可指定task_id)。
进入 Memory Core 目录后即可查看全部参数:
cd MemoryCore npm run import:opik -- --help从源码的usage()(index.ts)可以看到参数分为四组:数据源(Opik)、目标(Memory Core)、执行控制、密钥环境变量。命令使用 Node 内置node:util的parseArgs解析,strict: true模式下未知参数会直接报错(index.ts)。
配置 Opik 地址与分页读取
推荐只配置 Opik 根地址和 workspace:
export OPIK_URL='http://opik.example.com:5173' export OPIK_WORKSPACE='default'也兼容直接粘贴 UI 地址:
http://opik.example.com:5173/default/projects?size=25关于 UI URL 有两个关键点(源码parseOpikBase实现,index.ts):
- UI URL 中的
size=25不会限制导入范围——工具会把 URL 规范化并转换成/api/v1/privateAPI 地址,size参数被清除,分页读取与页面展示无关; - URL 首段若既不是
api也不是v1,会被识别为 workspace(例如上面的default);显式传入--workspace或设置OPIK_WORKSPACE优先级更高。
分页策略在OpikClient(index.ts)中实现:projects()与traces()均从page=1起循环拉取,直到已读数量 >= total或某页为空才停止,--page-size默认100。Trace 读取时还会带上truncate=false与strip_attachments=true,前者保证拿到完整 input/output,后者剥离附件减少无效数据;最终 Trace 按start_time(回退到created_at)升序排序,保证导入顺序与对话发生顺序一致。
另外注意:OPIK_URL中不允许携带用户名/密码(parseOpikBase会直接抛错),凭据必须通过环境变量传递。
配置远端 Memory Core 与鉴权
以下地址仅为示例,请替换成实际 Gateway 地址:
export MEMORY_CORE_URL='http://memory-core.example.com:8423' export MEMORY_CORE_SERVICE_ID='default' export MEMORY_CORE_TEAM_ID='team-001' export MEMORY_CORE_AGENT_ID='agent-001' export MEMORY_CORE_USER_ID='user-001'可选 Task 隔离:
export MEMORY_CORE_TASK_ID='task-001'不需要 Task 时:
unset MEMORY_CORE_TASK_ID安全输入 API Key,避免写入代码或配置文件:
read -s "MEMORY_CORE_API_KEY?Memory Core API Key: " echo export MEMORY_CORE_API_KEY检查 Gateway 连通性:
curl --fail --silent --show-error "${MEMORY_CORE_URL}/health"源码层面,MemoryCoreClient(index.ts)对每次写请求固定携带以下 Header:
Authorization: Bearer ${MEMORY_CORE_API_KEY};x-tdai-service-id:来自MEMORY_CORE_SERVICE_ID,标识服务实例;Content-Type: application/json。
MEMORY_CORE_API_KEY在非 Dry Run 模式下为必填,缺少会直接报错退出(index.ts)。响应采用统一信封{ code, message, request_id, data }结构,code !== 0视为失败并携带request_id便于排查;成功时以data.accepted_ids.length(回退data.total_count)作为该批实际接收数,并与本批消息数比对,不一致即中止,防止静默丢数据(index.ts)。
先执行 Dry Run 验证
Dry Run 会读取真实 Opik 并转换 Trace,但不会写入 Memory Core 或断点文件:
npm run import:opik -- \ --project '5d0fd72d' \ --max-traces 5 \ --dry-run--project同时接受 Project 名称和 UUID,可以重复传入或使用逗号分隔:
npm run import:opik -- \ --project 'project-a' \ --project '019fb2e2-16a9-717d-98a8-0cd2e1bef87e' \ --dry-run不传--project会处理 workspace 下全部项目。项目数量较多时,应先指定项目和--max-traces做小批验证。
Dry Run 时每条 Trace 会打印一行[dry-run] project=... trace=... session=... batch=... messages=...,可以直观看到“一个 Trace 被拆成几个批次、每批几条消息”,并且--dry-run模式不要求MEMORY_CORE_API_KEY(index.ts),适合在目标环境未就绪时先行验证解析效果。
Trace 消息解析原理
工具的核心竞争力在于兼容多种常见的 Trace 结构。入口函数extractMessages(index.ts)的实现分两条路径:
路径一:识别消息数组。findMessageArrays(index.ts)在input/output中深度优先(最多 5 层)寻找“看起来像消息数组”的结构——数组内任一元素包含role字段(或元素.message 内含 role)。命中后优先按messages、conversation、history这三个键名查找,找不到再遍历对象所有值兜底。随后bestMessageArray对所有候选数组统一规范化后选取消息数量最多的一组,避免误命中子对象里的零散消息。
**路径二:prompt/response 兜底。**若找不到消息数组,则分别从 input 的userPrompt、user_prompt、prompt、query、input、content、text等键提取 prompt,从 output 的responseContent、answer、response、completion、output、content、text等键提取 answer,各生成一条 user/assistant 消息(index.ts)。
角色归一化(normalizeRole):user/human→ user;assistant/ai/model/bot→ assistant;仅在开启--include-system时,system/developer才会以带前缀(如[system])的 user 消息导入(index.ts)。
内容提取(contentToText)兼容字符串、{ type: "text", text }结构块、OpenAIchoices[].message等多态形式(index.ts)。
input/output 合并:mergeMessages通过检测 input 消息尾部与 output 消息头部的最大重叠(相同 role + 相同 content)来消除重复——很多 Agent 框架的 output 会回显完整上下文,这一步保证最终会话不出现重复轮次(index.ts)。
大小限制:单条消息超过MAX_MESSAGE_CHARS = 8192字符会被splitContent按字符边界(并避开 UTF-16 代理对截断)拆分;单次请求最多MAX_MESSAGES_PER_REQUEST = 100条消息,超出部分自动chunk成多个批次(index.ts)。
正式导入与 session_id 生成
Dry Run 确认解析无误后执行正式导入:
npm run import:opik -- \ --project '5d0fd72d' \ --max-traces 5 \ --state-file './opik-import-remote-state.json'成功时会输出:
[import] project=... trace=... accepted=... [done] seen_traces=5 imported_traces=5 ... imported_messages=...工具实际调用:
POST <MEMORY_CORE_URL>/v3/conversation/add写入范围由以下字段共同决定:
x-tdai-service-id:MEMORY_CORE_SERVICE_IDteam_id:MEMORY_CORE_TEAM_IDagent_id:MEMORY_CORE_AGENT_IDuser_id:MEMORY_CORE_USER_IDtask_id:MEMORY_CORE_TASK_ID,可选session_id: 根据 Opik Project ID 和thread_id/Trace ID 稳定生成
session_id的生成规则在buildSessionId(index.ts)中:优先使用 Trace 的thread_id(Opik 中同一条会话线程会共享 thread_id),缺失时回退到 Trace ID;随后做「可读化 + 哈希稳定化」——可读部分取前 48 字符并替换非法字符,再拼上 source 的 SHA-256 前 12 位,最终形如opik:<project_id>:<readable>:<hash12>。这样的设计保证:同一线程的 Trace 落在同一 session_id,重复执行不产生新会话,且不会与其他来源的 session 冲突。
Gateway 侧,/v3/conversation/add是强隔离覆盖路径之一(见 v2-router.ts 的V3_ALLOWED_SUBPATHS),请求体与响应契约见 v2-schemas.ts:session_id缺省时自动落到默认兼容桶DEFAULT_ISOLATION_ID,messages数组 1~100 条。写入 L0 后,Gateway 会通过notifyPipeline触发异步 L1 抽取(v2-router.ts),历史对话随即进入与实时链路一致的记忆提取流程。
断点续传:重复执行自动跳过已完成批次
默认启用断点续传。每个成功批次会立即写入--state-file,重新执行相同命令时自动跳过已完成批次:
npm run import:opik -- \ --project '5d0fd72d' \ --state-file './opik-import-remote-state.json'忽略已有断点:
npm run import:opik -- \ --project '5d0fd72d' \ --no-resume--no-resume可能造成重复导入,只应在明确需要重新导入时使用。
实现细节(index.ts):
- 断点文件默认
.opik-memory-import-state.json,格式为{ version: 1, completed: { "<checkpointKey>": { imported_at, accepted } } },version不匹配会拒绝加载; - 断点键为
project_id:trace_id:<batch 内容哈希>:<批次序号>(index.ts),批次内容哈希由 SHA-256 截取 20 位生成,因此消息内容变化会形成新断点键,天然避免内容漂移导致的重复或漏导; - 每个批次成功写入后立即原子落盘(先写
state-file.tmp-<pid>再 rename),文件权限0600,即使中途中断也不丢进度;断点文件不保存 API Key,可安全纳入版本控制或归档; - 失败场景下,Memory Core 接收数量与批次不符会直接抛错中止(而非标记断点),保证断点只记录“真实成功”的批次。
Pipeline 节流:等待 L1/L2/L3 空闲
默认每写入 20 个批次等待 L1 空闲,并在结束时等待 L1/L2/L3 全部空闲:
npm run import:opik -- \ --project '5d0fd72d' \ --wait-every 20 \ --state-file './opik-import-remote-state.json'如果目标环境关闭了记忆提取、只需要写 L0:
npm run import:opik -- \ --project '5d0fd72d' \ --wait-every 0 \ --no-final-wait \ --state-file './opik-import-remote-state.json'节流由MemoryCoreClient.waitForIdle(index.ts)实现:轮询POST /v2/pipeline/status,读取各层{ idle, queued, running }状态;l1模式只看 L1,all模式要求 L1/L2/L3 全空闲。轮询间隔--poll-ms(默认1000)与单次等待上限--max-wait-ms(默认600000,即 10 分钟)均可调。等待超时不会失败,只是告警“导入数据已落 L0,后台将继续处理”——因为 L0 是持久化流水,后台抽取异步完成,超时不影响数据完整性。需要注意/v2/pipeline/status是standalone 单机模式才开放的端点(v2-router.ts 注释明确 service 模式返回 404),因此在多租户 service 部署下应使用--wait-every 0 --no-final-wait。
回查导入结果
写入完成后,用/v3/conversation/query按隔离维度回查:
curl --fail --silent --show-error \ -X POST "${MEMORY_CORE_URL}/v3/conversation/query" \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer ${MEMORY_CORE_API_KEY}" \ -H "x-tdai-service-id: ${MEMORY_CORE_SERVICE_ID}" \ --data "$(cat <<JSON { "team_id": "${MEMORY_CORE_TEAM_ID}", "agent_id": "${MEMORY_CORE_AGENT_ID}", "user_id": "${MEMORY_CORE_USER_ID}", "limit": 100, "offset": 0 } JSON )"team_id/agent_id/user_id三者为查询隔离维度,也可传入session_id精确限定某条会话(session_id可选,缺省时按 (team, agent, user) 聚合查询,见 v2-router.ts 注释)。对比返回条数与导入的imported_messages,即可确认全量落库。
Opik 鉴权配置
自托管 Opik 默认可能不需要鉴权。启用鉴权时:
read -s "OPIK_API_KEY?Opik API Key: " echo export OPIK_API_KEY export OPIK_AUTH_SCHEME='Bearer'OPIK_AUTH_SCHEME为空时,OPIK_API_KEY会原样作为AuthorizationHeader。源码在OpikClient.headers()(index.ts)中实现:请求始终携带Comet-Workspace-Name(即 workspace,Comet/Opik 服务端用它识别租户),鉴权存在时拼接scheme + ' ' + key或裸 key。注意 OPIK 与 MEMORY_CORE 是两套独立的密钥体系,切勿混用。
常用参数一览
| 参数 | 默认值 | 说明 |
|---|---|---|
--project | 全部项目 | Project 名称或 UUID,可重复或逗号分隔 |
--page-size | 100 | Opik API 每页数量 |
--max-traces | 0 | 本次最多处理的 Trace 数,0表示不限 |
--state-file | .opik-memory-import-state.json | 断点文件 |
--dry-run | 关闭 | 只拉取和转换,不写入 |
--no-resume | 关闭 | 忽略断点,可能重复导入 |
--include-system | 关闭 | 将 system/developer 转成带前缀的 user 消息 |
--wait-every | 20 | 每 N 个写请求等待 L1,0禁用 |
--no-final-wait | 关闭 | 不等待最终 L1/L2/L3 空闲 |
--timeout-ms | 30000 | 单次 HTTP 请求超时 |
--retries | 4 | 网络错误、429 和 5xx 重试次数 |
表格之外,从usage()与parseCli还可以看到两个未在 README 表格中列出的节流参数:--poll-ms(Pipeline 轮询间隔,默认1000)与--max-wait-ms(单次等待上限,默认600000),以及对应的命令行等价物:--opik-url、--memory-url、--workspace、--team-id、--agent-id、--user-id、--task-id、--service-id,它们与同名环境变量二选一即可(命令行优先)。
重试与网络容错
HttpClient.json(index.ts)实现了指数退避重试:对网络错误、HTTP 429(限流)和 5xx(服务端错误)最多重试--retries(默认 4)次,退避间隔为min(1000 * 2^attempt, 10000) + 随机 0~250ms抖动;而 4xx 除 429 外(如 401/403/400)属于NonRetryableHttpError,直接抛出不浪费重试。每个请求通过AbortController在--timeout-ms(默认 30000ms)后中止。该重试策略同时作用于 Opik 读取与 Memory Core 写入,配合断点机制,能在网络抖动场景下安全完成大规模导入。
已验证链路与最佳实践
官方文档记录该工具已使用真实 Opik API 和隔离的本地 Memory Core 完成端到端验证:
- 分页读取 Opik Project 和 Trace
- 将 5 个 Trace 转换为 15 条 L0 消息
- 写入
/v3/conversation/add - 通过
/v3/conversation/query回查 15 条消息 - 重复执行时断点命中、写入数为 0
- SQLite 与 JSONL 镜像落盘数量一致
综合上述机制,生产环境推荐的导入流程是:
- 小批预演:
--project <name> --max-traces 5 --dry-run,确认消息抽取符合预期(查看[dry-run]行数与 session 分布); - 首次全量:指定
--state-file(建议放在单独目录),不传--max-traces或按需设置,配合默认节流参数; - 中断恢复:直接重跑同一条命令,断点自动跳过已完成批次;
- 校验:
/v3/conversation/query分页回查数量与imported_messages一致; - 特殊数据:需要保留 system 提示词时加
--include-system,service 多租户部署下使用--wait-every 0 --no-final-wait。
在整个过程中,所有密钥(OPIK_API_KEY、MEMORY_CORE_API_KEY)均只从环境变量读取,工具的parseOpikBase甚至禁止在 URL 中携带凭据,从源头规避了密钥落入命令历史或配置文件的泄露风险。
【免费下载链接】TencentDB-Agent-MemoryTencentDB Agent Memory is a team-level memory hub for AI Agents — turning conversations, docs, and code into four reusable memory assets (Chat Memory, Skill, LLM-Wiki, Code-Graph) that are governed, shared, and equipped across agents and frameworks.项目地址: https://gitcode.com/GitHub_Trending/te/TencentDB-Agent-Memory
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考