-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrun_thoughtforge.py
More file actions
349 lines (285 loc) · 10.6 KB
/
Copy pathrun_thoughtforge.py
File metadata and controls
349 lines (285 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
"""
run_thoughtforge.py — MindSpark: ThoughtForge interactive CLI.
Usage:
# Interactive REPL:
python run_thoughtforge.py
# Persistent chat mode:
python run_thoughtforge.py --chat
# Single query:
python run_thoughtforge.py "What is Yggdrasil?"
# With a GGUF model:
python run_thoughtforge.py --model /models/phi-3-mini-q4.gguf
# Chat with history file:
python run_thoughtforge.py --chat --history sessions/my_session.json
# With a system prompt:
python run_thoughtforge.py --chat --system "You are a Norse mythology expert."
# Show backend info:
python run_thoughtforge.py --backend
# Override hardware profile:
python run_thoughtforge.py --profile desktop_cpu
# Debug logging:
python run_thoughtforge.py --debug
"""
from __future__ import annotations
import argparse
import logging
import sys
# Ensure UTF-8 output on Windows terminals
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
from pathlib import Path
def _build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
prog="run_thoughtforge",
description="MindSpark: ThoughtForge — Memory-Enforced Cognition Engine",
)
p.add_argument(
"query",
nargs="?",
default=None,
help="Single query to run (omit for interactive REPL mode)",
)
p.add_argument(
"--model",
metavar="PATH",
default=None,
help="Path to a GGUF model file (optional — knowledge-only mode if omitted)",
)
p.add_argument(
"--profile",
metavar="PROFILE",
default="auto",
help="Hardware profile: auto|phone_low|pi_zero|pi_5|desktop_cpu|desktop_gpu|server_gpu",
)
p.add_argument(
"--retrieval",
metavar="PATH",
default=None,
choices=["sql", "vector", "hybrid"],
help="Retrieval path override: sql|vector|hybrid (default: auto from intent)",
)
p.add_argument(
"--chat",
action="store_true",
default=False,
help="Enter persistent chat mode",
)
p.add_argument(
"--history",
metavar="FILE",
default=None,
help="Load chat history from FILE at start; auto-save on exit",
)
p.add_argument(
"--system",
metavar="PROMPT",
default=None,
help="Set system prompt for this session",
)
p.add_argument(
"--backend",
action="store_true",
default=False,
help="Show current backend info and exit",
)
p.add_argument(
"--debug",
action="store_true",
default=False,
help="Enable DEBUG logging",
)
return p
def _setup_logging(debug: bool) -> None:
level = logging.DEBUG if debug else logging.WARNING
logging.basicConfig(
format="%(levelname)s [%(name)s] %(message)s",
level=level,
stream=sys.stderr,
)
def _display_result(result: object, query: str) -> None:
sep = "═" * 72
print(f"\n{sep}")
print(result.text) # type: ignore[attr-defined]
print(sep)
citations = getattr(result, "citations", [])
scores = getattr(result, "scores", None)
enforcement_passed = getattr(result, "enforcement_passed", False)
enforcement_notes = getattr(result, "enforcement_notes", "")
token_count = getattr(result, "token_count", 0)
cite_str = ", ".join(citations) if citations else "none"
confidence = scores.composite if scores else 0.0
quality = scores.quality_tier if scores else "—"
enf_str = "PASS" if enforcement_passed else f"REVIEW ({enforcement_notes})"
print(f"Citations : {cite_str}")
print(f"Confidence : {confidence:.3f} [{quality}]")
print(f"Enforcement : {enf_str}")
print(f"Tokens : {token_count}")
print()
def _show_backend_info() -> None:
from thoughtforge.inference.unified_backend import load_backend_from_config
backend = load_backend_from_config()
if backend is None:
print("Backend: none configured (knowledge-only mode)")
print("Run `python setup_thoughtforge.py` to configure a backend.")
return
print(f"Backend : {backend.backend_name()}")
healthy = backend.health_check()
print(f"Health : {'OK' if healthy else 'UNREACHABLE'}")
def _handle_chat_command(
command: str,
history: object,
history_file: str | None,
) -> None:
parts = command.strip().split(None, 1)
cmd = parts[0].lower()
if cmd == "/clear":
history.clear() # type: ignore[attr-defined]
print("History cleared.")
elif cmd == "/save":
if history_file:
history.save(Path(history_file)) # type: ignore[attr-defined]
print(f"Saved to {history_file}")
else:
dest = parts[1].strip() if len(parts) > 1 else ""
if dest:
history.save(Path(dest)) # type: ignore[attr-defined]
print(f"Saved to {dest}")
else:
print("Usage: /save [FILE] (no default history file set)")
elif cmd == "/load":
if len(parts) < 2:
print("Usage: /load FILE")
return
src = Path(parts[1].strip())
if not src.exists():
print(f"File not found: {src}")
return
from thoughtforge.cognition.chat_history import ChatHistory
loaded = ChatHistory.load(src)
# Replace messages in-place
history.messages.clear() # type: ignore[attr-defined]
history.messages.extend(loaded.messages) # type: ignore[attr-defined]
print(f"Loaded {len(history)} turns from {src}")
elif cmd == "/history":
print(history.format_for_display()) # type: ignore[attr-defined]
elif cmd in ("/quit", "/exit", "/q"):
raise KeyboardInterrupt
else:
print(f"Unknown command: {cmd}")
print("Commands: /clear /save [FILE] /load FILE /history /quit")
def _run_chat(
core: object,
retrieval_path: str | None,
history_file: str | None,
system_prompt: str | None,
) -> None:
from thoughtforge.cognition.chat_history import ChatHistory
from thoughtforge.inference.unified_backend import load_backend_from_config
history = ChatHistory(system_prompt=system_prompt or "")
if history_file and Path(history_file).exists():
history = ChatHistory.load(Path(history_file))
# Backend info for header
backend = load_backend_from_config()
backend_label = backend.backend_name() if backend else "knowledge-only"
print("MindSpark: ThoughtForge — Chat Mode")
print(f"Backend: {backend_label}")
if history_file:
print(f"History: {history_file} ({len(history)} turns loaded)")
print("Commands: /clear /save [FILE] /load FILE /history /quit")
print()
while True:
try:
user_input = input("You > ").strip()
except (EOFError, KeyboardInterrupt):
break
if not user_input:
continue
if user_input.startswith("/"):
try:
_handle_chat_command(user_input, history, history_file)
except KeyboardInterrupt:
break
continue
history.add_user(user_input)
try:
result = core.think( # type: ignore[attr-defined]
user_input,
retrieval_path=retrieval_path,
history=history,
)
except Exception as e:
print(f"[Forge Error] {e}", file=sys.stderr)
continue
history.add_assistant(result.text, turn_id=result.turn_id)
print(f"\nForge > {result.text}\n")
citations = getattr(result, "citations", [])
retrieval_confidence = getattr(result, "retrieval_confidence", 0.0)
enforcement_passed = getattr(result, "enforcement_passed", False)
if citations:
enf = "PASS" if enforcement_passed else "REVIEW"
print(
f" [Citations: {', '.join(citations[:3])} | "
f"Confidence: {retrieval_confidence:.2f} | {enf}]"
)
print()
if history_file:
history.save(Path(history_file))
print(f"History saved to {history_file}")
else:
print("The forge grows quiet. Walk well.")
def _run_single(core: object, query: str, retrieval_path: str | None) -> None:
result = core.think(query, retrieval_path=retrieval_path) # type: ignore[attr-defined]
_display_result(result, query)
def _run_repl(core: object, retrieval_path: str | None) -> None:
print("MindSpark: ThoughtForge")
print("The forge is ready. Type 'exit' or 'quit' to leave.\n")
while True:
try:
query = input("Forge> ").strip()
except (EOFError, KeyboardInterrupt):
print("\nThe forge grows quiet. Walk well.")
break
if not query:
continue
if query.lower() in ("exit", "quit", "q", ":q"):
print("The forge grows quiet. Walk well.")
break
try:
result = core.think(query, retrieval_path=retrieval_path) # type: ignore[attr-defined]
_display_result(result, query)
except Exception as e:
print(f"[Forge Error] {e}", file=sys.stderr)
def main() -> None:
parser = _build_parser()
args = parser.parse_args()
_setup_logging(args.debug)
# Import here so logging is set up first
from thoughtforge.cognition.core import ThoughtForgeCore
from thoughtforge.utils.errors import ThoughtForgeError
from thoughtforge.utils.logging_setup import setup_logging
if not args.debug:
setup_logging(config={"logging": {"level": "WARNING"}})
if args.backend:
_show_backend_info()
return
try:
# Load unified backend from user_config.yaml if present
from thoughtforge.inference.unified_backend import load_backend_from_config
unified_backend = load_backend_from_config()
model_path = Path(args.model) if args.model else None
core = ThoughtForgeCore(model_path=model_path, backend=unified_backend)
if args.chat:
_run_chat(core, args.retrieval, args.history, args.system)
elif args.query:
_run_single(core, args.query, args.retrieval)
else:
_run_repl(core, args.retrieval)
except ThoughtForgeError as exc:
print(f"\nThoughtForge Error: {exc.message}", file=sys.stderr)
if exc.suggested_fix:
print(f"Fix: {exc.suggested_fix}", file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("\nThe forge grows quiet. Walk well.")
if __name__ == "__main__":
main()