Skip to content

Commit f10f08c

Browse files
authored
feat(sessions): list_sessions 支持 user_id 为 None 时获取 app_name 下全部 session (#145)
list_sessions 的 user_id 参数改为可选,传 None 时返回指定 app 下 所有用户的会话列表(不含 events)。InMemory/SQL/Redis/EvalSessionService 实现统一调整,并补充中英文文档用法说明及单元测试。
1 parent 778ca59 commit f10f08c

10 files changed

Lines changed: 232 additions & 90 deletions

docs/mkdocs/en/session.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,18 @@ session = await session_service.get_session(
109109

110110
**List Sessions**:
111111
```python
112+
# Specify user_id: returns all sessions for that user (without events)
112113
session_list = await session_service.list_sessions(
113114
app_name="my_app",
114115
user_id="user_001"
115116
)
116-
# Returns ListSessionsResponse, containing all sessions for the user (without events)
117+
118+
# user_id=None: returns sessions across all users for the app
119+
all_session_list = await session_service.list_sessions(
120+
app_name="my_app",
121+
user_id=None
122+
)
123+
# Returns ListSessionsResponse, containing matching sessions (without events)
117124
```
118125

119126
**Delete Session**:
@@ -128,7 +135,7 @@ await session_service.delete_session(
128135
**Implementation Logic** (`_base_session_service.py`):
129136
- `create_session`: Creates a session, separates and stores app/user/session state
130137
- `get_session`: Retrieves a session, merges app/user/session state, applies event filtering
131-
- `list_sessions`: Lists sessions (excludes events to reduce data transfer)
138+
- `list_sessions`: Lists sessions (excludes events to reduce data transfer); passing `user_id=None` returns sessions across all users for the app
132139
- `delete_session`: Deletes a session and its associated data
133140

134141
---
@@ -611,6 +618,7 @@ session = await session_service.get_session(
611618
)
612619

613620
# List existing Sessions
621+
# Specify user_id to return that user's sessions; user_id=None returns sessions across all users for the app
614622
session_list = await session_service.list_sessions(
615623
app_name=app_name,
616624
user_id=user_id

docs/mkdocs/zh/session.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,18 @@ session = await session_service.get_session(
109109

110110
**列出会话**
111111
```python
112+
# 指定 user_id:返回该用户的所有会话(不含 events)
112113
session_list = await session_service.list_sessions(
113114
app_name="my_app",
114115
user_id="user_001"
115116
)
116-
# 返回 ListSessionsResponse,包含该用户的所有会话(不含 events)
117+
118+
# user_id 为 None:返回该 app 下所有用户的会话
119+
all_session_list = await session_service.list_sessions(
120+
app_name="my_app",
121+
user_id=None
122+
)
123+
# 返回 ListSessionsResponse,包含符合条件的所有会话(不含 events)
117124
```
118125

119126
**删除会话**
@@ -128,7 +135,7 @@ await session_service.delete_session(
128135
**实现逻辑**`_base_session_service.py`):
129136
- `create_session`:创建会话,分离并存储 app/user/session 状态
130137
- `get_session`:获取会话,合并 app/user/session 状态,应用事件过滤
131-
- `list_sessions`:列出会话列表(不包含 events,减少数据传输)
138+
- `list_sessions`:列出会话列表(不包含 events,减少数据传输)`user_id``None` 时返回该 app 下所有用户的会话
132139
- `delete_session`:删除会话及其关联数据
133140

134141
---
@@ -611,6 +618,7 @@ session = await session_service.get_session(
611618
)
612619

613620
# 列出存在的 Session
621+
# 指定 user_id 返回该用户的会话;user_id=None 返回该 app 下所有用户的会话
614622
session_list = await session_service.list_sessions(
615623
app_name=app_name,
616624
user_id=user_id

tests/sessions/test_in_memory_session_service.py

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@
3434
def _make_session_config(ttl_seconds=0, cleanup_interval=0.0, enable_ttl=False, **kwargs):
3535
config = SessionServiceConfig(**kwargs)
3636
if enable_ttl:
37-
config.ttl = SessionServiceConfig.create_ttl_config(
38-
enable=True, ttl_seconds=ttl_seconds, cleanup_interval_seconds=cleanup_interval)
37+
config.ttl = SessionServiceConfig.create_ttl_config(enable=True,
38+
ttl_seconds=ttl_seconds,
39+
cleanup_interval_seconds=cleanup_interval)
3940
else:
4041
config.clean_ttl_config()
4142
return config
@@ -56,7 +57,9 @@ def _make_event(author="agent", text="hello", state_delta=None, partial=False):
5657
# SessionWithTTL
5758
# ---------------------------------------------------------------------------
5859

60+
5961
class TestSessionWithTTL:
62+
6063
def test_update_and_get(self):
6164
session = Session(id="s1", app_name="app", user_id="user", save_key="k")
6265
wrapper = SessionWithTTL(session=session)
@@ -85,7 +88,9 @@ def test_get_non_expired(self):
8588
# StateWithTTL
8689
# ---------------------------------------------------------------------------
8790

91+
8892
class TestStateWithTTL:
93+
8994
def test_update(self):
9095
wrapper = StateWithTTL()
9196
result = wrapper.update({"key": "value"})
@@ -119,7 +124,9 @@ def test_update_expired_resets_then_updates(self):
119124
# InMemorySessionService — create_session
120125
# ---------------------------------------------------------------------------
121126

127+
122128
class TestInMemoryCreateSession:
129+
123130
async def test_create_basic_session(self):
124131
svc = InMemorySessionService(session_config=_make_session_config())
125132
session = await svc.create_session(app_name="app", user_id="user")
@@ -143,13 +150,13 @@ async def test_create_with_whitespace_id_generates_uuid(self):
143150

144151
async def test_create_with_state(self):
145152
svc = InMemorySessionService(session_config=_make_session_config())
146-
session = await svc.create_session(
147-
app_name="app", user_id="user",
148-
state={
149-
"session_key": "session_val",
150-
f"{State.APP_PREFIX}app_key": "app_val",
151-
f"{State.USER_PREFIX}user_key": "user_val",
152-
})
153+
session = await svc.create_session(app_name="app",
154+
user_id="user",
155+
state={
156+
"session_key": "session_val",
157+
f"{State.APP_PREFIX}app_key": "app_val",
158+
f"{State.USER_PREFIX}user_key": "user_val",
159+
})
153160
assert session.state["session_key"] == "session_val"
154161
assert session.state[f"{State.APP_PREFIX}app_key"] == "app_val"
155162
assert session.state[f"{State.USER_PREFIX}user_key"] == "user_val"
@@ -168,7 +175,9 @@ async def test_create_returns_deep_copy(self):
168175
# InMemorySessionService — get_session
169176
# ---------------------------------------------------------------------------
170177

178+
171179
class TestInMemoryGetSession:
180+
172181
async def test_get_existing_session(self):
173182
svc = InMemorySessionService(session_config=_make_session_config())
174183
created = await svc.create_session(app_name="app", user_id="user", session_id="s1")
@@ -185,13 +194,14 @@ async def test_get_nonexistent_session(self):
185194

186195
async def test_get_returns_merged_state(self):
187196
svc = InMemorySessionService(session_config=_make_session_config())
188-
await svc.create_session(
189-
app_name="app", user_id="user", session_id="s1",
190-
state={
191-
"sk": "sv",
192-
f"{State.APP_PREFIX}ak": "av",
193-
f"{State.USER_PREFIX}uk": "uv",
194-
})
197+
await svc.create_session(app_name="app",
198+
user_id="user",
199+
session_id="s1",
200+
state={
201+
"sk": "sv",
202+
f"{State.APP_PREFIX}ak": "av",
203+
f"{State.USER_PREFIX}uk": "uv",
204+
})
195205
result = await svc.get_session(app_name="app", user_id="user", session_id="s1")
196206
assert result.state["sk"] == "sv"
197207
assert result.state[f"{State.APP_PREFIX}ak"] == "av"
@@ -212,7 +222,9 @@ async def test_get_returns_deep_copy(self):
212222
# InMemorySessionService — list_sessions
213223
# ---------------------------------------------------------------------------
214224

225+
215226
class TestInMemoryListSessions:
227+
216228
async def test_list_empty(self):
217229
svc = InMemorySessionService(session_config=_make_session_config())
218230
result = await svc.list_sessions(app_name="app", user_id="user")
@@ -251,12 +263,31 @@ async def test_list_nonexistent_user(self):
251263
assert result.sessions == []
252264
await svc.close()
253265

266+
async def test_list_all_users_when_user_id_none(self):
267+
svc = InMemorySessionService(session_config=_make_session_config())
268+
await svc.create_session(app_name="app", user_id="user1", session_id="s1")
269+
await svc.create_session(app_name="app", user_id="user2", session_id="s2")
270+
result = await svc.list_sessions(app_name="app", user_id=None)
271+
ids = sorted(s.id for s in result.sessions)
272+
assert ids == ["s1", "s2"]
273+
await svc.close()
274+
275+
async def test_list_all_users_filtered_by_app(self):
276+
svc = InMemorySessionService(session_config=_make_session_config())
277+
await svc.create_session(app_name="app1", user_id="user1", session_id="s1")
278+
await svc.create_session(app_name="app2", user_id="user1", session_id="s2")
279+
result = await svc.list_sessions(app_name="app1", user_id=None)
280+
assert [s.id for s in result.sessions] == ["s1"]
281+
await svc.close()
282+
254283

255284
# ---------------------------------------------------------------------------
256285
# InMemorySessionService — delete_session
257286
# ---------------------------------------------------------------------------
258287

288+
259289
class TestInMemoryDeleteSession:
290+
260291
async def test_delete_existing(self):
261292
svc = InMemorySessionService(session_config=_make_session_config())
262293
await svc.create_session(app_name="app", user_id="user", session_id="s1")
@@ -275,7 +306,9 @@ async def test_delete_nonexistent(self):
275306
# InMemorySessionService — append_event
276307
# ---------------------------------------------------------------------------
277308

309+
278310
class TestInMemoryAppendEvent:
311+
279312
async def test_append_basic(self):
280313
svc = InMemorySessionService(session_config=_make_session_config())
281314
session = await svc.create_session(app_name="app", user_id="user", session_id="s1")
@@ -351,7 +384,9 @@ async def test_append_updates_conversation_count(self):
351384
# InMemorySessionService — update_session
352385
# ---------------------------------------------------------------------------
353386

387+
354388
class TestInMemoryUpdateSession:
389+
355390
async def test_update_existing(self):
356391
svc = InMemorySessionService(session_config=_make_session_config())
357392
session = await svc.create_session(app_name="app", user_id="user", session_id="s1")
@@ -384,7 +419,9 @@ async def test_update_nonexistent_session(self):
384419
# InMemorySessionService — cleanup
385420
# ---------------------------------------------------------------------------
386421

422+
387423
class TestInMemoryCleanupExpired:
424+
388425
async def test_cleanup_removes_expired_sessions(self):
389426
config = _make_session_config(enable_ttl=True, ttl_seconds=1, cleanup_interval=3600.0)
390427
svc = InMemorySessionService(session_config=config)
@@ -436,7 +473,9 @@ async def test_cleanup_nothing_expired(self):
436473
# InMemorySessionService — cleanup task lifecycle
437474
# ---------------------------------------------------------------------------
438475

476+
439477
class TestInMemoryCleanupTask:
478+
440479
def test_no_cleanup_task_when_ttl_disabled(self):
441480
svc = InMemorySessionService(session_config=_make_session_config())
442481
assert svc._InMemorySessionService__cleanup_task is None
@@ -490,7 +529,9 @@ async def test_cleanup_loop_handles_error(self):
490529
# InMemorySessionService — internal helpers
491530
# ---------------------------------------------------------------------------
492531

532+
493533
class TestInMemoryInternalHelpers:
534+
494535
async def test_get_app_state_nonexistent(self):
495536
svc = InMemorySessionService(session_config=_make_session_config())
496537
assert svc._get_app_state("nonexistent") == {}

0 commit comments

Comments
 (0)