mem-llm 1.3.0__py3-none-any.whl → 1.3.2__py3-none-any.whl
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.
Potentially problematic release.
This version of mem-llm might be problematic. Click here for more details.
- mem_llm/__init__.py +9 -2
- mem_llm/config_manager.py +3 -1
- mem_llm/mem_agent.py +400 -16
- mem_llm/memory_db.py +186 -4
- mem_llm/memory_manager.py +10 -1
- mem_llm/response_metrics.py +221 -0
- mem_llm/vector_store.py +278 -0
- {mem_llm-1.3.0.dist-info → mem_llm-1.3.2.dist-info}/METADATA +109 -34
- {mem_llm-1.3.0.dist-info → mem_llm-1.3.2.dist-info}/RECORD +12 -10
- {mem_llm-1.3.0.dist-info → mem_llm-1.3.2.dist-info}/WHEEL +0 -0
- {mem_llm-1.3.0.dist-info → mem_llm-1.3.2.dist-info}/entry_points.txt +0 -0
- {mem_llm-1.3.0.dist-info → mem_llm-1.3.2.dist-info}/top_level.txt +0 -0
mem_llm/memory_db.py
CHANGED
|
@@ -9,15 +9,32 @@ import threading
|
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
from typing import Dict, List, Optional, Tuple
|
|
11
11
|
from pathlib import Path
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# Optional vector store support
|
|
17
|
+
try:
|
|
18
|
+
from .vector_store import create_vector_store, VectorStore
|
|
19
|
+
VECTOR_STORE_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
VECTOR_STORE_AVAILABLE = False
|
|
22
|
+
VectorStore = None
|
|
12
23
|
|
|
13
24
|
|
|
14
25
|
class SQLMemoryManager:
|
|
15
26
|
"""SQLite-based memory management system with thread-safety"""
|
|
16
27
|
|
|
17
|
-
def __init__(self, db_path: str = "memories/memories.db"
|
|
28
|
+
def __init__(self, db_path: str = "memories/memories.db",
|
|
29
|
+
enable_vector_search: bool = False,
|
|
30
|
+
vector_store_type: str = "chroma",
|
|
31
|
+
embedding_model: str = "all-MiniLM-L6-v2"):
|
|
18
32
|
"""
|
|
19
33
|
Args:
|
|
20
34
|
db_path: SQLite database file path
|
|
35
|
+
enable_vector_search: Enable vector/semantic search (optional)
|
|
36
|
+
vector_store_type: Type of vector store ('chroma', etc.)
|
|
37
|
+
embedding_model: Embedding model name (sentence-transformers)
|
|
21
38
|
"""
|
|
22
39
|
self.db_path = Path(db_path)
|
|
23
40
|
|
|
@@ -29,6 +46,35 @@ class SQLMemoryManager:
|
|
|
29
46
|
self.conn = None
|
|
30
47
|
self._lock = threading.RLock() # Reentrant lock for thread safety
|
|
31
48
|
self._init_database()
|
|
49
|
+
|
|
50
|
+
# Vector store (optional)
|
|
51
|
+
self.enable_vector_search = enable_vector_search
|
|
52
|
+
self.vector_store: Optional[VectorStore] = None
|
|
53
|
+
|
|
54
|
+
if enable_vector_search:
|
|
55
|
+
if not VECTOR_STORE_AVAILABLE:
|
|
56
|
+
logger.warning(
|
|
57
|
+
"Vector search requested but dependencies not available. "
|
|
58
|
+
"Install with: pip install chromadb sentence-transformers"
|
|
59
|
+
)
|
|
60
|
+
self.enable_vector_search = False
|
|
61
|
+
else:
|
|
62
|
+
try:
|
|
63
|
+
persist_dir = str(db_dir / "vector_store")
|
|
64
|
+
self.vector_store = create_vector_store(
|
|
65
|
+
store_type=vector_store_type,
|
|
66
|
+
collection_name="knowledge_base",
|
|
67
|
+
persist_directory=persist_dir,
|
|
68
|
+
embedding_model=embedding_model
|
|
69
|
+
)
|
|
70
|
+
if self.vector_store:
|
|
71
|
+
logger.info(f"Vector search enabled: {vector_store_type}")
|
|
72
|
+
else:
|
|
73
|
+
logger.warning("Failed to initialize vector store, falling back to keyword search")
|
|
74
|
+
self.enable_vector_search = False
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"Error initializing vector store: {e}")
|
|
77
|
+
self.enable_vector_search = False
|
|
32
78
|
|
|
33
79
|
def _init_database(self) -> None:
|
|
34
80
|
"""Create database and tables"""
|
|
@@ -312,22 +358,44 @@ class SQLMemoryManager:
|
|
|
312
358
|
""", (category, question, answer,
|
|
313
359
|
json.dumps(keywords or []), priority))
|
|
314
360
|
|
|
361
|
+
kb_id = cursor.lastrowid
|
|
315
362
|
self.conn.commit()
|
|
316
|
-
|
|
363
|
+
|
|
364
|
+
# Sync to vector store if enabled
|
|
365
|
+
if self.enable_vector_search and self.vector_store:
|
|
366
|
+
try:
|
|
367
|
+
self._sync_to_vector_store(kb_id)
|
|
368
|
+
except Exception as e:
|
|
369
|
+
logger.warning(f"Failed to sync KB entry to vector store: {e}")
|
|
370
|
+
|
|
371
|
+
return kb_id
|
|
317
372
|
|
|
318
373
|
def search_knowledge(self, query: str, category: Optional[str] = None,
|
|
319
|
-
limit: int = 5) -> List[Dict]:
|
|
374
|
+
limit: int = 5, use_vector_search: Optional[bool] = None) -> List[Dict]:
|
|
320
375
|
"""
|
|
321
|
-
Bilgi bankasında arama yapar (
|
|
376
|
+
Bilgi bankasında arama yapar (keyword matching veya semantic search)
|
|
322
377
|
|
|
323
378
|
Args:
|
|
324
379
|
query: Arama sorgusu
|
|
325
380
|
category: Kategori filtresi (opsiyonel)
|
|
326
381
|
limit: Maksimum sonuç sayısı
|
|
382
|
+
use_vector_search: Force vector search (None = auto-detect)
|
|
327
383
|
|
|
328
384
|
Returns:
|
|
329
385
|
Bulunan kayıtlar
|
|
330
386
|
"""
|
|
387
|
+
# Use vector search if enabled and available
|
|
388
|
+
if use_vector_search is None:
|
|
389
|
+
use_vector_search = self.enable_vector_search
|
|
390
|
+
|
|
391
|
+
if use_vector_search and self.vector_store:
|
|
392
|
+
return self._vector_search(query, category, limit)
|
|
393
|
+
else:
|
|
394
|
+
return self._keyword_search(query, category, limit)
|
|
395
|
+
|
|
396
|
+
def _keyword_search(self, query: str, category: Optional[str] = None,
|
|
397
|
+
limit: int = 5) -> List[Dict]:
|
|
398
|
+
"""Traditional keyword-based search"""
|
|
331
399
|
cursor = self.conn.cursor()
|
|
332
400
|
|
|
333
401
|
# Extract important keywords from query (remove question words)
|
|
@@ -378,6 +446,120 @@ class SQLMemoryManager:
|
|
|
378
446
|
|
|
379
447
|
return [dict(row) for row in cursor.fetchall()]
|
|
380
448
|
|
|
449
|
+
def _vector_search(self, query: str, category: Optional[str] = None,
|
|
450
|
+
limit: int = 5) -> List[Dict]:
|
|
451
|
+
"""Vector-based semantic search"""
|
|
452
|
+
if not self.vector_store:
|
|
453
|
+
return []
|
|
454
|
+
|
|
455
|
+
# Prepare metadata filter
|
|
456
|
+
filter_metadata = None
|
|
457
|
+
if category:
|
|
458
|
+
filter_metadata = {"category": category}
|
|
459
|
+
|
|
460
|
+
# Search in vector store
|
|
461
|
+
vector_results = self.vector_store.search(
|
|
462
|
+
query=query,
|
|
463
|
+
limit=limit * 2, # Get more results to filter by category if needed
|
|
464
|
+
filter_metadata=filter_metadata
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Map vector results back to KB format
|
|
468
|
+
results = []
|
|
469
|
+
for result in vector_results[:limit]:
|
|
470
|
+
# Extract metadata
|
|
471
|
+
metadata = result.get('metadata', {})
|
|
472
|
+
|
|
473
|
+
results.append({
|
|
474
|
+
'category': metadata.get('category', ''),
|
|
475
|
+
'question': metadata.get('question', ''),
|
|
476
|
+
'answer': result.get('text', ''),
|
|
477
|
+
'priority': metadata.get('priority', 0),
|
|
478
|
+
'score': result.get('score', 0.0), # Similarity score
|
|
479
|
+
'vector_search': True
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
return results
|
|
483
|
+
|
|
484
|
+
def _sync_to_vector_store(self, kb_id: int) -> None:
|
|
485
|
+
"""Sync a single KB entry to vector store"""
|
|
486
|
+
if not self.vector_store:
|
|
487
|
+
return
|
|
488
|
+
|
|
489
|
+
cursor = self.conn.cursor()
|
|
490
|
+
cursor.execute("""
|
|
491
|
+
SELECT id, category, question, answer, keywords, priority
|
|
492
|
+
FROM knowledge_base
|
|
493
|
+
WHERE id = ?
|
|
494
|
+
""", (kb_id,))
|
|
495
|
+
|
|
496
|
+
row = cursor.fetchone()
|
|
497
|
+
if row:
|
|
498
|
+
doc = {
|
|
499
|
+
'id': str(row['id']),
|
|
500
|
+
'text': f"{row['question']}\n{row['answer']}", # Combine for better search
|
|
501
|
+
'metadata': {
|
|
502
|
+
'category': row['category'],
|
|
503
|
+
'question': row['question'],
|
|
504
|
+
'answer': row['answer'],
|
|
505
|
+
'keywords': row['keywords'],
|
|
506
|
+
'priority': row['priority'],
|
|
507
|
+
'kb_id': row['id']
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
self.vector_store.add_documents([doc])
|
|
511
|
+
|
|
512
|
+
def sync_all_kb_to_vector_store(self) -> int:
|
|
513
|
+
"""
|
|
514
|
+
Sync all existing KB entries to vector store
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Number of entries synced
|
|
518
|
+
"""
|
|
519
|
+
if not self.vector_store:
|
|
520
|
+
return 0
|
|
521
|
+
|
|
522
|
+
cursor = self.conn.cursor()
|
|
523
|
+
cursor.execute("""
|
|
524
|
+
SELECT id, category, question, answer, keywords, priority
|
|
525
|
+
FROM knowledge_base
|
|
526
|
+
WHERE active = 1
|
|
527
|
+
""")
|
|
528
|
+
|
|
529
|
+
rows = cursor.fetchall()
|
|
530
|
+
documents = []
|
|
531
|
+
|
|
532
|
+
for row in rows:
|
|
533
|
+
doc = {
|
|
534
|
+
'id': str(row['id']),
|
|
535
|
+
'text': f"{row['question']}\n{row['answer']}",
|
|
536
|
+
'metadata': {
|
|
537
|
+
'category': row['category'],
|
|
538
|
+
'question': row['question'],
|
|
539
|
+
'answer': row['answer'],
|
|
540
|
+
'keywords': row['keywords'],
|
|
541
|
+
'priority': row['priority'],
|
|
542
|
+
'kb_id': row['id']
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
documents.append(doc)
|
|
546
|
+
|
|
547
|
+
if documents:
|
|
548
|
+
try:
|
|
549
|
+
# Add in batches for better performance
|
|
550
|
+
batch_size = 100
|
|
551
|
+
for i in range(0, len(documents), batch_size):
|
|
552
|
+
batch = documents[i:i + batch_size]
|
|
553
|
+
self.vector_store.add_documents(batch)
|
|
554
|
+
logger.debug(f"Synced {len(batch)} KB entries to vector store")
|
|
555
|
+
|
|
556
|
+
logger.info(f"Synced {len(documents)} KB entries to vector store")
|
|
557
|
+
except Exception as e:
|
|
558
|
+
logger.error(f"Error syncing KB to vector store: {e}")
|
|
559
|
+
return 0
|
|
560
|
+
|
|
561
|
+
return len(documents)
|
|
562
|
+
|
|
381
563
|
def get_statistics(self) -> Dict:
|
|
382
564
|
"""
|
|
383
565
|
Genel istatistikleri döndürür
|
mem_llm/memory_manager.py
CHANGED
|
@@ -43,7 +43,16 @@ class MemoryManager:
|
|
|
43
43
|
with open(user_file, 'r', encoding='utf-8') as f:
|
|
44
44
|
data = json.load(f)
|
|
45
45
|
self.conversations[user_id] = data.get('conversations', [])
|
|
46
|
-
|
|
46
|
+
profile = data.get('profile', {})
|
|
47
|
+
|
|
48
|
+
# Parse preferences if it's a JSON string (legacy format)
|
|
49
|
+
if isinstance(profile.get('preferences'), str):
|
|
50
|
+
try:
|
|
51
|
+
profile['preferences'] = json.loads(profile['preferences'])
|
|
52
|
+
except:
|
|
53
|
+
profile['preferences'] = {}
|
|
54
|
+
|
|
55
|
+
self.user_profiles[user_id] = profile
|
|
47
56
|
return data
|
|
48
57
|
else:
|
|
49
58
|
# Create empty memory for new user
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Response Metrics Module
|
|
3
|
+
=======================
|
|
4
|
+
|
|
5
|
+
Tracks and analyzes LLM response quality metrics including:
|
|
6
|
+
- Response latency
|
|
7
|
+
- Confidence scoring
|
|
8
|
+
- Knowledge base usage
|
|
9
|
+
- Source tracking
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, asdict
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Dict, Any, Optional, List
|
|
15
|
+
import json
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ChatResponse:
|
|
20
|
+
"""
|
|
21
|
+
Comprehensive response object with quality metrics
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
text: The actual response text
|
|
25
|
+
confidence: Confidence score 0.0-1.0 (higher = more confident)
|
|
26
|
+
source: Response source ("knowledge_base", "model", "tool", "hybrid")
|
|
27
|
+
latency: Response time in milliseconds
|
|
28
|
+
timestamp: When the response was generated
|
|
29
|
+
kb_results_count: Number of KB results used (0 if none)
|
|
30
|
+
metadata: Additional context (model name, temperature, etc.)
|
|
31
|
+
"""
|
|
32
|
+
text: str
|
|
33
|
+
confidence: float
|
|
34
|
+
source: str
|
|
35
|
+
latency: float
|
|
36
|
+
timestamp: datetime
|
|
37
|
+
kb_results_count: int = 0
|
|
38
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
39
|
+
|
|
40
|
+
def __post_init__(self):
|
|
41
|
+
"""Validate metrics after initialization"""
|
|
42
|
+
# Ensure confidence is in valid range
|
|
43
|
+
if not 0.0 <= self.confidence <= 1.0:
|
|
44
|
+
raise ValueError(f"Confidence must be between 0.0 and 1.0, got {self.confidence}")
|
|
45
|
+
|
|
46
|
+
# Validate source
|
|
47
|
+
valid_sources = ["knowledge_base", "model", "tool", "hybrid"]
|
|
48
|
+
if self.source not in valid_sources:
|
|
49
|
+
raise ValueError(f"Source must be one of {valid_sources}, got {self.source}")
|
|
50
|
+
|
|
51
|
+
# Ensure latency is positive
|
|
52
|
+
if self.latency < 0:
|
|
53
|
+
raise ValueError(f"Latency cannot be negative, got {self.latency}")
|
|
54
|
+
|
|
55
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
56
|
+
"""Convert to dictionary for JSON serialization"""
|
|
57
|
+
data = asdict(self)
|
|
58
|
+
data['timestamp'] = self.timestamp.isoformat()
|
|
59
|
+
return data
|
|
60
|
+
|
|
61
|
+
def to_json(self) -> str:
|
|
62
|
+
"""Convert to JSON string"""
|
|
63
|
+
return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'ChatResponse':
|
|
67
|
+
"""Create ChatResponse from dictionary"""
|
|
68
|
+
data['timestamp'] = datetime.fromisoformat(data['timestamp'])
|
|
69
|
+
return cls(**data)
|
|
70
|
+
|
|
71
|
+
def get_quality_label(self) -> str:
|
|
72
|
+
"""Get human-readable quality label"""
|
|
73
|
+
if self.confidence >= 0.90:
|
|
74
|
+
return "Excellent"
|
|
75
|
+
elif self.confidence >= 0.80:
|
|
76
|
+
return "High"
|
|
77
|
+
elif self.confidence >= 0.65:
|
|
78
|
+
return "Medium"
|
|
79
|
+
elif self.confidence >= 0.50:
|
|
80
|
+
return "Low"
|
|
81
|
+
else:
|
|
82
|
+
return "Very Low"
|
|
83
|
+
|
|
84
|
+
def is_fast(self, threshold_ms: float = 1000.0) -> bool:
|
|
85
|
+
"""Check if response was fast (< threshold)"""
|
|
86
|
+
return self.latency < threshold_ms
|
|
87
|
+
|
|
88
|
+
def __str__(self) -> str:
|
|
89
|
+
"""Human-readable string representation"""
|
|
90
|
+
return (
|
|
91
|
+
f"ChatResponse(text_length={len(self.text)}, "
|
|
92
|
+
f"confidence={self.confidence:.2f}, "
|
|
93
|
+
f"source={self.source}, "
|
|
94
|
+
f"latency={self.latency:.0f}ms, "
|
|
95
|
+
f"quality={self.get_quality_label()})"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ResponseMetricsAnalyzer:
|
|
100
|
+
"""Analyzes and aggregates response metrics over time"""
|
|
101
|
+
|
|
102
|
+
def __init__(self):
|
|
103
|
+
self.metrics_history: List[ChatResponse] = []
|
|
104
|
+
|
|
105
|
+
def add_metric(self, response: ChatResponse) -> None:
|
|
106
|
+
"""Add a response metric to history"""
|
|
107
|
+
self.metrics_history.append(response)
|
|
108
|
+
|
|
109
|
+
def get_average_latency(self, last_n: Optional[int] = None) -> float:
|
|
110
|
+
"""Calculate average latency for last N responses"""
|
|
111
|
+
metrics = self.metrics_history[-last_n:] if last_n else self.metrics_history
|
|
112
|
+
if not metrics:
|
|
113
|
+
return 0.0
|
|
114
|
+
return sum(m.latency for m in metrics) / len(metrics)
|
|
115
|
+
|
|
116
|
+
def get_average_confidence(self, last_n: Optional[int] = None) -> float:
|
|
117
|
+
"""Calculate average confidence for last N responses"""
|
|
118
|
+
metrics = self.metrics_history[-last_n:] if last_n else self.metrics_history
|
|
119
|
+
if not metrics:
|
|
120
|
+
return 0.0
|
|
121
|
+
return sum(m.confidence for m in metrics) / len(metrics)
|
|
122
|
+
|
|
123
|
+
def get_kb_usage_rate(self, last_n: Optional[int] = None) -> float:
|
|
124
|
+
"""Calculate knowledge base usage rate (0.0-1.0)"""
|
|
125
|
+
metrics = self.metrics_history[-last_n:] if last_n else self.metrics_history
|
|
126
|
+
if not metrics:
|
|
127
|
+
return 0.0
|
|
128
|
+
kb_used = sum(1 for m in metrics if m.kb_results_count > 0)
|
|
129
|
+
return kb_used / len(metrics)
|
|
130
|
+
|
|
131
|
+
def get_source_distribution(self, last_n: Optional[int] = None) -> Dict[str, int]:
|
|
132
|
+
"""Get distribution of response sources"""
|
|
133
|
+
metrics = self.metrics_history[-last_n:] if last_n else self.metrics_history
|
|
134
|
+
distribution = {}
|
|
135
|
+
for metric in metrics:
|
|
136
|
+
distribution[metric.source] = distribution.get(metric.source, 0) + 1
|
|
137
|
+
return distribution
|
|
138
|
+
|
|
139
|
+
def get_summary(self, last_n: Optional[int] = None) -> Dict[str, Any]:
|
|
140
|
+
"""Get comprehensive metrics summary"""
|
|
141
|
+
metrics = self.metrics_history[-last_n:] if last_n else self.metrics_history
|
|
142
|
+
|
|
143
|
+
if not metrics:
|
|
144
|
+
return {
|
|
145
|
+
"total_responses": 0,
|
|
146
|
+
"avg_latency_ms": 0.0,
|
|
147
|
+
"avg_confidence": 0.0,
|
|
148
|
+
"kb_usage_rate": 0.0,
|
|
149
|
+
"source_distribution": {},
|
|
150
|
+
"fast_response_rate": 0.0
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fast_responses = sum(1 for m in metrics if m.is_fast())
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
"total_responses": len(metrics),
|
|
157
|
+
"avg_latency_ms": round(self.get_average_latency(last_n), 2),
|
|
158
|
+
"avg_confidence": round(self.get_average_confidence(last_n), 3),
|
|
159
|
+
"kb_usage_rate": round(self.get_kb_usage_rate(last_n), 3),
|
|
160
|
+
"source_distribution": self.get_source_distribution(last_n),
|
|
161
|
+
"fast_response_rate": round(fast_responses / len(metrics), 3),
|
|
162
|
+
"quality_distribution": self._get_quality_distribution(metrics)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
def _get_quality_distribution(self, metrics: List[ChatResponse]) -> Dict[str, int]:
|
|
166
|
+
"""Get distribution of quality labels"""
|
|
167
|
+
distribution = {}
|
|
168
|
+
for metric in metrics:
|
|
169
|
+
quality = metric.get_quality_label()
|
|
170
|
+
distribution[quality] = distribution.get(quality, 0) + 1
|
|
171
|
+
return distribution
|
|
172
|
+
|
|
173
|
+
def clear_history(self) -> None:
|
|
174
|
+
"""Clear all metrics history"""
|
|
175
|
+
self.metrics_history.clear()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def calculate_confidence(
|
|
179
|
+
kb_results_count: int,
|
|
180
|
+
temperature: float,
|
|
181
|
+
used_memory: bool,
|
|
182
|
+
response_length: int
|
|
183
|
+
) -> float:
|
|
184
|
+
"""
|
|
185
|
+
Calculate confidence score based on multiple factors
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
kb_results_count: Number of KB results used
|
|
189
|
+
temperature: Model temperature setting
|
|
190
|
+
used_memory: Whether conversation memory was used
|
|
191
|
+
response_length: Length of response in characters
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Confidence score between 0.0 and 1.0
|
|
195
|
+
"""
|
|
196
|
+
base_confidence = 0.50
|
|
197
|
+
|
|
198
|
+
# KB contribution (0-0.35)
|
|
199
|
+
if kb_results_count > 0:
|
|
200
|
+
kb_boost = min(0.35, 0.10 + (kb_results_count * 0.05))
|
|
201
|
+
base_confidence += kb_boost
|
|
202
|
+
|
|
203
|
+
# Memory contribution (0-0.10)
|
|
204
|
+
if used_memory:
|
|
205
|
+
base_confidence += 0.10
|
|
206
|
+
|
|
207
|
+
# Temperature factor (lower temp = higher confidence)
|
|
208
|
+
# Temperature usually 0.0-1.0, we give 0-0.15 boost
|
|
209
|
+
temp_factor = (1.0 - min(temperature, 1.0)) * 0.15
|
|
210
|
+
base_confidence += temp_factor
|
|
211
|
+
|
|
212
|
+
# Response length factor (very short = lower confidence)
|
|
213
|
+
# Penalize very short responses (< 20 chars)
|
|
214
|
+
if response_length < 20:
|
|
215
|
+
base_confidence *= 0.8
|
|
216
|
+
elif response_length < 50:
|
|
217
|
+
base_confidence *= 0.9
|
|
218
|
+
|
|
219
|
+
# Ensure confidence stays in valid range
|
|
220
|
+
return max(0.0, min(1.0, base_confidence))
|
|
221
|
+
|