laxminarasimha6 commited on
Commit
db41332
·
verified ·
1 Parent(s): 09f6996

Upload 9 files

Browse files
Files changed (9) hide show
  1. .gitattributes +37 -35
  2. .gitignore +10 -0
  3. README.md +39 -14
  4. app.py +610 -0
  5. config.py +31 -0
  6. main.py +151 -0
  7. models.py +47 -0
  8. requirements.txt +15 -0
  9. utils.py +34 -0
.gitattributes CHANGED
@@ -1,35 +1,37 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ examples/1.png filter=lfs diff=lfs merge=lfs -text
37
+ examples/document.jpg filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.so
4
+ .env
5
+ venv/
6
+ *.mp3
7
+ *.png
8
+ *.glb
9
+ output/
10
+ static/
README.md CHANGED
@@ -1,14 +1,39 @@
1
- ---
2
- title: Nexus Bot
3
- emoji: 🏃
4
- colorFrom: red
5
- colorTo: yellow
6
- sdk: gradio
7
- sdk_version: 5.16.1
8
- app_file: app.py
9
- pinned: false
10
- license: mit
11
- short_description: '# NexusAI - Advanced Multimodal Assistant '
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NexusAI - Advanced Multimodal Assistant
2
+
3
+ NexusAI is a cutting-edge AI assistant that seamlessly integrates multiple AI capabilities:
4
+ - Intelligent Chat & Text Generation
5
+ - Natural Voice Synthesis
6
+ - High-Quality Image Creation
7
+ - 3D Model Generation
8
+ - Web Intelligence
9
+ - Code Analysis & Generation
10
+
11
+ ## Key Commands
12
+
13
+ - `@voice1` or `@voice2`: Natural voice synthesis
14
+ - `@create`: Generate stunning images
15
+ - `@shape`: Design 3D models from descriptions
16
+ - `@search`: Intelligent web search
17
+ - `@code`: Advanced code analysis
18
+
19
+ ## Features
20
+
21
+ - State-of-the-art language models for natural conversation
22
+ - Advanced image generation using latest stable diffusion
23
+ - Real-time voice synthesis
24
+ - Intelligent web search and content analysis
25
+ - Code generation and explanation
26
+ - 3D model creation from text descriptions
27
+
28
+ ## Models Used
29
+
30
+ - Vision-Language Model: Qwen2.5-VL-7B-Instruct
31
+ - Text Generation: TinyLlama-1.1B-Chat-v1.0
32
+ - Image Generation: SDXL
33
+ - 3D Generation: ShapE
34
+
35
+ ## Setup
36
+
37
+ ```bash
38
+ pip install -r requirements.txt
39
+ python app.py
app.py ADDED
@@ -0,0 +1,610 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import random
4
+ import uuid
5
+ import json
6
+ import time
7
+ import asyncio
8
+ import tempfile
9
+ from threading import Thread
10
+ import base64
11
+ import shutil
12
+ import re
13
+
14
+ import gradio as gr
15
+ import spaces
16
+ import torch
17
+ import numpy as np
18
+ from PIL import Image
19
+ import edge_tts
20
+ import trimesh
21
+
22
+ from transformers import (
23
+ AutoModelForCausalLM,
24
+ AutoTokenizer,
25
+ TextIteratorStreamer,
26
+ Qwen2VLForConditionalGeneration,
27
+ AutoProcessor,
28
+ )
29
+ from transformers.image_utils import load_image
30
+
31
+ from diffusers import StableDiffusionXLPipeline, EulerAncestralDiscreteScheduler
32
+ from diffusers import ShapEImg2ImgPipeline, ShapEPipeline
33
+ from diffusers.utils import export_to_ply
34
+
35
+ # Global constants and helper functions
36
+
37
+ MAX_SEED = np.iinfo(np.int32).max
38
+
39
+ def randomize_seed_fn(seed: int, randomize_seed: bool) -> int:
40
+ if randomize_seed:
41
+ seed = random.randint(0, MAX_SEED)
42
+ return seed
43
+
44
+ def glb_to_data_url(glb_path: str) -> str:
45
+ """
46
+ Reads a GLB file from disk and returns a data URL with a base64 encoded representation.
47
+ (Not used in this method.)
48
+ """
49
+ with open(glb_path, "rb") as f:
50
+ data = f.read()
51
+ b64_data = base64.b64encode(data).decode("utf-8")
52
+ return f"data:model/gltf-binary;base64,{b64_data}"
53
+
54
+ # Model class for Text-to-3D Generation (ShapE)
55
+
56
+ class Model:
57
+ def __init__(self):
58
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
59
+ self.pipe = ShapEPipeline.from_pretrained("openai/shap-e", torch_dtype=torch.float16)
60
+ self.pipe.to(self.device)
61
+ # Ensure the text encoder is in half precision to avoid dtype mismatches.
62
+ if torch.cuda.is_available():
63
+ try:
64
+ self.pipe.text_encoder = self.pipe.text_encoder.half()
65
+ except AttributeError:
66
+ pass
67
+
68
+ self.pipe_img = ShapEImg2ImgPipeline.from_pretrained("openai/shap-e-img2img", torch_dtype=torch.float16)
69
+ self.pipe_img.to(self.device)
70
+ # Use getattr with a default value to avoid AttributeError if text_encoder is missing.
71
+ if torch.cuda.is_available():
72
+ text_encoder_img = getattr(self.pipe_img, "text_encoder", None)
73
+ if text_encoder_img is not None:
74
+ self.pipe_img.text_encoder = text_encoder_img.half()
75
+
76
+ def to_glb(self, ply_path: str) -> str:
77
+ mesh = trimesh.load(ply_path)
78
+ # Rotate the mesh for proper orientation
79
+ rot = trimesh.transformations.rotation_matrix(-np.pi / 2, [1, 0, 0])
80
+ mesh.apply_transform(rot)
81
+ rot = trimesh.transformations.rotation_matrix(np.pi, [0, 1, 0])
82
+ mesh.apply_transform(rot)
83
+ mesh_path = tempfile.NamedTemporaryFile(suffix=".glb", delete=False)
84
+ mesh.export(mesh_path.name, file_type="glb")
85
+ return mesh_path.name
86
+
87
+ def run_text(self, prompt: str, seed: int = 0, guidance_scale: float = 15.0, num_steps: int = 64) -> str:
88
+ generator = torch.Generator(device=self.device).manual_seed(seed)
89
+ images = self.pipe(
90
+ prompt,
91
+ generator=generator,
92
+ guidance_scale=guidance_scale,
93
+ num_inference_steps=num_steps,
94
+ output_type="mesh",
95
+ ).images
96
+ ply_path = tempfile.NamedTemporaryFile(suffix=".ply", delete=False, mode="w+b")
97
+ export_to_ply(images[0], ply_path.name)
98
+ return self.to_glb(ply_path.name)
99
+
100
+ def run_image(self, image: Image.Image, seed: int = 0, guidance_scale: float = 3.0, num_steps: int = 64) -> str:
101
+ generator = torch.Generator(device=self.device).manual_seed(seed)
102
+ images = self.pipe_img(
103
+ image,
104
+ generator=generator,
105
+ guidance_scale=guidance_scale,
106
+ num_inference_steps=num_steps,
107
+ output_type="mesh",
108
+ ).images
109
+ ply_path = tempfile.NamedTemporaryFile(suffix=".ply", delete=False, mode="w+b")
110
+ export_to_ply(images[0], ply_path.name)
111
+ return self.to_glb(ply_path.name)
112
+
113
+ # New Tools for Web Functionality using DuckDuckGo and smolagents
114
+
115
+ from typing import Any, Optional
116
+ from smolagents.tools import Tool
117
+ import duckduckgo_search
118
+
119
+ class DuckDuckGoSearchTool(Tool):
120
+ name = "web_search"
121
+ description = "Performs a duckduckgo web search based on your query (think a Google search) then returns the top search results."
122
+ inputs = {'query': {'type': 'string', 'description': 'The search query to perform.'}}
123
+ output_type = "string"
124
+
125
+ def __init__(self, max_results=10, **kwargs):
126
+ super().__init__()
127
+ self.max_results = max_results
128
+ try:
129
+ from duckduckgo_search import DDGS
130
+ except ImportError as e:
131
+ raise ImportError(
132
+ "You must install package `duckduckgo_search` to run this tool: for instance run `pip install duckduckgo-search`."
133
+ ) from e
134
+ self.ddgs = DDGS(**kwargs)
135
+
136
+ def forward(self, query: str) -> str:
137
+ results = self.ddgs.text(query, max_results=self.max_results)
138
+ if len(results) == 0:
139
+ raise Exception("No results found! Try a less restrictive/shorter query.")
140
+ postprocessed_results = [
141
+ f"[{result['title']}]({result['href']})\n{result['body']}" for result in results
142
+ ]
143
+ return "## Search Results\n\n" + "\n\n".join(postprocessed_results)
144
+
145
+ class VisitWebpageTool(Tool):
146
+ name = "visit_webpage"
147
+ description = "Visits a webpage at the given url and reads its content as a markdown string. Use this to browse webpages."
148
+ inputs = {'url': {'type': 'string', 'description': 'The url of the webpage to visit.'}}
149
+ output_type = "string"
150
+
151
+ def __init__(self, *args, **kwargs):
152
+ self.is_initialized = False
153
+
154
+ def forward(self, url: str) -> str:
155
+ try:
156
+ import requests
157
+ from markdownify import markdownify
158
+ from requests.exceptions import RequestException
159
+
160
+ from smolagents.utils import truncate_content
161
+ except ImportError as e:
162
+ raise ImportError(
163
+ "You must install packages `markdownify` and `requests` to run this tool: for instance run `pip install markdownify requests`."
164
+ ) from e
165
+ try:
166
+ # Send a GET request to the URL with a 20-second timeout
167
+ response = requests.get(url, timeout=20)
168
+ response.raise_for_status() # Raise an exception for bad status codes
169
+
170
+ # Convert the HTML content to Markdown
171
+ markdown_content = markdownify(response.text).strip()
172
+
173
+ # Remove multiple line breaks
174
+ markdown_content = re.sub(r"\n{3,}", "\n\n", markdown_content)
175
+
176
+ return truncate_content(markdown_content, 10000)
177
+
178
+ except requests.exceptions.Timeout:
179
+ return "The request timed out. Please try again later or check the URL."
180
+ except RequestException as e:
181
+ return f"Error fetching the webpage: {str(e)}"
182
+ except Exception as e:
183
+ return f"An unexpected error occurred: {str(e)}"
184
+
185
+ # rAgent Reasoning using Llama mode OpenAI
186
+
187
+ from openai import OpenAI
188
+
189
+ ACCESS_TOKEN = os.getenv("HF_TOKEN")
190
+ ragent_client = OpenAI(
191
+ base_url="https://api-inference.huggingface.co/v1/",
192
+ api_key=ACCESS_TOKEN,
193
+ )
194
+
195
+ SYSTEM_PROMPT = """
196
+
197
+ "You are an expert assistant who solves tasks using Python code. Follow these steps:\n"
198
+ "1. **Thought**: Explain your reasoning and plan for solving the task.\n"
199
+ "2. **Code**: Write Python code to implement your solution.\n"
200
+ "3. **Observation**: Analyze the output of the code and summarize the results.\n"
201
+ "4. **Final Answer**: Provide a concise conclusion or final result.\n\n"
202
+ f"Task: {task}"
203
+
204
+ """
205
+
206
+ def ragent_reasoning(prompt: str, history: list[dict], max_tokens: int = 1024, temperature: float = 0.7, top_p: float = 0.95):
207
+ """
208
+ Uses the Llama mode OpenAI model to perform a structured reasoning chain.
209
+ """
210
+ messages = [{"role": "system", "content": SYSTEM_PROMPT}]
211
+ # Incorporate conversation history (if any)
212
+ for msg in history:
213
+ if msg.get("role") == "user":
214
+ messages.append({"role": "user", "content": msg["content"]})
215
+ elif msg.get("role") == "assistant":
216
+ messages.append({"role": "assistant", "content": msg["content"]})
217
+ messages.append({"role": "user", "content": prompt})
218
+ response = ""
219
+ stream = ragent_client.chat.completions.create(
220
+ model="meta-llama/Meta-Llama-3.1-8B-Instruct",
221
+ max_tokens=max_tokens,
222
+ stream=True,
223
+ temperature=temperature,
224
+ top_p=top_p,
225
+ messages=messages,
226
+ )
227
+ for message in stream:
228
+ token = message.choices[0].delta.content
229
+ response += token
230
+ yield response
231
+
232
+ # Gradio UI configuration
233
+
234
+ DESCRIPTION = """
235
+ # Agent Dino 🌠 """
236
+
237
+ css = '''
238
+ h1 {
239
+ text-align: center;
240
+ display: block;
241
+ }
242
+
243
+ #duplicate-button {
244
+ margin: auto;
245
+ color: #fff;
246
+ background: #1565c0;
247
+ border-radius: 100vh;
248
+ }
249
+ '''
250
+
251
+ MAX_MAX_NEW_TOKENS = 2048
252
+ DEFAULT_MAX_NEW_TOKENS = 1024
253
+ MAX_INPUT_TOKEN_LENGTH = int(os.getenv("MAX_INPUT_TOKEN_LENGTH", "4096"))
254
+
255
+ device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
256
+
257
+ # Load Models and Pipelines for Chat, Image, and Multimodal Processing
258
+ # Load the text-only model and tokenizer (for pure text chat)
259
+
260
+ model_id = "prithivMLmods/FastThink-0.5B-Tiny"
261
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
262
+ model = AutoModelForCausalLM.from_pretrained(
263
+ model_id,
264
+ device_map="auto",
265
+ torch_dtype=torch.bfloat16,
266
+ )
267
+ model.eval()
268
+
269
+ # Voices for text-to-speech
270
+ TTS_VOICES = [
271
+ "en-US-JennyNeural", # @tts1
272
+ "en-US-GuyNeural", # @tts2
273
+ ]
274
+
275
+ # Load multimodal processor and model (e.g. for OCR and image processing)
276
+ MODEL_ID = "prithivMLmods/Qwen2-VL-OCR-2B-Instruct"
277
+ processor = AutoProcessor.from_pretrained(MODEL_ID, trust_remote_code=True)
278
+ model_m = Qwen2VLForConditionalGeneration.from_pretrained(
279
+ MODEL_ID,
280
+ trust_remote_code=True,
281
+ torch_dtype=torch.float16
282
+ ).to("cuda").eval()
283
+
284
+ # Asynchronous text-to-speech
285
+
286
+ async def text_to_speech(text: str, voice: str, output_file="output.mp3"):
287
+ """Convert text to speech using Edge TTS and save as MP3"""
288
+ communicate = edge_tts.Communicate(text, voice)
289
+ await communicate.save(output_file)
290
+ return output_file
291
+
292
+ # Utility function to clean conversation history
293
+
294
+ def clean_chat_history(chat_history):
295
+ """
296
+ Filter out any chat entries whose "content" is not a string.
297
+ This helps prevent errors when concatenating previous messages.
298
+ """
299
+ cleaned = []
300
+ for msg in chat_history:
301
+ if isinstance(msg, dict) and isinstance(msg.get("content"), str):
302
+ cleaned.append(msg)
303
+ return cleaned
304
+
305
+ # Stable Diffusion XL Pipeline for Image Generation
306
+
307
+ MODEL_ID_SD = os.getenv("MODEL_VAL_PATH") # SDXL Model repository path via env variable
308
+ MAX_IMAGE_SIZE = int(os.getenv("MAX_IMAGE_SIZE", "4096"))
309
+ USE_TORCH_COMPILE = os.getenv("USE_TORCH_COMPILE", "0") == "1"
310
+ ENABLE_CPU_OFFLOAD = os.getenv("ENABLE_CPU_OFFLOAD", "0") == "1"
311
+ BATCH_SIZE = int(os.getenv("BATCH_SIZE", "1")) # For batched image generation
312
+
313
+ sd_pipe = StableDiffusionXLPipeline.from_pretrained(
314
+ MODEL_ID_SD,
315
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
316
+ use_safetensors=True,
317
+ add_watermarker=False,
318
+ ).to(device)
319
+ sd_pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(sd_pipe.scheduler.config)
320
+
321
+ if torch.cuda.is_available():
322
+ sd_pipe.text_encoder = sd_pipe.text_encoder.half()
323
+
324
+ if USE_TORCH_COMPILE:
325
+ sd_pipe.compile()
326
+
327
+ if ENABLE_CPU_OFFLOAD:
328
+ sd_pipe.enable_model_cpu_offload()
329
+
330
+ def save_image(img: Image.Image) -> str:
331
+ """Save a PIL image with a unique filename and return the path."""
332
+ unique_name = str(uuid.uuid4()) + ".png"
333
+ img.save(unique_name)
334
+ return unique_name
335
+
336
+ @spaces.GPU(duration=60, enable_queue=True)
337
+ def generate_image_fn(
338
+ prompt: str,
339
+ negative_prompt: str = "",
340
+ use_negative_prompt: bool = False,
341
+ seed: int = 1,
342
+ width: int = 1024,
343
+ height: int = 1024,
344
+ guidance_scale: float = 3,
345
+ num_inference_steps: int = 25,
346
+ randomize_seed: bool = False,
347
+ use_resolution_binning: bool = True,
348
+ num_images: int = 1,
349
+ progress=gr.Progress(track_tqdm=True),
350
+ ):
351
+ """Generate images using the SDXL pipeline."""
352
+ seed = int(randomize_seed_fn(seed, randomize_seed))
353
+ generator = torch.Generator(device=device).manual_seed(seed)
354
+
355
+ options = {
356
+ "prompt": [prompt] * num_images,
357
+ "negative_prompt": [negative_prompt] * num_images if use_negative_prompt else None,
358
+ "width": width,
359
+ "height": height,
360
+ "guidance_scale": guidance_scale,
361
+ "num_inference_steps": num_inference_steps,
362
+ "generator": generator,
363
+ "output_type": "pil",
364
+ }
365
+ if use_resolution_binning:
366
+ options["use_resolution_binning"] = True
367
+
368
+ images = []
369
+ # Process in batches
370
+ for i in range(0, num_images, BATCH_SIZE):
371
+ batch_options = options.copy()
372
+ batch_options["prompt"] = options["prompt"][i:i+BATCH_SIZE]
373
+ if "negative_prompt" in batch_options and batch_options["negative_prompt"] is not None:
374
+ batch_options["negative_prompt"] = options["negative_prompt"][i:i+BATCH_SIZE]
375
+ if device.type == "cuda":
376
+ with torch.autocast("cuda", dtype=torch.float16):
377
+ outputs = sd_pipe(**batch_options)
378
+ else:
379
+ outputs = sd_pipe(**batch_options)
380
+ images.extend(outputs.images)
381
+ image_paths = [save_image(img) for img in images]
382
+ return image_paths, seed
383
+
384
+ # Text-to-3D Generation using the ShapE Pipeline
385
+
386
+ @spaces.GPU(duration=120, enable_queue=True)
387
+ def generate_3d_fn(
388
+ prompt: str,
389
+ seed: int = 1,
390
+ guidance_scale: float = 15.0,
391
+ num_steps: int = 64,
392
+ randomize_seed: bool = False,
393
+ ):
394
+ """
395
+ Generate a 3D model from text using the ShapE pipeline.
396
+ Returns a tuple of (glb_file_path, used_seed).
397
+ """
398
+ seed = int(randomize_seed_fn(seed, randomize_seed))
399
+ model3d = Model()
400
+ glb_path = model3d.run_text(prompt, seed=seed, guidance_scale=guidance_scale, num_steps=num_steps)
401
+ return glb_path, seed
402
+
403
+ # Chat Generation Function with support for @tts, @image, @3d, @web, and @rAgent commands
404
+
405
+ @spaces.GPU
406
+ def generate(
407
+ input_dict: dict,
408
+ chat_history: list[dict],
409
+ max_new_tokens: int = 1024,
410
+ temperature: float = 0.6,
411
+ top_p: float = 0.9,
412
+ top_k: int = 50,
413
+ repetition_penalty: float = 1.2,
414
+ ):
415
+ """
416
+ Generates chatbot responses with support for multimodal input and special commands:
417
+ - "@tts1" or "@tts2": triggers text-to-speech.
418
+ - "@image": triggers image generation using the SDXL pipeline.
419
+ - "@3d": triggers 3D model generation using the ShapE pipeline.
420
+ - "@web": triggers a web search or webpage visit.
421
+ - "@rAgent": initiates a reasoning chain using Llama mode OpenAI.
422
+ """
423
+ text = input_dict["text"]
424
+ files = input_dict.get("files", [])
425
+
426
+ # --- 3D Generation branch ---
427
+ if text.strip().lower().startswith("@3d"):
428
+ prompt = text[len("@3d"):].strip()
429
+ yield "🌀 Hold tight, generating a 3D mesh GLB file....."
430
+ glb_path, used_seed = generate_3d_fn(
431
+ prompt=prompt,
432
+ seed=1,
433
+ guidance_scale=15.0,
434
+ num_steps=64,
435
+ randomize_seed=True,
436
+ )
437
+ # Copy the GLB file to a static folder.
438
+ static_folder = os.path.join(os.getcwd(), "static")
439
+ if not os.path.exists(static_folder):
440
+ os.makedirs(static_folder)
441
+ new_filename = f"mesh_{uuid.uuid4()}.glb"
442
+ new_filepath = os.path.join(static_folder, new_filename)
443
+ shutil.copy(glb_path, new_filepath)
444
+
445
+ yield gr.File(new_filepath)
446
+ return
447
+
448
+ # --- Image Generation branch ---
449
+ if text.strip().lower().startswith("@image"):
450
+ prompt = text[len("@image"):].strip()
451
+ yield "🪧 Generating image..."
452
+ image_paths, used_seed = generate_image_fn(
453
+ prompt=prompt,
454
+ negative_prompt="",
455
+ use_negative_prompt=False,
456
+ seed=1,
457
+ width=1024,
458
+ height=1024,
459
+ guidance_scale=3,
460
+ num_inference_steps=25,
461
+ randomize_seed=True,
462
+ use_resolution_binning=True,
463
+ num_images=1,
464
+ )
465
+ yield gr.Image(image_paths[0])
466
+ return
467
+
468
+ # --- Web Search/Visit branch ---
469
+ if text.strip().lower().startswith("@web"):
470
+ web_command = text[len("@web"):].strip()
471
+ # If the command starts with "visit", then treat the rest as a URL
472
+ if web_command.lower().startswith("visit"):
473
+ url = web_command[len("visit"):].strip()
474
+ yield "🌍 Visiting webpage..."
475
+ visitor = VisitWebpageTool()
476
+ content = visitor.forward(url)
477
+ yield content
478
+ else:
479
+ # Otherwise, treat the rest as a search query.
480
+ query = web_command
481
+ yield "🧤 Performing a web search ..."
482
+ searcher = DuckDuckGoSearchTool()
483
+ results = searcher.forward(query)
484
+ yield results
485
+ return
486
+
487
+ # --- rAgent Reasoning branch ---
488
+ if text.strip().lower().startswith("@ragent"):
489
+ prompt = text[len("@ragent"):].strip()
490
+ yield "📝 Initiating reasoning chain using Llama mode..."
491
+ # Pass the current chat history (cleaned) to help inform the chain.
492
+ for partial in ragent_reasoning(prompt, clean_chat_history(chat_history)):
493
+ yield partial
494
+ return
495
+
496
+ # --- Text and TTS branch ---
497
+ tts_prefix = "@tts"
498
+ is_tts = any(text.strip().lower().startswith(f"{tts_prefix}{i}") for i in range(1, 3))
499
+ voice_index = next((i for i in range(1, 3) if text.strip().lower().startswith(f"{tts_prefix}{i}")), None)
500
+
501
+ if is_tts and voice_index:
502
+ voice = TTS_VOICES[voice_index - 1]
503
+ text = text.replace(f"{tts_prefix}{voice_index}", "").strip()
504
+ conversation = [{"role": "user", "content": text}]
505
+ else:
506
+ voice = None
507
+ text = text.replace(tts_prefix, "").strip()
508
+ conversation = clean_chat_history(chat_history)
509
+ conversation.append({"role": "user", "content": text})
510
+
511
+ if files:
512
+ if len(files) > 1:
513
+ images = [load_image(image) for image in files]
514
+ elif len(files) == 1:
515
+ images = [load_image(files[0])]
516
+ else:
517
+ images = []
518
+ messages = [{
519
+ "role": "user",
520
+ "content": [
521
+ *[{"type": "image", "image": image} for image in images],
522
+ {"type": "text", "text": text},
523
+ ]
524
+ }]
525
+ prompt = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
526
+ inputs = processor(text=[prompt], images=images, return_tensors="pt", padding=True).to("cuda")
527
+ streamer = TextIteratorStreamer(processor, skip_prompt=True, skip_special_tokens=True)
528
+ generation_kwargs = {**inputs, "streamer": streamer, "max_new_tokens": max_new_tokens}
529
+ thread = Thread(target=model_m.generate, kwargs=generation_kwargs)
530
+ thread.start()
531
+
532
+ buffer = ""
533
+ yield "🤔 Thinking..."
534
+ for new_text in streamer:
535
+ buffer += new_text
536
+ buffer = buffer.replace("<|im_end|>", "")
537
+ time.sleep(0.01)
538
+ yield buffer
539
+ else:
540
+ input_ids = tokenizer.apply_chat_template(conversation, add_generation_prompt=True, return_tensors="pt")
541
+ if input_ids.shape[1] > MAX_INPUT_TOKEN_LENGTH:
542
+ input_ids = input_ids[:, -MAX_INPUT_TOKEN_LENGTH:]
543
+ gr.Warning(f"Trimmed input from conversation as it was longer than {MAX_INPUT_TOKEN_LENGTH} tokens.")
544
+ input_ids = input_ids.to(model.device)
545
+ streamer = TextIteratorStreamer(tokenizer, timeout=20.0, skip_prompt=True, skip_special_tokens=True)
546
+ generation_kwargs = {
547
+ "input_ids": input_ids,
548
+ "streamer": streamer,
549
+ "max_new_tokens": max_new_tokens,
550
+ "do_sample": True,
551
+ "top_p": top_p,
552
+ "top_k": top_k,
553
+ "temperature": temperature,
554
+ "num_beams": 1,
555
+ "repetition_penalty": repetition_penalty,
556
+ }
557
+ t = Thread(target=model.generate, kwargs=generation_kwargs)
558
+ t.start()
559
+
560
+ outputs = []
561
+ for new_text in streamer:
562
+ outputs.append(new_text)
563
+ yield "".join(outputs)
564
+
565
+ final_response = "".join(outputs)
566
+ yield final_response
567
+
568
+ if is_tts and voice:
569
+ output_file = asyncio.run(text_to_speech(final_response, voice))
570
+ yield gr.Audio(output_file, autoplay=True)
571
+
572
+ # Gradio Chat Interface Setup and Launch
573
+
574
+ demo = gr.ChatInterface(
575
+ fn=generate,
576
+ additional_inputs=[
577
+ gr.Slider(label="Max new tokens", minimum=1, maximum=MAX_MAX_NEW_TOKENS, step=1, value=DEFAULT_MAX_NEW_TOKENS),
578
+ gr.Slider(label="Temperature", minimum=0.1, maximum=4.0, step=0.1, value=0.6),
579
+ gr.Slider(label="Top-p (nucleus sampling)", minimum=0.05, maximum=1.0, step=0.05, value=0.9),
580
+ gr.Slider(label="Top-k", minimum=1, maximum=1000, step=1, value=50),
581
+ gr.Slider(label="Repetition penalty", minimum=1.0, maximum=2.0, step=0.05, value=1.2),
582
+ ],
583
+ examples=[
584
+ ["@tts2 What causes rainbows to form?"],
585
+ ["@3d A birthday cupcake with cherry"],
586
+ [{"text": "summarize the letter", "files": ["examples/1.png"]}],
587
+ ["@image Chocolate dripping from a donut against a yellow background, in the style of brocore, hyper-realistic"],
588
+ ["@rAgent Explain how a binary search algorithm works."],
589
+ ["@web Is Grok-3 Beats DeepSeek-R1 at Reasoning ?"],
590
+
591
+ ],
592
+ cache_examples=False,
593
+ type="messages",
594
+ description=DESCRIPTION,
595
+ css=css,
596
+ fill_height=True,
597
+ textbox=gr.MultimodalTextbox(label="Query Input", file_types=["image"], file_count="multiple", placeholder="@tts1-♀, @tts2-♂, @image-image gen, @3d-3d mesh gen, @rAgent-coding, @web-websearch, default-{text gen}{image-text-text}"),
598
+ stop_btn="Stop Generation",
599
+ multimodal=True,
600
+ )
601
+
602
+ # Ensure the static folder exists
603
+ if not os.path.exists("static"):
604
+ os.makedirs("static")
605
+
606
+ from fastapi.staticfiles import StaticFiles
607
+ demo.app.mount("/static", StaticFiles(directory="static"), name="static")
608
+
609
+ if __name__ == "__main__":
610
+ demo.queue(max_size=20).launch(share=True)
config.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+
4
+ # Model Configuration
5
+ MODEL_CONFIG = {
6
+ "base_model": "TinyLlama/TinyLlama-1.1B-Chat-v1.0",
7
+ "vision_model": "Qwen/Qwen2.5-VL-7B-Instruct",
8
+ "image_model": "stabilityai/stable-diffusion-xl-base-1.0",
9
+ "shape_model": "openai/shap-e"
10
+ }
11
+
12
+ # System Configuration
13
+ MAX_TOKENS = 2048
14
+ DEFAULT_TOKENS = 1024
15
+ MAX_INPUT_LENGTH = int(os.getenv("MAX_INPUT_LENGTH", "4096"))
16
+ DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
17
+
18
+ # Voice Configuration
19
+ VOICE_OPTIONS = [
20
+ "en-US-JennyMultilingualV2", # @voice1
21
+ "en-US-ChristopherMultilingualV2", # @voice2
22
+ ]
23
+
24
+ # Command Prefixes
25
+ COMMANDS = {
26
+ "voice": "@voice",
27
+ "image": "@create",
28
+ "shape": "@shape",
29
+ "search": "@search",
30
+ "code": "@code"
31
+ }
main.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import uuid
4
+ import json
5
+ import time
6
+ import asyncio
7
+ import tempfile
8
+ from threading import Thread
9
+ import base64
10
+ import shutil
11
+
12
+ import gradio as gr
13
+ import torch
14
+ from PIL import Image
15
+ import edge_tts
16
+
17
+ from models import (
18
+ load_base_model,
19
+ load_vision_model,
20
+ load_image_model,
21
+ load_shape_model,
22
+ generate_3d_model
23
+ )
24
+ from utils import (
25
+ clean_history,
26
+ save_image,
27
+ text_to_speech,
28
+ web_search,
29
+ code_analysis
30
+ )
31
+ from config import (
32
+ MODEL_CONFIG,
33
+ MAX_TOKENS,
34
+ DEFAULT_TOKENS,
35
+ VOICE_OPTIONS,
36
+ COMMANDS,
37
+ DEVICE
38
+ )
39
+
40
+
41
+ def setup_interface(description, css):
42
+ # Load models
43
+ base_model, tokenizer = load_base_model(MODEL_CONFIG["base_model"])
44
+ vision_model, processor = load_vision_model(MODEL_CONFIG["vision_model"])
45
+ image_model = load_image_model(MODEL_CONFIG["image_model"])
46
+ shape_model = load_shape_model(MODEL_CONFIG["shape_model"])
47
+
48
+ def generate(
49
+ input_dict: dict,
50
+ chat_history: list[dict],
51
+ max_new_tokens: int = DEFAULT_TOKENS,
52
+ temperature: float = 0.7,
53
+ top_p: float = 0.9,
54
+ top_k: int = 50,
55
+ repetition_penalty: float = 1.2,
56
+ ):
57
+ text = input_dict["text"]
58
+ files = input_dict.get("files", [])
59
+
60
+ # Handle 3D Generation
61
+ if text.strip().lower().startswith(COMMANDS["shape"]):
62
+ prompt = text[len(COMMANDS["shape"]):].strip()
63
+ yield "🌟 Creating 3D model from your description..."
64
+ model_path = generate_3d_model(shape_model, prompt)
65
+ yield gr.File(model_path)
66
+ return
67
+
68
+ # Handle Image Generation
69
+ if text.strip().lower().startswith(COMMANDS["image"]):
70
+ prompt = text[len(COMMANDS["image"]):].strip()
71
+ yield "🎨 Generating your image..."
72
+ image_path = image_model.generate(prompt)
73
+ yield gr.Image(image_path)
74
+ return
75
+
76
+ # Handle Web Search
77
+ if text.strip().lower().startswith(COMMANDS["search"]):
78
+ query = text[len(COMMANDS["search"]):].strip()
79
+ yield "🔍 Searching the web..."
80
+ results = web_search(query)
81
+ yield results
82
+ return
83
+
84
+ # Handle Code Analysis
85
+ if text.strip().lower().startswith(COMMANDS["code"]):
86
+ prompt = text[len(COMMANDS["code"]):].strip()
87
+ yield "💻 Analyzing code..."
88
+ analysis = code_analysis(prompt)
89
+ yield analysis
90
+ return
91
+
92
+ # Handle Voice Synthesis
93
+ voice_command = COMMANDS["voice"]
94
+ is_voice = text.strip().lower().startswith(voice_command)
95
+ voice_index = 1 if f"{voice_command}1" in text.lower() else 2 if f"{voice_command}2" in text.lower() else None
96
+
97
+ if is_voice and voice_index:
98
+ voice = VOICE_OPTIONS[voice_index - 1]
99
+ text = text.replace(f"{voice_command}{voice_index}", "").strip()
100
+ conversation = [{"role": "user", "content": text}]
101
+ else:
102
+ voice = None
103
+ conversation = clean_history(chat_history)
104
+ conversation.append({"role": "user", "content": text})
105
+
106
+ # Handle Image-Text Input
107
+ if files:
108
+ response = vision_model.generate(text, files, processor)
109
+ yield response
110
+ return
111
+
112
+ # Handle Text Generation
113
+ response = base_model.generate(
114
+ conversation,
115
+ tokenizer,
116
+ max_new_tokens,
117
+ temperature,
118
+ top_p,
119
+ top_k,
120
+ repetition_penalty
121
+ )
122
+ yield response
123
+
124
+ if voice:
125
+ audio_file = asyncio.run(text_to_speech(response, voice))
126
+ yield gr.Audio(audio_file, autoplay=True)
127
+
128
+ # Create Gradio Interface
129
+ demo = gr.ChatInterface(
130
+ fn=generate,
131
+ additional_inputs=[
132
+ gr.Slider(label="Response Length", minimum=1, maximum=MAX_TOKENS, value=DEFAULT_TOKENS),
133
+ gr.Slider(label="Creativity", minimum=0.1, maximum=1.0, value=0.7),
134
+ gr.Slider(label="Focus", minimum=0.05, maximum=1.0, value=0.9),
135
+ gr.Slider(label="Diversity", minimum=1, maximum=100, value=50),
136
+ gr.Slider(label="Consistency", minimum=1.0, maximum=2.0, value=1.2),
137
+ ],
138
+ title="NexusAI",
139
+ description=description,
140
+ theme=gr.themes.Soft(),
141
+ css=css,
142
+ examples=[
143
+ ["@voice2 Tell me about quantum computing"],
144
+ ["@shape A futuristic space station"],
145
+ ["@create A serene landscape with northern lights"],
146
+ ["@code Explain how to implement a binary search tree"],
147
+ ["@search Latest developments in fusion energy"],
148
+ ]
149
+ )
150
+
151
+ return demo
models.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from transformers import (
3
+ AutoModelForCausalLM,
4
+ AutoTokenizer,
5
+ AutoProcessor,
6
+ pipeline
7
+ )
8
+ from diffusers import (
9
+ StableDiffusionXLPipeline,
10
+ ShapEPipeline
11
+ )
12
+ from config import DEVICE
13
+
14
+ def load_base_model(model_id):
15
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
16
+ model = AutoModelForCausalLM.from_pretrained(
17
+ model_id,
18
+ device_map="auto",
19
+ torch_dtype=torch.bfloat16
20
+ )
21
+ return model, tokenizer
22
+
23
+ def load_vision_model(model_id):
24
+ processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True)
25
+ model = pipeline(
26
+ "visual-question-answering",
27
+ model=model_id,
28
+ device=DEVICE
29
+ )
30
+ return model, processor
31
+
32
+ def load_image_model(model_id):
33
+ return StableDiffusionXLPipeline.from_pretrained(
34
+ model_id,
35
+ torch_dtype=torch.float16,
36
+ use_safetensors=True
37
+ ).to(DEVICE)
38
+
39
+ def load_shape_model(model_id):
40
+ return ShapEPipeline.from_pretrained(
41
+ model_id,
42
+ torch_dtype=torch.float16
43
+ ).to(DEVICE)
44
+
45
+ def generate_3d_model(model, prompt):
46
+ # Implementation of 3D model generation
47
+ pass
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ torch>=2.0.0
3
+ transformers>=4.30.0
4
+ diffusers>=0.24.0
5
+ pillow>=9.0.0
6
+ numpy>=1.24.0
7
+ edge-tts>=6.1.3
8
+ trimesh>=4.0.0
9
+ duckduckgo-search>=3.9.0
10
+ markdownify>=0.11.6
11
+ requests>=2.31.0
12
+ openai>=1.0.0
13
+ smolagents>=0.1.0
14
+ fastapi>=0.0.6
15
+ python-multipart>=0.0.6
utils.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+ from PIL import Image
4
+ import edge_tts
5
+ from duckduckgo_search import DDGS
6
+
7
+ def clean_history(chat_history):
8
+ return [msg for msg in chat_history if isinstance(msg.get("content"), str)]
9
+
10
+ def save_image(image: Image.Image) -> str:
11
+ filename = f"output_{uuid.uuid4()}.png"
12
+ image.save(filename)
13
+ return filename
14
+
15
+ async def text_to_speech(text: str, voice: str):
16
+ output_file = f"speech_{uuid.uuid4()}.mp3"
17
+ communicate = edge_tts.Communicate(text, voice)
18
+ await communicate.save(output_file)
19
+ return output_file
20
+
21
+ def web_search(query: str) -> str:
22
+ with DDGS() as ddgs:
23
+ results = ddgs.text(query, max_results=5)
24
+ return format_search_results(results)
25
+
26
+ def code_analysis(prompt: str) -> str:
27
+ # Implementation of code analysis
28
+ pass
29
+
30
+ def format_search_results(results):
31
+ formatted = "### Search Results\n\n"
32
+ for result in results:
33
+ formatted += f"**{result['title']}**\n{result['body']}\n[Link]({result['link']})\n\n"
34
+ return formatted