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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trace-memory
3
- Version: 1.0.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
  [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](https://python.org)
40
+ [![PyPI version](https://badge.fury.io/py/trace-memory.svg)](https://badge.fury.io/py/trace-memory)
40
41
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
41
42
  [![OpenAI-compatible](https://img.shields.io/badge/LLM-OpenAI%20compatible-orange)](https://platform.openai.com/docs/api-reference)
43
+ [![Benchmark](https://img.shields.io/badge/MemoryAgentBench-83.8%25-success)](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()`), an LLM call classifies whether the exchange continues the current topic or starts a new branch. If a new branch, the LLM selects the most appropriate parent in the ancestry chain.
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 branch.
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
- git clone https://github.com/husain34/TRACE.git
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 openai
269
- from trace_memory import CTree, VectorDatabase, PromptSynthesizer
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 = CTree(api_key="sk-...", model="gpt-4o-mini", embed_fn=embed)
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
- # 2. Each turn: build prompt → call LLM → store exchange
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
  [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](https://python.org)
11
+ [![PyPI version](https://badge.fury.io/py/trace-memory.svg)](https://badge.fury.io/py/trace-memory)
11
12
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
12
13
  [![OpenAI-compatible](https://img.shields.io/badge/LLM-OpenAI%20compatible-orange)](https://platform.openai.com/docs/api-reference)
14
+ [![Benchmark](https://img.shields.io/badge/MemoryAgentBench-83.8%25-success)](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()`), an LLM call classifies whether the exchange continues the current topic or starts a new branch. If a new branch, the LLM selects the most appropriate parent in the ancestry chain.
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 branch.
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
- git clone https://github.com/husain34/TRACE.git
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 openai
240
- from trace_memory import CTree, VectorDatabase, PromptSynthesizer
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 = CTree(api_key="sk-...", model="gpt-4o-mini", embed_fn=embed)
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
- # 2. Each turn: build prompt → call LLM → store exchange
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.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
- base_url = os.getenv("OPENAI_BASE_URL", "http://127.0.0.1:1234/v1")
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=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
- raise ValueError(
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 candidate_nodes[-1]
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\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 (or N/A)\", \"new_topic_parent_index\": <index or N/A>}}\n\n"
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.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
  [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](https://python.org)
40
+ [![PyPI version](https://badge.fury.io/py/trace-memory.svg)](https://badge.fury.io/py/trace-memory)
40
41
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
41
42
  [![OpenAI-compatible](https://img.shields.io/badge/LLM-OpenAI%20compatible-orange)](https://platform.openai.com/docs/api-reference)
43
+ [![Benchmark](https://img.shields.io/badge/MemoryAgentBench-83.8%25-success)](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()`), an LLM call classifies whether the exchange continues the current topic or starts a new branch. If a new branch, the LLM selects the most appropriate parent in the ancestry chain.
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 branch.
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
- git clone https://github.com/husain34/TRACE.git
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 openai
269
- from trace_memory import CTree, VectorDatabase, PromptSynthesizer
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 = CTree(api_key="sk-...", model="gpt-4o-mini", embed_fn=embed)
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
- # 2. Each turn: build prompt → call LLM → store exchange
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