neuralnode 2.0.3__tar.gz → 2.0.5__tar.gz

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.
Files changed (117) hide show
  1. {neuralnode-2.0.3 → neuralnode-2.0.5}/PKG-INFO +1 -1
  2. neuralnode-2.0.5/horus_chat_voice.py +61 -0
  3. {neuralnode-2.0.3 → neuralnode-2.0.5}/pyproject.toml +1 -1
  4. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/__init__.py +1 -1
  5. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/horus.py +73 -17
  6. {neuralnode-2.0.3 → neuralnode-2.0.5}/.env.example +0 -0
  7. {neuralnode-2.0.3 → neuralnode-2.0.5}/.github/workflows/tests.yml +0 -0
  8. {neuralnode-2.0.3 → neuralnode-2.0.5}/Dockerfile +0 -0
  9. {neuralnode-2.0.3 → neuralnode-2.0.5}/LICENSE +0 -0
  10. {neuralnode-2.0.3 → neuralnode-2.0.5}/README.md +0 -0
  11. {neuralnode-2.0.3 → neuralnode-2.0.5}/docker-compose.yml +0 -0
  12. {neuralnode-2.0.3 → neuralnode-2.0.5}/docs/documentation.md +0 -0
  13. {neuralnode-2.0.3 → neuralnode-2.0.5}/docs/ecosystem_plan.md +0 -0
  14. {neuralnode-2.0.3 → neuralnode-2.0.5}/docs/replica_voice_ids.csv +0 -0
  15. {neuralnode-2.0.3 → neuralnode-2.0.5}/docs/replica_voice_ids.md +0 -0
  16. {neuralnode-2.0.3 → neuralnode-2.0.5}/docs/telegram_guide.md +0 -0
  17. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/agent_with_tools.py +0 -0
  18. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/basic_chat.py +0 -0
  19. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/01_basic_usage.py +0 -0
  20. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/02_with_token.py +0 -0
  21. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/03_one_liner.py +0 -0
  22. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/04_custom_cache.py +0 -0
  23. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/05_4bit_quantization.py +0 -0
  24. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/06_8bit_quantization.py +0 -0
  25. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/07_multi_gpu.py +0 -0
  26. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/08_flash_attention.py +0 -0
  27. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/09_data_types.py +0 -0
  28. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/10_generation_params.py +0 -0
  29. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/11_streaming.py +0 -0
  30. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/12_chat_templates.py +0 -0
  31. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/13_offline_mode.py +0 -0
  32. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/14_force_download.py +0 -0
  33. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/15_model_info.py +0 -0
  34. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/16_cpu_offloading.py +0 -0
  35. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/17_cpu_only.py +0 -0
  36. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/18_production_setup.py +0 -0
  37. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/19_gguf_4bit.py +0 -0
  38. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/20_gguf_5bit.py +0 -0
  39. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/21_gguf_6bit.py +0 -0
  40. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/22_gguf_8bit.py +0 -0
  41. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/23_gguf_16bit.py +0 -0
  42. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/24_list_models.py +0 -0
  43. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/25_interactive_chat.py +0 -0
  44. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_codes_camples/README.md +0 -0
  45. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_download_guide.py +0 -0
  46. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_examples.py +0 -0
  47. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_tq_ready_gguf.py +0 -0
  48. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/horus_transformers_features.py +0 -0
  49. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/local_models.py +0 -0
  50. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/neuralnode_v21_complete_demo.py +0 -0
  51. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/shade_model_with_tools.py +0 -0
  52. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/telegram_bot_demo.py +0 -0
  53. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/thinking_mode_example.py +0 -0
  54. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/tts_demo.py +0 -0
  55. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/turboquant_example.py +0 -0
  56. {neuralnode-2.0.3 → neuralnode-2.0.5}/examples/v3_features.py +0 -0
  57. {neuralnode-2.0.3 → neuralnode-2.0.5}/neuralnode_horus_replica_telegram.ipynb +0 -0
  58. {neuralnode-2.0.3 → neuralnode-2.0.5}/nn.md +0 -0
  59. {neuralnode-2.0.3 → neuralnode-2.0.5}/publish.bat +0 -0
  60. {neuralnode-2.0.3 → neuralnode-2.0.5}/publish.sh +0 -0
  61. {neuralnode-2.0.3 → neuralnode-2.0.5}/replica_output_85218.mp3 +0 -0
  62. {neuralnode-2.0.3 → neuralnode-2.0.5}/requirements_shade.txt +0 -0
  63. {neuralnode-2.0.3 → neuralnode-2.0.5}/scripts/setup.py +0 -0
  64. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/debug_import.py +0 -0
  65. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/agents/__init__.py +0 -0
  66. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/chains/__init__.py +0 -0
  67. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/config/__init__.py +0 -0
  68. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/core/__init__.py +0 -0
  69. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/core/openai_blocker.py +0 -0
  70. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/diagnostics/__init__.py +0 -0
  71. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/integrations/discord.py +0 -0
  72. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/integrations/slack.py +0 -0
  73. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/integrations/telegram.py +0 -0
  74. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/integrations/whatsapp.py +0 -0
  75. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/memory/__init__.py +0 -0
  76. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/memory/advanced.py +0 -0
  77. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/prompts/__init__.py +0 -0
  78. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/__init__.py +0 -0
  79. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/base.py +0 -0
  80. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/__init__.py +0 -0
  81. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/ai21.py +0 -0
  82. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/anthropic.py +0 -0
  83. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/cohere.py +0 -0
  84. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/deepseek.py +0 -0
  85. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/fireworks.py +0 -0
  86. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/google.py +0 -0
  87. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/groq.py +0 -0
  88. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/mistral.py +0 -0
  89. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/perplexity.py +0 -0
  90. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat/together.py +0 -0
  91. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/chat_models.py +0 -0
  92. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/embeddings.py +0 -0
  93. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/local/__init__.py +0 -0
  94. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/local_providers.py +0 -0
  95. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/text_generation.py +0 -0
  96. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/providers/universal_local.py +0 -0
  97. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/rag/__init__.py +0 -0
  98. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/rag/loaders.py +0 -0
  99. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/reasoning/__init__.py +0 -0
  100. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/replica.py +0 -0
  101. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/speech/__init__.py +0 -0
  102. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/thinking.py +0 -0
  103. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/tools/__init__.py +0 -0
  104. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/tools/advanced.py +0 -0
  105. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/tools/multisearch.py +0 -0
  106. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/tools/system/__init__.py +0 -0
  107. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/tools/system/operations.py +0 -0
  108. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/tools/web/__init__.py +0 -0
  109. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/tts/__init__.py +0 -0
  110. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/turboquant.py +0 -0
  111. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/utils/__init__.py +0 -0
  112. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/utils/dependencies.py +0 -0
  113. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/utils/logger.py +0 -0
  114. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/utils/metrics.py +0 -0
  115. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/vectorstores/__init__.py +0 -0
  116. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/neuralnode/vision/__init__.py +0 -0
  117. {neuralnode-2.0.3 → neuralnode-2.0.5}/src/nn/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neuralnode
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary: Comprehensive AI Framework with 50+ LLM Providers, Advanced Agents, Chains, Memory, RAG, and 100+ Tools
5
5
  Project-URL: Homepage, https://assem.cloud/
6
6
  Project-URL: Documentation, https://neuralnode.readthedocs.io
@@ -0,0 +1,61 @@
1
+ """
2
+ Horus Chat with Replica TTS (Female EN-US Voice)
3
+ Run: pip install edge-tts>=6.1.0
4
+ """
5
+ import neuralnode as nn
6
+ import os
7
+ import platform
8
+
9
+ # Initialize Replica TTS with female EN-US voice
10
+ tts = nn.ReplicaTTS(voice_id="replic-aria-language{en-us}")
11
+
12
+ def play_audio(file_path):
13
+ """Play audio file based on OS"""
14
+ system = platform.system()
15
+ try:
16
+ if system == "Windows":
17
+ import winsound
18
+ winsound.PlaySound(file_path, winsound.SND_FILENAME | winsound.SND_ASYNC)
19
+ elif system == "Darwin": # macOS
20
+ os.system(f"afplay '{file_path}' &")
21
+ else: # Linux
22
+ os.system(f"mpg321 '{file_path}' & 2>/dev/null || aplay '{file_path}' & 2>/dev/null || cvlc '{file_path}' --play-and-exit & 2>/dev/null")
23
+ except Exception as e:
24
+ print(f"⚠️ Could not play audio: {e}")
25
+ print(f"📁 Audio saved to: {file_path}")
26
+
27
+ # Load Horus model
28
+ model = nn.HorusModel(
29
+ model_id="tokenaii/Hours-1.0-4B-GGUF/Horus-1.0-4B-Q4_K_M.gguf"
30
+ ).load()
31
+
32
+ messages = [
33
+ {"role": "system", "content": "You're a medical AI assistant."}
34
+ ]
35
+
36
+ print("Horus chat started. Type 'exit' to quit.\n")
37
+
38
+ while True:
39
+ user_text = input("You: ").strip()
40
+ if not user_text:
41
+ continue
42
+ if user_text.lower() in {"exit", "quit", "q"}:
43
+ print("Bye.")
44
+ break
45
+
46
+ messages.append({"role": "user", "content": user_text})
47
+ resp = model.chat(messages)
48
+ assistant_text = resp.content.strip()
49
+
50
+ print(f"Horus: {assistant_text}\n")
51
+
52
+ # Generate and play audio
53
+ print("🔊 Generating audio...")
54
+ audio_file = tts.speak(assistant_text, blocking=True)
55
+ print(f"🎵 Playing: {audio_file}")
56
+ play_audio(audio_file)
57
+
58
+ messages.append({"role": "assistant", "content": assistant_text})
59
+
60
+ # optional cleanup
61
+ model.unload()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "neuralnode"
7
- version = "2.0.3"
7
+ version = "2.0.5"
8
8
  description = "Comprehensive AI Framework with 50+ LLM Providers, Advanced Agents, Chains, Memory, RAG, and 100+ Tools"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -42,7 +42,7 @@ Quick Start::
42
42
  text = sr.listen()
43
43
  """
44
44
 
45
- __version__ = "2.0.3"
45
+ __version__ = "2.0.5"
46
46
  __author__ = "NeuralNode Contributors"
47
47
 
48
48
  # ── Core types ────────────────────────────────────────────────────────────────
@@ -112,6 +112,7 @@ UNIFIED_SYSTEM_PROMPT = (
112
112
  "6) For instructions that require steps, provide clear actionable steps without filler.\n"
113
113
  "7) If the user asks for code, produce correct runnable code and mention assumptions briefly.\n"
114
114
  "8) If the user request is unsafe or harmful, refuse briefly and offer a safe alternative.\n"
115
+ "9) Do not repeatedly introduce yourself. Only provide your identity if the user explicitly asks who you are.\n"
115
116
  "\n"
116
117
  "Quality checks before responding:\n"
117
118
  "- Is the answer grounded in provided context or clearly stated assumptions?\n"
@@ -237,10 +238,10 @@ class HorusProvider(BaseLLMProvider):
237
238
  force_download: bool = False,
238
239
  resume_download: bool = False,
239
240
  max_new_tokens: int = 512,
240
- temperature: float = 0.7,
241
- top_p: float = 0.9,
241
+ temperature: float = 0.3,
242
+ top_p: float = 0.85,
242
243
  top_k: int = 50,
243
- repetition_penalty: float = 1.1,
244
+ repetition_penalty: float = 1.2,
244
245
  do_sample: bool = True,
245
246
  num_beams: int = 1,
246
247
  early_stopping: bool = False,
@@ -433,13 +434,18 @@ class HorusProvider(BaseLLMProvider):
433
434
  repo_id, filename = self._split_repo_and_filename(self.model_id)
434
435
  model_path = filename
435
436
  if repo_id and HF_HUB_AVAILABLE:
436
- model_path = hf_hub_download(
437
- repo_id=repo_id,
438
- filename=filename,
439
- cache_dir=self.cache_dir,
440
- local_files_only=self.local_files_only,
441
- token=self.token,
442
- )
437
+ with warnings.catch_warnings():
438
+ warnings.filterwarnings(
439
+ "ignore",
440
+ message=r".*You are sending unauthenticated requests to the HF Hub.*",
441
+ )
442
+ model_path = hf_hub_download(
443
+ repo_id=repo_id,
444
+ filename=filename,
445
+ cache_dir=self.cache_dir,
446
+ local_files_only=self.local_files_only,
447
+ token=self.token,
448
+ )
443
449
 
444
450
  llama_kwargs: Dict[str, Any] = {
445
451
  "model_path": model_path,
@@ -788,20 +794,61 @@ class HorusProvider(BaseLLMProvider):
788
794
  output = self.model(
789
795
  prompt,
790
796
  max_tokens=int(settings.get("max_new_tokens", 512)),
791
- temperature=settings.get("temperature", 0.7),
792
- top_p=settings.get("top_p", 0.9),
797
+ temperature=settings.get("temperature", 0.3),
798
+ top_p=settings.get("top_p", 0.85),
793
799
  top_k=settings.get("top_k", 50),
794
- repeat_penalty=settings.get("repetition_penalty", 1.1),
795
- stop=["\nUser:", "\nSystem:", "\nTool:"],
800
+ repeat_penalty=settings.get("repetition_penalty", 1.2),
801
+ stop=["<|end|>", "<|user|>", "\n<|user|>", "<|assistant|>", "\n<|assistant|>"],
796
802
  echo=False,
797
803
  )
798
- return output["choices"][0]["text"].strip()
804
+ return self._clean_generated_text(output["choices"][0]["text"])
805
+
806
+ def _clean_generated_text(self, text: str) -> str:
807
+ """Clean generation artifacts from Horus outputs."""
808
+ cleaned = text or ""
809
+ cleaned = cleaned.replace("<|assistant|>", "").replace("<|user|>", "").replace("<|end|>", "")
810
+ cleaned = re.sub(r"(?im)^\s*assistant\s*[::]?\s*", "", cleaned)
811
+ cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
812
+ cleaned = cleaned.strip()
813
+ # Cut obvious accidental next-turn continuation if present.
814
+ for marker in ("\nUser:", "\nSystem:", "\nTool:"):
815
+ idx = cleaned.find(marker)
816
+ if idx > 0:
817
+ cleaned = cleaned[:idx].strip()
818
+ return cleaned
819
+
820
+ @staticmethod
821
+ def _is_identity_question(user_text: str) -> bool:
822
+ q = (user_text or "").strip().lower()
823
+ identity_markers = (
824
+ "who are you",
825
+ "what are you",
826
+ "your name",
827
+ "من انت",
828
+ "مين انت",
829
+ "ما اسمك",
830
+ "اسمك ايه",
831
+ "عرف نفسك",
832
+ )
833
+ return any(marker in q for marker in identity_markers)
834
+
835
+ @staticmethod
836
+ def _strip_redundant_identity_prefix(text: str) -> str:
837
+ patterns = [
838
+ r"^\s*i(?:\s*am|'m)\s+horus,\s*an ai model developed by tokenai\.?\s*",
839
+ r"^\s*i(?:\s*am|'m)\s+horus\.?\s*",
840
+ r"^\s*أنا\s+horus[^.!\n]*[.!\n]\s*",
841
+ ]
842
+ cleaned = text
843
+ for pattern in patterns:
844
+ cleaned = re.sub(pattern, "", cleaned, flags=re.IGNORECASE)
845
+ return cleaned.strip() or text
799
846
 
800
847
  def chat(
801
848
  self,
802
849
  messages: List[Dict[str, Any]],
803
850
  model: Optional[str] = None,
804
- temperature: float = 0.7,
851
+ temperature: Optional[float] = None,
805
852
  max_tokens: Optional[int] = None,
806
853
  top_p: Optional[float] = None,
807
854
  frequency_penalty: Optional[float] = None,
@@ -833,7 +880,7 @@ class HorusProvider(BaseLLMProvider):
833
880
 
834
881
  prompt = self._render_prompt(normalized)
835
882
  generation_kwargs = {
836
- "temperature": temperature,
883
+ "temperature": temperature if temperature is not None else self.generation_config["temperature"],
837
884
  "max_new_tokens": max_tokens or self.generation_config["max_new_tokens"],
838
885
  "top_p": top_p if top_p is not None else self.generation_config["top_p"],
839
886
  **kwargs,
@@ -847,6 +894,15 @@ class HorusProvider(BaseLLMProvider):
847
894
  else:
848
895
  content = self._generate_transformers_text(prompt, **generation_kwargs)
849
896
 
897
+ # Reduce repetitive self-introduction unless identity was explicitly requested.
898
+ last_user_message = ""
899
+ for m in reversed(normalized):
900
+ if m.get("role") == "user":
901
+ last_user_message = m.get("content", "")
902
+ break
903
+ if not self._is_identity_question(last_user_message):
904
+ content = self._strip_redundant_identity_prefix(content)
905
+
850
906
  # Parse tool calls from response if tools were provided
851
907
  tool_calls = []
852
908
  if tools:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes