mem01session 0.1.0__py3-none-any.whl
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.
- mem01session/__init__.py +25 -0
- mem01session/demo.py +614 -0
- mem01session/fixtures/build_week_conversations.json +42 -0
- mem01session/items.py +66 -0
- mem01session/memory_block.py +127 -0
- mem01session/metrics.py +37 -0
- mem01session/py.typed +1 -0
- mem01session/runtime.py +672 -0
- mem01session/session.py +522 -0
- mem01session-0.1.0.dist-info/METADATA +283 -0
- mem01session-0.1.0.dist-info/RECORD +14 -0
- mem01session-0.1.0.dist-info/WHEEL +4 -0
- mem01session-0.1.0.dist-info/entry_points.txt +2 -0
- mem01session-0.1.0.dist-info/licenses/LICENSE +21 -0
mem01session/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""OpenAI Agents SDK embedded session adapter for mem01."""
|
|
2
|
+
|
|
3
|
+
from .metrics import ContextEstimate, estimate_prompt_context
|
|
4
|
+
from .runtime import (
|
|
5
|
+
EmbeddedMem01Runtime,
|
|
6
|
+
SharedRuntimeLease,
|
|
7
|
+
acquire_shared_runtime,
|
|
8
|
+
close_shared_runtimes,
|
|
9
|
+
)
|
|
10
|
+
from .session import Mem01MemoryError, Mem01Session
|
|
11
|
+
|
|
12
|
+
memSession = Mem01Session
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"ContextEstimate",
|
|
16
|
+
"EmbeddedMem01Runtime",
|
|
17
|
+
"Mem01Session",
|
|
18
|
+
"Mem01MemoryError",
|
|
19
|
+
"memSession",
|
|
20
|
+
"SharedRuntimeLease",
|
|
21
|
+
"acquire_shared_runtime",
|
|
22
|
+
"close_shared_runtimes",
|
|
23
|
+
"estimate_prompt_context",
|
|
24
|
+
]
|
|
25
|
+
__version__ = "0.1.0"
|
mem01session/demo.py
ADDED
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
"""Deterministic three-lane evidence demo and optional live smoke test."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import asyncio
|
|
7
|
+
import copy
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from collections.abc import AsyncIterator, Mapping, Sequence
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from datetime import UTC, datetime
|
|
13
|
+
from importlib import metadata, resources
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from tempfile import TemporaryDirectory
|
|
16
|
+
from typing import Any, cast
|
|
17
|
+
from uuid import uuid4
|
|
18
|
+
|
|
19
|
+
from agents import Agent, Model, RunConfig, Runner, SQLiteSession
|
|
20
|
+
from agents.items import ModelResponse
|
|
21
|
+
from agents.usage import Usage
|
|
22
|
+
from mem01 import ApplyResult
|
|
23
|
+
from mem01 import env as mem01_env
|
|
24
|
+
from mem01.types import (
|
|
25
|
+
Belief,
|
|
26
|
+
BeliefSource,
|
|
27
|
+
BeliefStatus,
|
|
28
|
+
PackedMemory,
|
|
29
|
+
ScopeIds,
|
|
30
|
+
)
|
|
31
|
+
from openai.types.responses import ResponseOutputMessage, ResponseOutputText
|
|
32
|
+
|
|
33
|
+
from .items import text_from_item
|
|
34
|
+
from .metrics import (
|
|
35
|
+
OFFLINE_PREPARED_INPUT_MEASUREMENT,
|
|
36
|
+
TOKEN_ESTIMATOR_UTF8_BYTES_UPPER_BOUND,
|
|
37
|
+
estimate_prompt_context,
|
|
38
|
+
)
|
|
39
|
+
from .session import Mem01Session
|
|
40
|
+
|
|
41
|
+
USER_ID = "build-week-user"
|
|
42
|
+
LIVE_MODEL = "gpt-5.6-sol"
|
|
43
|
+
ANSWER_MODEL = "local-deterministic-fake"
|
|
44
|
+
EXTRACTION_MODEL = "local-deterministic-fixture-extractor"
|
|
45
|
+
MAX_MEMORY_TOKENS = 800
|
|
46
|
+
TOKEN_ESTIMATOR = TOKEN_ESTIMATOR_UTF8_BYTES_UPPER_BOUND
|
|
47
|
+
CHECKPOINTS = (1, 10, 40)
|
|
48
|
+
ARTIFACT_PATH = Path(__file__).parents[2] / "artifacts" / "prepared-input-scaling.json"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class DemoConversation:
|
|
53
|
+
"""One immutable input from the checked-in Build Week scenario."""
|
|
54
|
+
|
|
55
|
+
conversation: int
|
|
56
|
+
user_input: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True, slots=True)
|
|
60
|
+
class DeterministicDemoResult:
|
|
61
|
+
"""Auditable prepared-state evidence; outputs are observations only."""
|
|
62
|
+
|
|
63
|
+
prepared_inputs: dict[str, dict[int, list[Any]]]
|
|
64
|
+
observed_answers: dict[str, dict[int, str]]
|
|
65
|
+
beliefs: list[Belief]
|
|
66
|
+
mem01_session_ids: tuple[str, ...]
|
|
67
|
+
mem01_user_ids: tuple[str, ...]
|
|
68
|
+
sdk_runs: int
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def load_conversation_fixture() -> tuple[DemoConversation, ...]:
|
|
72
|
+
"""Load the packaged, checked-in 40-conversation fixture."""
|
|
73
|
+
fixture = resources.files("mem01session").joinpath(
|
|
74
|
+
"fixtures/build_week_conversations.json"
|
|
75
|
+
)
|
|
76
|
+
raw = json.loads(fixture.read_text(encoding="utf-8"))
|
|
77
|
+
return tuple(
|
|
78
|
+
DemoConversation(
|
|
79
|
+
conversation=int(record["conversation"]),
|
|
80
|
+
user_input=str(record["user_input"]),
|
|
81
|
+
)
|
|
82
|
+
for record in raw
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _stored_at(day: int) -> datetime:
|
|
87
|
+
return datetime(2026, 7, day, 12, tzinfo=UTC)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class _DeterministicMemoryRuntime:
|
|
91
|
+
"""Small local runtime that models only fixture facts, with no I/O."""
|
|
92
|
+
|
|
93
|
+
def __init__(self) -> None:
|
|
94
|
+
scope_ids = ScopeIds(user_id=USER_ID)
|
|
95
|
+
self._beliefs = [
|
|
96
|
+
Belief(
|
|
97
|
+
id="fixture-location-nyc",
|
|
98
|
+
content="Lives in New York City",
|
|
99
|
+
status=BeliefStatus.SUPERSEDED,
|
|
100
|
+
scope_ids=scope_ids,
|
|
101
|
+
source=BeliefSource.EXTRACTION,
|
|
102
|
+
created_at=_stored_at(1),
|
|
103
|
+
updated_at=_stored_at(2),
|
|
104
|
+
),
|
|
105
|
+
Belief(
|
|
106
|
+
id="fixture-rent",
|
|
107
|
+
content="Monthly rent was $2,400",
|
|
108
|
+
scope_ids=scope_ids,
|
|
109
|
+
source=BeliefSource.EXTRACTION,
|
|
110
|
+
created_at=_stored_at(1),
|
|
111
|
+
updated_at=_stored_at(1),
|
|
112
|
+
),
|
|
113
|
+
Belief(
|
|
114
|
+
id="fixture-location-sf",
|
|
115
|
+
content="Lives in San Francisco",
|
|
116
|
+
scope_ids=scope_ids,
|
|
117
|
+
source=BeliefSource.EXTRACTION,
|
|
118
|
+
created_at=_stored_at(2),
|
|
119
|
+
updated_at=_stored_at(2),
|
|
120
|
+
supersedes_id="fixture-location-nyc",
|
|
121
|
+
),
|
|
122
|
+
]
|
|
123
|
+
self._seen_first_fact = False
|
|
124
|
+
self._seen_move = False
|
|
125
|
+
|
|
126
|
+
def _visible_beliefs(self) -> list[Belief]:
|
|
127
|
+
visible: list[Belief] = []
|
|
128
|
+
for belief in self._beliefs:
|
|
129
|
+
if belief.id in {"fixture-location-nyc", "fixture-rent"}:
|
|
130
|
+
if self._seen_first_fact:
|
|
131
|
+
visible.append(belief)
|
|
132
|
+
elif self._seen_move:
|
|
133
|
+
visible.append(belief)
|
|
134
|
+
return visible
|
|
135
|
+
|
|
136
|
+
async def remember(
|
|
137
|
+
self,
|
|
138
|
+
messages: list[dict[str, str]],
|
|
139
|
+
*,
|
|
140
|
+
user_id: str,
|
|
141
|
+
session_id: str | None = None,
|
|
142
|
+
) -> object:
|
|
143
|
+
del session_id
|
|
144
|
+
if user_id != USER_ID:
|
|
145
|
+
raise ValueError("deterministic demo user mismatch")
|
|
146
|
+
user_text = "\n".join(
|
|
147
|
+
message["content"] for message in messages if message["role"] == "user"
|
|
148
|
+
)
|
|
149
|
+
if "NYC" in user_text and "$2,400" in user_text:
|
|
150
|
+
self._seen_first_fact = True
|
|
151
|
+
if "moved to SF" in user_text:
|
|
152
|
+
self._seen_move = True
|
|
153
|
+
return type("RememberResult", (), {"apply": ApplyResult()})()
|
|
154
|
+
|
|
155
|
+
async def recall(
|
|
156
|
+
self,
|
|
157
|
+
query: str,
|
|
158
|
+
*,
|
|
159
|
+
user_id: str,
|
|
160
|
+
max_memory_tokens: int,
|
|
161
|
+
include_history: bool = False,
|
|
162
|
+
) -> PackedMemory:
|
|
163
|
+
del query, include_history
|
|
164
|
+
if user_id != USER_ID:
|
|
165
|
+
raise ValueError("deterministic demo user mismatch")
|
|
166
|
+
beliefs = self._visible_beliefs()
|
|
167
|
+
return PackedMemory(
|
|
168
|
+
beliefs=beliefs,
|
|
169
|
+
text="\n".join(belief.content for belief in beliefs),
|
|
170
|
+
tokens_used=0,
|
|
171
|
+
max_memory_tokens=max_memory_tokens,
|
|
172
|
+
candidate_count=len(beliefs),
|
|
173
|
+
latency_ms=0,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
async def history(
|
|
177
|
+
self,
|
|
178
|
+
*,
|
|
179
|
+
user_id: str,
|
|
180
|
+
include_invalidated: bool = False,
|
|
181
|
+
limit: int = 100,
|
|
182
|
+
) -> list[Belief]:
|
|
183
|
+
if user_id != USER_ID:
|
|
184
|
+
raise ValueError("deterministic demo user mismatch")
|
|
185
|
+
beliefs = self._visible_beliefs()
|
|
186
|
+
if not include_invalidated:
|
|
187
|
+
beliefs = [
|
|
188
|
+
belief for belief in beliefs if belief.status == BeliefStatus.ACTIVE
|
|
189
|
+
]
|
|
190
|
+
return beliefs[-limit:]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class _DeterministicModel(Model):
|
|
194
|
+
"""Local model used only to drive the real Agent/Runner preparation path."""
|
|
195
|
+
|
|
196
|
+
def __init__(self) -> None:
|
|
197
|
+
self.prepared_inputs: list[list[Any]] = []
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def _answer(input_items: Sequence[Any]) -> str:
|
|
201
|
+
texts = [
|
|
202
|
+
text for item in input_items if (text := text_from_item(item)) is not None
|
|
203
|
+
]
|
|
204
|
+
current = texts[-1].lower() if texts else ""
|
|
205
|
+
context = "\n".join(texts)
|
|
206
|
+
if "sister" in current:
|
|
207
|
+
return "I do not have your sister's name stored."
|
|
208
|
+
if "where do i live" in current:
|
|
209
|
+
if "San Francisco" in context and "$2,400" in context:
|
|
210
|
+
return "You live in San Francisco; your prior rent was $2,400."
|
|
211
|
+
has_location = "moved to SF" in context or "live in NYC" in context
|
|
212
|
+
if has_location and "$2,400" in context:
|
|
213
|
+
return "The available conversation contains location and rent details."
|
|
214
|
+
return "I do not have that personal information stored."
|
|
215
|
+
return "Noted."
|
|
216
|
+
|
|
217
|
+
async def get_response(
|
|
218
|
+
self,
|
|
219
|
+
system_instructions: str | None,
|
|
220
|
+
input: Any,
|
|
221
|
+
*args: Any,
|
|
222
|
+
**kwargs: Any,
|
|
223
|
+
) -> ModelResponse:
|
|
224
|
+
del system_instructions, args, kwargs
|
|
225
|
+
prepared = copy.deepcopy(list(input))
|
|
226
|
+
self.prepared_inputs.append(prepared)
|
|
227
|
+
answer = self._answer(prepared)
|
|
228
|
+
output = ResponseOutputMessage(
|
|
229
|
+
id=f"deterministic-message-{len(self.prepared_inputs)}",
|
|
230
|
+
content=[
|
|
231
|
+
ResponseOutputText(
|
|
232
|
+
annotations=[],
|
|
233
|
+
text=answer,
|
|
234
|
+
type="output_text",
|
|
235
|
+
logprobs=[],
|
|
236
|
+
)
|
|
237
|
+
],
|
|
238
|
+
role="assistant",
|
|
239
|
+
status="completed",
|
|
240
|
+
type="message",
|
|
241
|
+
)
|
|
242
|
+
return ModelResponse(
|
|
243
|
+
output=[output],
|
|
244
|
+
usage=Usage(requests=1, input_tokens=0, output_tokens=0, total_tokens=0),
|
|
245
|
+
response_id=f"deterministic-response-{len(self.prepared_inputs)}",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
async def stream_response(
|
|
249
|
+
self,
|
|
250
|
+
system_instructions: str | None,
|
|
251
|
+
input: Any,
|
|
252
|
+
*args: Any,
|
|
253
|
+
**kwargs: Any,
|
|
254
|
+
) -> AsyncIterator[Any]:
|
|
255
|
+
del system_instructions, input, args, kwargs
|
|
256
|
+
if False:
|
|
257
|
+
yield None
|
|
258
|
+
raise AssertionError("deterministic demo does not stream")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _agent(model: Model) -> Agent[Any]:
|
|
262
|
+
return Agent(
|
|
263
|
+
name="Mem01Session deterministic evidence agent",
|
|
264
|
+
instructions=(
|
|
265
|
+
"Use only prepared conversation or memory. Treat model outputs as "
|
|
266
|
+
"observations, not test guarantees."
|
|
267
|
+
),
|
|
268
|
+
model=model,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
async def _run_one(
|
|
273
|
+
model: _DeterministicModel,
|
|
274
|
+
prompt: str,
|
|
275
|
+
session: Any,
|
|
276
|
+
*,
|
|
277
|
+
run_config: RunConfig,
|
|
278
|
+
) -> tuple[list[Any], str]:
|
|
279
|
+
result = await Runner.run(
|
|
280
|
+
_agent(model),
|
|
281
|
+
prompt,
|
|
282
|
+
session=session,
|
|
283
|
+
run_config=run_config,
|
|
284
|
+
)
|
|
285
|
+
return copy.deepcopy(model.prepared_inputs[-1]), str(result.final_output)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
async def run_deterministic_demo(
|
|
289
|
+
*,
|
|
290
|
+
max_memory_tokens: int = MAX_MEMORY_TOKENS,
|
|
291
|
+
) -> DeterministicDemoResult:
|
|
292
|
+
"""Run 40 inputs through three real SDK preparation strategies, key-free."""
|
|
293
|
+
fixture = load_conversation_fixture()
|
|
294
|
+
snapshots: dict[str, dict[int, list[Any]]] = {
|
|
295
|
+
"fresh_stock": {},
|
|
296
|
+
"reused_stock": {},
|
|
297
|
+
"mem01session": {},
|
|
298
|
+
}
|
|
299
|
+
observations: dict[str, dict[int, str]] = {
|
|
300
|
+
"fresh_stock": {},
|
|
301
|
+
"reused_stock": {},
|
|
302
|
+
"mem01session": {},
|
|
303
|
+
}
|
|
304
|
+
memory_ids: list[str] = []
|
|
305
|
+
memory_users: list[str] = []
|
|
306
|
+
|
|
307
|
+
with TemporaryDirectory(prefix="mem01session-deterministic-") as directory:
|
|
308
|
+
root = Path(directory)
|
|
309
|
+
fresh_model = _DeterministicModel()
|
|
310
|
+
for turn in fixture:
|
|
311
|
+
session = SQLiteSession(
|
|
312
|
+
f"fresh-stock-{turn.conversation}",
|
|
313
|
+
db_path=root / "fresh-stock.db",
|
|
314
|
+
)
|
|
315
|
+
try:
|
|
316
|
+
prepared, answer = await _run_one(
|
|
317
|
+
fresh_model,
|
|
318
|
+
turn.user_input,
|
|
319
|
+
session,
|
|
320
|
+
run_config=RunConfig(tracing_disabled=True),
|
|
321
|
+
)
|
|
322
|
+
finally:
|
|
323
|
+
session.close()
|
|
324
|
+
if turn.conversation in CHECKPOINTS:
|
|
325
|
+
snapshots["fresh_stock"][turn.conversation] = prepared
|
|
326
|
+
observations["fresh_stock"][turn.conversation] = answer
|
|
327
|
+
|
|
328
|
+
reused_model = _DeterministicModel()
|
|
329
|
+
reused_session = SQLiteSession(
|
|
330
|
+
"reused-stock",
|
|
331
|
+
db_path=root / "reused-stock.db",
|
|
332
|
+
)
|
|
333
|
+
try:
|
|
334
|
+
for turn in fixture:
|
|
335
|
+
prepared, answer = await _run_one(
|
|
336
|
+
reused_model,
|
|
337
|
+
turn.user_input,
|
|
338
|
+
reused_session,
|
|
339
|
+
run_config=RunConfig(tracing_disabled=True),
|
|
340
|
+
)
|
|
341
|
+
if turn.conversation in CHECKPOINTS:
|
|
342
|
+
snapshots["reused_stock"][turn.conversation] = prepared
|
|
343
|
+
observations["reused_stock"][turn.conversation] = answer
|
|
344
|
+
finally:
|
|
345
|
+
reused_session.close()
|
|
346
|
+
|
|
347
|
+
runtime = _DeterministicMemoryRuntime()
|
|
348
|
+
memory_model = _DeterministicModel()
|
|
349
|
+
for turn in fixture:
|
|
350
|
+
session_id = f"mem01-conversation-{turn.conversation}"
|
|
351
|
+
memory_ids.append(session_id)
|
|
352
|
+
memory_users.append(USER_ID)
|
|
353
|
+
memory_session = Mem01Session(
|
|
354
|
+
session_id,
|
|
355
|
+
USER_ID,
|
|
356
|
+
runtime=runtime, # type: ignore[arg-type]
|
|
357
|
+
conversation_db=root / f"memory-{turn.conversation}.db",
|
|
358
|
+
max_memory_tokens=max_memory_tokens,
|
|
359
|
+
strict=True,
|
|
360
|
+
)
|
|
361
|
+
try:
|
|
362
|
+
config = memory_session.run_config()
|
|
363
|
+
config.tracing_disabled = True
|
|
364
|
+
prepared, answer = await _run_one(
|
|
365
|
+
memory_model,
|
|
366
|
+
turn.user_input,
|
|
367
|
+
memory_session,
|
|
368
|
+
run_config=config,
|
|
369
|
+
)
|
|
370
|
+
finally:
|
|
371
|
+
await memory_session.close()
|
|
372
|
+
if turn.conversation in CHECKPOINTS:
|
|
373
|
+
snapshots["mem01session"][turn.conversation] = prepared
|
|
374
|
+
observations["mem01session"][turn.conversation] = answer
|
|
375
|
+
|
|
376
|
+
beliefs = await runtime.history(
|
|
377
|
+
user_id=USER_ID,
|
|
378
|
+
include_invalidated=True,
|
|
379
|
+
limit=100,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
return DeterministicDemoResult(
|
|
383
|
+
prepared_inputs=snapshots,
|
|
384
|
+
observed_answers=observations,
|
|
385
|
+
beliefs=beliefs,
|
|
386
|
+
mem01_session_ids=tuple(memory_ids),
|
|
387
|
+
mem01_user_ids=tuple(memory_users),
|
|
388
|
+
sdk_runs=len(fixture) * 3,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def generate_artifact_records(
|
|
393
|
+
result: DeterministicDemoResult,
|
|
394
|
+
*,
|
|
395
|
+
max_memory_tokens: int = MAX_MEMORY_TOKENS,
|
|
396
|
+
) -> list[dict[str, Any]]:
|
|
397
|
+
"""Create generated offline measurements at conversations 1, 10, and 40."""
|
|
398
|
+
agents_version = metadata.version("openai-agents")
|
|
399
|
+
records: list[dict[str, Any]] = []
|
|
400
|
+
for strategy in ("fresh_stock", "reused_stock", "mem01session"):
|
|
401
|
+
for conversation in CHECKPOINTS:
|
|
402
|
+
estimate = estimate_prompt_context(
|
|
403
|
+
result.prepared_inputs[strategy][conversation]
|
|
404
|
+
)
|
|
405
|
+
records.append(
|
|
406
|
+
{
|
|
407
|
+
"strategy": strategy,
|
|
408
|
+
"conversation": conversation,
|
|
409
|
+
"prepared_input_items": estimate.item_count,
|
|
410
|
+
"prepared_input_characters": estimate.text_characters,
|
|
411
|
+
"estimated_tokens": estimate.estimated_tokens,
|
|
412
|
+
"measurement": OFFLINE_PREPARED_INPUT_MEASUREMENT,
|
|
413
|
+
"token_estimator": TOKEN_ESTIMATOR,
|
|
414
|
+
"openai_agents_version": agents_version,
|
|
415
|
+
"answer_model": ANSWER_MODEL,
|
|
416
|
+
"extraction_model": EXTRACTION_MODEL,
|
|
417
|
+
"max_memory_tokens": max_memory_tokens,
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
return records
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def render_artifact(records: Sequence[Mapping[str, Any]]) -> str:
|
|
424
|
+
"""Render the canonical generated JSON artifact."""
|
|
425
|
+
return json.dumps(list(records), indent=2, ensure_ascii=False) + "\n"
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
async def write_artifact(path: Path = ARTIFACT_PATH) -> Path:
|
|
429
|
+
"""Regenerate the artifact from a fresh isolated deterministic run."""
|
|
430
|
+
result = await run_deterministic_demo()
|
|
431
|
+
content = render_artifact(generate_artifact_records(result))
|
|
432
|
+
await asyncio.to_thread(_write_text, path, content)
|
|
433
|
+
return path
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _write_text(path: Path, content: str) -> None:
|
|
437
|
+
"""Write generated output from a worker when called by async entry points."""
|
|
438
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
439
|
+
path.write_text(content, encoding="utf-8")
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def report_as_dict(result: DeterministicDemoResult) -> dict[str, Any]:
|
|
443
|
+
"""Return stable human-facing evidence without serializing raw SDK items."""
|
|
444
|
+
return {
|
|
445
|
+
"mode": "deterministic-key-free",
|
|
446
|
+
"sdk_runs": result.sdk_runs,
|
|
447
|
+
"fixture_conversations": len(load_conversation_fixture()),
|
|
448
|
+
"measurements": generate_artifact_records(result),
|
|
449
|
+
"observed_answers": result.observed_answers,
|
|
450
|
+
"belief_lifecycle": [
|
|
451
|
+
{"id": belief.id, "content": belief.content, "status": belief.status.value}
|
|
452
|
+
for belief in result.beliefs
|
|
453
|
+
],
|
|
454
|
+
"note": "Observed answers are illustrative, not pass/fail guarantees.",
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _settings_problems(environ: Mapping[str, str]) -> list[str]:
|
|
459
|
+
problems: list[str] = []
|
|
460
|
+
if not environ.get("OPENAI_API_KEY", "").strip():
|
|
461
|
+
problems.append("OPENAI_API_KEY is missing")
|
|
462
|
+
if not environ.get("DATABASE_URL", "").strip():
|
|
463
|
+
problems.append("DATABASE_URL is missing")
|
|
464
|
+
model = environ.get("MEM01_LLM_MODEL", "").strip() or LIVE_MODEL
|
|
465
|
+
if model != LIVE_MODEL:
|
|
466
|
+
problems.append(f"MEM01_LLM_MODEL must be {LIVE_MODEL}")
|
|
467
|
+
return problems
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
async def _run_live(environ: Mapping[str, str]) -> int:
|
|
471
|
+
problems = _settings_problems(environ)
|
|
472
|
+
if problems:
|
|
473
|
+
print(json.dumps({"ready": False, "problems": problems}))
|
|
474
|
+
return 2
|
|
475
|
+
|
|
476
|
+
invocation_id = uuid4().hex
|
|
477
|
+
user_id = f"build-week-live-{invocation_id}"
|
|
478
|
+
scenario = (
|
|
479
|
+
("initial_location_and_rent", "I live in NYC and my monthly rent is $2,400."),
|
|
480
|
+
("location_change", "I moved to SF this week."),
|
|
481
|
+
(
|
|
482
|
+
"current_location_and_prior_rent",
|
|
483
|
+
"Where do I live now, and what was my prior monthly rent?",
|
|
484
|
+
),
|
|
485
|
+
("unsupported_sister_name", "What is my sister's name?"),
|
|
486
|
+
)
|
|
487
|
+
sessions: list[Mem01Session] = []
|
|
488
|
+
observations: list[dict[str, Any]] = []
|
|
489
|
+
try:
|
|
490
|
+
agent = Agent(
|
|
491
|
+
name="Mem01Session live demo",
|
|
492
|
+
instructions=(
|
|
493
|
+
"Use only the current conversation and supplied memory. "
|
|
494
|
+
"If a personal fact is unsupported, say it is not stored."
|
|
495
|
+
),
|
|
496
|
+
model=LIVE_MODEL,
|
|
497
|
+
)
|
|
498
|
+
for conversation, (label, prompt) in enumerate(scenario, start=1):
|
|
499
|
+
session = Mem01Session(
|
|
500
|
+
f"live-{invocation_id}-{conversation}",
|
|
501
|
+
user_id,
|
|
502
|
+
strict=True,
|
|
503
|
+
)
|
|
504
|
+
sessions.append(session)
|
|
505
|
+
config = session.run_config()
|
|
506
|
+
config.tracing_disabled = True
|
|
507
|
+
result = await Runner.run(
|
|
508
|
+
agent,
|
|
509
|
+
prompt,
|
|
510
|
+
session=cast(Any, session),
|
|
511
|
+
run_config=config,
|
|
512
|
+
)
|
|
513
|
+
observations.append(
|
|
514
|
+
{
|
|
515
|
+
"conversation": conversation,
|
|
516
|
+
"label": label,
|
|
517
|
+
"type": "model_observation",
|
|
518
|
+
"output": str(result.final_output),
|
|
519
|
+
}
|
|
520
|
+
)
|
|
521
|
+
beliefs = await sessions[-1].memory_history(
|
|
522
|
+
include_invalidated=True,
|
|
523
|
+
limit=100,
|
|
524
|
+
)
|
|
525
|
+
lifecycle = [
|
|
526
|
+
{
|
|
527
|
+
"content": belief.content,
|
|
528
|
+
"status": belief.status.value,
|
|
529
|
+
}
|
|
530
|
+
for belief in beliefs or []
|
|
531
|
+
]
|
|
532
|
+
except Exception as error:
|
|
533
|
+
print(json.dumps({"live": "failed", "error_type": type(error).__name__}))
|
|
534
|
+
return 1
|
|
535
|
+
finally:
|
|
536
|
+
for session in sessions:
|
|
537
|
+
try:
|
|
538
|
+
await session.close()
|
|
539
|
+
except Exception:
|
|
540
|
+
pass
|
|
541
|
+
|
|
542
|
+
print(
|
|
543
|
+
json.dumps(
|
|
544
|
+
{
|
|
545
|
+
"mode": "live",
|
|
546
|
+
"model": LIVE_MODEL,
|
|
547
|
+
"conversation_count": len(scenario),
|
|
548
|
+
"observations": observations,
|
|
549
|
+
"lifecycle": lifecycle,
|
|
550
|
+
"note": "Outputs are observations, not deterministic guarantees.",
|
|
551
|
+
},
|
|
552
|
+
indent=2,
|
|
553
|
+
ensure_ascii=False,
|
|
554
|
+
)
|
|
555
|
+
)
|
|
556
|
+
return 0
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
async def _async_main(arguments: argparse.Namespace) -> int:
|
|
560
|
+
if arguments.check:
|
|
561
|
+
await asyncio.to_thread(mem01_env.load_env, override=False)
|
|
562
|
+
problems = _settings_problems(os.environ)
|
|
563
|
+
print(json.dumps({"ready": not problems, "problems": problems}))
|
|
564
|
+
return 2 if problems else 0
|
|
565
|
+
if arguments.live:
|
|
566
|
+
await asyncio.to_thread(mem01_env.load_env, override=False)
|
|
567
|
+
return await _run_live(os.environ)
|
|
568
|
+
result = await run_deterministic_demo()
|
|
569
|
+
if arguments.write_artifact is not None:
|
|
570
|
+
path = Path(arguments.write_artifact)
|
|
571
|
+
await asyncio.to_thread(
|
|
572
|
+
_write_text,
|
|
573
|
+
path,
|
|
574
|
+
render_artifact(generate_artifact_records(result)),
|
|
575
|
+
)
|
|
576
|
+
report = report_as_dict(result)
|
|
577
|
+
if arguments.json:
|
|
578
|
+
print(json.dumps(report, indent=2, ensure_ascii=False))
|
|
579
|
+
else:
|
|
580
|
+
print("Mem01Session deterministic Build Week evidence")
|
|
581
|
+
print(f"SDK runs: {report['sdk_runs']}; fixture: 40 conversations")
|
|
582
|
+
for record in report["measurements"]:
|
|
583
|
+
print(
|
|
584
|
+
f"{record['strategy']:>13} conversation {record['conversation']:>2}: "
|
|
585
|
+
f"{record['prepared_input_items']:>2} items, "
|
|
586
|
+
f"{record['estimated_tokens']:>4} estimated tokens"
|
|
587
|
+
)
|
|
588
|
+
print("Observed answers are illustrative, not pass/fail guarantees.")
|
|
589
|
+
return 0
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
593
|
+
"""CLI entry point; deterministic and key-free unless ``--live`` is set."""
|
|
594
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
595
|
+
mode = parser.add_mutually_exclusive_group()
|
|
596
|
+
mode.add_argument("--live", action="store_true", help="Run one real API smoke test")
|
|
597
|
+
mode.add_argument(
|
|
598
|
+
"--check",
|
|
599
|
+
action="store_true",
|
|
600
|
+
help="Validate live settings without initializing clients or making calls",
|
|
601
|
+
)
|
|
602
|
+
parser.add_argument("--json", action="store_true", help="Print deterministic JSON")
|
|
603
|
+
parser.add_argument(
|
|
604
|
+
"--write-artifact",
|
|
605
|
+
nargs="?",
|
|
606
|
+
const=str(ARTIFACT_PATH),
|
|
607
|
+
metavar="PATH",
|
|
608
|
+
help="Regenerate the evidence artifact (default: repository artifact path)",
|
|
609
|
+
)
|
|
610
|
+
return asyncio.run(_async_main(parser.parse_args(argv)))
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
if __name__ == "__main__":
|
|
614
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[
|
|
2
|
+
{"conversation": 1, "user_input": "I live in NYC and my monthly rent is $2,400."},
|
|
3
|
+
{"conversation": 2, "user_input": "I moved to SF this week."},
|
|
4
|
+
{"conversation": 3, "user_input": "I prefer tea to coffee."},
|
|
5
|
+
{"conversation": 4, "user_input": "I usually work from home on Fridays."},
|
|
6
|
+
{"conversation": 5, "user_input": "I am planning a weekend hike."},
|
|
7
|
+
{"conversation": 6, "user_input": "I like concise project updates."},
|
|
8
|
+
{"conversation": 7, "user_input": "I am reading a book about urban design."},
|
|
9
|
+
{"conversation": 8, "user_input": "I need to buy groceries after work."},
|
|
10
|
+
{"conversation": 9, "user_input": "I prefer morning meetings."},
|
|
11
|
+
{"conversation": 10, "user_input": "Where do I live now, and what was my prior monthly rent?"},
|
|
12
|
+
{"conversation": 11, "user_input": "I enjoy walking to nearby parks."},
|
|
13
|
+
{"conversation": 12, "user_input": "I am learning to cook a new recipe."},
|
|
14
|
+
{"conversation": 13, "user_input": "I keep my notes in plain text."},
|
|
15
|
+
{"conversation": 14, "user_input": "I prefer quiet places for focused work."},
|
|
16
|
+
{"conversation": 15, "user_input": "I am reviewing my monthly budget."},
|
|
17
|
+
{"conversation": 16, "user_input": "I like science fiction films."},
|
|
18
|
+
{"conversation": 17, "user_input": "I plan tasks at the start of each week."},
|
|
19
|
+
{"conversation": 18, "user_input": "I enjoy trying neighborhood restaurants."},
|
|
20
|
+
{"conversation": 19, "user_input": "I prefer written follow-ups after meetings."},
|
|
21
|
+
{"conversation": 20, "user_input": "I am organizing old photographs."},
|
|
22
|
+
{"conversation": 21, "user_input": "I listen to instrumental music while working."},
|
|
23
|
+
{"conversation": 22, "user_input": "I am practicing a short presentation."},
|
|
24
|
+
{"conversation": 23, "user_input": "I like simple user interfaces."},
|
|
25
|
+
{"conversation": 24, "user_input": "I take a short walk at lunch."},
|
|
26
|
+
{"conversation": 25, "user_input": "I am comparing two open-source libraries."},
|
|
27
|
+
{"conversation": 26, "user_input": "I prefer reusable checklists."},
|
|
28
|
+
{"conversation": 27, "user_input": "I am cleaning up my downloads folder."},
|
|
29
|
+
{"conversation": 28, "user_input": "I like to test changes before sharing them."},
|
|
30
|
+
{"conversation": 29, "user_input": "I am planning meals for next week."},
|
|
31
|
+
{"conversation": 30, "user_input": "I prefer direct answers with supporting evidence."},
|
|
32
|
+
{"conversation": 31, "user_input": "I am setting aside time for documentation."},
|
|
33
|
+
{"conversation": 32, "user_input": "I enjoy visiting independent bookstores."},
|
|
34
|
+
{"conversation": 33, "user_input": "I am reviewing an accessibility checklist."},
|
|
35
|
+
{"conversation": 34, "user_input": "I prefer small, focused pull requests."},
|
|
36
|
+
{"conversation": 35, "user_input": "I am preparing for a design review."},
|
|
37
|
+
{"conversation": 36, "user_input": "I like keeping local development reproducible."},
|
|
38
|
+
{"conversation": 37, "user_input": "I am checking a release candidate."},
|
|
39
|
+
{"conversation": 38, "user_input": "I prefer examples that run without credentials."},
|
|
40
|
+
{"conversation": 39, "user_input": "Where do I live now, and what was my prior monthly rent?"},
|
|
41
|
+
{"conversation": 40, "user_input": "What is my sister's name?"}
|
|
42
|
+
]
|