OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / opencog-kb / language-learning / chatbot / persist.scm

154 lines · scm ref:
;
; persist.scm - AtomSpace persistence to disk
;
; Saves/loads knowledge between sessions using RocksDB or JSON fallback.
;

(use-modules (opencog))
(use-modules (ice-9 format))
(use-modules (ice-9 rdelim))
(use-modules (srfi srfi-1))

;; ═══════════════════════════════════════════════════════════════════
;; CONFIGURATION
;; ═══════════════════════════════════════════════════════════════════

(define *data-dir* (string-append (dirname (current-filename)) "/data"))
(define *words-file* (string-append *data-dir* "/words.scm"))
(define *pairs-file* (string-append *data-dir* "/pairs.scm"))
(define *counters-file* (string-append *data-dir* "/counters.scm"))

;; ═══════════════════════════════════════════════════════════════════
;; ENSURE DATA DIRECTORY
;; ═══════════════════════════════════════════════════════════════════

(define (ensure-data-dir)
  (unless (file-exists? *data-dir*)
    (mkdir *data-dir*)))

;; ═══════════════════════════════════════════════════════════════════
;; SAVE KNOWLEDGE
;; ═══════════════════════════════════════════════════════════════════

(define (save-knowledge)
  "Save all knowledge to disk"
  (ensure-data-dir)
  (display "[persist] Saving knowledge...\n")

  ;; Save word counts
  (call-with-output-file *words-file*
    (lambda (port)
      (display ";; Word counts\n" port)
      (display "(define saved-words '(\n" port)
      (for-each
        (lambda (node)
          (let ((name (cog-name node)))
            (when (string-prefix? "word:" name)
              (let ((word (substring name 5))
                    (count (get-word-count node)))
                (format port "  (~s . ~a)\n" word count)))))
        (cog-get-atoms 'ConceptNode #t))
      (display "))\n" port)))

  ;; Save pair counts
  (call-with-output-file *pairs-file*
    (lambda (port)
      (display ";; Pair counts\n" port)
      (display "(define saved-pairs '(\n" port)
      (for-each
        (lambda (edge)
          (let ((outgoing (cog-outgoing-set edge)))
            (when (= (length outgoing) 2)
              (let ((bond (car outgoing))
                    (lst (cadr outgoing)))
                (when (and (equal? (cog-name bond) "pair-bond")
                           (eq? (cog-type lst) 'ListLink))
                  (let* ((words (cog-outgoing-set lst))
                         (w1-name (cog-name (car words)))
                         (w2-name (cog-name (cadr words)))
                         (count (get-pair-count edge)))
                    (when (and (string-prefix? "word:" w1-name)
                               (string-prefix? "word:" w2-name))
                      (format port "  ((~s . ~s) . ~a)\n"
                              (substring w1-name 5)
                              (substring w2-name 5)
                              count))))))))
        (cog-get-atoms 'EdgeLink #t))
      (display "))\n" port)))

  ;; Save global counters
  (call-with-output-file *counters-file*
    (lambda (port)
      (format port ";; Global counters\n")
      (format port "(define saved-total-words ~a)\n" *total-words*)
      (format port "(define saved-total-pairs ~a)\n" *total-pairs*)))

  (display "[persist] Saved.\n"))

;; ═══════════════════════════════════════════════════════════════════
;; LOAD KNOWLEDGE
;; ═══════════════════════════════════════════════════════════════════

(define (load-knowledge)
  "Load knowledge from disk"
  (display "[persist] Loading knowledge...\n")

  (when (file-exists? *counters-file*)
    (load *counters-file*)
    (set! *total-words* saved-total-words)
    (set! *total-pairs* saved-total-pairs)
    (display (format #f "[persist] Counters: words=~a pairs=~a\n"
                     *total-words* *total-pairs*)))

  (when (file-exists? *words-file*)
    (load *words-file*)
    (for-each
      (lambda (entry)
        (let ((word (car entry))
              (count (cdr entry)))
          (let ((node (cog-new-node 'ConceptNode
                        (string-append "word:" word))))
            (cog-set-value! node (Predicate "count")
              (FloatValue count)))))
      saved-words)
    (display (format #f "[persist] Loaded ~a words\n" (length saved-words))))

  (when (file-exists? *pairs-file*)
    (load *pairs-file*)
    (for-each
      (lambda (entry)
        (let* ((pair-key (car entry))
               (count (cdr entry))
               (w1 (car pair-key))
               (w2 (cdr pair-key)))
          (let* ((n1 (cog-new-node 'ConceptNode (string-append "word:" w1)))
                 (n2 (cog-new-node 'ConceptNode (string-append "word:" w2)))
                 (bond (cog-new-node 'ConceptNode "pair-bond"))
                 (edge (cog-new-link 'EdgeLink bond
                         (cog-new-link 'ListLink n1 n2))))
            (cog-set-value! edge (Predicate "count")
              (FloatValue count)))))
      saved-pairs)
    (display (format #f "[persist] Loaded ~a pairs\n" (length saved-pairs))))

  (display "[persist] Done.\n"))

;; ═══════════════════════════════════════════════════════════════════
;; AUTO-SAVE HOOK
;; ═══════════════════════════════════════════════════════════════════

(define *last-save-time* (current-time))
(define *save-interval* 300)  ; Save every 5 minutes
(define *changes-since-save* 0)
(define *save-threshold* 50)  ; Or after 50 changes

(define (maybe-auto-save)
  "Save if enough time/changes have passed"
  (set! *changes-since-save* (+ 1 *changes-since-save*))
  (let ((now (current-time)))
    (when (or (> *changes-since-save* *save-threshold*)
              (> (- now *last-save-time*) *save-interval*))
      (save-knowledge)
      (set! *changes-since-save* 0)
      (set! *last-save-time* now))))