solana-agent 27.3.5__py3-none-any.whl → 27.3.7__py3-none-any.whl

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 (33) hide show
  1. solana_agent/__init__.py +1 -3
  2. solana_agent/adapters/mongodb_adapter.py +5 -2
  3. solana_agent/adapters/openai_adapter.py +32 -27
  4. solana_agent/adapters/pinecone_adapter.py +91 -63
  5. solana_agent/client/solana_agent.py +38 -23
  6. solana_agent/domains/agent.py +7 -13
  7. solana_agent/domains/routing.py +5 -5
  8. solana_agent/factories/agent_factory.py +49 -34
  9. solana_agent/interfaces/client/client.py +22 -13
  10. solana_agent/interfaces/plugins/plugins.py +2 -1
  11. solana_agent/interfaces/providers/data_storage.py +9 -2
  12. solana_agent/interfaces/providers/llm.py +26 -12
  13. solana_agent/interfaces/providers/memory.py +1 -1
  14. solana_agent/interfaces/providers/vector_storage.py +3 -9
  15. solana_agent/interfaces/services/agent.py +21 -6
  16. solana_agent/interfaces/services/knowledge_base.py +6 -8
  17. solana_agent/interfaces/services/query.py +16 -5
  18. solana_agent/interfaces/services/routing.py +0 -1
  19. solana_agent/plugins/manager.py +14 -9
  20. solana_agent/plugins/registry.py +13 -11
  21. solana_agent/plugins/tools/__init__.py +0 -5
  22. solana_agent/plugins/tools/auto_tool.py +1 -0
  23. solana_agent/repositories/memory.py +20 -22
  24. solana_agent/services/__init__.py +1 -1
  25. solana_agent/services/agent.py +119 -89
  26. solana_agent/services/knowledge_base.py +182 -131
  27. solana_agent/services/query.py +48 -24
  28. solana_agent/services/routing.py +30 -18
  29. {solana_agent-27.3.5.dist-info → solana_agent-27.3.7.dist-info}/METADATA +6 -3
  30. solana_agent-27.3.7.dist-info/RECORD +39 -0
  31. solana_agent-27.3.5.dist-info/RECORD +0 -39
  32. {solana_agent-27.3.5.dist-info → solana_agent-27.3.7.dist-info}/LICENSE +0 -0
  33. {solana_agent-27.3.5.dist-info → solana_agent-27.3.7.dist-info}/WHEEL +0 -0
@@ -4,8 +4,8 @@ Agent service implementation.
4
4
  This service manages AI and human agents, their registration, tool assignments,
5
5
  and response generation.
6
6
  """
7
+
7
8
  import asyncio
8
- from copy import deepcopy
9
9
  import datetime as main_datetime
10
10
  from datetime import datetime
11
11
  import json
@@ -53,7 +53,10 @@ class AgentService(AgentServiceInterface):
53
53
  )
54
54
 
55
55
  def register_ai_agent(
56
- self, name: str, instructions: str, specialization: str,
56
+ self,
57
+ name: str,
58
+ instructions: str,
59
+ specialization: str,
57
60
  ) -> None:
58
61
  """Register an AI agent with its specialization.
59
62
 
@@ -95,16 +98,19 @@ class AgentService(AgentServiceInterface):
95
98
  system_prompt += f"\n\nVOICE OF THE BRAND:\n{self.business_mission.voice}"
96
99
 
97
100
  if self.business_mission.values:
98
- values_text = "\n".join([
99
- f"- {value.get('name', '')}: {value.get('description', '')}"
100
- for value in self.business_mission.values
101
- ])
101
+ values_text = "\n".join(
102
+ [
103
+ f"- {value.get('name', '')}: {value.get('description', '')}"
104
+ for value in self.business_mission.values
105
+ ]
106
+ )
102
107
  system_prompt += f"\n\nBUSINESS VALUES:\n{values_text}"
103
108
 
104
109
  # Add goals if available
105
110
  if self.business_mission.goals:
106
111
  goals_text = "\n".join(
107
- [f"- {goal}" for goal in self.business_mission.goals])
112
+ [f"- {goal}" for goal in self.business_mission.goals]
113
+ )
108
114
  system_prompt += f"\n\nBUSINESS GOALS:\n{goals_text}"
109
115
 
110
116
  return system_prompt
@@ -140,7 +146,9 @@ class AgentService(AgentServiceInterface):
140
146
  """
141
147
  return self.tool_registry.get_agent_tools(agent_name)
142
148
 
143
- async def execute_tool(self, agent_name: str, tool_name: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
149
+ async def execute_tool(
150
+ self, agent_name: str, tool_name: str, parameters: Dict[str, Any]
151
+ ) -> Dict[str, Any]:
144
152
  """Execute a tool on behalf of an agent."""
145
153
 
146
154
  if not self.tool_registry:
@@ -156,7 +164,7 @@ class AgentService(AgentServiceInterface):
156
164
  if not any(t.get("name") == tool_name for t in agent_tools):
157
165
  return {
158
166
  "status": "error",
159
- "message": f"Agent '{agent_name}' doesn't have access to tool '{tool_name}'"
167
+ "message": f"Agent '{agent_name}' doesn't have access to tool '{tool_name}'",
160
168
  }
161
169
 
162
170
  try:
@@ -164,6 +172,7 @@ class AgentService(AgentServiceInterface):
164
172
  return result
165
173
  except Exception as e:
166
174
  import traceback
175
+
167
176
  print(traceback.format_exc())
168
177
  return {"status": "error", "message": f"Error executing tool: {str(e)}"}
169
178
 
@@ -174,11 +183,22 @@ class AgentService(AgentServiceInterface):
174
183
  query: Union[str, bytes],
175
184
  memory_context: str = "",
176
185
  output_format: Literal["text", "audio"] = "text",
177
- audio_voice: Literal["alloy", "ash", "ballad", "coral", "echo",
178
- "fable", "onyx", "nova", "sage", "shimmer"] = "nova",
186
+ audio_voice: Literal[
187
+ "alloy",
188
+ "ash",
189
+ "ballad",
190
+ "coral",
191
+ "echo",
192
+ "fable",
193
+ "onyx",
194
+ "nova",
195
+ "sage",
196
+ "shimmer",
197
+ ] = "nova",
179
198
  audio_instructions: str = "You speak in a friendly and helpful manner.",
180
- audio_output_format: Literal['mp3', 'opus',
181
- 'aac', 'flac', 'wav', 'pcm'] = "aac",
199
+ audio_output_format: Literal[
200
+ "mp3", "opus", "aac", "flac", "wav", "pcm"
201
+ ] = "aac",
182
202
  prompt: Optional[str] = None,
183
203
  ) -> AsyncGenerator[Union[str, bytes], None]: # pragma: no cover
184
204
  """Generate a response with support for text/audio input/output."""
@@ -186,8 +206,12 @@ class AgentService(AgentServiceInterface):
186
206
  if not agent:
187
207
  error_msg = f"Agent '{agent_name}' not found."
188
208
  if output_format == "audio":
189
- async for chunk in self.llm_provider.tts(error_msg, instructions=audio_instructions,
190
- response_format=audio_output_format, voice=audio_voice):
209
+ async for chunk in self.llm_provider.tts(
210
+ error_msg,
211
+ instructions=audio_instructions,
212
+ response_format=audio_output_format,
213
+ voice=audio_voice,
214
+ ):
191
215
  yield chunk
192
216
  else:
193
217
  yield error_msg
@@ -200,30 +224,33 @@ class AgentService(AgentServiceInterface):
200
224
  # --- 2. Add Tool Usage Instructions EARLY ---
201
225
  tool_usage_prompt_text = ""
202
226
  if self.tool_registry:
203
- tool_usage_prompt_text = self._get_tool_usage_prompt(
204
- agent_name)
227
+ tool_usage_prompt_text = self._get_tool_usage_prompt(agent_name)
205
228
  if tool_usage_prompt_text:
206
229
  system_prompt_parts.append(
207
- f"\n\n--- TOOL USAGE INSTRUCTIONS ---{tool_usage_prompt_text}")
230
+ f"\n\n--- TOOL USAGE INSTRUCTIONS ---{tool_usage_prompt_text}"
231
+ )
208
232
  print(
209
- f"Tools available to agent {agent_name}: {[t.get('name') for t in self.get_agent_tools(agent_name)]}")
233
+ f"Tools available to agent {agent_name}: {[t.get('name') for t in self.get_agent_tools(agent_name)]}"
234
+ )
210
235
 
211
236
  # --- 3. Add User ID ---
212
- system_prompt_parts.append(f"\n\n--- USER & SESSION INFO ---")
237
+ system_prompt_parts.append("\n\n--- USER & SESSION INFO ---")
213
238
  system_prompt_parts.append(f"User ID: {user_id}")
214
239
 
215
240
  # --- 4. Add Memory Context ---
216
241
  if memory_context:
217
242
  # Make the header clearly separate it
218
243
  system_prompt_parts.append(
219
- f"\n\n--- CONVERSATION HISTORY (Memory Context) ---")
244
+ "\n\n--- CONVERSATION HISTORY (Memory Context) ---"
245
+ )
220
246
  system_prompt_parts.append(memory_context)
221
247
 
222
248
  # --- 5. Add Additional Prompt (if provided) ---
223
249
  if prompt:
224
250
  # Make the header clearly separate it
225
251
  system_prompt_parts.append(
226
- f"\n\n--- ADDITIONAL INSTRUCTIONS FOR THIS TURN ---")
252
+ "\n\n--- ADDITIONAL INSTRUCTIONS FOR THIS TURN ---"
253
+ )
227
254
  system_prompt_parts.append(prompt)
228
255
 
229
256
  # --- Assemble the final system prompt ---
@@ -237,15 +264,13 @@ class AgentService(AgentServiceInterface):
237
264
  tool_buffer = ""
238
265
  pending_chunk = "" # To hold text that might contain partial markers
239
266
  is_tool_call = False
240
- window_size = 30 # Increased window size for better detection
241
267
 
242
268
  # Define start and end markers
243
269
  start_marker = "[TOOL]"
244
270
  end_marker = "[/TOOL]"
245
271
 
246
272
  # Generate and stream response (ALWAYS use non-realtime for text generation)
247
- print(
248
- f"Generating response with {len(query)} characters of query text")
273
+ print(f"Generating response with {len(query)} characters of query text")
249
274
  async for chunk in self.llm_provider.generate_text(
250
275
  prompt=query,
251
276
  system_prompt=final_system_prompt,
@@ -263,7 +288,8 @@ class AgentService(AgentServiceInterface):
263
288
  # STEP 1: Check for tool call start marker
264
289
  if start_marker in combined_chunk and not is_tool_call:
265
290
  print(
266
- f"Found tool start marker in chunk of length {len(combined_chunk)}")
291
+ f"Found tool start marker in chunk of length {len(combined_chunk)}"
292
+ )
267
293
  is_tool_call = True
268
294
 
269
295
  # Extract text before the marker and the marker itself with everything after
@@ -285,20 +311,18 @@ class AgentService(AgentServiceInterface):
285
311
 
286
312
  # Check if the tool call is complete
287
313
  if end_marker in tool_buffer:
288
- print(
289
- f"Tool call complete, buffer size: {len(tool_buffer)}")
314
+ print(f"Tool call complete, buffer size: {len(tool_buffer)}")
290
315
 
291
316
  # Process the tool call
292
317
  response_text = await self._handle_tool_call(
293
- agent_name=agent_name,
294
- tool_text=tool_buffer
318
+ agent_name=agent_name, tool_text=tool_buffer
295
319
  )
296
320
 
297
321
  # Clean the response to remove any markers or formatting
298
- response_text = self._clean_tool_response(
299
- response_text)
322
+ response_text = self._clean_tool_response(response_text)
300
323
  print(
301
- f"Tool execution complete, result size: {len(response_text)}")
324
+ f"Tool execution complete, result size: {len(response_text)}"
325
+ )
302
326
 
303
327
  # Create new prompt with search/tool results
304
328
  # Ensure query is string
@@ -307,36 +331,42 @@ class AgentService(AgentServiceInterface):
307
331
  # --- REBUILD the system prompt for the follow-up call ---
308
332
  # Start with base prompt again
309
333
  follow_up_system_prompt_parts = [
310
- self.get_agent_system_prompt(agent_name)]
334
+ self.get_agent_system_prompt(agent_name)
335
+ ]
311
336
  # Add the instruction NOT to use tools again
312
337
  follow_up_system_prompt_parts.append(
313
- "\n\nCRITICAL: You have received the results from a tool. Base your response on the 'Search Result' provided in the user prompt. DO NOT use the tool calling format again for this turn.")
314
- follow_up_system_prompt_parts.append(
315
- f"\n\n--- USER & SESSION INFO ---")
338
+ "\n\nCRITICAL: You have received the results from a tool. Base your response on the 'Search Result' provided in the user prompt. DO NOT use the tool calling format again for this turn."
339
+ )
316
340
  follow_up_system_prompt_parts.append(
317
- f"User ID: {user_id}")
341
+ "\n\n--- USER & SESSION INFO ---"
342
+ )
343
+ follow_up_system_prompt_parts.append(f"User ID: {user_id}")
318
344
  if memory_context:
319
345
  # Make the header clearly separate it
320
346
  follow_up_system_prompt_parts.append(
321
- f"\n\n--- CONVERSATION HISTORY (Memory Context) ---")
322
- follow_up_system_prompt_parts.append(
323
- memory_context)
347
+ "\n\n--- CONVERSATION HISTORY (Memory Context) ---"
348
+ )
349
+ follow_up_system_prompt_parts.append(memory_context)
324
350
  if prompt:
325
351
  # Make the header clearly separate it
326
352
  follow_up_system_prompt_parts.append(
327
- f"\n\n--- ADDITIONAL INSTRUCTIONS FOR THIS TURN ---")
353
+ "\n\n--- ADDITIONAL INSTRUCTIONS FOR THIS TURN ---"
354
+ )
328
355
  follow_up_system_prompt_parts.append(prompt)
329
356
 
330
357
  # --- Assemble the final follow_up prompt ---
331
358
  final_follow_up_system_prompt = "\n".join(
332
- follow_up_system_prompt_parts)
359
+ follow_up_system_prompt_parts
360
+ )
333
361
  # --- End Rebuild ---"
334
362
 
335
363
  # Generate a new response with the tool results
336
364
  print("Generating new response with tool results")
337
365
  if output_format == "text":
338
366
  # Stream the follow-up response for text output
339
- async for processed_chunk in self.llm_provider.generate_text(
367
+ async for (
368
+ processed_chunk
369
+ ) in self.llm_provider.generate_text(
340
370
  prompt=user_prompt,
341
371
  system_prompt=final_follow_up_system_prompt,
342
372
  api_key=self.api_key,
@@ -348,15 +378,16 @@ class AgentService(AgentServiceInterface):
348
378
  else:
349
379
  # For audio output, collect the full response first
350
380
  tool_response = ""
351
- async for processed_chunk in self.llm_provider.generate_text(
381
+ async for (
382
+ processed_chunk
383
+ ) in self.llm_provider.generate_text(
352
384
  prompt=user_prompt,
353
385
  system_prompt=final_follow_up_system_prompt,
354
386
  ):
355
387
  tool_response += processed_chunk
356
388
 
357
389
  # Clean and add to our complete text record and audio buffer
358
- tool_response = self._clean_for_audio(
359
- tool_response)
390
+ tool_response = self._clean_for_audio(tool_response)
360
391
  complete_text_response += tool_response
361
392
  full_response_buffer += tool_response
362
393
 
@@ -380,8 +411,7 @@ class AgentService(AgentServiceInterface):
380
411
  # Everything except the partial marker
381
412
  chunk_to_yield = combined_chunk[:-i]
382
413
  potential_marker = True
383
- print(
384
- f"Potential partial marker detected: '{pending_chunk}'")
414
+ print(f"Potential partial marker detected: '{pending_chunk}'")
385
415
  break
386
416
 
387
417
  if potential_marker:
@@ -405,7 +435,8 @@ class AgentService(AgentServiceInterface):
405
435
  # Process any incomplete tool call as regular text
406
436
  if is_tool_call and tool_buffer:
407
437
  print(
408
- f"Incomplete tool call detected, returning as regular text: {len(tool_buffer)} chars")
438
+ f"Incomplete tool call detected, returning as regular text: {len(tool_buffer)} chars"
439
+ )
409
440
  if output_format == "text":
410
441
  yield tool_buffer
411
442
 
@@ -417,28 +448,28 @@ class AgentService(AgentServiceInterface):
417
448
  if output_format == "audio" and full_response_buffer:
418
449
  # Clean text before TTS
419
450
  print(
420
- f"Processing {len(full_response_buffer)} characters for audio output")
421
- full_response_buffer = self._clean_for_audio(
422
- full_response_buffer)
451
+ f"Processing {len(full_response_buffer)} characters for audio output"
452
+ )
453
+ full_response_buffer = self._clean_for_audio(full_response_buffer)
423
454
 
424
455
  # Process the entire response with TTS
425
456
  async for audio_chunk in self.llm_provider.tts(
426
457
  text=full_response_buffer,
427
458
  voice=audio_voice,
428
459
  response_format=audio_output_format,
429
- instructions=audio_instructions
460
+ instructions=audio_instructions,
430
461
  ):
431
462
  yield audio_chunk
432
463
 
433
464
  # Store the complete text response
434
465
  self.last_text_response = complete_text_response
435
- print(
436
- f"Response generation complete: {len(complete_text_response)} chars")
466
+ print(f"Response generation complete: {len(complete_text_response)} chars")
437
467
 
438
468
  except Exception as e:
439
469
  error_msg = f"I apologize, but I encountered an error: {str(e)}"
440
470
  print(f"Error in generate_response: {str(e)}")
441
471
  import traceback
472
+
442
473
  print(traceback.format_exc())
443
474
 
444
475
  if output_format == "audio":
@@ -446,7 +477,7 @@ class AgentService(AgentServiceInterface):
446
477
  error_msg,
447
478
  voice=audio_voice,
448
479
  response_format=audio_output_format,
449
- instructions=audio_instructions
480
+ instructions=audio_instructions,
450
481
  ):
451
482
  yield chunk
452
483
  else:
@@ -465,7 +496,7 @@ class AgentService(AgentServiceInterface):
465
496
  chunk_size = 4096
466
497
 
467
498
  for i in range(0, len(data), chunk_size):
468
- yield data[i:i + chunk_size]
499
+ yield data[i : i + chunk_size]
469
500
  # Small delay to simulate streaming
470
501
  await asyncio.sleep(0.01)
471
502
 
@@ -514,6 +545,7 @@ class AgentService(AgentServiceInterface):
514
545
 
515
546
  except Exception as e:
516
547
  import traceback
548
+
517
549
  print(traceback.format_exc())
518
550
  return f"Error processing tool call: {str(e)}"
519
551
 
@@ -524,8 +556,6 @@ class AgentService(AgentServiceInterface):
524
556
  if not tools:
525
557
  return ""
526
558
 
527
- # Get actual tool names
528
- available_tool_names = [tool.get("name", "") for tool in tools]
529
559
  tools_json = json.dumps(tools, indent=2)
530
560
 
531
561
  return f"""
@@ -578,62 +608,62 @@ class AgentService(AgentServiceInterface):
578
608
  return ""
579
609
 
580
610
  # Remove Markdown links - [text](url) -> text
581
- text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text)
611
+ text = re.sub(r"\[([^\]]+)\]\([^\)]+\)", r"\1", text)
582
612
 
583
613
  # Remove inline code with backticks
584
- text = re.sub(r'`([^`]+)`', r'\1', text)
614
+ text = re.sub(r"`([^`]+)`", r"\1", text)
585
615
 
586
616
  # Remove bold formatting - **text** or __text__ -> text
587
- text = re.sub(r'(\*\*|__)(.*?)\1', r'\2', text)
617
+ text = re.sub(r"(\*\*|__)(.*?)\1", r"\2", text)
588
618
 
589
619
  # Remove italic formatting - *text* or _text_ -> text
590
- text = re.sub(r'(\*|_)(.*?)\1', r'\2', text)
620
+ text = re.sub(r"(\*|_)(.*?)\1", r"\2", text)
591
621
 
592
622
  # Remove headers - ## Header -> Header
593
- text = re.sub(r'^\s*#+\s*(.*?)$', r'\1', text, flags=re.MULTILINE)
623
+ text = re.sub(r"^\s*#+\s*(.*?)$", r"\1", text, flags=re.MULTILINE)
594
624
 
595
625
  # Remove blockquotes - > Text -> Text
596
- text = re.sub(r'^\s*>\s*(.*?)$', r'\1', text, flags=re.MULTILINE)
626
+ text = re.sub(r"^\s*>\s*(.*?)$", r"\1", text, flags=re.MULTILINE)
597
627
 
598
628
  # Remove horizontal rules (---, ***, ___)
599
- text = re.sub(r'^\s*[-*_]{3,}\s*$', '', text, flags=re.MULTILINE)
629
+ text = re.sub(r"^\s*[-*_]{3,}\s*$", "", text, flags=re.MULTILINE)
600
630
 
601
631
  # Remove list markers - * Item or - Item or 1. Item -> Item
602
- text = re.sub(r'^\s*[-*+]\s+(.*?)$', r'\1', text, flags=re.MULTILINE)
603
- text = re.sub(r'^\s*\d+\.\s+(.*?)$', r'\1', text, flags=re.MULTILINE)
632
+ text = re.sub(r"^\s*[-*+]\s+(.*?)$", r"\1", text, flags=re.MULTILINE)
633
+ text = re.sub(r"^\s*\d+\.\s+(.*?)$", r"\1", text, flags=re.MULTILINE)
604
634
 
605
635
  # Remove multiple consecutive newlines (keep just one)
606
- text = re.sub(r'\n{3,}', '\n\n', text)
636
+ text = re.sub(r"\n{3,}", "\n\n", text)
607
637
 
608
638
  # Remove emojis and other non-pronounceable characters
609
639
  # Common emoji Unicode ranges
610
640
  emoji_pattern = re.compile(
611
641
  "["
612
- "\U0001F600-\U0001F64F" # emoticons
613
- "\U0001F300-\U0001F5FF" # symbols & pictographs
614
- "\U0001F680-\U0001F6FF" # transport & map symbols
615
- "\U0001F700-\U0001F77F" # alchemical symbols
616
- "\U0001F780-\U0001F7FF" # Geometric Shapes
617
- "\U0001F800-\U0001F8FF" # Supplemental Arrows-C
618
- "\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs
619
- "\U0001FA00-\U0001FA6F" # Chess Symbols
620
- "\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A
621
- "\U00002702-\U000027B0" # Dingbats
622
- "\U000024C2-\U0000257F" # Enclosed characters
623
- "\U00002600-\U000026FF" # Miscellaneous Symbols
624
- "\U00002700-\U000027BF" # Dingbats
625
- "\U0000FE00-\U0000FE0F" # Variation Selectors
626
- "\U0001F1E0-\U0001F1FF" # Flags (iOS)
642
+ "\U0001f600-\U0001f64f" # emoticons
643
+ "\U0001f300-\U0001f5ff" # symbols & pictographs
644
+ "\U0001f680-\U0001f6ff" # transport & map symbols
645
+ "\U0001f700-\U0001f77f" # alchemical symbols
646
+ "\U0001f780-\U0001f7ff" # Geometric Shapes
647
+ "\U0001f800-\U0001f8ff" # Supplemental Arrows-C
648
+ "\U0001f900-\U0001f9ff" # Supplemental Symbols and Pictographs
649
+ "\U0001fa00-\U0001fa6f" # Chess Symbols
650
+ "\U0001fa70-\U0001faff" # Symbols and Pictographs Extended-A
651
+ "\U00002702-\U000027b0" # Dingbats
652
+ "\U000024c2-\U0000257f" # Enclosed characters
653
+ "\U00002600-\U000026ff" # Miscellaneous Symbols
654
+ "\U00002700-\U000027bf" # Dingbats
655
+ "\U0000fe00-\U0000fe0f" # Variation Selectors
656
+ "\U0001f1e0-\U0001f1ff" # Flags (iOS)
627
657
  "]+",
628
- flags=re.UNICODE
658
+ flags=re.UNICODE,
629
659
  )
630
- text = emoji_pattern.sub(r' ', text)
660
+ text = emoji_pattern.sub(r" ", text)
631
661
 
632
662
  # Replace special characters that can cause issues with TTS
633
- text = re.sub(r'[^\w\s\.\,\;\:\?\!\'\"\-\(\)]', ' ', text)
663
+ text = re.sub(r"[^\w\s\.\,\;\:\?\!\'\"\-\(\)]", " ", text)
634
664
 
635
665
  # Replace multiple spaces with a single space
636
- text = re.sub(r'\s+', ' ', text)
666
+ text = re.sub(r"\s+", " ", text)
637
667
 
638
668
  return text.strip()
639
669