npcpy 1.2.17__tar.gz → 1.2.19__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.
Files changed (67) hide show
  1. {npcpy-1.2.17/npcpy.egg-info → npcpy-1.2.19}/PKG-INFO +1 -1
  2. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/ft/memory_trainer.py +7 -4
  3. npcpy-1.2.19/npcpy/memory/memory_processor.py +65 -0
  4. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/npc_compiler.py +187 -18
  5. {npcpy-1.2.17 → npcpy-1.2.19/npcpy.egg-info}/PKG-INFO +1 -1
  6. {npcpy-1.2.17 → npcpy-1.2.19}/setup.py +1 -1
  7. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_npc_compiler.py +114 -0
  8. npcpy-1.2.17/npcpy/memory/memory_processor.py +0 -155
  9. {npcpy-1.2.17 → npcpy-1.2.19}/LICENSE +0 -0
  10. {npcpy-1.2.17 → npcpy-1.2.19}/MANIFEST.in +0 -0
  11. {npcpy-1.2.17 → npcpy-1.2.19}/README.md +0 -0
  12. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/__init__.py +0 -0
  13. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/data/__init__.py +0 -0
  14. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/data/audio.py +0 -0
  15. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/data/data_models.py +0 -0
  16. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/data/image.py +0 -0
  17. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/data/load.py +0 -0
  18. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/data/text.py +0 -0
  19. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/data/video.py +0 -0
  20. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/data/web.py +0 -0
  21. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/ft/__init__.py +0 -0
  22. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/ft/diff.py +0 -0
  23. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/ft/ge.py +0 -0
  24. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/ft/rl.py +0 -0
  25. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/ft/sft.py +0 -0
  26. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/gen/__init__.py +0 -0
  27. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/gen/audio_gen.py +0 -0
  28. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/gen/embeddings.py +0 -0
  29. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/gen/image_gen.py +0 -0
  30. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/gen/response.py +0 -0
  31. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/gen/video_gen.py +0 -0
  32. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/llm_funcs.py +0 -0
  33. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/main.py +0 -0
  34. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/memory/__init__.py +0 -0
  35. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/memory/command_history.py +0 -0
  36. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/memory/kg_vis.py +0 -0
  37. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/memory/knowledge_graph.py +0 -0
  38. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/memory/search.py +0 -0
  39. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/mix/__init__.py +0 -0
  40. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/mix/debate.py +0 -0
  41. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/npc_sysenv.py +0 -0
  42. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/npcs.py +0 -0
  43. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/serve.py +0 -0
  44. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/sql/__init__.py +0 -0
  45. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/sql/model_runner.py +0 -0
  46. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/sql/npcsql.py +0 -0
  47. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/tools.py +0 -0
  48. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/work/__init__.py +0 -0
  49. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/work/desktop.py +0 -0
  50. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/work/plan.py +0 -0
  51. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy/work/trigger.py +0 -0
  52. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy.egg-info/SOURCES.txt +0 -0
  53. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy.egg-info/dependency_links.txt +0 -0
  54. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy.egg-info/requires.txt +0 -0
  55. {npcpy-1.2.17 → npcpy-1.2.19}/npcpy.egg-info/top_level.txt +0 -0
  56. {npcpy-1.2.17 → npcpy-1.2.19}/setup.cfg +0 -0
  57. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_audio.py +0 -0
  58. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_command_history.py +0 -0
  59. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_image.py +0 -0
  60. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_llm_funcs.py +0 -0
  61. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_load.py +0 -0
  62. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_npcsql.py +0 -0
  63. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_response.py +0 -0
  64. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_serve.py +0 -0
  65. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_text.py +0 -0
  66. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_tools.py +0 -0
  67. {npcpy-1.2.17 → npcpy-1.2.19}/tests/test_web.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcpy
3
- Version: 1.2.17
3
+ Version: 1.2.19
4
4
  Summary: npcpy is the premier open-source library for integrating LLMs and Agents into python systems.
5
5
  Home-page: https://github.com/NPC-Worldwide/npcpy
6
6
  Author: Christopher Agostino
@@ -1,7 +1,10 @@
1
- import torch
2
- import torch.nn as nn
3
- from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
4
- from torch.utils.data import Dataset
1
+ try:
2
+ from torch.utils.data import Dataset
3
+ import torch
4
+ import torch.nn as nn
5
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
6
+ except:
7
+ pass
5
8
  import json
6
9
  from typing import List, Dict, Tuple
7
10
  import random
@@ -0,0 +1,65 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Dict, Any, Optional
3
+ from datetime import datetime
4
+ import threading
5
+ import queue
6
+ import time
7
+
8
+ @dataclass
9
+ class MemoryItem:
10
+ message_id: str
11
+ conversation_id: str
12
+ npc: str
13
+ team: str
14
+ directory_path: str
15
+ content: str
16
+ context: str
17
+ model: str
18
+ provider: str
19
+
20
+
21
+ def memory_approval_ui(memories: List[Dict]) -> List[Dict]:
22
+ """Simple CLI interface for memory approval"""
23
+ if not memories:
24
+ return []
25
+
26
+ print(f"\n📝 {len(memories)} memories ready for approval:")
27
+
28
+ approvals = []
29
+ for i, memory in enumerate(memories, 1):
30
+ print(f"\n--- Memory {i}/{len(memories)} ---")
31
+ print(f"NPC: {memory['npc']}")
32
+ print(f"Content: {memory['content'][:200]}{'...' if len(memory['content']) > 200 else ''}")
33
+
34
+ while True:
35
+ choice = input("(a)pprove, (r)eject, (e)dit, (s)kip, (q)uit, (A)pprove all: ").strip().lower()
36
+
37
+ if choice == 'a':
38
+ approvals.append({"memory_id": memory['memory_id'], "decision": "human-approved"})
39
+ break
40
+ elif choice == 'r':
41
+ approvals.append({"memory_id": memory['memory_id'], "decision": "human-rejected"})
42
+ break
43
+ elif choice == 'e':
44
+ edited = input("Edit memory: ").strip()
45
+ if edited:
46
+ approvals.append({
47
+ "memory_id": memory['memory_id'],
48
+ "decision": "human-edited",
49
+ "final_memory": edited
50
+ })
51
+ break
52
+ elif choice == 's':
53
+ break
54
+ elif choice == 'q':
55
+ return approvals
56
+ elif choice == 'A':
57
+
58
+ for remaining_memory in memories[i-1:]:
59
+ approvals.append({
60
+ "memory_id": remaining_memory['memory_id'],
61
+ "decision": "human-approved"
62
+ })
63
+ return approvals
64
+
65
+ return approvals
@@ -26,7 +26,7 @@ from npcpy.npc_sysenv import (
26
26
  get_system_message,
27
27
 
28
28
  )
29
- from npcpy.memory.command_history import CommandHistory
29
+ from npcpy.memory.command_history import CommandHistory, generate_message_id
30
30
 
31
31
  class SilentUndefined(Undefined):
32
32
  def _fail_with_undefined_error(self, *args, **kwargs):
@@ -498,7 +498,7 @@ def load_jinxs_from_directory(directory):
498
498
  return jinxs
499
499
 
500
500
  def get_npc_action_space(npc=None, team=None):
501
- """Get action space for NPC including memory search, db query, and core capabilities"""
501
+ """Get action space for NPC including memory CRUD and core capabilities"""
502
502
  actions = DEFAULT_ACTION_SPACE.copy()
503
503
 
504
504
  if npc:
@@ -510,24 +510,35 @@ def get_npc_action_space(npc=None, team=None):
510
510
  if npc.command_history:
511
511
  core_tools.extend([
512
512
  npc.search_my_conversations,
513
- npc.search_my_memories
513
+ npc.search_my_memories,
514
+ npc.create_memory,
515
+ npc.read_memory,
516
+ npc.update_memory,
517
+ npc.delete_memory,
518
+ npc.search_memories,
519
+ npc.get_all_memories,
520
+ npc.archive_old_memories,
521
+ npc.get_memory_stats
514
522
  ])
515
523
 
516
524
  if npc.db_conn:
517
525
  core_tools.append(npc.query_database)
518
526
 
519
527
  if hasattr(npc, 'tools') and npc.tools:
528
+ core_tools.extend([func for func in npc.tool_map.values() if callable(func)])
529
+
530
+ if core_tools:
520
531
  tools_schema, tool_map = auto_tools(core_tools)
521
532
  actions.update({
522
533
  f"use_{tool.__name__}": {
523
- "description": extract_function_info(tool).get('description'),
534
+ "description": f"Use {tool.__name__} capability",
524
535
  "handler": tool,
525
536
  "context": lambda **_: f"Available as automated capability",
526
537
  "output_keys": {"result": {"description": "Tool execution result", "type": "string"}}
527
538
  }
528
539
  for tool in core_tools
529
540
  })
530
-
541
+
531
542
  if team and hasattr(team, 'npcs') and len(team.npcs) > 1:
532
543
  available_npcs = [name for name in team.npcs.keys() if name != (npc.name if npc else None)]
533
544
 
@@ -537,13 +548,11 @@ def get_npc_action_space(npc=None, team=None):
537
548
  return agent_pass_handler(command, extracted_data, **kwargs)
538
549
 
539
550
  actions["pass_to_npc"] = {
540
- "description": "Pass the request to another NPC in the team - BUT ONLY if the task truly requires their specific expertise and you cannot handle it yourself",
551
+ "description": "Pass request to another NPC - only when task requires their specific expertise",
541
552
  "handler": team_aware_handler,
542
553
  "context": lambda npc=npc, team=team, **_: (
543
- f"Use this SPARINGLY when the request absolutely requires another team member's expertise. "
544
554
  f"Available NPCs: {', '.join(available_npcs)}. "
545
- f"IMPORTANT: If you can handle the task yourself with your {npc.name if npc else 'current'} skills, DO NOT pass it. "
546
- f"Only pass when you genuinely cannot complete the task due to lack of domain expertise."
555
+ f"Only pass when you genuinely cannot complete the task."
547
556
  ),
548
557
  "output_keys": {
549
558
  "target_npc": {
@@ -556,8 +565,6 @@ def get_npc_action_space(npc=None, team=None):
556
565
  return actions
557
566
 
558
567
 
559
-
560
-
561
568
  def extract_jinx_inputs(args: List[str], jinx: Jinx) -> Dict[str, Any]:
562
569
  inputs = {}
563
570
 
@@ -1074,14 +1081,14 @@ class NPC:
1074
1081
 
1075
1082
  def get_llm_response(self,
1076
1083
  request,
1077
- jinxs= None,
1084
+ jinxs=None,
1078
1085
  tools=None,
1079
- tool_map= None,
1086
+ tool_map=None,
1080
1087
  tool_choice=None,
1081
- messages: Optional[List[Dict[str, str]]] = None,
1082
- auto_process_tool_calls: bool = True,
1088
+ messages=None,
1089
+ auto_process_tool_calls=True,
1083
1090
  **kwargs):
1084
- """Get a response from the LLM with automatic tool integration"""
1091
+ """Get response from LLM with automatic tool integration including memory CRUD"""
1085
1092
 
1086
1093
  if tools is None and tool_map is None and tool_choice is None:
1087
1094
  core_tools = [
@@ -1092,7 +1099,15 @@ class NPC:
1092
1099
  if self.command_history:
1093
1100
  core_tools.extend([
1094
1101
  self.search_my_conversations,
1095
- self.search_my_memories
1102
+ self.search_my_memories,
1103
+ self.create_memory,
1104
+ self.read_memory,
1105
+ self.update_memory,
1106
+ self.delete_memory,
1107
+ self.search_memories,
1108
+ self.get_all_memories,
1109
+ self.archive_old_memories,
1110
+ self.get_memory_stats
1096
1111
  ])
1097
1112
 
1098
1113
  if self.db_conn:
@@ -1122,6 +1137,7 @@ class NPC:
1122
1137
  )
1123
1138
 
1124
1139
  return response
1140
+
1125
1141
 
1126
1142
 
1127
1143
 
@@ -1674,7 +1690,7 @@ class NPC:
1674
1690
 
1675
1691
 
1676
1692
 
1677
- def execute_jinx_command(
1693
+ def execute_jinx_command(self,
1678
1694
  jinx: Jinx,
1679
1695
  args: List[str],
1680
1696
  messages=None,
@@ -1695,6 +1711,159 @@ class NPC:
1695
1711
  )
1696
1712
 
1697
1713
  return {"messages": messages, "output": jinx_output}
1714
+ def create_memory(self, content: str, memory_type: str = "observation") -> Optional[int]:
1715
+ """Create a new memory entry"""
1716
+ if not self.command_history:
1717
+ return None
1718
+
1719
+ message_id = generate_message_id()
1720
+ conversation_id = self.command_history.get_most_recent_conversation_id()
1721
+ conversation_id = conversation_id.get('conversation_id') if conversation_id else 'direct_memory'
1722
+
1723
+ team_name = getattr(self.team, 'name', 'default_team') if self.team else 'default_team'
1724
+ directory_path = os.getcwd()
1725
+
1726
+ return self.command_history.add_memory_to_database(
1727
+ message_id=message_id,
1728
+ conversation_id=conversation_id,
1729
+ npc=self.name,
1730
+ team=team_name,
1731
+ directory_path=directory_path,
1732
+ initial_memory=content,
1733
+ status='active',
1734
+ model=self.model,
1735
+ provider=self.provider
1736
+ )
1737
+
1738
+ def read_memory(self, memory_id: int) -> Optional[Dict[str, Any]]:
1739
+ """Read a specific memory by ID"""
1740
+ if not self.command_history:
1741
+ return None
1742
+
1743
+ stmt = "SELECT * FROM memory_lifecycle WHERE id = :memory_id"
1744
+ return self.command_history._fetch_one(stmt, {"memory_id": memory_id})
1745
+
1746
+ def update_memory(self, memory_id: int, new_content: str = None, status: str = None) -> bool:
1747
+ """Update memory content or status"""
1748
+ if not self.command_history:
1749
+ return False
1750
+
1751
+ updates = []
1752
+ params = {"memory_id": memory_id}
1753
+
1754
+ if new_content is not None:
1755
+ updates.append("final_memory = :final_memory")
1756
+ params["final_memory"] = new_content
1757
+
1758
+ if status is not None:
1759
+ updates.append("status = :status")
1760
+ params["status"] = status
1761
+
1762
+ if not updates:
1763
+ return False
1764
+
1765
+ stmt = f"UPDATE memory_lifecycle SET {', '.join(updates)} WHERE id = :memory_id"
1766
+
1767
+ try:
1768
+ with self.command_history.engine.begin() as conn:
1769
+ conn.execute(text(stmt), params)
1770
+ return True
1771
+ except Exception as e:
1772
+ print(f"Error updating memory {memory_id}: {e}")
1773
+ return False
1774
+
1775
+ def delete_memory(self, memory_id: int) -> bool:
1776
+ """Delete a memory by ID"""
1777
+ if not self.command_history:
1778
+ return False
1779
+
1780
+ stmt = "DELETE FROM memory_lifecycle WHERE id = :memory_id AND npc = :npc"
1781
+
1782
+ try:
1783
+ with self.command_history.engine.begin() as conn:
1784
+ result = conn.execute(text(stmt), {"memory_id": memory_id, "npc": self.name})
1785
+ return result.rowcount > 0
1786
+ except Exception as e:
1787
+ print(f"Error deleting memory {memory_id}: {e}")
1788
+ return False
1789
+
1790
+ def search_memories(self, query: str, limit: int = 10, status_filter: str = None) -> List[Dict[str, Any]]:
1791
+ """Search memories with optional status filtering"""
1792
+ if not self.command_history:
1793
+ return []
1794
+
1795
+ team_name = getattr(self.team, 'name', 'default_team') if self.team else 'default_team'
1796
+ directory_path = os.getcwd()
1797
+
1798
+ return self.command_history.search_memory(
1799
+ query=query,
1800
+ npc=self.name,
1801
+ team=team_name,
1802
+ directory_path=directory_path,
1803
+ status_filter=status_filter,
1804
+ limit=limit
1805
+ )
1806
+
1807
+ def get_all_memories(self, limit: int = 50, status_filter: str = None) -> List[Dict[str, Any]]:
1808
+ """Get all memories for this NPC with optional status filtering"""
1809
+ if not self.command_history:
1810
+ return []
1811
+
1812
+ if limit is None:
1813
+ limit = 50
1814
+
1815
+ conditions = ["npc = :npc"]
1816
+ params = {"npc": self.name, "limit": limit}
1817
+
1818
+ if status_filter:
1819
+ conditions.append("status = :status")
1820
+ params["status"] = status_filter
1821
+
1822
+ stmt = f"""
1823
+ SELECT * FROM memory_lifecycle
1824
+ WHERE {' AND '.join(conditions)}
1825
+ ORDER BY created_at DESC
1826
+ LIMIT :limit
1827
+ """
1828
+
1829
+ return self.command_history._fetch_all(stmt, params)
1830
+
1831
+
1832
+ def archive_old_memories(self, days_old: int = 30) -> int:
1833
+ """Archive memories older than specified days"""
1834
+ if not self.command_history:
1835
+ return 0
1836
+
1837
+ stmt = """
1838
+ UPDATE memory_lifecycle
1839
+ SET status = 'archived'
1840
+ WHERE npc = :npc
1841
+ AND status = 'active'
1842
+ AND datetime(created_at) < datetime('now', '-{} days')
1843
+ """.format(days_old)
1844
+
1845
+ try:
1846
+ with self.command_history.engine.begin() as conn:
1847
+ result = conn.execute(text(stmt), {"npc": self.name})
1848
+ return result.rowcount
1849
+ except Exception as e:
1850
+ print(f"Error archiving memories: {e}")
1851
+ return 0
1852
+
1853
+ def get_memory_stats(self) -> Dict[str, int]:
1854
+ """Get memory statistics for this NPC"""
1855
+ if not self.command_history:
1856
+ return {}
1857
+
1858
+ stmt = """
1859
+ SELECT status, COUNT(*) as count
1860
+ FROM memory_lifecycle
1861
+ WHERE npc = :npc
1862
+ GROUP BY status
1863
+ """
1864
+
1865
+ results = self.command_history._fetch_all(stmt, {"npc": self.name})
1866
+ return {row['status']: row['count'] for row in results}
1698
1867
 
1699
1868
 
1700
1869
  class Team:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcpy
3
- Version: 1.2.17
3
+ Version: 1.2.19
4
4
  Summary: npcpy is the premier open-source library for integrating LLMs and Agents into python systems.
5
5
  Home-page: https://github.com/NPC-Worldwide/npcpy
6
6
  Author: Christopher Agostino
@@ -82,7 +82,7 @@ extra_files = package_files("npcpy/npc_team/")
82
82
 
83
83
  setup(
84
84
  name="npcpy",
85
- version="1.2.17",
85
+ version="1.2.19",
86
86
  packages=find_packages(exclude=["tests*"]),
87
87
  install_requires=base_requirements,
88
88
  extras_require={
@@ -223,3 +223,117 @@ def test_npc_execute_jinx():
223
223
  print(f"NPC jinx execution result: {result}")
224
224
  except Exception as e:
225
225
  print(f"NPC jinx execution failed: {e}")
226
+
227
+
228
+
229
+ import os
230
+ import tempfile
231
+ import sqlite3
232
+ from datetime import datetime
233
+ import sys
234
+
235
+
236
+ from npcpy.memory.command_history import CommandHistory, get_db_connection
237
+ from npcpy.tools import auto_tools
238
+
239
+ def test_npc_memory_crud_integration():
240
+ """Integration test for NPC memory CRUD operations via auto tools"""
241
+
242
+ test_db_path = "test_npc.db"
243
+ db_conn = get_db_connection(test_db_path)
244
+
245
+ test_npc = NPC(
246
+ name="memory_test_npc",
247
+ primary_directive="Test NPC with memory CRUD capabilities",
248
+ model="llama3.2",
249
+ provider="ollama",
250
+ db_conn=db_conn,
251
+ memory=True
252
+ )
253
+
254
+ print(f"NPC '{test_npc.name}' initialized successfully")
255
+ print(f"Database connection: {test_npc.db_conn is not None}")
256
+ print(f"Command history: {test_npc.command_history is not None}")
257
+
258
+ print("\n=== Testing Memory CRUD via Auto Tools ===")
259
+
260
+ print("\n1. Testing create_memory via LLM response")
261
+ create_response = test_npc.get_llm_response(
262
+ "Create a memory about user preferring Python over JavaScript for backend work",
263
+ auto_process_tool_calls=True
264
+ )
265
+
266
+ if create_response.get('tool_results'):
267
+ memory_id = create_response['tool_results'][0]['result']
268
+ print(f"✓ Memory created with ID: {memory_id}")
269
+ else:
270
+ print("✗ No memory created")
271
+
272
+ print("\n2. Testing search_memories via LLM")
273
+ search_response = test_npc.get_llm_response(
274
+ "Search my memories for anything about Python",
275
+ auto_process_tool_calls=True
276
+ )
277
+
278
+ if search_response.get('tool_results'):
279
+ search_results = search_response['tool_results'][0]['result']
280
+ print(f"✓ Found {len(search_results) if isinstance(search_results, list) else 1} memories")
281
+ else:
282
+ print("✗ No search results")
283
+
284
+ print("\n3. Testing get_all_memories via LLM")
285
+ all_memories_response = test_npc.get_llm_response(
286
+ "Show me all my current memories",
287
+ auto_process_tool_calls=True
288
+ )
289
+
290
+ if all_memories_response.get('tool_results'):
291
+ all_memories = all_memories_response['tool_results'][0]['result']
292
+ print(f"✓ Retrieved {len(all_memories) if isinstance(all_memories, list) else 1} total memories")
293
+ else:
294
+ print("✗ No memories retrieved")
295
+
296
+ print("\n4. Testing memory stats via LLM")
297
+ stats_response = test_npc.get_llm_response(
298
+ "Give me statistics about my memories",
299
+ auto_process_tool_calls=True
300
+ )
301
+
302
+ if stats_response.get('tool_results'):
303
+ stats = stats_response['tool_results'][0]['result']
304
+ print(f"✓ Memory stats: {stats}")
305
+ else:
306
+ print("✗ No stats retrieved")
307
+
308
+ print("\n=== Testing Direct Tool Access ===")
309
+
310
+ print("\n5. Direct memory creation")
311
+ memory_id = test_npc.create_memory("Direct memory creation test", "test")
312
+ print(f"✓ Created memory ID: {memory_id}")
313
+
314
+ if memory_id:
315
+ print("\n6. Reading created memory")
316
+ memory_data = test_npc.read_memory(memory_id)
317
+ print(f"✓ Memory content: {memory_data['initial_memory'] if memory_data else 'None'}")
318
+
319
+ print("\n7. Updating memory")
320
+ update_success = test_npc.update_memory(
321
+ memory_id,
322
+ new_content="Updated test memory content",
323
+ status="verified"
324
+ )
325
+ print(f"✓ Update success: {update_success}")
326
+
327
+ print("\n8. Reading updated memory")
328
+ updated_memory = test_npc.read_memory(memory_id)
329
+ if updated_memory:
330
+ print(f"✓ Updated content: {updated_memory['final_memory']}")
331
+ print(f"✓ New status: {updated_memory['status']}")
332
+
333
+ print("\n9. Getting final memory statistics")
334
+ final_stats = test_npc.get_memory_stats()
335
+ print(f"✓ Final memory stats: {final_stats}")
336
+
337
+ print("\n=== Test Complete ===")
338
+ print("✓ Memory CRUD operations successfully integrated as auto tools")
339
+ return True
@@ -1,155 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import List, Dict, Any, Optional
3
- from datetime import datetime
4
- import threading
5
- import queue
6
- import time
7
-
8
- @dataclass
9
- class MemoryItem:
10
- message_id: str
11
- conversation_id: str
12
- npc: str
13
- team: str
14
- directory_path: str
15
- content: str
16
- context: str
17
- model: str
18
- provider: str
19
-
20
- class MemoryApprovalQueue:
21
- def __init__(self, command_history):
22
- self.command_history = command_history
23
- self.pending_queue = queue.Queue()
24
- self.approval_results = queue.Queue()
25
- self.processing_thread = None
26
- self.running = False
27
-
28
- def add_memory(self, memory_item: MemoryItem):
29
- """Add memory to processing queue (non-blocking)"""
30
- self.pending_queue.put(memory_item)
31
-
32
- def start_background_processing(self):
33
- """Start background thread for memory processing"""
34
- if self.processing_thread and self.processing_thread.is_alive():
35
- return
36
-
37
- self.running = True
38
- self.processing_thread = threading.Thread(target=self._process_queue)
39
- self.processing_thread.daemon = True
40
- self.processing_thread.start()
41
-
42
- def _process_queue(self):
43
- """Background processing of memory queue"""
44
- while self.running:
45
- try:
46
-
47
- batch = []
48
- try:
49
-
50
- memory = self.pending_queue.get(timeout=1.0)
51
- batch.append(memory)
52
-
53
-
54
- while len(batch) < 10:
55
- try:
56
- memory = self.pending_queue.get_nowait()
57
- batch.append(memory)
58
- except queue.Empty:
59
- break
60
-
61
- self._process_memory_batch(batch)
62
-
63
- except queue.Empty:
64
- continue
65
-
66
- except Exception as e:
67
- print(f"Error in memory processing: {e}")
68
- time.sleep(1)
69
-
70
- def _process_memory_batch(self, memories: List[MemoryItem]):
71
- """Process a batch of memories"""
72
- for memory in memories:
73
-
74
- memory_id = self.command_history.add_memory_to_database(
75
- message_id=memory.message_id,
76
- conversation_id=memory.conversation_id,
77
- npc=memory.npc,
78
- team=memory.team,
79
- directory_path=memory.directory_path,
80
- initial_memory=memory.content,
81
- status="pending_approval",
82
- model=memory.model,
83
- provider=memory.provider
84
- )
85
-
86
-
87
- self.approval_results.put({
88
- "memory_id": memory_id,
89
- "content": memory.content,
90
- "context": memory.context,
91
- "npc": memory.npc
92
- })
93
-
94
- def get_approval_batch(self, max_items: int = 5) -> List[Dict]:
95
- """Get batch of memories ready for approval"""
96
- batch = []
97
- try:
98
- while len(batch) < max_items:
99
- item = self.approval_results.get_nowait()
100
- batch.append(item)
101
- except queue.Empty:
102
- pass
103
- return batch
104
-
105
- def stop_processing(self):
106
- """Stop background processing"""
107
- self.running = False
108
- if self.processing_thread:
109
- self.processing_thread.join(timeout=2.0)
110
-
111
- def memory_approval_ui(memories: List[Dict]) -> List[Dict]:
112
- """Simple CLI interface for memory approval"""
113
- if not memories:
114
- return []
115
-
116
- print(f"\n📝 {len(memories)} memories ready for approval:")
117
-
118
- approvals = []
119
- for i, memory in enumerate(memories, 1):
120
- print(f"\n--- Memory {i}/{len(memories)} ---")
121
- print(f"NPC: {memory['npc']}")
122
- print(f"Content: {memory['content'][:200]}{'...' if len(memory['content']) > 200 else ''}")
123
-
124
- while True:
125
- choice = input("(a)pprove, (r)eject, (e)dit, (s)kip, (q)uit, (A)pprove all: ").strip().lower()
126
-
127
- if choice == 'a':
128
- approvals.append({"memory_id": memory['memory_id'], "decision": "human-approved"})
129
- break
130
- elif choice == 'r':
131
- approvals.append({"memory_id": memory['memory_id'], "decision": "human-rejected"})
132
- break
133
- elif choice == 'e':
134
- edited = input("Edit memory: ").strip()
135
- if edited:
136
- approvals.append({
137
- "memory_id": memory['memory_id'],
138
- "decision": "human-edited",
139
- "final_memory": edited
140
- })
141
- break
142
- elif choice == 's':
143
- break
144
- elif choice == 'q':
145
- return approvals
146
- elif choice == 'A':
147
-
148
- for remaining_memory in memories[i-1:]:
149
- approvals.append({
150
- "memory_id": remaining_memory['memory_id'],
151
- "decision": "human-approved"
152
- })
153
- return approvals
154
-
155
- return approvals
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
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
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
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
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
File without changes
File without changes