File size: 3,375 Bytes
564b5ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Episodic SQLite storage."""
import json
import sqlite3
import time
import logging
from typing import Dict, Any, List
from api.deps import load_config, get_logger

logger = get_logger("kapo.memory.episodic")


class EpisodicDB:
    def __init__(self):
        cfg = load_config()
        self.db_path = cfg.get("DB_PATH") or "./episodic.db"
        self._init_db()

    def _init_db(self):
        conn = sqlite3.connect(self.db_path)
        cur = conn.cursor()
        cur.execute(
            """

            CREATE TABLE IF NOT EXISTS experiences (

                id INTEGER PRIMARY KEY AUTOINCREMENT,

                task TEXT,

                plan TEXT,

                tools_used TEXT,

                result TEXT,

                success INTEGER,

                timestamp TEXT

            )

            """
        )
        conn.commit()
        conn.close()

    def insert_experience(self, task: str, plan: Dict[str, Any], tools_used: Dict[str, Any], result: Dict[str, Any], success: int):
        conn = sqlite3.connect(self.db_path)
        cur = conn.cursor()
        cur.execute(
            """INSERT INTO experiences(task, plan, tools_used, result, success, timestamp)

               VALUES(?,?,?,?,?,?)""",
            (task, json.dumps(plan), json.dumps(tools_used), json.dumps(result), success, time.strftime("%Y-%m-%dT%H:%M:%S")),
        )
        conn.commit()
        conn.close()

    def list_recent(self, limit: int = 20) -> List[Dict[str, Any]]:
        conn = sqlite3.connect(self.db_path)
        cur = conn.cursor()
        cur.execute("SELECT task, plan, tools_used, result, success, timestamp FROM experiences ORDER BY id DESC LIMIT ?", (limit,))
        rows = cur.fetchall()
        conn.close()
        out = []
        for r in rows:
            out.append({
                "task": r[0],
                "plan": json.loads(r[1]),
                "tools_used": json.loads(r[2]),
                "result": json.loads(r[3]),
                "success": r[4],
                "timestamp": r[5],
            })
        return out

    def search_similar(self, text: str, top_k: int = 3):
        """??? ?????? ??? embeddings ?????? (?????? ?????)."""
        try:
            from sentence_transformers import SentenceTransformer
            cfg = load_config()
            model = cfg.get("EMBED_MODEL") or "sentence-transformers/all-MiniLM-L6-v2"
            embedder = SentenceTransformer(model)
            records = self.list_recent(limit=200)
            if not records:
                return []
            texts = [r.get("task", "") for r in records]
            vectors = embedder.encode(texts, show_progress_bar=False)
            qv = embedder.encode([text])[0]
            # cosine similarity
            def cos(a, b):
                import math
                dot = sum(x*y for x, y in zip(a, b))
                na = math.sqrt(sum(x*x for x in a))
                nb = math.sqrt(sum(x*x for x in b))
                return dot / (na*nb + 1e-9)
            scored = [(cos(qv, v), r) for v, r in zip(vectors, records)]
            scored.sort(key=lambda x: x[0], reverse=True)
            return [r for _, r in scored[:top_k]]
        except Exception:
            logger.exception("Similarity search failed")
            return []