unique-user-memory 2026.34.0.dev2__tar.gz → 2026.34.0.dev4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-user-memory
3
- Version: 2026.34.0.dev2
3
+ Version: 2026.34.0.dev4
4
4
  Summary:
5
5
  Author: Fabian Schläpfer
6
6
  Author-email: Fabian Schläpfer <fabian@unique.ch>
7
7
  License: Proprietary
8
8
  Requires-Dist: jinja2>=3.1.6
9
9
  Requires-Dist: pydantic>=2.8.2
10
- Requires-Dist: unique-sdk>=2026.34.0.dev2,<2026.34.0rc0
11
- Requires-Dist: unique-toolkit>=2026.34.0.dev5,<2026.34.0rc0
10
+ Requires-Dist: unique-sdk>=2026.34.0.dev5,<2026.34.0rc0
11
+ Requires-Dist: unique-toolkit>=2026.34.0.dev11,<2026.34.0rc0
12
12
  Requires-Python: >=3.12, <4
13
13
  Description-Content-Type: text/markdown
14
14
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique_user_memory"
3
- version = "2026.34.0.dev2"
3
+ version = "2026.34.0.dev4"
4
4
  description = ""
5
5
  authors = [
6
6
  { name = "Fabian Schläpfer", email = "fabian@unique.ch" },
@@ -11,8 +11,8 @@ requires-python = ">=3.12,<4"
11
11
  dependencies = [
12
12
  "jinja2>=3.1.6",
13
13
  "pydantic>=2.8.2",
14
- "unique-sdk>=2026.34.0.dev2,<2026.34.0rc0",
15
- "unique-toolkit>=2026.34.0.dev5,<2026.34.0rc0",
14
+ "unique-sdk>=2026.34.0.dev5,<2026.34.0rc0",
15
+ "unique-toolkit>=2026.34.0.dev11,<2026.34.0rc0",
16
16
  ]
17
17
 
18
18
  [dependency-groups]
@@ -2,13 +2,25 @@ import asyncio
2
2
  from unittest.mock import AsyncMock, MagicMock
3
3
 
4
4
  import pytest
5
+ from unique_toolkit.app.schemas import (
6
+ ChatEvent,
7
+ ChatEventAssistantMessage,
8
+ ChatEventPayload,
9
+ ChatEventUserMessage,
10
+ EventName,
11
+ )
5
12
  from unique_toolkit.chat.schemas import MessageLogStatus
6
13
  from unique_toolkit.language_model.default_language_model import (
7
14
  DEFAULT_LANGUAGE_MODEL,
8
15
  )
9
16
  from unique_toolkit.language_model.infos import LanguageModelInfo
10
17
  from unique_toolkit.language_model.invocation_stats import LanguageModelInvocationStats
11
- from unique_toolkit.language_model.schemas import LanguageModelTokenUsage
18
+ from unique_toolkit.language_model.schemas import (
19
+ LanguageModelAssistantMessage,
20
+ LanguageModelCompletionChoice,
21
+ LanguageModelResponse,
22
+ LanguageModelTokenUsage,
23
+ )
12
24
 
13
25
  from unique_user_memory.config import UserMemoryConfig
14
26
  from unique_user_memory.user_memory import (
@@ -36,6 +48,45 @@ from unique_user_memory.user_memory_prompts import (
36
48
  _TEST_LANGUAGE_MODEL = LanguageModelInfo.from_name(DEFAULT_LANGUAGE_MODEL)
37
49
 
38
50
 
51
+ def _chat_event() -> ChatEvent:
52
+ return ChatEvent(
53
+ id="event_1",
54
+ event=EventName.EXTERNAL_MODULE_CHOSEN,
55
+ user_id="user_1",
56
+ company_id="company_1",
57
+ payload=ChatEventPayload(
58
+ assistant_id="assistant_1",
59
+ chat_id="chat_1",
60
+ name="module",
61
+ description="module_description",
62
+ configuration={},
63
+ user_message=ChatEventUserMessage(
64
+ id="user_message_1",
65
+ text="Remember this",
66
+ created_at="2021-01-01T00:00:00Z",
67
+ language="EN",
68
+ original_text="Remember this",
69
+ ),
70
+ assistant_message=ChatEventAssistantMessage(
71
+ id="assistant_message_1",
72
+ created_at="2021-01-01T00:00:00Z",
73
+ ),
74
+ ),
75
+ )
76
+
77
+
78
+ def _completion_response(content: str) -> LanguageModelResponse:
79
+ return LanguageModelResponse(
80
+ choices=[
81
+ LanguageModelCompletionChoice(
82
+ index=0,
83
+ message=LanguageModelAssistantMessage(content=content),
84
+ finish_reason="stop",
85
+ )
86
+ ]
87
+ )
88
+
89
+
39
90
  def test_memory_profile_keeps_follow_up_tasks_but_excludes_open_questions() -> None:
40
91
  profile = empty_profile("user_1")
41
92
  consolidation_prompt = consolidation_system_prompt(2000)
@@ -223,6 +274,69 @@ async def test_condense_user_memory_accepts_frontmatter_output(
223
274
  assert result == condensed
224
275
 
225
276
 
277
+ @pytest.mark.ai
278
+ @pytest.mark.asyncio
279
+ async def test_user_memory_llm_paths_forward_event_attribution(
280
+ monkeypatch: pytest.MonkeyPatch,
281
+ ) -> None:
282
+ """Purpose: Verify gate, rewrite, and condense use event-attributed completions.
283
+ Why this matters: Every user-memory ModelUsage row must link to its chat.
284
+ Setup summary: Exercise all shared service paths and inspect toolkit calls.
285
+ """
286
+ rewritten = "# User Memory\n\n## Identity\n- Prefers concise answers"
287
+ condensed = "# User Memory\n\n## Identity\n- Concise"
288
+ complete_async = AsyncMock(
289
+ side_effect=[
290
+ _completion_response("UPDATE"),
291
+ _completion_response(rewritten),
292
+ _completion_response(condensed),
293
+ ]
294
+ )
295
+ monkeypatch.setattr(
296
+ "unique_toolkit.language_model.service.complete_async",
297
+ complete_async,
298
+ )
299
+ event = _chat_event()
300
+ logger = MagicMock()
301
+
302
+ gate_result = await should_consolidate_memory(
303
+ current_memory=empty_profile("user_1"),
304
+ user_id="user_1",
305
+ user_message="Remember concise answers",
306
+ assistant_message="Noted",
307
+ language_model=_TEST_LANGUAGE_MODEL,
308
+ event=event,
309
+ logger=logger,
310
+ )
311
+ rewrite_result = await consolidate_user_memory(
312
+ current_memory=empty_profile("user_1"),
313
+ user_id="user_1",
314
+ user_message="Remember concise answers",
315
+ assistant_message="Noted",
316
+ config=UserMemoryConfig(consolidation_gate_enabled=False),
317
+ language_model=_TEST_LANGUAGE_MODEL,
318
+ event=event,
319
+ logger=logger,
320
+ )
321
+ condense_result = await condense_user_memory(
322
+ content=empty_profile("user_1"),
323
+ max_tokens=2000,
324
+ language_model=_TEST_LANGUAGE_MODEL,
325
+ event=event,
326
+ logger=logger,
327
+ )
328
+
329
+ assert gate_result is True
330
+ assert rewritten in rewrite_result
331
+ assert condense_result == condensed
332
+ assert complete_async.await_count == 3
333
+ assert all(
334
+ call.kwargs["chat_id"] == "chat_1"
335
+ and call.kwargs["assistant_id"] == "assistant_1"
336
+ for call in complete_async.await_args_list
337
+ )
338
+
339
+
226
340
  def test_count_tokens_uses_language_model_encoder() -> None:
227
341
  language_model = MagicMock()
228
342
  language_model.get_encoder.return_value = lambda content: content.split()