1from sentence_transformers import SentenceTransformer
2import tensorflow_hub as hub
3from sklearn.metrics.pairwise import cosine_similarity
4from fuzzywuzzy import fuzz
5from langdetect import detect
6from rank_bm25 import BM25Okapi
7import re
8
9# Load all models
10print("Loading CACA models...")
11
12with open('caca_models/bm25_index.pkl', 'rb') as f:
13 bm25 = pickle.load(f)
14
15with open('caca_models/tfidf_vectorizer.pkl', 'rb') as f:
16 tfidf_vectorizer = pickle.load(f)
17
18with open('caca_models/tfidf_matrix.pkl', 'rb') as f:
19 tfidf_matrix = pickle.load(f)
20
21faiss_mini = faiss.read_index('caca_models/faiss_mini_index.bin')
22faiss_mpnet = faiss.read_index('caca_models/faiss_mpnet_index.bin')
23
24sbert_mini_embeddings = np.load('caca_models/sbert_mini_embeddings.npy')
25sbert_mpnet_embeddings = np.load('caca_models/sbert_mpnet_embeddings.npy')
26use_embeddings = np.load('caca_models/use_embeddings.npy')
27
28with open('caca_models/queries.json', 'r', encoding='utf-8') as f:
29 queries = json.load(f)
30
31with open('caca_models/responses.json', 'r', encoding='utf-8') as f:
32 responses = json.load(f)
33
34with open('caca_models/query_patterns.json', 'r', encoding='utf-8') as f:
35 query_patterns = json.load(f)
36
37with open('caca_models/config.json', 'r', encoding='utf-8') as f:
38 config = json.load(f)
39
40with open('caca_models/patterns.json', 'r', encoding='utf-8') as f:
41 PATTERNS = json.load(f)
42
43with open('caca_models/keywords.json', 'r', encoding='utf-8') as f:
44 IMPORTANT_KEYWORDS = json.load(f)
45
46# Load transformer models
47sbert_mini = SentenceTransformer('all-MiniLM-L6-v2')
48sbert_mpnet = SentenceTransformer('paraphrase-mpnet-base-v2')
49use_model = hub.load("https://tfhub.dev/google/universal-sentence-encoder/4")
50
51print("✅ All models loaded!")
52
53# Helper functions
54def preprocess_text(text):
55 text = text.lower()
56 text = re.sub(r'[^\w\s]', ' ', text)
57 text = re.sub(r'\s+', ' ', text).strip()
58 return text
59
60def ngram_similarity(text1, text2, n=3):
61 ngrams1 = set([text1[i:i+n] for i in range(len(text1)-n+1)])
62 ngrams2 = set([text2[i:i+n] for i in range(len(text2)-n+1)])
63 if not ngrams1 or not ngrams2:
64 return 0.0
65 return len(ngrams1 & ngrams2) / len(ngrams1 | ngrams2)
66
67def jaccard_similarity(text1, text2):
68 set1, set2 = set(text1.split()), set(text2.split())
69 if not set1 or not set2:
70 return 0.0
71 return len(set1 & set2) / len(set1 | set2)
72
73def detect_pattern(query):
74 for pattern, tag in PATTERNS.items():
75 if re.search(pattern, query, re.IGNORECASE):
76 return tag
77 return None
78
79def detect_language(text):
80 try:
81 return detect(text)
82 except:
83 return 'id'
84
85# Main chat function
86def chat(query, verbose=False):
87 """Chat with CACA"""
88 query_clean = preprocess_text(query)
89 lang = detect_language(query_clean)
90
91 scores = np.zeros(len(queries))
92 weights = config['techniques']
93
94 # 1. BM25
95 bm25_scores = bm25.get_scores(query_clean.split())
96 bm25_scores = (bm25_scores - bm25_scores.min()) / (bm25_scores.max() - bm25_scores.min() + 1e-10)
97 scores += weights['bm25'] * bm25_scores
98
99 # 2. TF-IDF
100 query_tfidf = tfidf_vectorizer.transform([query_clean])
101 tfidf_scores = cosine_similarity(query_tfidf, tfidf_matrix).flatten()
102 scores += weights['tfidf'] * tfidf_scores
103
104 # 3. SBERT MiniLM
105 query_mini = sbert_mini.encode([query_clean])
106 faiss.normalize_L2(query_mini)
107 D_mini, I_mini = faiss_mini.search(query_mini, len(queries))
108 sbert_mini_scores = np.zeros(len(queries))
109 sbert_mini_scores[I_mini[0]] = D_mini[0]
110 sbert_mini_scores = (sbert_mini_scores - sbert_mini_scores.min()) / (sbert_mini_scores.max() - sbert_mini_scores.min() + 1e-10)
111 scores += weights['sbert_mini'] * sbert_mini_scores
112
113 # 4. SBERT MPNet
114 query_mpnet = sbert_mpnet.encode([query_clean])
115 faiss.normalize_L2(query_mpnet)
116 D_mpnet, I_mpnet = faiss_mpnet.search(query_mpnet, len(queries))
117 sbert_mpnet_scores = np.zeros(len(queries))
118 sbert_mpnet_scores[I_mpnet[0]] = D_mpnet[0]
119 sbert_mpnet_scores = (sbert_mpnet_scores - sbert_mpnet_scores.min()) / (sbert_mpnet_scores.max() - sbert_mpnet_scores.min() + 1e-10)
120 scores += weights['sbert_mpnet'] * sbert_mpnet_scores
121
122 # 5. USE
123 query_use = use_model([query_clean]).numpy()
124 use_scores = cosine_similarity(query_use, use_embeddings).flatten()
125 use_scores = (use_scores - use_scores.min()) / (use_scores.max() - use_scores.min() + 1e-10)
126 scores += weights['use'] * use_scores
127
128 # 6-8. Fuzzy, Jaccard, N-gram (Top 100)
129 top_100_idx = np.argsort(scores)[-100:]
130
131 fuzzy_scores = np.zeros(len(queries))
132 jaccard_scores = np.zeros(len(queries))
133 ngram_scores = np.zeros(len(queries))
134
135 for idx in top_100_idx:
136 fuzzy_scores[idx] = fuzz.ratio(query_clean, queries[idx]) / 100.0
137 jaccard_scores[idx] = jaccard_similarity(query_clean, queries[idx])
138 ngram_scores[idx] = ngram_similarity(query_clean, queries[idx])
139
140 scores += weights['fuzzy'] * fuzzy_scores
141 scores += weights['jaccard'] * jaccard_scores
142 scores += weights['ngram'] * ngram_scores
143
144 # 9. Pattern Matching
145 pattern_tag = detect_pattern(query_clean)
146 pattern_scores = np.zeros(len(queries))
147 if pattern_tag:
148 for i, tag in enumerate(query_patterns):
149 if tag == pattern_tag:
150 pattern_scores[i] = 1.0
151 scores += weights['pattern'] * pattern_scores
152
153 # 10. Keyword Boost
154 keyword_scores = np.zeros(len(queries))
155 query_words = query_clean.split()
156 for i, q in enumerate(queries):
157 boost = sum(1 for kw in IMPORTANT_KEYWORDS if kw in q and kw in query_words)
158 keyword_scores[i] = boost / len(IMPORTANT_KEYWORDS) if IMPORTANT_KEYWORDS else 0
159 scores += weights['keyword_boost'] * keyword_scores
160
161 # Get best match
162 top_idx = np.argmax(scores)
163
164 result = {
165 'response': responses[top_idx],
166 'score': float(scores[top_idx]),
167 'matched_query': queries[top_idx],
168 'detected_language': lang,
169 'pattern': pattern_tag
170 }
171
172 if verbose:
173 result['technique_scores'] = {
174 'bm25': float(bm25_scores[top_idx]),
175 'tfidf': float(tfidf_scores[top_idx]),
176 'sbert_mini': float(sbert_mini_scores[top_idx]),
177 'sbert_mpnet': float(sbert_mpnet_scores[top_idx]),
178 'use': float(use_scores[top_idx]),
179 'fuzzy': float(fuzzy_scores[top_idx]),
180 'jaccard': float(jaccard_scores[top_idx]),
181 'ngram': float(ngram_scores[top_idx]),
182 'pattern': float(pattern_scores[top_idx]),
183 'keyword': float(keyword_scores[top_idx])
184 }
185
186 return result
187
188# Test CACA
189print("\n🤖 Testing CACA...")
190result = chat("Halo CACA, apa kabar?", verbose=True)
191print(f"User: Halo CACA, apa kabar?")
192print(f"CACA: {result['response']}")
193print(f"Score: {result['score']:.4f}")
194print(f"Language: {result['detected_language']}")
195print(f"Pattern: {result['pattern']}")
196
197if 'technique_scores' in result:
198 print("\nTechnique Scores:")
199 for tech, score in sorted(result['technique_scores'].items(), key=lambda x: x[1], reverse=True):
200 print(f" {tech}: {score:.4f}")