trace-memory 1.0.0__tar.gz → 1.0.3__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.
- {trace_memory-1.0.0/trace_memory.egg-info → trace_memory-1.0.3}/PKG-INFO +27 -33
- {trace_memory-1.0.0 → trace_memory-1.0.3}/README.md +26 -32
- {trace_memory-1.0.0 → trace_memory-1.0.3}/pyproject.toml +1 -1
- {trace_memory-1.0.0 → trace_memory-1.0.3}/trace_memory/_llm_utils.py +3 -2
- {trace_memory-1.0.0 → trace_memory-1.0.3}/trace_memory/ctree.py +21 -18
- {trace_memory-1.0.0 → trace_memory-1.0.3/trace_memory.egg-info}/PKG-INFO +27 -33
- {trace_memory-1.0.0 → trace_memory-1.0.3}/LICENSE +0 -0
- {trace_memory-1.0.0 → trace_memory-1.0.3}/NOTICE +0 -0
- {trace_memory-1.0.0 → trace_memory-1.0.3}/setup.cfg +0 -0
- {trace_memory-1.0.0 → trace_memory-1.0.3}/trace_memory/__init__.py +0 -0
- {trace_memory-1.0.0 → trace_memory-1.0.3}/trace_memory/prompt_synthesizer.py +0 -0
- {trace_memory-1.0.0 → trace_memory-1.0.3}/trace_memory/vector_db.py +0 -0
- {trace_memory-1.0.0 → trace_memory-1.0.3}/trace_memory.egg-info/SOURCES.txt +0 -0
- {trace_memory-1.0.0 → trace_memory-1.0.3}/trace_memory.egg-info/dependency_links.txt +0 -0
- {trace_memory-1.0.0 → trace_memory-1.0.3}/trace_memory.egg-info/requires.txt +0 -0
- {trace_memory-1.0.0 → trace_memory-1.0.3}/trace_memory.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trace-memory
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: TRACE — Temporal Retrieval And Context Engine: self-healing hierarchical memory for LLM agents.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/husain34/TRACE
|
|
@@ -37,8 +37,10 @@ Dynamic: license-file
|
|
|
37
37
|
> Reduce context loss. Reduce broken agent tasks. Stop paying for tokens on chat history you don't need.
|
|
38
38
|
|
|
39
39
|
[](https://python.org)
|
|
40
|
+
[](https://badge.fury.io/py/trace-memory)
|
|
40
41
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
41
42
|
[](https://platform.openai.com/docs/api-reference)
|
|
43
|
+
[](benchmark_results/trace_120b_eventqa_64k_results.json)
|
|
42
44
|
|
|
43
45
|
> **This repo has two parts — they are completely independent:**
|
|
44
46
|
> - 🧠 **`trace_memory/`** — the lightweight memory engine. Install it, import it, and integrate it into your own app. Zero UI, zero bloat.
|
|
@@ -137,9 +139,9 @@ TRACE builds on the open-source **ChatIndex** architecture (credit: Mingtian Zha
|
|
|
137
139
|
- **Internal nodes (TopicNodes)** — LLM-generated topic labels and summaries for each branch.
|
|
138
140
|
- **Root** — a virtual anchor node.
|
|
139
141
|
|
|
140
|
-
Every time an exchange is added (`tree.add()`),
|
|
142
|
+
Every time an exchange is added (`tree.add()`), TRACE forces the creation of a **new TopicNode** containing exactly one exchange. The LLM determines if it continues the current topic or starts a new branch. If it continues the topic, the new node is simply chained as a direct child of the previous one. This deep chronological chaining solves context truncation and ensures perfect, granular summarization for every single exchange.
|
|
141
143
|
|
|
142
|
-
This gives TRACE a **structured map of the entire conversation history** — not a flat log — with topic metadata at every
|
|
144
|
+
This gives TRACE a **deep, structured map of the entire conversation history** — not a flat log — with highly granular topic metadata at every single link in the chain.
|
|
143
145
|
|
|
144
146
|
**What TRACE adds to ChatIndex**: ChatIndex primarily retrieves context through a single traversal path. While effective for hierarchical exploration, information spread across multiple semantically related branches may require multiple retrieval steps. TRACE augments this with vector-based retrieval across topic summaries, allowing context from multiple branches to be surfaced simultaneously.
|
|
145
147
|
|
|
@@ -239,44 +241,29 @@ When `prune_trivial_leaves=True` is passed to `reorganize()`, TRACE detects Mess
|
|
|
239
241
|
|
|
240
242
|
## 4. Installation
|
|
241
243
|
|
|
242
|
-
### From Source (recommended until PyPI release)
|
|
243
|
-
|
|
244
244
|
```bash
|
|
245
|
-
|
|
246
|
-
cd TRACE
|
|
247
|
-
pip install -e .
|
|
245
|
+
pip install trace-memory
|
|
248
246
|
```
|
|
249
247
|
|
|
250
|
-
### Dependencies installed automatically
|
|
251
|
-
|
|
252
|
-
| Package | Purpose |
|
|
253
|
-
|---|---|
|
|
254
|
-
| `openai>=1.0.0` | LLM API client (works with any OpenAI-compatible endpoint) |
|
|
255
|
-
| `python-dotenv>=1.0.0` | `.env` file support |
|
|
256
|
-
|
|
257
|
-
> **Note**: TRACE requires the bundled `trace._llm_utils` module (which provides `ChatGPT_API` and `extract_json`) for internal LLM calls.
|
|
258
|
-
>
|
|
259
|
-
> **Note**: TRACE's VectorDatabase uses only Python's built-in `sqlite3` and `struct` modules — no external vector DB dependency required.
|
|
260
|
-
|
|
261
248
|
---
|
|
262
249
|
|
|
263
250
|
### Minimal Integration (10 lines)
|
|
264
251
|
|
|
265
|
-
If you already have a chat loop and just want to plug TRACE in, this is all you need
|
|
252
|
+
If you already have a chat loop and just want to plug TRACE in, this is all you need. Since TRACE includes a powerful built-in local embedder (`BAAI/bge-base-en-v1.5`), you don't even need to configure your own embedding model!
|
|
266
253
|
|
|
267
254
|
```python
|
|
268
|
-
import
|
|
269
|
-
from trace_memory import
|
|
270
|
-
|
|
271
|
-
# 1. Boot
|
|
272
|
-
client = openai.OpenAI(api_key="sk-...", base_url="http://127.0.0.1:1234/v1")
|
|
273
|
-
def embed(text): return client.embeddings.create(input=[text], model="nomic-embed-text").data[0].embedding
|
|
255
|
+
from trace_memory.ctree import CTree
|
|
256
|
+
from trace_memory.vector_db import VectorDatabase
|
|
257
|
+
from trace_memory.prompt_synthesizer import PromptSynthesizer
|
|
274
258
|
|
|
275
|
-
tree
|
|
259
|
+
# 1. Boot the tree and attach the Vector DB
|
|
260
|
+
tree = CTree(api_key="sk-...", model="gpt-4o-mini")
|
|
276
261
|
tree.vdb = VectorDatabase("session.db")
|
|
262
|
+
|
|
263
|
+
# 2. Setup the Prompt Synthesizer
|
|
277
264
|
synth = PromptSynthesizer(ctree=tree, vector_db=tree.vdb)
|
|
278
265
|
|
|
279
|
-
#
|
|
266
|
+
# 3. Each turn: build prompt → call LLM → store exchange
|
|
280
267
|
while True:
|
|
281
268
|
user_input = input("You: ")
|
|
282
269
|
system_prompt = synth.synthesize_prompt(
|
|
@@ -347,7 +334,8 @@ from trace_memory import CTree
|
|
|
347
334
|
```python
|
|
348
335
|
CTree(
|
|
349
336
|
max_children: int = 5,
|
|
350
|
-
api_key: str = None, # falls back to OPENAI_API_KEY env var
|
|
337
|
+
api_key: str = None, # optional: falls back to OPENAI_API_KEY env var or "lm-studio"
|
|
338
|
+
base_url: str = None, # optional: route to custom endpoints (e.g. vLLM, Ollama, LM Studio)
|
|
351
339
|
model: str = "gpt-4o-mini",
|
|
352
340
|
auto_save_path: str = None, # auto-saves tree structure (not VDB) after every add() if set
|
|
353
341
|
embed_fn: Callable = None, # optional: inject your embed function at construction time
|
|
@@ -417,7 +405,7 @@ tree.save("sessions/chat_001.json", save_conversation=True)
|
|
|
417
405
|
|
|
418
406
|
---
|
|
419
407
|
|
|
420
|
-
##### `CTree.load(filepath: str, api_key: str = None, model: str = None) -> CTree`
|
|
408
|
+
##### `CTree.load(filepath: str, api_key: str = None, base_url: str = None, model: str = None) -> CTree`
|
|
421
409
|
|
|
422
410
|
Restore a tree from a JSON file.
|
|
423
411
|
|
|
@@ -616,12 +604,18 @@ ConversationVector(
|
|
|
616
604
|
### 7.1 With LM Studio (local)
|
|
617
605
|
|
|
618
606
|
```python
|
|
607
|
+
from trace_memory import CTree
|
|
608
|
+
# Connect directly via parameters
|
|
609
|
+
tree = CTree(
|
|
610
|
+
base_url="http://127.0.0.1:1234/v1",
|
|
611
|
+
api_key="lm-studio",
|
|
612
|
+
model="meta-llama-3.1-8b-instruct"
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
# Or set environment variables globally
|
|
619
616
|
import os
|
|
620
617
|
os.environ["OPENAI_BASE_URL"] = "http://127.0.0.1:1234/v1"
|
|
621
618
|
os.environ["OPENAI_API_KEY"] = "lm-studio"
|
|
622
|
-
|
|
623
|
-
from trace_memory import CTree
|
|
624
|
-
tree = CTree(model="meta-llama-3.1-8b-instruct")
|
|
625
619
|
```
|
|
626
620
|
|
|
627
621
|
Or use a `.env` file in your project root:
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
> Reduce context loss. Reduce broken agent tasks. Stop paying for tokens on chat history you don't need.
|
|
9
9
|
|
|
10
10
|
[](https://python.org)
|
|
11
|
+
[](https://badge.fury.io/py/trace-memory)
|
|
11
12
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
12
13
|
[](https://platform.openai.com/docs/api-reference)
|
|
14
|
+
[](benchmark_results/trace_120b_eventqa_64k_results.json)
|
|
13
15
|
|
|
14
16
|
> **This repo has two parts — they are completely independent:**
|
|
15
17
|
> - 🧠 **`trace_memory/`** — the lightweight memory engine. Install it, import it, and integrate it into your own app. Zero UI, zero bloat.
|
|
@@ -108,9 +110,9 @@ TRACE builds on the open-source **ChatIndex** architecture (credit: Mingtian Zha
|
|
|
108
110
|
- **Internal nodes (TopicNodes)** — LLM-generated topic labels and summaries for each branch.
|
|
109
111
|
- **Root** — a virtual anchor node.
|
|
110
112
|
|
|
111
|
-
Every time an exchange is added (`tree.add()`),
|
|
113
|
+
Every time an exchange is added (`tree.add()`), TRACE forces the creation of a **new TopicNode** containing exactly one exchange. The LLM determines if it continues the current topic or starts a new branch. If it continues the topic, the new node is simply chained as a direct child of the previous one. This deep chronological chaining solves context truncation and ensures perfect, granular summarization for every single exchange.
|
|
112
114
|
|
|
113
|
-
This gives TRACE a **structured map of the entire conversation history** — not a flat log — with topic metadata at every
|
|
115
|
+
This gives TRACE a **deep, structured map of the entire conversation history** — not a flat log — with highly granular topic metadata at every single link in the chain.
|
|
114
116
|
|
|
115
117
|
**What TRACE adds to ChatIndex**: ChatIndex primarily retrieves context through a single traversal path. While effective for hierarchical exploration, information spread across multiple semantically related branches may require multiple retrieval steps. TRACE augments this with vector-based retrieval across topic summaries, allowing context from multiple branches to be surfaced simultaneously.
|
|
116
118
|
|
|
@@ -210,44 +212,29 @@ When `prune_trivial_leaves=True` is passed to `reorganize()`, TRACE detects Mess
|
|
|
210
212
|
|
|
211
213
|
## 4. Installation
|
|
212
214
|
|
|
213
|
-
### From Source (recommended until PyPI release)
|
|
214
|
-
|
|
215
215
|
```bash
|
|
216
|
-
|
|
217
|
-
cd TRACE
|
|
218
|
-
pip install -e .
|
|
216
|
+
pip install trace-memory
|
|
219
217
|
```
|
|
220
218
|
|
|
221
|
-
### Dependencies installed automatically
|
|
222
|
-
|
|
223
|
-
| Package | Purpose |
|
|
224
|
-
|---|---|
|
|
225
|
-
| `openai>=1.0.0` | LLM API client (works with any OpenAI-compatible endpoint) |
|
|
226
|
-
| `python-dotenv>=1.0.0` | `.env` file support |
|
|
227
|
-
|
|
228
|
-
> **Note**: TRACE requires the bundled `trace._llm_utils` module (which provides `ChatGPT_API` and `extract_json`) for internal LLM calls.
|
|
229
|
-
>
|
|
230
|
-
> **Note**: TRACE's VectorDatabase uses only Python's built-in `sqlite3` and `struct` modules — no external vector DB dependency required.
|
|
231
|
-
|
|
232
219
|
---
|
|
233
220
|
|
|
234
221
|
### Minimal Integration (10 lines)
|
|
235
222
|
|
|
236
|
-
If you already have a chat loop and just want to plug TRACE in, this is all you need
|
|
223
|
+
If you already have a chat loop and just want to plug TRACE in, this is all you need. Since TRACE includes a powerful built-in local embedder (`BAAI/bge-base-en-v1.5`), you don't even need to configure your own embedding model!
|
|
237
224
|
|
|
238
225
|
```python
|
|
239
|
-
import
|
|
240
|
-
from trace_memory import
|
|
241
|
-
|
|
242
|
-
# 1. Boot
|
|
243
|
-
client = openai.OpenAI(api_key="sk-...", base_url="http://127.0.0.1:1234/v1")
|
|
244
|
-
def embed(text): return client.embeddings.create(input=[text], model="nomic-embed-text").data[0].embedding
|
|
226
|
+
from trace_memory.ctree import CTree
|
|
227
|
+
from trace_memory.vector_db import VectorDatabase
|
|
228
|
+
from trace_memory.prompt_synthesizer import PromptSynthesizer
|
|
245
229
|
|
|
246
|
-
tree
|
|
230
|
+
# 1. Boot the tree and attach the Vector DB
|
|
231
|
+
tree = CTree(api_key="sk-...", model="gpt-4o-mini")
|
|
247
232
|
tree.vdb = VectorDatabase("session.db")
|
|
233
|
+
|
|
234
|
+
# 2. Setup the Prompt Synthesizer
|
|
248
235
|
synth = PromptSynthesizer(ctree=tree, vector_db=tree.vdb)
|
|
249
236
|
|
|
250
|
-
#
|
|
237
|
+
# 3. Each turn: build prompt → call LLM → store exchange
|
|
251
238
|
while True:
|
|
252
239
|
user_input = input("You: ")
|
|
253
240
|
system_prompt = synth.synthesize_prompt(
|
|
@@ -318,7 +305,8 @@ from trace_memory import CTree
|
|
|
318
305
|
```python
|
|
319
306
|
CTree(
|
|
320
307
|
max_children: int = 5,
|
|
321
|
-
api_key: str = None, # falls back to OPENAI_API_KEY env var
|
|
308
|
+
api_key: str = None, # optional: falls back to OPENAI_API_KEY env var or "lm-studio"
|
|
309
|
+
base_url: str = None, # optional: route to custom endpoints (e.g. vLLM, Ollama, LM Studio)
|
|
322
310
|
model: str = "gpt-4o-mini",
|
|
323
311
|
auto_save_path: str = None, # auto-saves tree structure (not VDB) after every add() if set
|
|
324
312
|
embed_fn: Callable = None, # optional: inject your embed function at construction time
|
|
@@ -388,7 +376,7 @@ tree.save("sessions/chat_001.json", save_conversation=True)
|
|
|
388
376
|
|
|
389
377
|
---
|
|
390
378
|
|
|
391
|
-
##### `CTree.load(filepath: str, api_key: str = None, model: str = None) -> CTree`
|
|
379
|
+
##### `CTree.load(filepath: str, api_key: str = None, base_url: str = None, model: str = None) -> CTree`
|
|
392
380
|
|
|
393
381
|
Restore a tree from a JSON file.
|
|
394
382
|
|
|
@@ -587,12 +575,18 @@ ConversationVector(
|
|
|
587
575
|
### 7.1 With LM Studio (local)
|
|
588
576
|
|
|
589
577
|
```python
|
|
578
|
+
from trace_memory import CTree
|
|
579
|
+
# Connect directly via parameters
|
|
580
|
+
tree = CTree(
|
|
581
|
+
base_url="http://127.0.0.1:1234/v1",
|
|
582
|
+
api_key="lm-studio",
|
|
583
|
+
model="meta-llama-3.1-8b-instruct"
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
# Or set environment variables globally
|
|
590
587
|
import os
|
|
591
588
|
os.environ["OPENAI_BASE_URL"] = "http://127.0.0.1:1234/v1"
|
|
592
589
|
os.environ["OPENAI_API_KEY"] = "lm-studio"
|
|
593
|
-
|
|
594
|
-
from trace_memory import CTree
|
|
595
|
-
tree = CTree(model="meta-llama-3.1-8b-instruct")
|
|
596
590
|
```
|
|
597
591
|
|
|
598
592
|
Or use a `.env` file in your project root:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "trace-memory"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.3"
|
|
8
8
|
description = "TRACE — Temporal Retrieval And Context Engine: self-healing hierarchical memory for LLM agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -20,6 +20,7 @@ def ChatGPT_API(
|
|
|
20
20
|
model: str,
|
|
21
21
|
prompt: str,
|
|
22
22
|
api_key: str = None,
|
|
23
|
+
base_url: str = None,
|
|
23
24
|
chat_history: list = None,
|
|
24
25
|
temperature: float = 0,
|
|
25
26
|
max_tokens: int = None,
|
|
@@ -42,10 +43,10 @@ def ChatGPT_API(
|
|
|
42
43
|
str — Response text, or "Error" after all retries are exhausted.
|
|
43
44
|
"""
|
|
44
45
|
max_retries = 10
|
|
45
|
-
|
|
46
|
+
base_url_to_use = base_url or os.getenv("OPENAI_BASE_URL", "http://127.0.0.1:1234/v1")
|
|
46
47
|
api_key_to_use = api_key or os.getenv("OPENAI_API_KEY") or "lm-studio"
|
|
47
48
|
|
|
48
|
-
client = openai.OpenAI(api_key=api_key_to_use, base_url=
|
|
49
|
+
client = openai.OpenAI(api_key=api_key_to_use, base_url=base_url_to_use)
|
|
49
50
|
|
|
50
51
|
for i in range(max_retries):
|
|
51
52
|
try:
|
|
@@ -257,6 +257,7 @@ class CTree:
|
|
|
257
257
|
self,
|
|
258
258
|
max_children: int = 5,
|
|
259
259
|
api_key: str = None,
|
|
260
|
+
base_url: str = None,
|
|
260
261
|
model: str = "gpt-4o-mini",
|
|
261
262
|
auto_save_path: str = None,
|
|
262
263
|
embed_fn: Optional[Callable[[str], List[float]]] = None,
|
|
@@ -268,6 +269,7 @@ class CTree:
|
|
|
268
269
|
self._archived_nodes: List[MessageNode] = []
|
|
269
270
|
self.vdb = None # inject: VectorDatabase instance
|
|
270
271
|
self.embed_fn = embed_fn or _default_embed_fn # inject: callable(text) -> List[float]
|
|
272
|
+
self.base_url = base_url
|
|
271
273
|
|
|
272
274
|
# API key resolution
|
|
273
275
|
if api_key:
|
|
@@ -275,10 +277,7 @@ class CTree:
|
|
|
275
277
|
elif os.getenv("OPENAI_API_KEY"):
|
|
276
278
|
self.api_key = os.getenv("OPENAI_API_KEY")
|
|
277
279
|
else:
|
|
278
|
-
|
|
279
|
-
"API key must be provided via api_key= or the "
|
|
280
|
-
"OPENAI_API_KEY environment variable."
|
|
281
|
-
)
|
|
280
|
+
self.api_key = "lm-studio"
|
|
282
281
|
|
|
283
282
|
self.root: TopicNode = TopicNode(
|
|
284
283
|
topic_name="ROOT",
|
|
@@ -482,11 +481,13 @@ class CTree:
|
|
|
482
481
|
classification = self._llm_classify_message_exchange(
|
|
483
482
|
user_msg, assistant_msg, candidate_nodes, system_msg
|
|
484
483
|
)
|
|
484
|
+
topic_name = classification.get("new_topic_name", "")
|
|
485
485
|
if classification["belongs_to_current"]:
|
|
486
|
-
return
|
|
486
|
+
return self._create_new_topic_for_message(
|
|
487
|
+
user_msg, assistant_msg, candidate_nodes[-1], topic_name, system_msg
|
|
488
|
+
)
|
|
487
489
|
parent_index = classification.get("new_topic_parent_index", len(candidate_nodes) - 1)
|
|
488
490
|
parent_node = candidate_nodes[parent_index]
|
|
489
|
-
topic_name = classification.get("new_topic_name", "")
|
|
490
491
|
return self._create_new_topic_for_message(
|
|
491
492
|
user_msg, assistant_msg, parent_node, topic_name, system_msg
|
|
492
493
|
)
|
|
@@ -534,7 +535,7 @@ class CTree:
|
|
|
534
535
|
f"Message: {content}\n\nRespond with ONLY the topic name, nothing else."
|
|
535
536
|
)
|
|
536
537
|
try:
|
|
537
|
-
return ChatGPT_API(self.model, prompt, api_key=self.api_key, temperature=0.3, max_tokens=50).strip()
|
|
538
|
+
return ChatGPT_API(self.model, prompt, api_key=self.api_key, base_url=self.base_url, temperature=0.3, max_tokens=50).strip()
|
|
538
539
|
except Exception as e:
|
|
539
540
|
print(f"LLM error in topic generation: {e}")
|
|
540
541
|
return f"Topic at index {len(self.conversation)}"
|
|
@@ -553,7 +554,7 @@ class CTree:
|
|
|
553
554
|
f"Respond with ONLY the topic name, nothing else."
|
|
554
555
|
)
|
|
555
556
|
try:
|
|
556
|
-
return ChatGPT_API(self.model, prompt, api_key=self.api_key, temperature=0.3, max_tokens=50).strip()
|
|
557
|
+
return ChatGPT_API(self.model, prompt, api_key=self.api_key, base_url=self.base_url, temperature=0.3, max_tokens=50).strip()
|
|
557
558
|
except Exception as e:
|
|
558
559
|
print(f"LLM error in topic generation from message: {e}")
|
|
559
560
|
return f"Topic at message {len(self.conversation) // 2}"
|
|
@@ -567,7 +568,7 @@ class CTree:
|
|
|
567
568
|
f"in 1-2 sentences.\n\n{content}\n\nSummary:"
|
|
568
569
|
)
|
|
569
570
|
try:
|
|
570
|
-
return ChatGPT_API(self.model, prompt, api_key=self.api_key, temperature=0.3, max_tokens=100).strip()
|
|
571
|
+
return ChatGPT_API(self.model, prompt, api_key=self.api_key, base_url=self.base_url, temperature=0.3, max_tokens=100).strip()
|
|
571
572
|
except Exception as e:
|
|
572
573
|
print(f"LLM error in summarization: {e}")
|
|
573
574
|
return f"Discussion about {topic_name}"
|
|
@@ -601,14 +602,15 @@ class CTree:
|
|
|
601
602
|
f"3. CRITICAL: When choosing new_topic_parent_index — if the new topic is completely unrelated "
|
|
602
603
|
f"to all listed ancestors, you MUST choose index 0 (the root/top-level). Only choose a deeper "
|
|
603
604
|
f"ancestor if the new topic is a genuine sub-topic of that ancestor.\n"
|
|
604
|
-
f"4. Do NOT nest an unrelated topic under the current topic just because it is the most recent.\n
|
|
605
|
+
f"4. Do NOT nest an unrelated topic under the current topic just because it is the most recent.\n"
|
|
606
|
+
f"5. ALWAYS provide a concise 'new_topic_name' (2-5 words) that summarizes this specific exchange, regardless of belongs_to_current.\n\n"
|
|
605
607
|
f"Respond ONLY with valid JSON:\n"
|
|
606
608
|
f"{{\"reasoning\": \"brief explanation\", \"belongs_to_current\": true|false, "
|
|
607
|
-
f"\"new_topic_name\": \"name
|
|
609
|
+
f"\"new_topic_name\": \"name\", \"new_topic_parent_index\": <index or N/A>}}\n\n"
|
|
608
610
|
f"Output ONLY the JSON, no other text."
|
|
609
611
|
)
|
|
610
612
|
try:
|
|
611
|
-
response = ChatGPT_API(self.model, prompt, api_key=self.api_key, temperature=0.1, max_tokens=200)
|
|
613
|
+
response = ChatGPT_API(self.model, prompt, api_key=self.api_key, base_url=self.base_url, temperature=0.1, max_tokens=200)
|
|
612
614
|
result = extract_json(response)
|
|
613
615
|
if "new_topic_parent_index" in result:
|
|
614
616
|
raw = result["new_topic_parent_index"]
|
|
@@ -647,7 +649,7 @@ class CTree:
|
|
|
647
649
|
f"Output ONLY the JSON, no other text."
|
|
648
650
|
)
|
|
649
651
|
try:
|
|
650
|
-
response = ChatGPT_API(self.model, prompt, api_key=self.api_key, temperature=0.3, max_tokens=500)
|
|
652
|
+
response = ChatGPT_API(self.model, prompt, api_key=self.api_key, base_url=self.base_url, temperature=0.3, max_tokens=500)
|
|
651
653
|
result = extract_json(response)
|
|
652
654
|
if isinstance(result, list):
|
|
653
655
|
return result
|
|
@@ -671,7 +673,7 @@ class CTree:
|
|
|
671
673
|
f'Respond with ONLY: {{"reasoning": "...", "split_index": <int 1–{len(topic_children)-1}>}}'
|
|
672
674
|
)
|
|
673
675
|
try:
|
|
674
|
-
response = ChatGPT_API(self.model, prompt, api_key=self.api_key, temperature=0.2, max_tokens=200)
|
|
676
|
+
response = ChatGPT_API(self.model, prompt, api_key=self.api_key, base_url=self.base_url, temperature=0.2, max_tokens=200)
|
|
675
677
|
result = extract_json(response)
|
|
676
678
|
split_index = _safe_int(result.get("split_index"), len(topic_children) // 2)
|
|
677
679
|
return max(1, min(split_index, len(topic_children) - 1))
|
|
@@ -687,7 +689,7 @@ class CTree:
|
|
|
687
689
|
f"Subtopics: {text}\n\nRespond with ONLY the topic name."
|
|
688
690
|
)
|
|
689
691
|
try:
|
|
690
|
-
name = ChatGPT_API(self.model, prompt, api_key=self.api_key, temperature=0.3, max_tokens=50).strip()
|
|
692
|
+
name = ChatGPT_API(self.model, prompt, api_key=self.api_key, base_url=self.base_url, temperature=0.3, max_tokens=50).strip()
|
|
691
693
|
return name.strip("\"'")
|
|
692
694
|
except Exception:
|
|
693
695
|
return f"{parent_node.topic_name} – Part"
|
|
@@ -705,7 +707,7 @@ class CTree:
|
|
|
705
707
|
f'"topic_indices": [...]}}]}}'
|
|
706
708
|
)
|
|
707
709
|
try:
|
|
708
|
-
response = ChatGPT_API(self.model, prompt, api_key=self.api_key, temperature=0.3, max_tokens=800)
|
|
710
|
+
response = ChatGPT_API(self.model, prompt, api_key=self.api_key, base_url=self.base_url, temperature=0.3, max_tokens=800)
|
|
709
711
|
result = extract_json(response)
|
|
710
712
|
if isinstance(result, dict) and "groups" in result:
|
|
711
713
|
groups = result["groups"]
|
|
@@ -879,7 +881,7 @@ class CTree:
|
|
|
879
881
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
880
882
|
|
|
881
883
|
@classmethod
|
|
882
|
-
def load(cls, filepath: str, api_key: str = None, model: str = None, embed_fn: Optional[Callable[[str], List[float]]] = None) -> "CTree":
|
|
884
|
+
def load(cls, filepath: str, api_key: str = None, base_url: str = None, model: str = None, embed_fn: Optional[Callable[[str], List[float]]] = None) -> "CTree":
|
|
883
885
|
"""
|
|
884
886
|
Reconstruct a ``CTree`` from a previously saved JSON file.
|
|
885
887
|
|
|
@@ -903,6 +905,7 @@ class CTree:
|
|
|
903
905
|
tree = cls(
|
|
904
906
|
max_children = data.get("max_children", 5),
|
|
905
907
|
api_key = api_key,
|
|
908
|
+
base_url = base_url,
|
|
906
909
|
model = model or "gpt-4o-mini",
|
|
907
910
|
embed_fn = embed_fn,
|
|
908
911
|
)
|
|
@@ -994,7 +997,7 @@ class CTree:
|
|
|
994
997
|
f'{{"related": true|false, "reason": "short reason", "merge_direction": "a_into_b"|"b_into_a"|"N/A"}}'
|
|
995
998
|
)
|
|
996
999
|
try:
|
|
997
|
-
response = ChatGPT_API(self.model, prompt, api_key=self.api_key, temperature=0.1, max_tokens=300)
|
|
1000
|
+
response = ChatGPT_API(self.model, prompt, api_key=self.api_key, base_url=self.base_url, temperature=0.1, max_tokens=300)
|
|
998
1001
|
result = extract_json(response)
|
|
999
1002
|
result.setdefault("related", False)
|
|
1000
1003
|
result.setdefault("merge_direction", "N/A")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trace-memory
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: TRACE — Temporal Retrieval And Context Engine: self-healing hierarchical memory for LLM agents.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/husain34/TRACE
|
|
@@ -37,8 +37,10 @@ Dynamic: license-file
|
|
|
37
37
|
> Reduce context loss. Reduce broken agent tasks. Stop paying for tokens on chat history you don't need.
|
|
38
38
|
|
|
39
39
|
[](https://python.org)
|
|
40
|
+
[](https://badge.fury.io/py/trace-memory)
|
|
40
41
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
41
42
|
[](https://platform.openai.com/docs/api-reference)
|
|
43
|
+
[](benchmark_results/trace_120b_eventqa_64k_results.json)
|
|
42
44
|
|
|
43
45
|
> **This repo has two parts — they are completely independent:**
|
|
44
46
|
> - 🧠 **`trace_memory/`** — the lightweight memory engine. Install it, import it, and integrate it into your own app. Zero UI, zero bloat.
|
|
@@ -137,9 +139,9 @@ TRACE builds on the open-source **ChatIndex** architecture (credit: Mingtian Zha
|
|
|
137
139
|
- **Internal nodes (TopicNodes)** — LLM-generated topic labels and summaries for each branch.
|
|
138
140
|
- **Root** — a virtual anchor node.
|
|
139
141
|
|
|
140
|
-
Every time an exchange is added (`tree.add()`),
|
|
142
|
+
Every time an exchange is added (`tree.add()`), TRACE forces the creation of a **new TopicNode** containing exactly one exchange. The LLM determines if it continues the current topic or starts a new branch. If it continues the topic, the new node is simply chained as a direct child of the previous one. This deep chronological chaining solves context truncation and ensures perfect, granular summarization for every single exchange.
|
|
141
143
|
|
|
142
|
-
This gives TRACE a **structured map of the entire conversation history** — not a flat log — with topic metadata at every
|
|
144
|
+
This gives TRACE a **deep, structured map of the entire conversation history** — not a flat log — with highly granular topic metadata at every single link in the chain.
|
|
143
145
|
|
|
144
146
|
**What TRACE adds to ChatIndex**: ChatIndex primarily retrieves context through a single traversal path. While effective for hierarchical exploration, information spread across multiple semantically related branches may require multiple retrieval steps. TRACE augments this with vector-based retrieval across topic summaries, allowing context from multiple branches to be surfaced simultaneously.
|
|
145
147
|
|
|
@@ -239,44 +241,29 @@ When `prune_trivial_leaves=True` is passed to `reorganize()`, TRACE detects Mess
|
|
|
239
241
|
|
|
240
242
|
## 4. Installation
|
|
241
243
|
|
|
242
|
-
### From Source (recommended until PyPI release)
|
|
243
|
-
|
|
244
244
|
```bash
|
|
245
|
-
|
|
246
|
-
cd TRACE
|
|
247
|
-
pip install -e .
|
|
245
|
+
pip install trace-memory
|
|
248
246
|
```
|
|
249
247
|
|
|
250
|
-
### Dependencies installed automatically
|
|
251
|
-
|
|
252
|
-
| Package | Purpose |
|
|
253
|
-
|---|---|
|
|
254
|
-
| `openai>=1.0.0` | LLM API client (works with any OpenAI-compatible endpoint) |
|
|
255
|
-
| `python-dotenv>=1.0.0` | `.env` file support |
|
|
256
|
-
|
|
257
|
-
> **Note**: TRACE requires the bundled `trace._llm_utils` module (which provides `ChatGPT_API` and `extract_json`) for internal LLM calls.
|
|
258
|
-
>
|
|
259
|
-
> **Note**: TRACE's VectorDatabase uses only Python's built-in `sqlite3` and `struct` modules — no external vector DB dependency required.
|
|
260
|
-
|
|
261
248
|
---
|
|
262
249
|
|
|
263
250
|
### Minimal Integration (10 lines)
|
|
264
251
|
|
|
265
|
-
If you already have a chat loop and just want to plug TRACE in, this is all you need
|
|
252
|
+
If you already have a chat loop and just want to plug TRACE in, this is all you need. Since TRACE includes a powerful built-in local embedder (`BAAI/bge-base-en-v1.5`), you don't even need to configure your own embedding model!
|
|
266
253
|
|
|
267
254
|
```python
|
|
268
|
-
import
|
|
269
|
-
from trace_memory import
|
|
270
|
-
|
|
271
|
-
# 1. Boot
|
|
272
|
-
client = openai.OpenAI(api_key="sk-...", base_url="http://127.0.0.1:1234/v1")
|
|
273
|
-
def embed(text): return client.embeddings.create(input=[text], model="nomic-embed-text").data[0].embedding
|
|
255
|
+
from trace_memory.ctree import CTree
|
|
256
|
+
from trace_memory.vector_db import VectorDatabase
|
|
257
|
+
from trace_memory.prompt_synthesizer import PromptSynthesizer
|
|
274
258
|
|
|
275
|
-
tree
|
|
259
|
+
# 1. Boot the tree and attach the Vector DB
|
|
260
|
+
tree = CTree(api_key="sk-...", model="gpt-4o-mini")
|
|
276
261
|
tree.vdb = VectorDatabase("session.db")
|
|
262
|
+
|
|
263
|
+
# 2. Setup the Prompt Synthesizer
|
|
277
264
|
synth = PromptSynthesizer(ctree=tree, vector_db=tree.vdb)
|
|
278
265
|
|
|
279
|
-
#
|
|
266
|
+
# 3. Each turn: build prompt → call LLM → store exchange
|
|
280
267
|
while True:
|
|
281
268
|
user_input = input("You: ")
|
|
282
269
|
system_prompt = synth.synthesize_prompt(
|
|
@@ -347,7 +334,8 @@ from trace_memory import CTree
|
|
|
347
334
|
```python
|
|
348
335
|
CTree(
|
|
349
336
|
max_children: int = 5,
|
|
350
|
-
api_key: str = None, # falls back to OPENAI_API_KEY env var
|
|
337
|
+
api_key: str = None, # optional: falls back to OPENAI_API_KEY env var or "lm-studio"
|
|
338
|
+
base_url: str = None, # optional: route to custom endpoints (e.g. vLLM, Ollama, LM Studio)
|
|
351
339
|
model: str = "gpt-4o-mini",
|
|
352
340
|
auto_save_path: str = None, # auto-saves tree structure (not VDB) after every add() if set
|
|
353
341
|
embed_fn: Callable = None, # optional: inject your embed function at construction time
|
|
@@ -417,7 +405,7 @@ tree.save("sessions/chat_001.json", save_conversation=True)
|
|
|
417
405
|
|
|
418
406
|
---
|
|
419
407
|
|
|
420
|
-
##### `CTree.load(filepath: str, api_key: str = None, model: str = None) -> CTree`
|
|
408
|
+
##### `CTree.load(filepath: str, api_key: str = None, base_url: str = None, model: str = None) -> CTree`
|
|
421
409
|
|
|
422
410
|
Restore a tree from a JSON file.
|
|
423
411
|
|
|
@@ -616,12 +604,18 @@ ConversationVector(
|
|
|
616
604
|
### 7.1 With LM Studio (local)
|
|
617
605
|
|
|
618
606
|
```python
|
|
607
|
+
from trace_memory import CTree
|
|
608
|
+
# Connect directly via parameters
|
|
609
|
+
tree = CTree(
|
|
610
|
+
base_url="http://127.0.0.1:1234/v1",
|
|
611
|
+
api_key="lm-studio",
|
|
612
|
+
model="meta-llama-3.1-8b-instruct"
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
# Or set environment variables globally
|
|
619
616
|
import os
|
|
620
617
|
os.environ["OPENAI_BASE_URL"] = "http://127.0.0.1:1234/v1"
|
|
621
618
|
os.environ["OPENAI_API_KEY"] = "lm-studio"
|
|
622
|
-
|
|
623
|
-
from trace_memory import CTree
|
|
624
|
-
tree = CTree(model="meta-llama-3.1-8b-instruct")
|
|
625
619
|
```
|
|
626
620
|
|
|
627
621
|
Or use a `.env` file in your project root:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|