1use std::io::{BufRead, BufReader, Read, Write};
15use std::net::TcpListener;
16use tokio::runtime::Handle;
17use tokio::task::block_in_place;
18
19use crate::bridge::MemoryBridge;
20
21fn rerank_results(
24 query: &str,
25 results: &[serde_json::Value],
26 model: &str,
27) -> Vec<serde_json::Value> {
28 let client = reqwest::blocking::Client::new();
29 let mut scored: Vec<(f64, serde_json::Value)> = results
30 .iter()
31 .map(|r| {
32 let content = r.get("content").and_then(|v| v.as_str()).unwrap_or("");
33 let truncated: String = content.chars().take(500).collect();
34 let prompt = format!(
35 "Rate the relevance of this document to the query on a scale of 1-5. Reply with ONLY the number.\nQuery: {query}\nDocument: {truncated}\nRating:"
36 );
37 let body = serde_json::json!({
38 "model": model,
39 "prompt": prompt,
40 "stream": false,
41 "options": {"temperature": 0, "num_predict": 1}
42 });
43 let rating = client
44 .post("http://127.0.0.1:11434/api/generate")
45 .json(&body)
46 .send()
47 .ok()
48 .and_then(|resp| resp.json::<serde_json::Value>().ok())
49 .and_then(|v| {
50 v.get("response")
51 .and_then(|r| r.as_str())
52 .and_then(|s| s.trim().chars().next())
53 .and_then(|c| c.to_digit(10))
54 .map(|d| d as f64)
55 })
56 .unwrap_or(1.0);
57 (rating, r.clone())
58 })
59 .collect();
60 scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
61 scored
62 .into_iter()
63 .map(|(score, mut r)| {
64 if let Some(obj) = r.as_object_mut() {
65 obj.insert("rerank_score".to_string(), serde_json::json!(score));
66 }
67 r
68 })
69 .collect()
70}
71
72pub fn start_http_server(port: u16, bridge: MemoryBridge, handle: Handle) {
73 std::thread::spawn(move || {
74 let listener = match TcpListener::bind(("127.0.0.1", port)) {
75 Ok(l) => {
76 eprintln!("HTTP search server listening on 127.0.0.1:{}", port);
77 l
78 }
79 Err(e) => {
80 eprintln!("Failed to bind HTTP port {}: {}", port, e);
81 return;
82 }
83 };
84
85 for stream in listener.incoming() {
86 let stream = match stream {
87 Ok(s) => s,
88 Err(_) => continue,
89 };
90
91 let bridge = bridge.clone();
92 let h = handle.clone();
93 std::thread::spawn(move || {
94 handle_connection(stream, bridge, h);
95 });
96 }
97 });
98}
99
100fn handle_connection(
101 mut stream: std::net::TcpStream,
102 bridge: MemoryBridge,
103 handle: Handle,
104) {
105 let mut reader = BufReader::new(stream.try_clone().expect("clone"));
106 let mut request_line = String::new();
107 if reader.read_line(&mut request_line).is_err() {
108 return;
109 }
110
111 let parts: Vec<&str> = request_line.split_whitespace().collect();
112 if parts.len() < 2 {
113 return;
114 }
115 let method = parts[0];
116 let path = parts[1];
117
118 let mut content_length = 0;
119 loop {
120 let mut header = String::new();
121 if reader.read_line(&mut header).is_err() {
122 return;
123 }
124 if header.trim().is_empty() {
125 break;
126 }
127 if let Some(len_str) = header
128 .strip_prefix("Content-Length:")
129 .or_else(|| header.strip_prefix("content-length:"))
130 {
131 content_length = len_str.trim().parse().unwrap_or(0);
132 }
133 }
134
135 let mut body = vec![0u8; content_length];
136 if content_length > 0 && reader.read_exact(&mut body).is_err() {
137 return;
138 }
139 let body_str = String::from_utf8_lossy(&body);
140
141 let (status, response) = match (method, path) {
142 ("GET", "/health") => (
143 "200 OK",
144 serde_json::json!({"ok": true, "service": "semantic-memory-mcp"}),
145 ),
146 ("POST", "/search") => handle_search(&body_str, &bridge, &handle),
147 ("POST", "/search-routed") => handle_search_routed(&body_str, &bridge, &handle),
148 ("POST", "/rerank") => handle_rerank(&body_str),
149 ("POST", "/stats") => handle_stats(&bridge, &handle),
150 ("POST", "/add") => handle_add_fact(&body_str, &bridge, &handle),
151 ("POST", "/record-outcome") => handle_record_outcome(&body_str),
152 ("GET", "/verify-integrity") => handle_verify_integrity(&bridge, &handle),
153 _ => (
154 "404 Not Found",
155 serde_json::json!({"error": "not found", "path": path}),
156 ),
157 };
158
159 let response_str = serde_json::to_string(&response).unwrap_or_default();
160 let response_bytes = response_str.as_bytes();
161 let http_response = format!(
162 "HTTP/1.1 {}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
163 status,
164 response_bytes.len()
165 );
166
167 let _ = stream.write_all(http_response.as_bytes());
168 let _ = stream.write_all(response_bytes);
169 let _ = stream.flush();
170}
171
172fn handle_search(
173 body: &str,
174 bridge: &MemoryBridge,
175 handle: &Handle,
176) -> (&'static str, serde_json::Value) {
177 let params: serde_json::Value = match serde_json::from_str(body) {
178 Ok(v) => v,
179 Err(e) => {
180 return (
181 "400 Bad Request",
182 serde_json::json!({"ok": false, "error": format!("invalid JSON: {e}")}),
183 )
184 }
185 };
186
187 let query = params.get("query").and_then(|v| v.as_str()).unwrap_or("");
188 let top_k = params.get("top_k").and_then(|v| v.as_u64()).unwrap_or(5) as usize;
189 let namespaces: Option<Vec<String>> = params
190 .get("namespaces")
191 .and_then(|v| serde_json::from_value(v.clone()).ok());
192 let do_rerank = params.get("rerank").and_then(|v| v.as_bool()).unwrap_or(false);
193
194 if query.is_empty() {
195 return (
196 "400 Bad Request",
197 serde_json::json!({"ok": false, "error": "missing 'query' field"}),
198 );
199 }
200
201 let store = &bridge.store;
202 let ns_slice: Option<Vec<&str>> = namespaces
203 .as_ref()
204 .map(|v| v.iter().map(|s| s.as_str()).collect());
205 let fetch_k = if do_rerank { top_k * 2 } else { top_k };
207 let result = block_in_place(|| {
208 handle.block_on(store.search(query, Some(fetch_k), ns_slice.as_deref(), None))
209 });
210
211 match result {
212 Ok(results) => {
213 let json_results: Vec<serde_json::Value> = results
214 .iter()
215 .map(|r| {
216 let namespace = match &r.source {
217 semantic_memory::SearchSource::Fact { namespace, .. } => namespace.clone(),
218 semantic_memory::SearchSource::Chunk { document_title, .. } => document_title.clone(),
219 _ => String::new(),
220 };
221 serde_json::json!({
222 "result_id": r.source.result_id(),
223 "content": r.content,
224 "score": r.score,
225 "cosine_similarity": r.cosine_similarity,
226 "namespace": namespace,
227 })
228 })
229 .collect();
230
231 let final_results: Vec<serde_json::Value> = if do_rerank && !json_results.is_empty() {
232 rerank_results(query, &json_results, "granite4.1:3b")
233 .into_iter()
234 .take(top_k)
235 .collect()
236 } else {
237 json_results
238 };
239
240 let count = final_results.len();
241 let provenance = serde_json::json!({
242 "stages_fired": {
243 "bm25": true,
244 "vector": true,
245 "late_interaction": false,
246 "rerank": do_rerank,
247 },
248 "result_count": count,
249 "view": "semantic",
250 "widening_occurred": false,
251 "widening_reason": null,
252 "verification_status": "verified",
253 });
254 (
255 "200 OK",
256 serde_json::json!({
257 "ok": true,
258 "query": query,
259 "top_k": top_k,
260 "results": final_results,
261 "count": count,
262 "reranked": do_rerank,
263 "provenance": provenance,
264 }),
265 )
266 }
267 Err(e) => (
268 "500 Internal Server Error",
269 serde_json::json!({"ok": false, "error": format!("search error: {e}")}),
270 ),
271 }
272}
273
274fn handle_search_routed(
281 body: &str,
282 bridge: &MemoryBridge,
283 handle: &Handle,
284) -> (&'static str, serde_json::Value) {
285 let params: serde_json::Value = match serde_json::from_str(body) {
286 Ok(v) => v,
287 Err(e) => {
288 return (
289 "400 Bad Request",
290 serde_json::json!({"ok": false, "error": format!("invalid JSON: {e}")}),
291 )
292 }
293 };
294
295 let query = params.get("query").and_then(|v| v.as_str()).unwrap_or("");
296 let base_top_k = params.get("top_k").and_then(|v| v.as_u64()).unwrap_or(12) as usize;
297 let query_class = params.get("query_class").and_then(|v| v.as_str()).unwrap_or("A");
298 let namespaces: Option<Vec<String>> = params
299 .get("namespaces")
300 .and_then(|v| serde_json::from_value(v.clone()).ok());
301
302 if query.is_empty() {
303 return (
304 "400 Bad Request",
305 serde_json::json!({"ok": false, "error": "missing 'query' field"}),
306 );
307 }
308
309 let top_k = if query_class == "D" {
311 (base_top_k * 2).min(20)
312 } else {
313 base_top_k
314 };
315
316 let store = &bridge.store;
317 let ns_slice: Option<Vec<&str>> = namespaces
318 .as_ref()
319 .map(|v| v.iter().map(|s| s.as_str()).collect());
320
321 let result = if query_class == "C" {
323 use semantic_memory::{ExactnessProfile, SearchContext};
324 let mut ctx = SearchContext::default_now();
325 ctx.exactness_profile = ExactnessProfile::PreferExact;
326 block_in_place(|| {
327 handle.block_on(store.search_with_context(
328 query,
329 Some(top_k),
330 ns_slice.as_deref(),
331 None,
332 ctx,
333 ))
334 })
335 .map(|r| r.results)
336 } else {
337 block_in_place(|| {
338 handle.block_on(store.search(query, Some(top_k), ns_slice.as_deref(), None))
339 })
340 };
341
342 match result {
343 Ok(results) => {
344 let json_results: Vec<serde_json::Value> = results
345 .iter()
346 .map(|r| {
347 let namespace = match &r.source {
348 semantic_memory::SearchSource::Fact { namespace, .. } => namespace.clone(),
349 semantic_memory::SearchSource::Chunk { document_title, .. } => {
350 document_title.clone()
351 }
352 _ => String::new(),
353 };
354 serde_json::json!({
355 "result_id": r.source.result_id(),
356 "content": r.content,
357 "score": r.score,
358 "cosine_similarity": r.cosine_similarity,
359 "namespace": namespace,
360 "source_type": match &r.source {
361 semantic_memory::SearchSource::Fact { .. } => "fact",
362 semantic_memory::SearchSource::Chunk { .. } => "chunk",
363 semantic_memory::SearchSource::Message { .. } => "message",
364 _ => "unknown",
365 },
366 })
367 })
368 .collect();
369
370 let provenance = serde_json::json!({
372 "stages_fired": {
373 "bm25": results.iter().any(|r| r.bm25_rank.is_some()),
374 "vector": results.iter().any(|r| r.vector_rank.is_some()),
375 "late_interaction": true,
376 "discord": false,
377 "decoder": false,
378 },
379 "result_count": results.len(),
380 "view": "routed",
381 "query_class": query_class,
382 "widening_occurred": false,
383 "widening_reason": null,
384 "verification_status": "verified",
385 });
386
387 (
388 "200 OK",
389 serde_json::json!({
390 "ok": true,
391 "query": query,
392 "top_k": base_top_k,
393 "results": json_results,
394 "provenance": provenance,
395 "query_class": query_class,
396 "routed": true,
397 }),
398 )
399 }
400 Err(e) => (
401 "500 Internal Server Error",
402 serde_json::json!({"ok": false, "error": format!("search error: {e}")}),
403 ),
404 }
405}
406
407fn handle_stats(
408 bridge: &MemoryBridge,
409 handle: &Handle,
410) -> (&'static str, serde_json::Value) {
411 let store = &bridge.store;
412 let result = block_in_place(|| handle.block_on(store.stats()));
413 match result {
414 Ok(stats) => (
415 "200 OK",
416 serde_json::json!({
417 "ok": true,
418 "facts": stats.total_facts,
419 "documents": stats.total_documents,
420 "chunks": stats.total_chunks,
421 "db_size_mb": (stats.database_size_bytes as f64) / (1024.0 * 1024.0),
422 }),
423 ),
424 Err(e) => (
425 "500 Internal Server Error",
426 serde_json::json!({"ok": false, "error": format!("{e}")}),
427 ),
428 }
429}
430
431fn handle_rerank(body: &str) -> (&'static str, serde_json::Value) {
432 let params: serde_json::Value = match serde_json::from_str(body) {
433 Ok(v) => v,
434 Err(e) => {
435 return (
436 "400 Bad Request",
437 serde_json::json!({"ok": false, "error": format!("invalid JSON: {e}")}),
438 )
439 }
440 };
441
442 let query = params.get("query").and_then(|v| v.as_str()).unwrap_or("");
443 let model = params
444 .get("model")
445 .and_then(|v| v.as_str())
446 .unwrap_or("granite4.1:3b");
447 let results = match params.get("results").and_then(|v| v.as_array()) {
448 Some(r) => r.clone(),
449 None => {
450 return (
451 "400 Bad Request",
452 serde_json::json!({"ok": false, "error": "missing 'results' array"}),
453 )
454 }
455 };
456
457 if query.is_empty() {
458 return (
459 "400 Bad Request",
460 serde_json::json!({"ok": false, "error": "missing 'query' field"}),
461 );
462 }
463
464 let reranked = rerank_results(query, &results, model);
465 let count = reranked.len();
466 (
467 "200 OK",
468 serde_json::json!({
469 "ok": true,
470 "results": reranked,
471 "count": count,
472 }),
473 )
474}
475
476fn handle_add_fact(
477 body: &str,
478 bridge: &MemoryBridge,
479 handle: &Handle,
480) -> (&'static str, serde_json::Value) {
481 let params: serde_json::Value = match serde_json::from_str(body) {
482 Ok(v) => v,
483 Err(e) => {
484 return (
485 "400 Bad Request",
486 serde_json::json!({"ok": false, "error": format!("invalid JSON: {e}")}),
487 )
488 }
489 };
490
491 let content = params.get("content").and_then(|v| v.as_str()).unwrap_or("");
492 let namespace = params
493 .get("namespace")
494 .and_then(|v| v.as_str())
495 .unwrap_or("general");
496 let source = params.get("source").and_then(|v| v.as_str());
497
498 if content.is_empty() {
499 return (
500 "400 Bad Request",
501 serde_json::json!({"ok": false, "error": "missing 'content' field"}),
502 );
503 }
504
505 let store = &bridge.store;
506 let result =
507 block_in_place(|| handle.block_on(store.add_fact(namespace, content, source, None)));
508
509 match result {
510 Ok(fact_id) => (
511 "200 OK",
512 serde_json::json!({"ok": true, "fact_id": fact_id}),
513 ),
514 Err(e) => (
515 "500 Internal Server Error",
516 serde_json::json!({"ok": false, "error": format!("{e}")}),
517 ),
518 }
519}
520
521fn handle_record_outcome(body: &str) -> (&'static str, serde_json::Value) {
523 let params: serde_json::Value = match serde_json::from_str(body) {
524 Ok(v) => v,
525 Err(e) => {
526 return (
527 "400 Bad Request",
528 serde_json::json!({"ok": false, "error": format!("invalid JSON: {e}")}),
529 )
530 }
531 };
532
533 let query = params.get("query").and_then(|v| v.as_str()).unwrap_or("");
534 let outcome = params.get("outcome").and_then(|v| v.as_str()).unwrap_or("neutral");
535 let query_class = params.get("query_class").and_then(|v| v.as_str()).unwrap_or("A");
536
537 eprintln!(
538 "[record-outcome] query_class={} outcome={} query={:?}",
539 query_class, outcome, &query[..query.len().min(80)]
540 );
541
542 (
543 "200 OK",
544 serde_json::json!({"ok": true, "recorded": true, "outcome": outcome, "query_class": query_class}),
545 )
546}
547
548fn handle_verify_integrity(
550 bridge: &MemoryBridge,
551 handle: &Handle,
552) -> (&'static str, serde_json::Value) {
553 let store = &bridge.store;
554 let stats = block_in_place(|| handle.block_on(store.stats()));
555
556 match stats {
557 Ok(s) => {
558 let facts = s.total_facts;
559 let chunks = s.total_chunks;
560 let docs = s.total_documents;
561 let db_size = s.database_size_bytes;
562
563 let checks = serde_json::json!({
564 "facts_counted": facts > 0,
565 "chunks_present": chunks > 0,
566 "documents_present": docs > 0,
567 "db_size_reasonable": db_size > 1024,
568 "facts_to_chunks_ratio_ok": chunks >= facts,
569 });
570
571 let all_pass = checks.as_object()
572 .map(|m| m.values().all(|v| v.as_bool().unwrap_or(false)))
573 .unwrap_or(false);
574
575 (
576 "200 OK",
577 serde_json::json!({
578 "ok": true,
579 "integrity": all_pass,
580 "checks": checks,
581 "stats": {
582 "facts": facts,
583 "chunks": chunks,
584 "documents": docs,
585 "db_size_bytes": db_size,
586 },
587 "message": if all_pass { "All integrity checks passed" } else { "Some integrity checks failed" },
588 }),
589 )
590 }
591 Err(e) => (
592 "500 Internal Server Error",
593 serde_json::json!({"ok": false, "integrity": false, "error": format!("stats error: {e}")}),
594 ),
595 }
596}