Skip to content

Commit 0d1e1c1

Browse files
authored
Add support for memory operations in extended genai handler (#83)
2 parents 2ea7528 + 20879a9 commit 0d1e1c1

File tree

17 files changed

+1568
-72
lines changed

17 files changed

+1568
-72
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,6 @@ target
6161

6262
# Benchmark result files
6363
*-benchmark.json
64+
65+
# LoongSuite Extension
66+
.cursor/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Changelog
2+
3+
All notable changes to loongsuite project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## Unreleased
9+
10+
- Add support for memory operations. ([#83](https://github.com/alibaba/loongsuite-python-agent/pull/83))

util/opentelemetry-util-genai/README-loongsuite.rst

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ LoongSuite 扩展为 OpenTelemetry GenAI Util 包提供了额外的 Generative A
1414
- **execute_tool**: 工具执行操作
1515
- **retrieve**: 文档检索操作(向量数据库查询)
1616
- **rerank**: 文档重排序操作
17+
- **memory**: 记忆操作,支持记忆的增删改查等操作
1718

1819
这些扩展操作遵循 OpenTelemetry GenAI 语义约定,并与基础的 LLM 操作保持一致的使用体验。
1920

@@ -37,13 +38,22 @@ LoongSuite 扩展为 OpenTelemetry GenAI Util 包提供了额外的 Generative A
3738
- ``EVENT_ONLY``: 仅在 event 中捕获消息内容(结构化格式)
3839
- ``SPAN_AND_EVENT``: 同时在 span 和 event 中捕获消息内容
3940

41+
事件发出控制
42+
~~~~~~~~~~~~
43+
44+
设置环境变量 ``OTEL_INSTRUMENTATION_GENAI_EMIT_EVENT`` 来控制是否发出事件:
45+
46+
- ``true``: 启用事件发出(当内容捕获模式为 ``EVENT_ONLY`` 或 ``SPAN_AND_EVENT`` 时)
47+
- ``false``: 禁用事件发出(默认)
48+
4049
示例配置
4150
~~~~~~~~
4251

4352
::
4453

4554
export OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental
4655
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_AND_EVENT
56+
export OTEL_INSTRUMENTATION_GENAI_EMIT_EVENT=true
4757

4858

4959
支持的操作
@@ -91,7 +101,7 @@ Token 使用:
91101

92102
**事件支持:**
93103

94-
当配置为 ``EVENT_ONLY`` ``SPAN_AND_EVENT`` 模式且提供 LoggerProvider 时,会发出 ``gen_ai.client.agent.invoke.operation.details`` 事件,包含结构化的消息内容。
104+
``OTEL_INSTRUMENTATION_GENAI_EMIT_EVENT`` 设置为 ``true`` 且提供 LoggerProvider 时,会发出 ``gen_ai.client.agent.invoke.operation.details`` 事件,包含结构化的消息内容(受内容捕获模式控制)
95105

96106
**使用示例:**
97107

@@ -302,6 +312,125 @@ Token 使用:
302312
invocation.rerank_output_documents = [...]
303313

304314

315+
7. 记忆操作 (memory)
316+
~~~~~~~~~~~~~~~~~~~~
317+
318+
用于跟踪 AI Agent 的记忆操作,支持记忆的增删改查、搜索和历史查询等功能。
319+
320+
**支持的操作类型:**
321+
322+
- ``add``: 添加记忆记录
323+
- ``search``: 搜索记忆记录
324+
- ``update``: 更新记忆记录
325+
- ``batch_update``: 批量更新记忆记录
326+
- ``get``: 获取特定记忆记录
327+
- ``get_all``: 获取所有记忆记录
328+
- ``history``: 获取记忆历史
329+
- ``delete``: 删除记忆记录
330+
- ``batch_delete``: 批量删除记忆记录
331+
- ``delete_all``: 删除所有记忆记录
332+
333+
**支持的属性:**
334+
335+
基础属性:
336+
- ``gen_ai.operation.name``: 操作名称,固定为 "memory_operation"
337+
- ``gen_ai.memory.operation``: 记忆操作类型(必需)
338+
339+
标识符(条件必需):
340+
- ``gen_ai.memory.user_id``: 用户标识符
341+
- ``gen_ai.memory.agent_id``: Agent 标识符
342+
- ``gen_ai.memory.run_id``: 运行标识符
343+
- ``gen_ai.memory.app_id``: 应用标识符(用于托管平台)
344+
- ``gen_ai.memory.id``: 记忆 ID(用于 get、update、delete 操作)
345+
346+
操作参数(可选):
347+
- ``gen_ai.memory.limit``: 返回结果数量限制
348+
- ``gen_ai.memory.page``: 分页页码
349+
- ``gen_ai.memory.page_size``: 分页大小
350+
- ``gen_ai.memory.top_k``: 返回 Top K 结果数量(用于托管 API)
351+
- ``gen_ai.memory.memory_type``: 记忆类型(如 "procedural_memory")
352+
- ``gen_ai.memory.threshold``: 相似度阈值(用于搜索操作)
353+
- ``gen_ai.memory.rerank``: 是否启用重排序
354+
355+
记忆内容(受内容捕获模式控制):
356+
- ``gen_ai.memory.input.messages``: 原始记忆内容
357+
- ``gen_ai.memory.output.messages``: 查询结果
358+
359+
服务器信息:
360+
- ``server.address``: 服务器地址
361+
- ``server.port``: 服务器端口
362+
363+
**事件支持:**
364+
365+
当配置为 ``EVENT_ONLY`` 或 ``SPAN_AND_EVENT`` 模式且提供 LoggerProvider 时,会发出 ``gen_ai.memory.operation.details`` 事件,包含结构化的记忆内容。
366+
367+
**使用示例:**
368+
369+
::
370+
371+
from opentelemetry.util.genai._extended_memory import MemoryInvocation
372+
373+
# 添加记忆
374+
invocation = MemoryInvocation(operation="add")
375+
with handler.memory(invocation) as invocation:
376+
invocation.user_id = "user_123"
377+
invocation.agent_id = "agent_456"
378+
invocation.run_id = "run_789"
379+
invocation.input_messages = "用户喜欢苹果"
380+
invocation.server_address = "api.mem0.ai"
381+
invocation.server_port = 443
382+
383+
# 搜索记忆
384+
invocation = MemoryInvocation(operation="search")
385+
with handler.memory(invocation) as invocation:
386+
invocation.user_id = "user_123"
387+
invocation.agent_id = "agent_456"
388+
invocation.limit = 10
389+
invocation.threshold = 0.7
390+
invocation.rerank = True
391+
invocation.top_k = 5
392+
393+
# 执行搜索...
394+
invocation.output_messages = [
395+
{"memory_id": "mem1", "content": "用户喜欢苹果", "score": 0.95},
396+
{"memory_id": "mem2", "content": "用户喜欢橙子", "score": 0.88}
397+
]
398+
399+
# 更新记忆
400+
invocation = MemoryInvocation(operation="update")
401+
with handler.memory(invocation) as invocation:
402+
invocation.memory_id = "mem_abc123"
403+
invocation.user_id = "user_123"
404+
invocation.input_messages = "更新后的记忆内容"
405+
406+
# 获取记忆
407+
invocation = MemoryInvocation(operation="get")
408+
with handler.memory(invocation) as invocation:
409+
invocation.memory_id = "mem_xyz789"
410+
invocation.user_id = "user_123"
411+
invocation.agent_id = "agent_456"
412+
413+
# 获取所有记忆(带分页)
414+
invocation = MemoryInvocation(operation="get_all")
415+
with handler.memory(invocation) as invocation:
416+
invocation.user_id = "user_123"
417+
invocation.page = 1
418+
invocation.page_size = 100
419+
420+
# 获取记忆历史
421+
invocation = MemoryInvocation(operation="history")
422+
with handler.memory(invocation) as invocation:
423+
invocation.user_id = "user_123"
424+
invocation.agent_id = "agent_456"
425+
invocation.run_id = "run_789"
426+
427+
# 删除记忆
428+
invocation = MemoryInvocation(operation="delete")
429+
with handler.memory(invocation) as invocation:
430+
invocation.memory_id = "mem_to_delete"
431+
invocation.user_id = "user_123"
432+
433+
305434
错误处理
306435
--------
307436

@@ -456,6 +585,31 @@ Token 使用:
456585
457586
rerank_inv.rerank_output_documents = [...]
458587

588+
# 记忆操作
589+
from opentelemetry.util.genai._extended_memory import MemoryInvocation
590+
591+
# 添加记忆
592+
memory_inv = MemoryInvocation(operation="add")
593+
with handler.memory(memory_inv) as memory_inv:
594+
memory_inv.user_id = "user_123"
595+
memory_inv.agent_id = "ShoppingAssistant"
596+
memory_inv.input_messages = "用户偏好:喜欢轻薄型笔记本电脑"
597+
598+
# 执行添加记忆...
599+
600+
# 搜索记忆
601+
search_inv = MemoryInvocation(operation="search")
602+
with handler.memory(search_inv) as search_inv:
603+
search_inv.user_id = "user_123"
604+
search_inv.agent_id = "ShoppingAssistant"
605+
search_inv.limit = 5
606+
search_inv.threshold = 0.7
607+
608+
# 执行搜索...
609+
search_inv.output_messages = [
610+
{"memory_id": "mem1", "content": "用户偏好:喜欢轻薄型笔记本电脑", "score": 0.92}
611+
]
612+
459613

460614
设计文档
461615
--------
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
Extended memory operation utilities for GenAI operations.
17+
This package provides types and utility functions for memory operations.
18+
"""
19+
20+
from __future__ import annotations
21+
22+
from opentelemetry.util.genai._extended_memory.memory_utils import (
23+
MemoryInvocation,
24+
_apply_memory_finish_attributes,
25+
_maybe_emit_memory_event,
26+
)
27+
28+
__all__ = [
29+
"MemoryInvocation",
30+
"_apply_memory_finish_attributes",
31+
"_maybe_emit_memory_event",
32+
]
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import annotations
16+
17+
from dataclasses import dataclass, field
18+
from typing import Any, Dict
19+
20+
from opentelemetry.util.genai.types import ContextToken, Span
21+
22+
23+
def _new_str_any_dict() -> Dict[str, Any]:
24+
"""Helper function to create a new empty dict for default factory."""
25+
return {}
26+
27+
28+
@dataclass
29+
class MemoryInvocation:
30+
"""
31+
Represents a single memory operation invocation.
32+
When creating a MemoryInvocation object, only update the data attributes.
33+
The span and context_token attributes are set by the TelemetryHandler.
34+
"""
35+
36+
operation: str # Memory operation type (add, search, update, etc.)
37+
context_token: ContextToken | None = None
38+
span: Span | None = None
39+
attributes: Dict[str, Any] = field(default_factory=_new_str_any_dict)
40+
# Memory identifiers (conditionally required)
41+
user_id: str | None = None
42+
agent_id: str | None = None
43+
run_id: str | None = None
44+
app_id: str | None = None
45+
# Memory operation parameters (optional)
46+
memory_id: str | None = None
47+
limit: int | None = None
48+
page: int | None = None
49+
page_size: int | None = None
50+
top_k: int | None = None
51+
memory_type: str | None = None
52+
threshold: float | None = None
53+
rerank: bool | None = None
54+
# Memory content (optional, controlled by content capturing mode)
55+
input_messages: Any = None # Original memory content
56+
output_messages: Any = None # Query results
57+
# Server information
58+
server_address: str | None = None
59+
server_port: int | None = None

0 commit comments

Comments
 (0)