Reality123b commited on
Commit
e068a01
·
verified ·
1 Parent(s): feab500

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +734 -127
app.py CHANGED
@@ -1,19 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import FastAPI, HTTPException
2
  from fastapi.responses import StreamingResponse
3
  from pydantic import BaseModel
4
  from transformers import pipeline, TextStreamer
5
  import torch
6
- import re
7
- import threading
8
- import queue
9
- import time
10
- import random
11
- import duckduckgo_search
12
- from duckduckgo_search import DDGS
 
 
 
13
 
14
- # ------------------------
15
- # Config
16
- # ------------------------
17
  MAIN_MODEL = "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B"
18
  QUERY_MODEL = "HuggingFaceTB/SmolLM2-360M-Instruct"
19
  SUMMARY_MODEL = "HuggingFaceTB/SmolLM2-360M-Instruct"
@@ -21,155 +34,749 @@ DEVICE = 0 if torch.cuda.is_available() else "cpu"
21
 
22
  DEEPSEEK_MAX_TOKENS = 64000
23
  SMOLLM_MAX_TOKENS = 4192
24
-
25
  KG_UPDATE_INTERVAL = 60 # seconds
26
- knowledge_graph = {}
 
27
 
28
- # ------------------------
29
- # API + Models Init
30
- # ------------------------
31
- app = FastAPI()
32
 
33
- print("[Init] Loading models...")
34
- generator = pipeline("text-generation", model=MAIN_MODEL, device=DEVICE)
35
- query_generator = pipeline("text-generation", model=QUERY_MODEL, device=DEVICE)
36
- summarizer = pipeline("text-generation", model=SUMMARY_MODEL, device=DEVICE)
37
- print("[Init] Models loaded.")
 
 
 
 
 
 
 
 
 
 
38
 
39
  class ModelInput(BaseModel):
40
  prompt: str
41
  max_new_tokens: int = DEEPSEEK_MAX_TOKENS
42
 
43
- # ------------------------
44
- # KG Functions
45
- # ------------------------
46
- def generate_dynamic_query():
47
- prompt = (
48
- "Generate a short, specific search query about technology, startups, AI, or science. "
49
- "Be creative, realistic, and output only the query with no extra words."
50
- )
51
- output = query_generator(
52
- prompt,
53
- max_new_tokens=SMOLLM_MAX_TOKENS,
54
- truncation=True,
55
- do_sample=True,
56
- temperature=1.0,
57
- top_p=0.9
58
- )[0]["generated_text"].strip()
59
- query = output.split("\n")[0]
60
- query = re.sub(r"^Generate.*?:", "", query).strip()
61
- return query
62
-
63
- def summarize_text(text):
64
- summary_prompt = f"Summarize this in 3 concise sentences:\n\n{text}"
65
- return summarizer(
66
- summary_prompt,
67
- max_new_tokens=SMOLLM_MAX_TOKENS,
68
- truncation=True
69
- )[0]["generated_text"].strip()
70
-
71
- def search_ddg(query):
72
- with DDGS() as ddgs:
73
- results = list(ddgs.text(query, max_results=5))
74
- combined = " ".join(r["body"] for r in results if "body" in r)
75
- return combined.strip()
76
-
77
- def kg_updater():
78
- while True:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  try:
80
- query = generate_dynamic_query()
81
- print(f"[KG Updater] Searching DDG for query: {query}")
82
- raw_text = search_ddg(query)
83
- if len(raw_text) < 50:
84
- print("[KG Updater] Too little info found, retrying next cycle...")
85
- else:
86
- summary = summarize_text(raw_text)
87
- knowledge_graph[query] = summary
88
- print(f"[KG Updater] Knowledge graph updated for query: {query}")
 
 
 
 
 
 
 
 
 
 
 
89
  except Exception as e:
90
- print(f"[KG Updater ERROR] {e}")
91
- time.sleep(KG_UPDATE_INTERVAL)
92
-
93
- threading.Thread(target=kg_updater, daemon=True).start()
94
-
95
- def inject_relevant_kg(prompt):
96
- relevant_info = ""
97
- for k, v in knowledge_graph.items():
98
- if any(word.lower() in prompt.lower() for word in k.split()):
99
- relevant_info += f"\n[KG:{k}] {v}"
100
- if relevant_info:
101
- return f"{prompt}\n\nRelevant background info:\n{relevant_info}"
102
- return prompt
103
-
104
- # ------------------------
105
- # Streaming Generation
106
- # ------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  @app.post("/generate/stream")
108
- async def generate_stream(input: ModelInput):
 
109
  q = queue.Queue()
110
-
111
  def run_generation():
112
  try:
113
- tokenizer = generator.tokenizer
114
-
115
- def enqueue_token(token_ids):
 
 
 
 
 
 
116
  if hasattr(token_ids, "tolist"):
117
  token_ids = token_ids.tolist()
118
- text = tokenizer.decode(token_ids, skip_special_tokens=True)
119
  q.put(text)
120
-
121
- streamer = TextStreamer(tokenizer, skip_prompt=True)
122
- streamer.put = enqueue_token # intercept tokens
123
-
124
- enriched_prompt = inject_relevant_kg(input.prompt)
125
  generator(
126
  enriched_prompt,
127
- max_new_tokens=min(input.max_new_tokens, DEEPSEEK_MAX_TOKENS),
128
- do_sample=False,
129
- streamer=streamer
 
 
 
130
  )
 
131
  except Exception as e:
132
  q.put(f"[ERROR] {e}")
133
  finally:
134
- q.put(None)
135
-
 
136
  threading.Thread(target=run_generation, daemon=True).start()
137
-
138
  async def event_generator():
139
  while True:
140
- token = q.get()
141
- if token is None:
 
 
 
 
 
142
  break
143
- yield token
144
-
145
  return StreamingResponse(event_generator(), media_type="text/plain")
146
 
147
- # ------------------------
148
- # Non-stream endpoint
149
- # ------------------------
150
- @app.post("/generate")
151
- async def generate_text(input: ModelInput):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  try:
153
- enriched_prompt = inject_relevant_kg(input.prompt)
154
- output = generator(
155
- enriched_prompt,
156
- max_new_tokens=min(input.max_new_tokens, DEEPSEEK_MAX_TOKENS),
157
- do_sample=False
158
- )[0]["generated_text"]
159
- return {"generated_text": output}
160
  except Exception as e:
161
- raise HTTPException(status_code=500, detail=str(e))
162
 
163
- # ------------------------
164
- # KG endpoint
165
- # ------------------------
166
- @app.get("/knowledge")
167
- async def get_knowledge():
168
- return knowledge_graph
 
 
 
 
 
 
 
 
 
169
 
170
- # ------------------------
171
- # Root endpoint
172
- # ------------------------
173
  @app.get("/")
174
  async def root():
175
- return {"message": "Welcome to the Streaming Model API with KG Updater!"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import random
5
+ import re
6
+ import time
7
+ import threading
8
+ import queue
9
+ from datetime import datetime, timedelta
10
+ from typing import Dict, List, Optional, Any
11
+ from dataclasses import dataclass
12
+ from concurrent.futures import ThreadPoolExecutor
13
+
14
  from fastapi import FastAPI, HTTPException
15
  from fastapi.responses import StreamingResponse
16
  from pydantic import BaseModel
17
  from transformers import pipeline, TextStreamer
18
  import torch
19
+ import requests
20
+ from urllib.parse import quote
21
+ import networkx as nx
22
+ from sklearn.feature_extraction.text import TfidfVectorizer
23
+ from sklearn.metrics.pairwise import cosine_similarity
24
+ import numpy as np
25
+
26
+ # ========================================================================================
27
+ # CONFIGURATION
28
+ # ========================================================================================
29
 
 
 
 
30
  MAIN_MODEL = "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B"
31
  QUERY_MODEL = "HuggingFaceTB/SmolLM2-360M-Instruct"
32
  SUMMARY_MODEL = "HuggingFaceTB/SmolLM2-360M-Instruct"
 
34
 
35
  DEEPSEEK_MAX_TOKENS = 64000
36
  SMOLLM_MAX_TOKENS = 4192
 
37
  KG_UPDATE_INTERVAL = 60 # seconds
38
+ SEARCH_TIMEOUT = 10
39
+ MAX_RETRIES = 3
40
 
41
+ # ========================================================================================
42
+ # CORE DATA STRUCTURES
43
+ # ========================================================================================
 
44
 
45
+ @dataclass
46
+ class KnowledgeEntry:
47
+ query: str
48
+ content: str
49
+ summary: str
50
+ timestamp: datetime
51
+ relevance_score: float = 0.0
52
+ source_urls: List[str] = None
53
+
54
+ def __post_init__(self):
55
+ if self.source_urls is None:
56
+ self.source_urls = []
57
+
58
+ def is_expired(self, hours: int = 24) -> bool:
59
+ return datetime.now() - self.timestamp > timedelta(hours=hours)
60
 
61
  class ModelInput(BaseModel):
62
  prompt: str
63
  max_new_tokens: int = DEEPSEEK_MAX_TOKENS
64
 
65
+ # ========================================================================================
66
+ # SEARCH ENGINE WITH FALLBACKS
67
+ # ========================================================================================
68
+
69
+ class MultiSearchEngine:
70
+ """Robust search engine with multiple backends and fallbacks"""
71
+
72
+ def __init__(self):
73
+ self.search_engines = [
74
+ self._search_duckduckgo,
75
+ self._search_searx,
76
+ self._search_bing_fallback,
77
+ ]
78
+ self.current_engine = 0
79
+
80
+ def search(self, query: str, max_results: int = 5) -> List[Dict[str, str]]:
81
+ """Search with automatic fallback to different engines"""
82
+ for attempt in range(len(self.search_engines)):
83
+ try:
84
+ engine = self.search_engines[self.current_engine]
85
+ results = engine(query, max_results)
86
+ if results:
87
+ return results
88
+ except Exception as e:
89
+ logging.warning(f"Search engine {self.current_engine} failed: {e}")
90
+
91
+ # Rotate to next engine
92
+ self.current_engine = (self.current_engine + 1) % len(self.search_engines)
93
+
94
+ logging.error("All search engines failed")
95
+ return []
96
+
97
+ def _search_duckduckgo(self, query: str, max_results: int) -> List[Dict[str, str]]:
98
+ """DuckDuckGo search with rate limit handling"""
99
+ try:
100
+ from duckduckgo_search import DDGS
101
+ with DDGS() as ddgs:
102
+ results = []
103
+ for result in ddgs.text(query, max_results=max_results):
104
+ results.append({
105
+ 'title': result.get('title', ''),
106
+ 'body': result.get('body', ''),
107
+ 'url': result.get('href', ''),
108
+ })
109
+ return results
110
+ except Exception as e:
111
+ if "ratelimit" in str(e).lower():
112
+ time.sleep(random.uniform(5, 15)) # Random backoff
113
+ raise e
114
+
115
+ def _search_searx(self, query: str, max_results: int) -> List[Dict[str, str]]:
116
+ """Searx instance search"""
117
+ searx_instances = [
118
+ "https://searx.be",
119
+ "https://searx.info",
120
+ "https://search.privacy.sexy"
121
+ ]
122
+
123
+ for instance in searx_instances:
124
+ try:
125
+ url = f"{instance}/search"
126
+ params = {
127
+ 'q': query,
128
+ 'format': 'json',
129
+ 'categories': 'general'
130
+ }
131
+ response = requests.get(url, params=params, timeout=SEARCH_TIMEOUT)
132
+ if response.status_code == 200:
133
+ data = response.json()
134
+ results = []
135
+ for item in data.get('results', [])[:max_results]:
136
+ results.append({
137
+ 'title': item.get('title', ''),
138
+ 'body': item.get('content', ''),
139
+ 'url': item.get('url', ''),
140
+ })
141
+ return results
142
+ except Exception:
143
+ continue
144
+ raise Exception("All Searx instances failed")
145
+
146
+ def _search_bing_fallback(self, query: str, max_results: int) -> List[Dict[str, str]]:
147
+ """Fallback search using a simple web scraping approach"""
148
+ try:
149
+ # This would require additional implementation with web scraping
150
+ # For now, return empty to avoid dependency issues
151
+ return []
152
+ except Exception:
153
+ return []
154
+
155
+ # ========================================================================================
156
+ # AUTONOMOUS QUERY GENERATOR
157
+ # ========================================================================================
158
+
159
+ class AutonomousQueryGenerator:
160
+ """Generates diverse, realistic queries autonomously"""
161
+
162
+ def __init__(self, model_pipeline):
163
+ self.model = model_pipeline
164
+ self.query_history = set()
165
+ self.domain_templates = [
166
+ "latest breakthrough in {domain}",
167
+ "new {domain} research 2025",
168
+ "{domain} startup funding news",
169
+ "emerging trends in {domain}",
170
+ "AI applications in {domain}",
171
+ "{domain} market analysis 2025",
172
+ "innovative {domain} technology",
173
+ "{domain} industry developments"
174
+ ]
175
+ self.domains = [
176
+ "artificial intelligence", "machine learning", "robotics", "biotechnology",
177
+ "quantum computing", "blockchain", "cybersecurity", "fintech", "healthtech",
178
+ "edtech", "cleantech", "spacetech", "autonomous vehicles", "IoT", "5G",
179
+ "augmented reality", "virtual reality", "nanotechnology", "genomics",
180
+ "renewable energy", "smart cities", "edge computing", "cloud computing"
181
+ ]
182
+
183
+ def generate_query(self) -> str:
184
+ """Generate a unique, contextual query"""
185
+ max_attempts = 10
186
+
187
+ for _ in range(max_attempts):
188
+ # Choose generation strategy
189
+ strategy = random.choice([
190
+ self._generate_templated_query,
191
+ self._generate_model_query,
192
+ self._generate_trend_query,
193
+ self._generate_comparative_query
194
+ ])
195
+
196
+ query = strategy()
197
+
198
+ # Ensure uniqueness and quality
199
+ if query and len(query.split()) >= 3 and query not in self.query_history:
200
+ self.query_history.add(query)
201
+ # Limit history size
202
+ if len(self.query_history) > 1000:
203
+ self.query_history = set(list(self.query_history)[-800:])
204
+ return query
205
+
206
+ # Fallback to simple template
207
+ domain = random.choice(self.domains)
208
+ template = random.choice(self.domain_templates)
209
+ return template.format(domain=domain)
210
+
211
+ def _generate_templated_query(self) -> str:
212
+ """Generate query from templates"""
213
+ domain = random.choice(self.domains)
214
+ template = random.choice(self.domain_templates)
215
+ return template.format(domain=domain)
216
+
217
+ def _generate_model_query(self) -> str:
218
+ """Generate query using language model"""
219
+ prompts = [
220
+ "Generate a specific search query about cutting-edge technology:",
221
+ "What's a trending topic in AI or science right now? (one query only):",
222
+ "Create a search query about startup innovation:",
223
+ "Generate a query about recent scientific breakthroughs:"
224
+ ]
225
+
226
+ prompt = random.choice(prompts)
227
+
228
+ try:
229
+ output = self.model(
230
+ prompt,
231
+ max_new_tokens=50,
232
+ do_sample=True,
233
+ temperature=0.8,
234
+ top_p=0.9,
235
+ pad_token_id=self.model.tokenizer.eos_token_id
236
+ )[0]["generated_text"]
237
+
238
+ # Extract query from output
239
+ query = output.replace(prompt, "").strip()
240
+ query = re.sub(r'^["\'\-\s]*', '', query)
241
+ query = re.sub(r'["\'\.\s]*$', '', query)
242
+ query = query.split('\n')[0].strip()
243
+
244
+ return query if len(query) > 10 else ""
245
+
246
+ except Exception as e:
247
+ logging.warning(f"Model query generation failed: {e}")
248
+ return ""
249
+
250
+ def _generate_trend_query(self) -> str:
251
+ """Generate queries about current trends"""
252
+ trend_terms = ["2025", "latest", "new", "emerging", "breakthrough", "innovation"]
253
+ domain = random.choice(self.domains)
254
+ trend = random.choice(trend_terms)
255
+ return f"{trend} {domain} developments"
256
+
257
+ def _generate_comparative_query(self) -> str:
258
+ """Generate comparative queries"""
259
+ comparisons = [
260
+ "{} vs {} comparison",
261
+ "advantages of {} over {}",
262
+ "{} and {} integration",
263
+ "{} versus {} market share"
264
+ ]
265
+ domains = random.sample(self.domains, 2)
266
+ template = random.choice(comparisons)
267
+ return template.format(domains[0], domains[1])
268
+
269
+ # ========================================================================================
270
+ # INTELLIGENT KNOWLEDGE GRAPH
271
+ # ========================================================================================
272
+
273
+ class IntelligentKnowledgeGraph:
274
+ """Advanced knowledge graph with semantic understanding"""
275
+
276
+ def __init__(self):
277
+ self.graph = nx.DiGraph()
278
+ self.entries: Dict[str, KnowledgeEntry] = {}
279
+ self.vectorizer = TfidfVectorizer(max_features=1000, stop_words='english')
280
+ self.query_vectors = None
281
+ self.vector_queries = []
282
+
283
+ def add_knowledge(self, entry: KnowledgeEntry):
284
+ """Add knowledge entry with semantic indexing"""
285
+ self.entries[entry.query] = entry
286
+ self.graph.add_node(entry.query,
287
+ timestamp=entry.timestamp,
288
+ summary=entry.summary)
289
+
290
+ # Update semantic vectors
291
+ self._update_vectors()
292
+
293
+ # Create semantic connections
294
+ self._create_semantic_connections(entry.query)
295
+
296
+ def _update_vectors(self):
297
+ """Update TF-IDF vectors for semantic search"""
298
+ try:
299
+ queries_and_summaries = [
300
+ f"{query} {entry.summary}"
301
+ for query, entry in self.entries.items()
302
+ ]
303
+
304
+ if len(queries_and_summaries) > 0:
305
+ self.query_vectors = self.vectorizer.fit_transform(queries_and_summaries)
306
+ self.vector_queries = list(self.entries.keys())
307
+ except Exception as e:
308
+ logging.warning(f"Vector update failed: {e}")
309
+
310
+ def _create_semantic_connections(self, new_query: str):
311
+ """Create edges between semantically similar entries"""
312
+ if self.query_vectors is None or len(self.vector_queries) < 2:
313
+ return
314
+
315
+ try:
316
+ new_text = f"{new_query} {self.entries[new_query].summary}"
317
+ new_vector = self.vectorizer.transform([new_text])
318
+
319
+ similarities = cosine_similarity(new_vector, self.query_vectors)[0]
320
+
321
+ for i, similarity in enumerate(similarities):
322
+ other_query = self.vector_queries[i]
323
+ if other_query != new_query and similarity > 0.3:
324
+ self.graph.add_edge(new_query, other_query, weight=similarity)
325
+ self.graph.add_edge(other_query, new_query, weight=similarity)
326
+
327
+ except Exception as e:
328
+ logging.warning(f"Semantic connection creation failed: {e}")
329
+
330
+ def find_relevant_knowledge(self, prompt: str, max_entries: int = 5) -> List[KnowledgeEntry]:
331
+ """Find relevant knowledge entries for a given prompt"""
332
+ if not self.entries:
333
+ return []
334
+
335
  try:
336
+ # Vectorize the prompt
337
+ prompt_vector = self.vectorizer.transform([prompt])
338
+
339
+ # Calculate similarities
340
+ if self.query_vectors is not None:
341
+ similarities = cosine_similarity(prompt_vector, self.query_vectors)[0]
342
+
343
+ # Get top similar entries
344
+ relevant_indices = np.argsort(similarities)[-max_entries:][::-1]
345
+ relevant_entries = []
346
+
347
+ for idx in relevant_indices:
348
+ if similarities[idx] > 0.1: # Minimum relevance threshold
349
+ query = self.vector_queries[idx]
350
+ entry = self.entries[query]
351
+ entry.relevance_score = similarities[idx]
352
+ relevant_entries.append(entry)
353
+
354
+ return relevant_entries
355
+
356
  except Exception as e:
357
+ logging.warning(f"Relevance search failed: {e}")
358
+
359
+ # Fallback: simple keyword matching
360
+ relevant = []
361
+ prompt_words = set(prompt.lower().split())
362
+
363
+ for entry in self.entries.values():
364
+ entry_words = set((entry.query + " " + entry.summary).lower().split())
365
+ overlap = len(prompt_words.intersection(entry_words))
366
+ if overlap > 0:
367
+ entry.relevance_score = overlap / len(prompt_words)
368
+ relevant.append(entry)
369
+
370
+ return sorted(relevant, key=lambda x: x.relevance_score, reverse=True)[:max_entries]
371
+
372
+ def cleanup_expired(self, hours: int = 24):
373
+ """Remove expired knowledge entries"""
374
+ expired_queries = [
375
+ query for query, entry in self.entries.items()
376
+ if entry.is_expired(hours)
377
+ ]
378
+
379
+ for query in expired_queries:
380
+ del self.entries[query]
381
+ if self.graph.has_node(query):
382
+ self.graph.remove_node(query)
383
+
384
+ if expired_queries:
385
+ self._update_vectors()
386
+ logging.info(f"Cleaned up {len(expired_queries)} expired knowledge entries")
387
+
388
+ # ========================================================================================
389
+ # KNOWLEDGE EVOLUTION ENGINE
390
+ # ========================================================================================
391
+
392
+ class KnowledgeEvolutionEngine:
393
+ """Autonomous knowledge acquisition and evolution system"""
394
+
395
+ def __init__(self, query_generator, search_engine, summarizer):
396
+ self.query_generator = query_generator
397
+ self.search_engine = search_engine
398
+ self.summarizer = summarizer
399
+ self.knowledge_graph = IntelligentKnowledgeGraph()
400
+ self.running = False
401
+ self.evolution_thread = None
402
+
403
+ def start_evolution(self):
404
+ """Start the autonomous knowledge evolution process"""
405
+ if self.running:
406
+ return
407
+
408
+ self.running = True
409
+ self.evolution_thread = threading.Thread(target=self._evolution_loop, daemon=True)
410
+ self.evolution_thread.start()
411
+ logging.info("Knowledge evolution engine started")
412
+
413
+ def stop_evolution(self):
414
+ """Stop the knowledge evolution process"""
415
+ self.running = False
416
+ if self.evolution_thread:
417
+ self.evolution_thread.join()
418
+ logging.info("Knowledge evolution engine stopped")
419
+
420
+ def _evolution_loop(self):
421
+ """Main evolution loop"""
422
+ while self.running:
423
+ try:
424
+ self._evolution_cycle()
425
+ except Exception as e:
426
+ logging.error(f"Evolution cycle error: {e}")
427
+
428
+ # Wait for next cycle
429
+ time.sleep(KG_UPDATE_INTERVAL)
430
+
431
+ def _evolution_cycle(self):
432
+ """Single evolution cycle: query → search → summarize → store"""
433
+
434
+ # Generate autonomous query
435
+ query = self.query_generator.generate_query()
436
+ logging.info(f"[Evolution] Generated query: {query}")
437
+
438
+ # Search for information
439
+ search_results = self.search_engine.search(query, max_results=8)
440
+
441
+ if not search_results:
442
+ logging.warning(f"[Evolution] No search results for query: {query}")
443
+ return
444
+
445
+ # Combine and process results
446
+ combined_text = self._combine_search_results(search_results)
447
+
448
+ if len(combined_text.strip()) < 100:
449
+ logging.warning(f"[Evolution] Insufficient content for query: {query}")
450
+ return
451
+
452
+ # Generate summary
453
+ summary = self._generate_summary(combined_text, query)
454
+
455
+ if not summary:
456
+ logging.warning(f"[Evolution] Summary generation failed for query: {query}")
457
+ return
458
+
459
+ # Create knowledge entry
460
+ entry = KnowledgeEntry(
461
+ query=query,
462
+ content=combined_text[:2000], # Limit content size
463
+ summary=summary,
464
+ timestamp=datetime.now(),
465
+ source_urls=[r.get('url', '') for r in search_results if r.get('url')]
466
+ )
467
+
468
+ # Add to knowledge graph
469
+ self.knowledge_graph.add_knowledge(entry)
470
+
471
+ # Cleanup old knowledge
472
+ self.knowledge_graph.cleanup_expired()
473
+
474
+ logging.info(f"[Evolution] Knowledge updated for query: {query}")
475
+
476
+ def _combine_search_results(self, results: List[Dict[str, str]]) -> str:
477
+ """Combine search results into coherent text"""
478
+ combined = []
479
+
480
+ for i, result in enumerate(results):
481
+ title = result.get('title', '').strip()
482
+ body = result.get('body', '').strip()
483
+
484
+ if title and body:
485
+ combined.append(f"Source {i+1}: {title}\n{body}")
486
+ elif body:
487
+ combined.append(f"Source {i+1}: {body}")
488
+
489
+ return "\n\n".join(combined)
490
+
491
+ def _generate_summary(self, text: str, query: str) -> str:
492
+ """Generate intelligent summary of search results"""
493
+ # Truncate text to fit model limits
494
+ max_text_length = SMOLLM_MAX_TOKENS - 200 # Reserve tokens for prompt
495
+ if len(text) > max_text_length:
496
+ text = text[:max_text_length]
497
+
498
+ prompt = f"""Based on the search query "{query}", provide a concise 3-sentence summary of the key information below:
499
+
500
+ {text}
501
+
502
+ Summary:"""
503
+
504
+ try:
505
+ output = self.summarizer(
506
+ prompt,
507
+ max_new_tokens=min(150, SMOLLM_MAX_TOKENS - len(prompt.split())),
508
+ do_sample=False,
509
+ temperature=0.3,
510
+ pad_token_id=self.summarizer.tokenizer.eos_token_id
511
+ )[0]["generated_text"]
512
+
513
+ # Extract summary from output
514
+ summary = output.replace(prompt, "").strip()
515
+ summary = re.sub(r'^Summary:\s*', '', summary, flags=re.IGNORECASE)
516
+
517
+ # Clean up summary
518
+ sentences = summary.split('.')
519
+ clean_sentences = []
520
+ for sentence in sentences[:3]: # Max 3 sentences
521
+ sentence = sentence.strip()
522
+ if sentence and len(sentence) > 10:
523
+ clean_sentences.append(sentence)
524
+
525
+ final_summary = '. '.join(clean_sentences)
526
+ if final_summary and not final_summary.endswith('.'):
527
+ final_summary += '.'
528
+
529
+ return final_summary if len(final_summary) > 20 else ""
530
+
531
+ except Exception as e:
532
+ logging.error(f"Summary generation error: {e}")
533
+ return ""
534
+
535
+ def get_relevant_knowledge(self, prompt: str) -> str:
536
+ """Get relevant knowledge for injection into prompts"""
537
+ relevant_entries = self.knowledge_graph.find_relevant_knowledge(prompt, max_entries=3)
538
+
539
+ if not relevant_entries:
540
+ return ""
541
+
542
+ knowledge_text = "\n\nRelevant recent knowledge:\n"
543
+ for i, entry in enumerate(relevant_entries, 1):
544
+ age = datetime.now() - entry.timestamp
545
+ age_str = f"{age.total_seconds() / 3600:.1f}h ago"
546
+ knowledge_text += f"{i}. [{entry.query}] ({age_str}): {entry.summary}\n"
547
+
548
+ return knowledge_text
549
+
550
+ # ========================================================================================
551
+ # MAIN APPLICATION
552
+ # ========================================================================================
553
+
554
+ app = FastAPI(title="Single Agent Cognitive System", version="1.0.0")
555
+
556
+ # Global components
557
+ search_engine = None
558
+ knowledge_engine = None
559
+ generator = None
560
+ query_generator_model = None
561
+ summarizer = None
562
+
563
+ @app.on_event("startup")
564
+ async def startup_event():
565
+ """Initialize all components"""
566
+ global search_engine, knowledge_engine, generator, query_generator_model, summarizer
567
+
568
+ logging.basicConfig(level=logging.INFO)
569
+ logging.info("Initializing Single Agent Cognitive System...")
570
+
571
+ # Initialize models
572
+ try:
573
+ generator = pipeline("text-generation", model=MAIN_MODEL, device=DEVICE)
574
+ query_generator_model = pipeline("text-generation", model=QUERY_MODEL, device=DEVICE)
575
+ summarizer = pipeline("text-generation", model=SUMMARY_MODEL, device=DEVICE)
576
+ logging.info("Models loaded successfully")
577
+ except Exception as e:
578
+ logging.error(f"Model loading failed: {e}")
579
+ raise
580
+
581
+ # Initialize search engine
582
+ search_engine = MultiSearchEngine()
583
+
584
+ # Initialize query generator
585
+ query_generator = AutonomousQueryGenerator(query_generator_model)
586
+
587
+ # Initialize knowledge evolution engine
588
+ knowledge_engine = KnowledgeEvolutionEngine(
589
+ query_generator, search_engine, summarizer
590
+ )
591
+
592
+ # Start autonomous evolution
593
+ knowledge_engine.start_evolution()
594
+
595
+ logging.info("Single Agent Cognitive System initialized successfully")
596
+
597
+ @app.on_event("shutdown")
598
+ async def shutdown_event():
599
+ """Cleanup on shutdown"""
600
+ if knowledge_engine:
601
+ knowledge_engine.stop_evolution()
602
+
603
+ # ========================================================================================
604
+ # API ENDPOINTS
605
+ # ========================================================================================
606
+
607
+ @app.post("/generate")
608
+ async def generate_text(input_data: ModelInput):
609
+ """Generate text with knowledge injection"""
610
+ try:
611
+ # Inject relevant knowledge
612
+ enriched_prompt = input_data.prompt
613
+ if knowledge_engine:
614
+ relevant_knowledge = knowledge_engine.get_relevant_knowledge(input_data.prompt)
615
+ if relevant_knowledge:
616
+ enriched_prompt = input_data.prompt + relevant_knowledge
617
+
618
+ # Generate response
619
+ output = generator(
620
+ enriched_prompt,
621
+ max_new_tokens=min(input_data.max_new_tokens, DEEPSEEK_MAX_TOKENS),
622
+ do_sample=True,
623
+ temperature=0.7,
624
+ top_p=0.9,
625
+ pad_token_id=generator.tokenizer.eos_token_id
626
+ )[0]["generated_text"]
627
+
628
+ return {"generated_text": output, "enriched_prompt": enriched_prompt}
629
+
630
+ except Exception as e:
631
+ raise HTTPException(status_code=500, detail=str(e))
632
+
633
  @app.post("/generate/stream")
634
+ async def generate_stream(input_data: ModelInput):
635
+ """Stream text generation with knowledge injection"""
636
  q = queue.Queue()
637
+
638
  def run_generation():
639
  try:
640
+ # Inject relevant knowledge
641
+ enriched_prompt = input_data.prompt
642
+ if knowledge_engine:
643
+ relevant_knowledge = knowledge_engine.get_relevant_knowledge(input_data.prompt)
644
+ if relevant_knowledge:
645
+ enriched_prompt = input_data.prompt + relevant_knowledge
646
+
647
+ # Set up streaming
648
+ def token_callback(token_ids):
649
  if hasattr(token_ids, "tolist"):
650
  token_ids = token_ids.tolist()
651
+ text = generator.tokenizer.decode(token_ids, skip_special_tokens=True)
652
  q.put(text)
653
+
654
+ streamer = TextStreamer(generator.tokenizer, skip_prompt=True)
655
+ streamer.put = token_callback
656
+
657
+ # Generate with streaming
658
  generator(
659
  enriched_prompt,
660
+ max_new_tokens=min(input_data.max_new_tokens, DEEPSEEK_MAX_TOKENS),
661
+ do_sample=True,
662
+ temperature=0.7,
663
+ top_p=0.9,
664
+ streamer=streamer,
665
+ pad_token_id=generator.tokenizer.eos_token_id
666
  )
667
+
668
  except Exception as e:
669
  q.put(f"[ERROR] {e}")
670
  finally:
671
+ q.put(None) # End signal
672
+
673
+ # Start generation in background
674
  threading.Thread(target=run_generation, daemon=True).start()
675
+
676
  async def event_generator():
677
  while True:
678
+ try:
679
+ token = q.get(timeout=30) # 30 second timeout
680
+ if token is None:
681
+ break
682
+ yield token
683
+ except queue.Empty:
684
+ yield "[TIMEOUT]"
685
  break
686
+
 
687
  return StreamingResponse(event_generator(), media_type="text/plain")
688
 
689
+ @app.get("/knowledge")
690
+ async def get_knowledge_graph():
691
+ """Get current knowledge graph state"""
692
+ if not knowledge_engine:
693
+ return {"error": "Knowledge engine not initialized"}
694
+
695
+ kg = knowledge_engine.knowledge_graph
696
+ return {
697
+ "total_entries": len(kg.entries),
698
+ "entries": [
699
+ {
700
+ "query": entry.query,
701
+ "summary": entry.summary,
702
+ "timestamp": entry.timestamp.isoformat(),
703
+ "relevance_score": entry.relevance_score,
704
+ "sources_count": len(entry.source_urls)
705
+ }
706
+ for entry in list(kg.entries.values())[-20:] # Last 20 entries
707
+ ]
708
+ }
709
+
710
+ @app.get("/knowledge/search")
711
+ async def search_knowledge(query: str):
712
+ """Search knowledge graph"""
713
+ if not knowledge_engine:
714
+ return {"error": "Knowledge engine not initialized"}
715
+
716
+ relevant_entries = knowledge_engine.knowledge_graph.find_relevant_knowledge(query, max_entries=10)
717
+
718
+ return {
719
+ "query": query,
720
+ "results": [
721
+ {
722
+ "query": entry.query,
723
+ "summary": entry.summary,
724
+ "relevance_score": entry.relevance_score,
725
+ "timestamp": entry.timestamp.isoformat(),
726
+ "age_hours": (datetime.now() - entry.timestamp).total_seconds() / 3600
727
+ }
728
+ for entry in relevant_entries
729
+ ]
730
+ }
731
+
732
+ @app.post("/knowledge/force-update")
733
+ async def force_knowledge_update():
734
+ """Force a knowledge update cycle"""
735
+ if not knowledge_engine:
736
+ return {"error": "Knowledge engine not initialized"}
737
+
738
  try:
739
+ knowledge_engine._evolution_cycle()
740
+ return {"status": "Knowledge update completed"}
 
 
 
 
 
741
  except Exception as e:
742
+ return {"error": str(e)}
743
 
744
+ @app.get("/status")
745
+ async def get_system_status():
746
+ """Get system status"""
747
+ status = {
748
+ "models_loaded": generator is not None,
749
+ "search_engine_active": search_engine is not None,
750
+ "knowledge_engine_running": knowledge_engine is not None and knowledge_engine.running,
751
+ "knowledge_entries": 0,
752
+ "uptime_seconds": time.time() - startup_time if 'startup_time' in globals() else 0
753
+ }
754
+
755
+ if knowledge_engine:
756
+ status["knowledge_entries"] = len(knowledge_engine.knowledge_graph.entries)
757
+
758
+ return status
759
 
 
 
 
760
  @app.get("/")
761
  async def root():
762
+ """Root endpoint"""
763
+ return {
764
+ "name": "Single Agent Cognitive System",
765
+ "description": "Autonomous knowledge evolution with intelligent query generation",
766
+ "version": "1.0.0",
767
+ "features": [
768
+ "Autonomous query generation",
769
+ "Multi-engine search with fallbacks",
770
+ "Intelligent knowledge graph",
771
+ "Semantic relevance matching",
772
+ "Real-time knowledge injection",
773
+ "Streaming text generation"
774
+ ]
775
+ }
776
+
777
+ # Initialize startup time
778
+ startup_time = time.time()
779
+
780
+ if __name__ == "__main__":
781
+ import uvicorn
782
+ uvicorn.run(app, host="0.0.0.0", port=7860)