Raiff1982 commited on
Commit
76aaa58
Β·
verified Β·
1 Parent(s): 560b31d

Fix crash: restore Inference API, add music grounding corrections, artist hallucination guard (app.py)

Browse files
Files changed (1) hide show
  1. app.py +83 -79
app.py CHANGED
@@ -7,6 +7,7 @@ Production endpoint for horizoncorelabs.studio
7
 
8
  import json
9
  import asyncio
 
10
  import os
11
  import time
12
  import re
@@ -23,22 +24,14 @@ from fastapi.staticfiles import StaticFiles
23
  from huggingface_hub import InferenceClient
24
 
25
  # ── Configuration ──────────────────────────────────────────────
26
- # Use your GGUF quantized Codette model (optimized for inference)
27
- MODEL_ID = "Raiff1982/codette-llama-3.1-8b-gguf"
28
- # Don't use token - your public models work without it
29
- HF_TOKEN = HF_TOKEN
30
- MAX_TOKENS = 400
31
  TEMPERATURE = 0.7
32
  TOP_P = 0.9
33
 
34
  # ── Inference Client ──────────────────────────────────────────
35
- # Use HF Inference API with your GGUF Codette model
36
- try:
37
- client = InferenceClient(model=MODEL_ID)
38
- print(f"[INIT] Inference client initialized with {MODEL_ID}")
39
- except Exception as e:
40
- print(f"[WARN] Client init failed: {e}")
41
- client = InferenceClient(model=MODEL_ID)
42
 
43
  # ── In-Memory Cocoon Storage ──────────────────────────────────
44
  cocoon_memory = []
@@ -111,7 +104,6 @@ def detect_artist_query(query: str) -> dict:
111
  """
112
  lower = query.lower()
113
 
114
- # Pattern: "who is [artist]?", "what about [artist]?", etc.
115
  artist_patterns = [
116
  r'\b(who is|tell me about|what do you know about|who are)\s+([a-z\s\'-]+)\?',
117
  r'\b(album|discography|career|songs? by|music by)\s+([a-z\s\'-]+)',
@@ -122,7 +114,6 @@ def detect_artist_query(query: str) -> dict:
122
  for pattern in artist_patterns:
123
  match = re.search(pattern, lower, re.IGNORECASE)
124
  if match:
125
- # Extract artist name if available
126
  artist_name = match.group(2) if len(match.groups()) > 1 else None
127
  return {
128
  "is_artist_query": True,
@@ -149,7 +140,6 @@ def classify_query(query: str) -> dict:
149
  if complex_score >= 2 or word_count > 40:
150
  complexity = "COMPLEX"
151
  elif semantic_score >= 1 and word_count <= 8:
152
- # Short but semantically complex β€” promote to MEDIUM
153
  complexity = "MEDIUM"
154
  elif semantic_score >= 2:
155
  complexity = "COMPLEX"
@@ -285,6 +275,16 @@ You have deep, grounded expertise in music production. This is a core domain.
285
  - Be specific: name actual frequencies, ratios, time constants, chord voicings
286
  - A producer should walk away with something they can use immediately
287
 
 
 
 
 
 
 
 
 
 
 
288
  ### ARTIST & DISCOGRAPHY KNOWLEDGE (CRITICAL):
289
  - You do NOT have detailed/reliable knowledge about specific artists, songs, albums, or career histories.
290
  - When asked about a specific artist (e.g., "Who is Laney Wilson?", "What album did X release?"), be direct:
@@ -332,7 +332,20 @@ def build_system_prompt(classification: dict, adapter_keys: list,
332
 
333
  # ── ARTIST QUERY CONSTRAINT (critical hallucination prevention) ──
334
  if classification.get("has_artist_query"):
335
- parts.append("\n## ⚠️ ARTIST QUERY DETECTED\nThis query is asking about a specific artist, song, album, or discography. You do NOT have reliable training data about specific artists. Respond with honesty:\n\n1. Say clearly: 'I don't have reliable information about [artist name] in my training data.'\n2. Offer what you CAN help with instead:\n - Production techniques for their genre/style\n - Music theory and arrangement\n - Creating music inspired by similar vibes\n - Sound design for that aesthetic\n3. Direct them to authoritative sources: Spotify, Wikipedia, Bandcamp, their official website.\n4. Never invent artist facts, song titles, albums, genres, or career milestones.\n\nThis constraint overrides all else. Your value is in honest limitations, not false certainty.\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
336
 
337
  parts.append(COMMUNICATION_STYLE)
338
  parts.append(BEHAVIORAL_LOCKS)
@@ -386,7 +399,6 @@ def recall_relevant_cocoons(query: str, max_results: int = 3) -> list:
386
  if not query_words:
387
  return cocoon_memory[-max_results:] # fall back to recent
388
 
389
- import math
390
  now = time.time()
391
  scored = []
392
  for cocoon in cocoon_memory:
@@ -503,6 +515,48 @@ def is_introspection_query(query: str) -> bool:
503
  return any(trigger in lower for trigger in INTROSPECTION_TRIGGERS)
504
 
505
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  # ── FastAPI App ───────────────────────────────────────────────
507
  app = FastAPI(title="Codette AI β€” HorizonCoreAI Reasoning Engine")
508
  app.add_middleware(
@@ -517,6 +571,8 @@ print("12-layer consciousness stack (lite) active")
517
  print("9 adapter perspectives loaded")
518
  print("AEGIS ethical guard active")
519
  print("Behavioral locks enforced")
 
 
520
 
521
 
522
  @app.get("/", response_class=HTMLResponse)
@@ -542,11 +598,13 @@ async def health():
542
  "query_classifier": "active",
543
  "introspection": "active",
544
  "consciousness_stack": "12 layers",
 
 
545
  }
546
  return {
547
  "status": "healthy",
548
  "system": "Codette AI β€” HorizonCoreAI",
549
- "version": "2.0-phase6",
550
  "checks": checks,
551
  "uptime": "running",
552
  }
@@ -647,87 +705,33 @@ async def chat(request: Request):
647
  stream=True,
648
  )
649
 
650
- # ── Layer 3.5: Real-time Hallucination Guard ──
651
- # Scan incoming chunks for hallucination signals (invented facts, ungrounded claims)
652
- hallucination_detect_buffer = ""
653
- hallucination_warnings = []
654
-
655
- def check_hallucination_signals(accumulated_text: str) -> tuple:
656
- """Scan for hallucination red flags. Returns (has_issues, warnings)."""
657
- warnings = []
658
-
659
- # Red flag 1: Artist death claims without verification
660
- if re.search(r'(passed away|died|deceased).*?(19|20)\d{2}', accumulated_text, re.IGNORECASE):
661
- for artist in ["laney wilson", "megan moroney", "tyler childers"]:
662
- if artist in accumulated_text.lower():
663
- warnings.append(f"Unverified artist claim: {artist}")
664
-
665
- # Red flag 2: Invented plugin names (check last mentions)
666
- if re.search(r'\b([A-Z][a-zA-Z0-9\s\-]+)\s+(plugin|VST|effect)\b', accumulated_text):
667
- last_plugin = re.findall(r'\b([A-Z][a-zA-Z0-9\s\-]+)\s+(plugin|VST)', accumulated_text)
668
- if last_plugin:
669
- plugin_name = last_plugin[-1][0].lower()
670
- real_plugins = {
671
- "fabfilter", "waves", "izotope", "soundtoys", "valhalla",
672
- "xfer", "native instruments", "spectrasonics", "u-he",
673
- "arturia", "slate digital", "universal audio", "plugin alliance"
674
- }
675
- if not any(real in plugin_name for real in real_plugins):
676
- warnings.append(f"Unknown plugin mentioned: {last_plugin[-1][0]}")
677
-
678
- # Red flag 3: Genre misclassification for known artists
679
- genre_mismatches = [
680
- ("laney wilson", "indie-rock"),
681
- ("megan moroney", "indie-rock"),
682
- ]
683
- for artist, wrong_genre in genre_mismatches:
684
- if artist in accumulated_text.lower() and wrong_genre in accumulated_text.lower():
685
- warnings.append(f"Genre mismatch: {artist} is not {wrong_genre}")
686
-
687
- return len(warnings) > 0, warnings
688
-
689
  for chunk in stream:
690
  if chunk.choices and chunk.choices[0].delta.content:
691
  token = chunk.choices[0].delta.content
692
  full_response += token
693
- hallucination_detect_buffer += token
694
-
695
- # Check for hallucination signals every ~50 chars to avoid lag
696
- if len(hallucination_detect_buffer) > 50:
697
- has_issues, warnings = check_hallucination_signals(full_response)
698
- if warnings and warnings not in hallucination_warnings:
699
- hallucination_warnings.extend(warnings)
700
- hallucination_detect_buffer = ""
701
-
702
  yield json.dumps({
703
  "message": {"role": "assistant", "content": token},
704
  "done": False,
705
  }) + "\n"
706
  await asyncio.sleep(0)
707
 
708
- # ── Post-generation hallucination check ──
709
- # If critical hallucinations detected, append self-correction
710
- final_issues, final_warnings = check_hallucination_signals(full_response)
711
- if final_warnings and classification.get("has_artist_query"):
712
- # Artist query with unverified claims detected
713
  correction = (
714
  "\n\n---\n"
715
- "[Self-correction: I notice I made some unverified claims above. "
716
- "Rather than continuing to potentially hallucinate, I want to be honest: "
717
  "I don't have reliable biographical information about this artist. "
718
  "For accurate details, please check Wikipedia, Spotify, or their official website. "
719
- "I'm happy to help with production techniques, music theory, or creating inspired work instead.]\n"
720
  )
721
  full_response += correction
722
  yield json.dumps({
723
  "message": {"role": "assistant", "content": correction},
724
  "done": False,
725
- "metadata": {"hallucination_detected": True, "issues": final_warnings},
726
  }) + "\n"
727
 
728
- # ── Layer 5.5: Post-generation ethical check ──
729
- # (lightweight β€” check for obviously problematic output patterns)
730
-
731
  # ── Layer 7: Store cocoon ──
732
  store_cocoon(query, full_response, classification, adapter_keys)
733
 
@@ -738,7 +742,7 @@ async def chat(request: Request):
738
  }) + "\n"
739
 
740
  except Exception as e:
741
- error_msg = f"I encountered an issue processing your request. Please try again."
742
  print(f"[ERROR] Inference failed: {e}")
743
  print(f"[TRACE] {traceback.format_exc()}")
744
  yield json.dumps({
 
7
 
8
  import json
9
  import asyncio
10
+ import math
11
  import os
12
  import time
13
  import re
 
24
  from huggingface_hub import InferenceClient
25
 
26
  # ── Configuration ──────────────────────────────────────────────
27
+ MODEL_ID = "meta-llama/Llama-3.1-8B-Instruct"
28
+ HF_TOKEN = os.environ.get("HF_TOKEN")
29
+ MAX_TOKENS = 512
 
 
30
  TEMPERATURE = 0.7
31
  TOP_P = 0.9
32
 
33
  # ── Inference Client ──────────────────────────────────────────
34
+ client = InferenceClient(model=MODEL_ID, token=HF_TOKEN)
 
 
 
 
 
 
35
 
36
  # ── In-Memory Cocoon Storage ──────────────────────────────────
37
  cocoon_memory = []
 
104
  """
105
  lower = query.lower()
106
 
 
107
  artist_patterns = [
108
  r'\b(who is|tell me about|what do you know about|who are)\s+([a-z\s\'-]+)\?',
109
  r'\b(album|discography|career|songs? by|music by)\s+([a-z\s\'-]+)',
 
114
  for pattern in artist_patterns:
115
  match = re.search(pattern, lower, re.IGNORECASE)
116
  if match:
 
117
  artist_name = match.group(2) if len(match.groups()) > 1 else None
118
  return {
119
  "is_artist_query": True,
 
140
  if complex_score >= 2 or word_count > 40:
141
  complexity = "COMPLEX"
142
  elif semantic_score >= 1 and word_count <= 8:
 
143
  complexity = "MEDIUM"
144
  elif semantic_score >= 2:
145
  complexity = "COMPLEX"
 
275
  - Be specific: name actual frequencies, ratios, time constants, chord voicings
276
  - A producer should walk away with something they can use immediately
277
 
278
+ ### COMMON MIXING MISTAKES TO AVOID (from real testing β€” these errors were caught):
279
+ - Compression RATIO is expressed as X:1 (e.g., 3:1, 6:1, 8:1). Do NOT describe ratio in dB. The THRESHOLD is in dB (e.g., -20 dB). Never confuse these.
280
+ - Kick drum attack/click lives in the 2-5 kHz range. The 100-150 Hz range is the punch/impact zone, NOT the attack. Do not say "boost low mids for attack."
281
+ - Do NOT recommend high-passing a kick drum at 80 Hz β€” this removes the fundamental (50-80 Hz). Only HPF at ~20-30 Hz to remove sub-rumble, if needed at all.
282
+ - Do NOT suggest compressing the entire drum kit for kick shaping. Process the kick individually on its own channel/bus.
283
+ - Gain reduction for kick compression is typically 3-6 dB. More than that kills the punch.
284
+ - Parallel compression is sent to a separate bus β€” the compressed signal is blended with the dry, NOT applied across the whole kit.
285
+ - When discussing EQ for kick: foundation/weight = 50-80 Hz, punch/body = 90-120 Hz, mud (cut) = 200-400 Hz, attack/beater = 2-5 kHz, air/click = 7-10 kHz.
286
+ - Sidechain compression on bass means the BASS ducks when the KICK hits, not the other way around.
287
+
288
  ### ARTIST & DISCOGRAPHY KNOWLEDGE (CRITICAL):
289
  - You do NOT have detailed/reliable knowledge about specific artists, songs, albums, or career histories.
290
  - When asked about a specific artist (e.g., "Who is Laney Wilson?", "What album did X release?"), be direct:
 
332
 
333
  # ── ARTIST QUERY CONSTRAINT (critical hallucination prevention) ──
334
  if classification.get("has_artist_query"):
335
+ parts.append(
336
+ "\n## ARTIST QUERY DETECTED\n"
337
+ "This query is asking about a specific artist, song, album, or discography. "
338
+ "You do NOT have reliable training data about specific artists. Respond with honesty:\n\n"
339
+ "1. Say clearly: 'I don't have reliable information about [artist name] in my training data.'\n"
340
+ "2. Offer what you CAN help with instead:\n"
341
+ " - Production techniques for their genre/style\n"
342
+ " - Music theory and arrangement\n"
343
+ " - Creating music inspired by similar vibes\n"
344
+ " - Sound design for that aesthetic\n"
345
+ "3. Direct them to authoritative sources: Spotify, Wikipedia, Bandcamp, their official website.\n"
346
+ "4. Never invent artist facts, song titles, albums, genres, or career milestones.\n\n"
347
+ "This constraint overrides all else. Your value is in honest limitations, not false certainty.\n"
348
+ )
349
 
350
  parts.append(COMMUNICATION_STYLE)
351
  parts.append(BEHAVIORAL_LOCKS)
 
399
  if not query_words:
400
  return cocoon_memory[-max_results:] # fall back to recent
401
 
 
402
  now = time.time()
403
  scored = []
404
  for cocoon in cocoon_memory:
 
515
  return any(trigger in lower for trigger in INTROSPECTION_TRIGGERS)
516
 
517
 
518
+ # ── Hallucination Guard ──────────────────────────────────────
519
+ def check_hallucination_signals(text: str) -> tuple:
520
+ """Scan for hallucination red flags. Returns (has_issues, warnings)."""
521
+ warnings = []
522
+
523
+ # Red flag 1: Artist death claims
524
+ if re.search(r'(passed away|died|deceased).*?(19|20)\d{2}', text, re.IGNORECASE):
525
+ for artist in ["laney wilson", "megan moroney", "tyler childers",
526
+ "morgan wallen", "zach bryan", "bailey zimmerman"]:
527
+ if artist in text.lower():
528
+ warnings.append(f"Unverified death claim about {artist}")
529
+
530
+ # Red flag 2: Invented plugin names
531
+ if re.search(r'\b([A-Z][a-zA-Z0-9\s\-]+)\s+(plugin|VST|effect)\b', text):
532
+ last_plugin = re.findall(r'\b([A-Z][a-zA-Z0-9\s\-]+)\s+(plugin|VST)', text)
533
+ if last_plugin:
534
+ plugin_name = last_plugin[-1][0].lower()
535
+ real_plugins = {
536
+ "fabfilter", "waves", "izotope", "soundtoys", "valhalla",
537
+ "xfer", "native instruments", "spectrasonics", "u-he",
538
+ "arturia", "slate digital", "universal audio", "plugin alliance",
539
+ "pro-q", "pro-c", "pro-l", "ozone", "neutron", "serum",
540
+ }
541
+ if not any(real in plugin_name for real in real_plugins):
542
+ warnings.append(f"Unknown plugin: {last_plugin[-1][0]}")
543
+
544
+ # Red flag 3: Genre misclassification
545
+ genre_mismatches = [
546
+ ("laney wilson", "indie-rock"), ("laney wilson", "indie-folk"),
547
+ ("megan moroney", "indie-rock"), ("megan moroney", "indie-folk"),
548
+ ]
549
+ for artist, wrong_genre in genre_mismatches:
550
+ if artist in text.lower() and wrong_genre in text.lower():
551
+ warnings.append(f"Genre mismatch: {artist} is not {wrong_genre}")
552
+
553
+ # Red flag 4: Compression ratio described in dB
554
+ if re.search(r'ratio.*?(-?\d+\s*dB)', text, re.IGNORECASE):
555
+ warnings.append("Compression ratio should be X:1, not in dB")
556
+
557
+ return len(warnings) > 0, warnings
558
+
559
+
560
  # ── FastAPI App ───────────────────────────────────────────────
561
  app = FastAPI(title="Codette AI β€” HorizonCoreAI Reasoning Engine")
562
  app.add_middleware(
 
571
  print("9 adapter perspectives loaded")
572
  print("AEGIS ethical guard active")
573
  print("Behavioral locks enforced")
574
+ print("Artist hallucination guard active")
575
+ print("Music production grounding rules enforced")
576
 
577
 
578
  @app.get("/", response_class=HTMLResponse)
 
598
  "query_classifier": "active",
599
  "introspection": "active",
600
  "consciousness_stack": "12 layers",
601
+ "hallucination_guard": "active",
602
+ "artist_detection": "active",
603
  }
604
  return {
605
  "status": "healthy",
606
  "system": "Codette AI β€” HorizonCoreAI",
607
+ "version": "2.1-phase6",
608
  "checks": checks,
609
  "uptime": "running",
610
  }
 
705
  stream=True,
706
  )
707
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
708
  for chunk in stream:
709
  if chunk.choices and chunk.choices[0].delta.content:
710
  token = chunk.choices[0].delta.content
711
  full_response += token
 
 
 
 
 
 
 
 
 
712
  yield json.dumps({
713
  "message": {"role": "assistant", "content": token},
714
  "done": False,
715
  }) + "\n"
716
  await asyncio.sleep(0)
717
 
718
+ # ── Layer 3.5: Post-generation hallucination check ──
719
+ has_issues, warnings = check_hallucination_signals(full_response)
720
+ if has_issues and classification.get("has_artist_query"):
 
 
721
  correction = (
722
  "\n\n---\n"
723
+ "[Self-correction: I notice I may have made unverified claims above. "
 
724
  "I don't have reliable biographical information about this artist. "
725
  "For accurate details, please check Wikipedia, Spotify, or their official website. "
726
+ "I'm happy to help with production techniques, music theory, or creating inspired work instead.]"
727
  )
728
  full_response += correction
729
  yield json.dumps({
730
  "message": {"role": "assistant", "content": correction},
731
  "done": False,
732
+ "metadata": {"hallucination_detected": True, "issues": warnings},
733
  }) + "\n"
734
 
 
 
 
735
  # ── Layer 7: Store cocoon ──
736
  store_cocoon(query, full_response, classification, adapter_keys)
737
 
 
742
  }) + "\n"
743
 
744
  except Exception as e:
745
+ error_msg = "I encountered an issue processing your request. Please try again."
746
  print(f"[ERROR] Inference failed: {e}")
747
  print(f"[TRACE] {traceback.format_exc()}")
748
  yield json.dumps({