Spaces:
Runtime error
Runtime error
Робочий варіант, але потребує суттєвого доопрацювання
Browse files- =0.11.0 +0 -0
- Gradio_UI.py +39 -26
- agent.py +16 -88
- app.py +10 -5
- config.py +112 -0
- models_config.json +80 -0
- requirements.txt +10 -5
- tools/__init__.py +11 -0
- tools/final_answer.py +49 -29
- tools/healthcare_llm_visualizer.py +3 -3
- tools/visit_webpage.py +8 -3
- tools/web_search.py +19 -96
=0.11.0
ADDED
|
File without changes
|
Gradio_UI.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import logging
|
| 3 |
from pathlib import Path
|
| 4 |
-
import
|
|
|
|
| 5 |
|
| 6 |
logger = logging.getLogger(__name__)
|
| 7 |
|
|
@@ -10,9 +11,20 @@ class GradioUI:
|
|
| 10 |
self.agent = agent
|
| 11 |
self.file_upload_folder = Path(file_upload_folder)
|
| 12 |
self.file_upload_folder.mkdir(exist_ok=True)
|
|
|
|
| 13 |
|
| 14 |
def build_interface(self):
|
| 15 |
with gr.Blocks(theme=gr.themes.Soft()) as interface:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
with gr.Row():
|
| 17 |
chatbot = gr.Chatbot(
|
| 18 |
label="Research Assistant",
|
|
@@ -20,7 +32,6 @@ class GradioUI:
|
|
| 20 |
show_copy_button=True
|
| 21 |
)
|
| 22 |
|
| 23 |
-
# Hidden by default file upload section
|
| 24 |
with gr.Row(visible=False) as file_upload_row:
|
| 25 |
upload_file = gr.File(
|
| 26 |
label="Upload File",
|
|
@@ -34,21 +45,33 @@ class GradioUI:
|
|
| 34 |
|
| 35 |
with gr.Row():
|
| 36 |
text_input = gr.Textbox(
|
| 37 |
-
label="
|
| 38 |
-
placeholder="
|
| 39 |
lines=2
|
| 40 |
)
|
| 41 |
|
| 42 |
with gr.Row():
|
| 43 |
-
submit_btn = gr.Button("
|
| 44 |
-
clear_btn = gr.Button("
|
| 45 |
-
toggle_upload_btn = gr.Button("
|
| 46 |
|
| 47 |
# Store conversation state
|
| 48 |
state = gr.State([])
|
| 49 |
file_history = gr.State([])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
# Event handlers
|
| 52 |
def toggle_upload(visible):
|
| 53 |
return not visible
|
| 54 |
|
|
@@ -62,15 +85,14 @@ class GradioUI:
|
|
| 62 |
if file:
|
| 63 |
try:
|
| 64 |
file_path = self.file_upload_folder / file.name
|
| 65 |
-
# Save file
|
| 66 |
with open(file_path, 'wb') as f:
|
| 67 |
f.write(file.read())
|
| 68 |
history.append(str(file_path))
|
| 69 |
-
return gr.update(value=f"
|
| 70 |
except Exception as e:
|
| 71 |
-
logger.error(f"
|
| 72 |
-
return gr.update(value=f"
|
| 73 |
-
return gr.update(value="
|
| 74 |
|
| 75 |
upload_file.change(
|
| 76 |
fn=process_upload,
|
|
@@ -80,7 +102,7 @@ class GradioUI:
|
|
| 80 |
|
| 81 |
def user_message(message, chat_history, files):
|
| 82 |
if files:
|
| 83 |
-
message += f"\
|
| 84 |
chat_history.append((message, None))
|
| 85 |
return "", chat_history
|
| 86 |
|
|
@@ -93,11 +115,10 @@ class GradioUI:
|
|
| 93 |
chat_history[-1] = (chat_history[-1][0], response)
|
| 94 |
return chat_history
|
| 95 |
except Exception as e:
|
| 96 |
-
logger.error(f"
|
| 97 |
-
chat_history[-1] = (chat_history[-1][0], f"
|
| 98 |
return chat_history
|
| 99 |
|
| 100 |
-
# Submit handling
|
| 101 |
submit_btn.click(
|
| 102 |
user_message,
|
| 103 |
[text_input, state, file_history],
|
|
@@ -108,7 +129,6 @@ class GradioUI:
|
|
| 108 |
[chatbot]
|
| 109 |
)
|
| 110 |
|
| 111 |
-
# Clear handling
|
| 112 |
def clear_chat():
|
| 113 |
return [], []
|
| 114 |
|
|
@@ -123,11 +143,4 @@ class GradioUI:
|
|
| 123 |
|
| 124 |
def launch(self, **kwargs):
|
| 125 |
interface = self.build_interface()
|
| 126 |
-
interface.launch(**kwargs)
|
| 127 |
-
|
| 128 |
-
if __name__ == "__main__":
|
| 129 |
-
# Example usage
|
| 130 |
-
from agent import ResearchAgent
|
| 131 |
-
agent = ResearchAgent()
|
| 132 |
-
ui = GradioUI(agent)
|
| 133 |
-
ui.launch(share=True)
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import logging
|
| 3 |
from pathlib import Path
|
| 4 |
+
from config import ModelConfig
|
| 5 |
+
from typing import Tuple, List
|
| 6 |
|
| 7 |
logger = logging.getLogger(__name__)
|
| 8 |
|
|
|
|
| 11 |
self.agent = agent
|
| 12 |
self.file_upload_folder = Path(file_upload_folder)
|
| 13 |
self.file_upload_folder.mkdir(exist_ok=True)
|
| 14 |
+
self.model_config = ModelConfig()
|
| 15 |
|
| 16 |
def build_interface(self):
|
| 17 |
with gr.Blocks(theme=gr.themes.Soft()) as interface:
|
| 18 |
+
# Model selection dropdown
|
| 19 |
+
with gr.Row():
|
| 20 |
+
model_choices = self.model_config.get_available_models()
|
| 21 |
+
model_dropdown = gr.Dropdown(
|
| 22 |
+
choices=[f"{key} - {desc}" for key, desc in model_choices],
|
| 23 |
+
value=f"{self.model_config.get_default_model()} - {dict(model_choices)[self.model_config.get_default_model()]}",
|
| 24 |
+
label="Оберіть модель",
|
| 25 |
+
info="Оберіть модель для обробки запитів"
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
with gr.Row():
|
| 29 |
chatbot = gr.Chatbot(
|
| 30 |
label="Research Assistant",
|
|
|
|
| 32 |
show_copy_button=True
|
| 33 |
)
|
| 34 |
|
|
|
|
| 35 |
with gr.Row(visible=False) as file_upload_row:
|
| 36 |
upload_file = gr.File(
|
| 37 |
label="Upload File",
|
|
|
|
| 45 |
|
| 46 |
with gr.Row():
|
| 47 |
text_input = gr.Textbox(
|
| 48 |
+
label="Введіть ваш запит",
|
| 49 |
+
placeholder="Введіть ваш запит тут...",
|
| 50 |
lines=2
|
| 51 |
)
|
| 52 |
|
| 53 |
with gr.Row():
|
| 54 |
+
submit_btn = gr.Button("Надіслати", variant="primary")
|
| 55 |
+
clear_btn = gr.Button("Очистити")
|
| 56 |
+
toggle_upload_btn = gr.Button("Завантажити файл")
|
| 57 |
|
| 58 |
# Store conversation state
|
| 59 |
state = gr.State([])
|
| 60 |
file_history = gr.State([])
|
| 61 |
+
current_model = gr.State(self.model_config.get_default_model())
|
| 62 |
+
|
| 63 |
+
def change_model(choice):
|
| 64 |
+
model_key = choice.split(" - ")[0]
|
| 65 |
+
model_config = self.model_config.get_model_config(model_key)
|
| 66 |
+
self.agent.update_model(model_config)
|
| 67 |
+
return model_key
|
| 68 |
+
|
| 69 |
+
model_dropdown.change(
|
| 70 |
+
fn=change_model,
|
| 71 |
+
inputs=[model_dropdown],
|
| 72 |
+
outputs=[current_model]
|
| 73 |
+
)
|
| 74 |
|
|
|
|
| 75 |
def toggle_upload(visible):
|
| 76 |
return not visible
|
| 77 |
|
|
|
|
| 85 |
if file:
|
| 86 |
try:
|
| 87 |
file_path = self.file_upload_folder / file.name
|
|
|
|
| 88 |
with open(file_path, 'wb') as f:
|
| 89 |
f.write(file.read())
|
| 90 |
history.append(str(file_path))
|
| 91 |
+
return gr.update(value=f"Файл завантажено: {file.name}"), history
|
| 92 |
except Exception as e:
|
| 93 |
+
logger.error(f"Помилка завантаження: {e}")
|
| 94 |
+
return gr.update(value=f"Помилка завантаження: {str(e)}"), history
|
| 95 |
+
return gr.update(value="Файл не вибрано"), history
|
| 96 |
|
| 97 |
upload_file.change(
|
| 98 |
fn=process_upload,
|
|
|
|
| 102 |
|
| 103 |
def user_message(message, chat_history, files):
|
| 104 |
if files:
|
| 105 |
+
message += f"\nДоступні файли для аналізу: {', '.join(files)}"
|
| 106 |
chat_history.append((message, None))
|
| 107 |
return "", chat_history
|
| 108 |
|
|
|
|
| 115 |
chat_history[-1] = (chat_history[-1][0], response)
|
| 116 |
return chat_history
|
| 117 |
except Exception as e:
|
| 118 |
+
logger.error(f"Помилка відповіді: {e}")
|
| 119 |
+
chat_history[-1] = (chat_history[-1][0], f"Помилка: {str(e)}")
|
| 120 |
return chat_history
|
| 121 |
|
|
|
|
| 122 |
submit_btn.click(
|
| 123 |
user_message,
|
| 124 |
[text_input, state, file_history],
|
|
|
|
| 129 |
[chatbot]
|
| 130 |
)
|
| 131 |
|
|
|
|
| 132 |
def clear_chat():
|
| 133 |
return [], []
|
| 134 |
|
|
|
|
| 143 |
|
| 144 |
def launch(self, **kwargs):
|
| 145 |
interface = self.build_interface()
|
| 146 |
+
interface.launch(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agent.py
CHANGED
|
@@ -2,6 +2,7 @@ from smolagents import CodeAgent
|
|
| 2 |
import logging
|
| 3 |
from typing import Optional, List, Dict, Any
|
| 4 |
from datetime import datetime
|
|
|
|
| 5 |
|
| 6 |
logger = logging.getLogger(__name__)
|
| 7 |
|
|
@@ -67,54 +68,19 @@ class ResearchAgent(CodeAgent):
|
|
| 67 |
logger.error(f"Error formatting research report: {e}")
|
| 68 |
return str(content) # Return raw content if formatting fails
|
| 69 |
|
| 70 |
-
def
|
| 71 |
-
"""
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
"\nІнструкції для виконання:",
|
| 84 |
-
"1. Використовуйте web_search для пошуку актуальної наукової інформації",
|
| 85 |
-
"2. Аналізуйте знайдені джерела та підсумовуйте ключові висновки",
|
| 86 |
-
"3. Формуйте структурований звіт з усіма необхідними розділами",
|
| 87 |
-
"4. Обов'язково вказуйте посилання на використані джерела"
|
| 88 |
-
]
|
| 89 |
-
|
| 90 |
-
if available_files:
|
| 91 |
-
context_parts.append(f"\nДоступні файли для аналізу: {', '.join(available_files)}")
|
| 92 |
-
|
| 93 |
-
return "\n".join(context_parts)
|
| 94 |
-
|
| 95 |
-
def validate_search_results(self, results: str) -> bool:
|
| 96 |
-
"""
|
| 97 |
-
Validate that search results are meaningful.
|
| 98 |
-
|
| 99 |
-
Args:
|
| 100 |
-
results (str): Search results to validate
|
| 101 |
-
|
| 102 |
-
Returns:
|
| 103 |
-
bool: True if results are valid, False otherwise
|
| 104 |
-
"""
|
| 105 |
-
if not results or len(results.strip()) < 100:
|
| 106 |
-
return False
|
| 107 |
-
|
| 108 |
-
# Check for common error indicators
|
| 109 |
-
error_indicators = [
|
| 110 |
-
"no results found",
|
| 111 |
-
"error",
|
| 112 |
-
"failed",
|
| 113 |
-
"unauthorized",
|
| 114 |
-
"invalid"
|
| 115 |
-
]
|
| 116 |
-
|
| 117 |
-
return not any(indicator in results.lower() for indicator in error_indicators)
|
| 118 |
|
| 119 |
def process_query(self, query: str, available_files: Optional[List[str]] = None) -> str:
|
| 120 |
"""
|
|
@@ -130,12 +96,9 @@ class ResearchAgent(CodeAgent):
|
|
| 130 |
try:
|
| 131 |
logger.info(f"Processing research query: {query}")
|
| 132 |
|
| 133 |
-
# Prepare context
|
| 134 |
-
context = self.prepare_query_context(query, available_files)
|
| 135 |
-
|
| 136 |
# Execute query
|
| 137 |
result = self.run(
|
| 138 |
-
task=
|
| 139 |
stream=False, # We want complete results
|
| 140 |
reset=True # Fresh start for each query
|
| 141 |
)
|
|
@@ -158,39 +121,4 @@ class ResearchAgent(CodeAgent):
|
|
| 158 |
except Exception as e:
|
| 159 |
error_msg = f"Помилка при обробці запиту: {str(e)}"
|
| 160 |
logger.error(error_msg)
|
| 161 |
-
return error_msg
|
| 162 |
-
|
| 163 |
-
def add_tool(self, tool) -> None:
|
| 164 |
-
"""
|
| 165 |
-
Add a new tool to the agent's toolkit.
|
| 166 |
-
|
| 167 |
-
Args:
|
| 168 |
-
tool: Tool instance to add
|
| 169 |
-
"""
|
| 170 |
-
try:
|
| 171 |
-
self.available_tools[tool.name] = tool
|
| 172 |
-
self.tools.append(tool)
|
| 173 |
-
logger.info(f"Added new tool: {tool.name}")
|
| 174 |
-
except Exception as e:
|
| 175 |
-
logger.error(f"Error adding tool: {e}")
|
| 176 |
-
raise
|
| 177 |
-
|
| 178 |
-
def remove_tool(self, tool_name: str) -> None:
|
| 179 |
-
"""
|
| 180 |
-
Remove a tool from the agent's toolkit.
|
| 181 |
-
|
| 182 |
-
Args:
|
| 183 |
-
tool_name (str): Name of the tool to remove
|
| 184 |
-
"""
|
| 185 |
-
try:
|
| 186 |
-
if tool_name in self.available_tools:
|
| 187 |
-
tool = self.available_tools.pop(tool_name)
|
| 188 |
-
self.tools.remove(tool)
|
| 189 |
-
logger.info(f"Removed tool: {tool_name}")
|
| 190 |
-
except Exception as e:
|
| 191 |
-
logger.error(f"Error removing tool: {e}")
|
| 192 |
-
raise
|
| 193 |
-
|
| 194 |
-
def __str__(self) -> str:
|
| 195 |
-
"""String representation of the agent"""
|
| 196 |
-
return f"ResearchAgent(tools={list(self.available_tools.keys())})"
|
|
|
|
| 2 |
import logging
|
| 3 |
from typing import Optional, List, Dict, Any
|
| 4 |
from datetime import datetime
|
| 5 |
+
import os
|
| 6 |
|
| 7 |
logger = logging.getLogger(__name__)
|
| 8 |
|
|
|
|
| 68 |
logger.error(f"Error formatting research report: {e}")
|
| 69 |
return str(content) # Return raw content if formatting fails
|
| 70 |
|
| 71 |
+
def update_model(self, model_config):
|
| 72 |
+
"""Update the model configuration"""
|
| 73 |
+
try:
|
| 74 |
+
from smolagents import HfApiModel
|
| 75 |
+
self.model = HfApiModel(
|
| 76 |
+
model_id=model_config['model_id'],
|
| 77 |
+
token=os.getenv('HF_API_TOKEN'),
|
| 78 |
+
temperature=model_config['parameters'].get('temperature', 0.7)
|
| 79 |
+
)
|
| 80 |
+
logger.info(f"Model updated to: {model_config['model_id']}")
|
| 81 |
+
except Exception as e:
|
| 82 |
+
logger.error(f"Error updating model: {e}")
|
| 83 |
+
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
def process_query(self, query: str, available_files: Optional[List[str]] = None) -> str:
|
| 86 |
"""
|
|
|
|
| 96 |
try:
|
| 97 |
logger.info(f"Processing research query: {query}")
|
| 98 |
|
|
|
|
|
|
|
|
|
|
| 99 |
# Execute query
|
| 100 |
result = self.run(
|
| 101 |
+
task=query,
|
| 102 |
stream=False, # We want complete results
|
| 103 |
reset=True # Fresh start for each query
|
| 104 |
)
|
|
|
|
| 121 |
except Exception as e:
|
| 122 |
error_msg = f"Помилка при обробці запиту: {str(e)}"
|
| 123 |
logger.error(error_msg)
|
| 124 |
+
return error_msg
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -7,6 +7,7 @@ from agent import ResearchAgent
|
|
| 7 |
import os
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
import logging
|
|
|
|
| 10 |
|
| 11 |
# Configure logging
|
| 12 |
logging.basicConfig(
|
|
@@ -43,12 +44,16 @@ def initialize_model():
|
|
| 43 |
"""Initialize the language model"""
|
| 44 |
logger.info("Initializing language model...")
|
| 45 |
try:
|
|
|
|
|
|
|
|
|
|
| 46 |
model = HfApiModel(
|
| 47 |
-
model_id=
|
| 48 |
token=os.getenv('HF_API_TOKEN'),
|
| 49 |
-
temperature=
|
|
|
|
| 50 |
)
|
| 51 |
-
logger.info(f"Model initialized: {
|
| 52 |
return model
|
| 53 |
except Exception as e:
|
| 54 |
logger.error(f"Error initializing model: {e}")
|
|
@@ -63,11 +68,11 @@ def main():
|
|
| 63 |
tools = initialize_tools()
|
| 64 |
model = initialize_model()
|
| 65 |
|
| 66 |
-
# Initialize research agent
|
| 67 |
agent = ResearchAgent(
|
| 68 |
model=model,
|
| 69 |
tools=tools,
|
| 70 |
-
max_steps=int(os.getenv('MAX_STEPS',
|
| 71 |
verbosity_level=int(os.getenv('VERBOSITY_LEVEL', 1))
|
| 72 |
)
|
| 73 |
|
|
|
|
| 7 |
import os
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
import logging
|
| 10 |
+
from config import ModelConfig
|
| 11 |
|
| 12 |
# Configure logging
|
| 13 |
logging.basicConfig(
|
|
|
|
| 44 |
"""Initialize the language model"""
|
| 45 |
logger.info("Initializing language model...")
|
| 46 |
try:
|
| 47 |
+
model_config = ModelConfig()
|
| 48 |
+
default_model = model_config.get_model_config()
|
| 49 |
+
|
| 50 |
model = HfApiModel(
|
| 51 |
+
model_id=default_model['model_id'],
|
| 52 |
token=os.getenv('HF_API_TOKEN'),
|
| 53 |
+
temperature=default_model['parameters'].get('temperature', 0.7),
|
| 54 |
+
max_tokens=default_model['parameters'].get('max_tokens', 2048)
|
| 55 |
)
|
| 56 |
+
logger.info(f"Model initialized: {default_model['model_id']}")
|
| 57 |
return model
|
| 58 |
except Exception as e:
|
| 59 |
logger.error(f"Error initializing model: {e}")
|
|
|
|
| 68 |
tools = initialize_tools()
|
| 69 |
model = initialize_model()
|
| 70 |
|
| 71 |
+
# Initialize research agent with increased max_steps
|
| 72 |
agent = ResearchAgent(
|
| 73 |
model=model,
|
| 74 |
tools=tools,
|
| 75 |
+
max_steps=int(os.getenv('MAX_STEPS', 10)), # Increased from 6 to 10
|
| 76 |
verbosity_level=int(os.getenv('VERBOSITY_LEVEL', 1))
|
| 77 |
)
|
| 78 |
|
config.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
class ModelConfig:
|
| 5 |
+
def __init__(self, config_path='models_config.json'): # Changed path
|
| 6 |
+
self.config_path = Path(config_path)
|
| 7 |
+
self.ensure_config_exists()
|
| 8 |
+
self.load_config()
|
| 9 |
+
|
| 10 |
+
def ensure_config_exists(self):
|
| 11 |
+
"""Ensure config file exists"""
|
| 12 |
+
if not self.config_path.exists():
|
| 13 |
+
default_config = {
|
| 14 |
+
"models": {
|
| 15 |
+
"deepseek-r1": {
|
| 16 |
+
"model_id": "deepseek-ai/DeepSeek-R1",
|
| 17 |
+
"description": "Найбільш рейтингова модель за кількістю лайків",
|
| 18 |
+
"likes": 8659,
|
| 19 |
+
"downloads": 3468420,
|
| 20 |
+
"parameters": {
|
| 21 |
+
"max_tokens": 2048,
|
| 22 |
+
"temperature": 0.7,
|
| 23 |
+
"top_p": 0.95,
|
| 24 |
+
"frequency_penalty": 0.0,
|
| 25 |
+
"presence_penalty": 0.0
|
| 26 |
+
},
|
| 27 |
+
"recommended_use": ["складні дослідницькі завдання", "аналітика", "генерація тексту"]
|
| 28 |
+
},
|
| 29 |
+
"llama-3-instruct": {
|
| 30 |
+
"model_id": "meta-llama/Llama-3.1-8B-Instruct",
|
| 31 |
+
"description": "Потужна інструктивна модель від Meta",
|
| 32 |
+
"likes": 3612,
|
| 33 |
+
"downloads": 6035070,
|
| 34 |
+
"parameters": {
|
| 35 |
+
"max_tokens": 4096,
|
| 36 |
+
"temperature": 0.8,
|
| 37 |
+
"top_p": 0.9,
|
| 38 |
+
"frequency_penalty": 0.0,
|
| 39 |
+
"presence_penalty": 0.0
|
| 40 |
+
},
|
| 41 |
+
"recommended_use": ["діалоги", "інструкції", "навчальні матеріали"]
|
| 42 |
+
},
|
| 43 |
+
"mistral-instruct": {
|
| 44 |
+
"model_id": "mistralai/Mistral-7B-Instruct-v0.2",
|
| 45 |
+
"description": "Оптимізована інструктивна модель",
|
| 46 |
+
"likes": 2650,
|
| 47 |
+
"downloads": 3415777,
|
| 48 |
+
"parameters": {
|
| 49 |
+
"max_tokens": 2048,
|
| 50 |
+
"temperature": 0.3,
|
| 51 |
+
"top_p": 0.9,
|
| 52 |
+
"frequency_penalty": 0.0,
|
| 53 |
+
"presence_penalty": 0.0
|
| 54 |
+
},
|
| 55 |
+
"recommended_use": ["наукові дослідження", "структуровані відповіді"]
|
| 56 |
+
},
|
| 57 |
+
"gpt2": {
|
| 58 |
+
"model_id": "openai-community/gpt2",
|
| 59 |
+
"description": "Найбільш завантажувана модель",
|
| 60 |
+
"likes": 2565,
|
| 61 |
+
"downloads": 18299067,
|
| 62 |
+
"parameters": {
|
| 63 |
+
"max_tokens": 1024,
|
| 64 |
+
"temperature": 0.9,
|
| 65 |
+
"top_p": 0.9,
|
| 66 |
+
"frequency_penalty": 0.0,
|
| 67 |
+
"presence_penalty": 0.0
|
| 68 |
+
},
|
| 69 |
+
"recommended_use": ["базова генерація тексту", "прості завдання"]
|
| 70 |
+
},
|
| 71 |
+
"llama-3-1b": {
|
| 72 |
+
"model_id": "meta-llama/Llama-3.2-1B",
|
| 73 |
+
"description": "Легка версія Llama 3",
|
| 74 |
+
"likes": 1559,
|
| 75 |
+
"downloads": 8244484,
|
| 76 |
+
"parameters": {
|
| 77 |
+
"max_tokens": 2048,
|
| 78 |
+
"temperature": 0.8,
|
| 79 |
+
"top_p": 0.9,
|
| 80 |
+
"frequency_penalty": 0.0,
|
| 81 |
+
"presence_penalty": 0.0
|
| 82 |
+
},
|
| 83 |
+
"recommended_use": ["швидкі відповіді", "базова генерація"]
|
| 84 |
+
}
|
| 85 |
+
},
|
| 86 |
+
"default_model": "mistral-instruct",
|
| 87 |
+
"model_selection_criteria": {
|
| 88 |
+
"research": ["deepseek-r1", "mistral-instruct", "llama-3-instruct"],
|
| 89 |
+
"general": ["gpt2", "llama-3-1b"],
|
| 90 |
+
"instruction": ["mistral-instruct", "llama-3-instruct"]
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
self.config_path.write_text(json.dumps(default_config, indent=4, ensure_ascii=False))
|
| 94 |
+
|
| 95 |
+
def load_config(self):
|
| 96 |
+
"""Load configuration from file"""
|
| 97 |
+
self.config = json.loads(self.config_path.read_text())
|
| 98 |
+
|
| 99 |
+
def get_model_config(self, model_key=None):
|
| 100 |
+
"""Get configuration for specific model or default model"""
|
| 101 |
+
if model_key is None:
|
| 102 |
+
model_key = self.config['default_model']
|
| 103 |
+
return self.config['models'].get(model_key)
|
| 104 |
+
|
| 105 |
+
def get_available_models(self):
|
| 106 |
+
"""Get list of available models"""
|
| 107 |
+
return [(key, model['description'])
|
| 108 |
+
for key, model in self.config['models'].items()]
|
| 109 |
+
|
| 110 |
+
def get_default_model(self):
|
| 111 |
+
"""Get default model key"""
|
| 112 |
+
return self.config['default_model']
|
models_config.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"models": {
|
| 3 |
+
"deepseek-r1": {
|
| 4 |
+
"model_id": "deepseek-ai/DeepSeek-R1",
|
| 5 |
+
"description": "Найбільш рейтингова модель за кількістю лайків",
|
| 6 |
+
"likes": 8659,
|
| 7 |
+
"downloads": 3468420,
|
| 8 |
+
"parameters": {
|
| 9 |
+
"max_tokens": 2048,
|
| 10 |
+
"temperature": 0.7,
|
| 11 |
+
"top_p": 0.95,
|
| 12 |
+
"frequency_penalty": 0.0,
|
| 13 |
+
"presence_penalty": 0.0
|
| 14 |
+
},
|
| 15 |
+
"recommended_use": ["складні дослідницькі завдання", "аналітика", "генерація тексту"]
|
| 16 |
+
},
|
| 17 |
+
"llama-3-instruct": {
|
| 18 |
+
"model_id": "meta-llama/Llama-3.1-8B-Instruct",
|
| 19 |
+
"description": "Потужна інструктивна модель від Meta",
|
| 20 |
+
"likes": 3612,
|
| 21 |
+
"downloads": 6035070,
|
| 22 |
+
"parameters": {
|
| 23 |
+
"max_tokens": 4096,
|
| 24 |
+
"temperature": 0.8,
|
| 25 |
+
"top_p": 0.9,
|
| 26 |
+
"frequency_penalty": 0.0,
|
| 27 |
+
"presence_penalty": 0.0
|
| 28 |
+
},
|
| 29 |
+
"recommended_use": ["діалоги", "інструкції", "навчальні матеріали"]
|
| 30 |
+
},
|
| 31 |
+
"mistral-instruct": {
|
| 32 |
+
"model_id": "mistralai/Mistral-7B-Instruct-v0.2",
|
| 33 |
+
"description": "Оптимізована інструктивна модель",
|
| 34 |
+
"likes": 2650,
|
| 35 |
+
"downloads": 3415777,
|
| 36 |
+
"parameters": {
|
| 37 |
+
"max_tokens": 2048,
|
| 38 |
+
"temperature": 0.3,
|
| 39 |
+
"top_p": 0.9,
|
| 40 |
+
"frequency_penalty": 0.0,
|
| 41 |
+
"presence_penalty": 0.0
|
| 42 |
+
},
|
| 43 |
+
"recommended_use": ["наукові дослідження", "структуровані відповіді"]
|
| 44 |
+
},
|
| 45 |
+
"gpt2": {
|
| 46 |
+
"model_id": "openai-community/gpt2",
|
| 47 |
+
"description": "Найбільш завантажувана модель",
|
| 48 |
+
"likes": 2565,
|
| 49 |
+
"downloads": 18299067,
|
| 50 |
+
"parameters": {
|
| 51 |
+
"max_tokens": 1024,
|
| 52 |
+
"temperature": 0.9,
|
| 53 |
+
"top_p": 0.9,
|
| 54 |
+
"frequency_penalty": 0.0,
|
| 55 |
+
"presence_penalty": 0.0
|
| 56 |
+
},
|
| 57 |
+
"recommended_use": ["базова генерація тексту", "прості завдання"]
|
| 58 |
+
},
|
| 59 |
+
"llama-3-1b": {
|
| 60 |
+
"model_id": "meta-llama/Llama-3.2-1B",
|
| 61 |
+
"description": "Легка версія Llama 3",
|
| 62 |
+
"likes": 1559,
|
| 63 |
+
"downloads": 8244484,
|
| 64 |
+
"parameters": {
|
| 65 |
+
"max_tokens": 2048,
|
| 66 |
+
"temperature": 0.8,
|
| 67 |
+
"top_p": 0.9,
|
| 68 |
+
"frequency_penalty": 0.0,
|
| 69 |
+
"presence_penalty": 0.0
|
| 70 |
+
},
|
| 71 |
+
"recommended_use": ["швидкі відповіді", "базова генерація"]
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
"default_model": "mistral-instruct",
|
| 75 |
+
"model_selection_criteria": {
|
| 76 |
+
"research": ["deepseek-r1", "mistral-instruct", "llama-3-instruct"],
|
| 77 |
+
"general": ["gpt2", "llama-3-1b"],
|
| 78 |
+
"instruction": ["mistral-instruct", "llama-3-instruct"]
|
| 79 |
+
}
|
| 80 |
+
}
|
requirements.txt
CHANGED
|
@@ -1,5 +1,10 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
smolagents==0.1.0
|
| 2 |
+
gradio>=4.0.0
|
| 3 |
+
python-dotenv>=0.19.0
|
| 4 |
+
huggingface-hub>=0.19.0
|
| 5 |
+
duckduckgo-search>=3.0.0
|
| 6 |
+
markdownify>=0.11.0
|
| 7 |
+
requests>=2.28.0
|
| 8 |
+
logging>=0.5.1.2
|
| 9 |
+
pandas>=1.3.0
|
| 10 |
+
numpy>=1.21.0
|
tools/__init__.py
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .web_search import DuckDuckGoSearchTool
|
| 2 |
+
from .final_answer import FinalAnswerTool
|
| 3 |
+
from .healthcare_llm_visualizer import HealthcareLLMVisualizerTool
|
| 4 |
+
from .visit_webpage import VisitWebpageTool
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
'DuckDuckGoSearchTool',
|
| 8 |
+
'FinalAnswerTool',
|
| 9 |
+
'HealthcareLLMVisualizerTool',
|
| 10 |
+
'VisitWebpageTool'
|
| 11 |
+
]
|
tools/final_answer.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from typing import Any, Dict, Union
|
| 2 |
from smolagents.tools import Tool
|
| 3 |
import logging
|
| 4 |
from datetime import datetime
|
|
@@ -14,14 +14,11 @@ class FinalAnswerTool(Tool):
|
|
| 14 |
'description': 'The final research report content'
|
| 15 |
}
|
| 16 |
}
|
| 17 |
-
output_type = "
|
| 18 |
|
| 19 |
-
def
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
return content
|
| 23 |
-
|
| 24 |
-
required_sections = [
|
| 25 |
"Executive Summary",
|
| 26 |
"Introduction",
|
| 27 |
"Methodology",
|
|
@@ -31,28 +28,55 @@ class FinalAnswerTool(Tool):
|
|
| 31 |
"References"
|
| 32 |
]
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
f"# Research Report",
|
| 42 |
-
f"*Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n",
|
| 43 |
-
]
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
f"## {section}",
|
| 49 |
-
|
| 50 |
"" # Empty line for better readability
|
| 51 |
])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
"""Process and return the final answer"""
|
| 57 |
logger.info("Formatting final research report")
|
| 58 |
try:
|
|
@@ -62,8 +86,4 @@ class FinalAnswerTool(Tool):
|
|
| 62 |
except Exception as e:
|
| 63 |
error_msg = f"Error formatting research report: {str(e)}"
|
| 64 |
logger.error(error_msg)
|
| 65 |
-
return error_msg
|
| 66 |
-
|
| 67 |
-
def __init__(self, *args, **kwargs):
|
| 68 |
-
self.is_initialized = False
|
| 69 |
-
super().__init__(*args, **kwargs)
|
|
|
|
| 1 |
+
from typing import Any, Dict, Union, List
|
| 2 |
from smolagents.tools import Tool
|
| 3 |
import logging
|
| 4 |
from datetime import datetime
|
|
|
|
| 14 |
'description': 'The final research report content'
|
| 15 |
}
|
| 16 |
}
|
| 17 |
+
output_type = "string" # Changed from 'str' to 'string'
|
| 18 |
|
| 19 |
+
def __init__(self):
|
| 20 |
+
super().__init__()
|
| 21 |
+
self.sections = [
|
|
|
|
|
|
|
|
|
|
| 22 |
"Executive Summary",
|
| 23 |
"Introduction",
|
| 24 |
"Methodology",
|
|
|
|
| 28 |
"References"
|
| 29 |
]
|
| 30 |
|
| 31 |
+
def _format_list_content(self, content: List) -> str:
|
| 32 |
+
"""Format list content into readable text"""
|
| 33 |
+
try:
|
| 34 |
+
return "\n".join([f"- {item}" for item in content])
|
| 35 |
+
except Exception as e:
|
| 36 |
+
logger.error(f"Error formatting list content: {e}")
|
| 37 |
+
return str(content)
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
+
def _format_dict_content(self, content: Dict) -> str:
|
| 40 |
+
"""Format dictionary content into sections"""
|
| 41 |
+
formatted_sections = []
|
| 42 |
+
|
| 43 |
+
for section in self.sections:
|
| 44 |
+
section_content = content.get(section, f"{section} section was not provided")
|
| 45 |
+
if isinstance(section_content, list):
|
| 46 |
+
section_content = self._format_list_content(section_content)
|
| 47 |
+
formatted_sections.extend([
|
| 48 |
f"## {section}",
|
| 49 |
+
str(section_content),
|
| 50 |
"" # Empty line for better readability
|
| 51 |
])
|
| 52 |
+
|
| 53 |
+
return "\n".join(formatted_sections)
|
| 54 |
+
|
| 55 |
+
def _format_report(self, content: Any) -> str:
|
| 56 |
+
"""Format content as a proper research report"""
|
| 57 |
+
try:
|
| 58 |
+
# Create report header
|
| 59 |
+
report_parts = [
|
| 60 |
+
"# Науковий звіт",
|
| 61 |
+
f"*Згенеровано: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n"
|
| 62 |
+
]
|
| 63 |
|
| 64 |
+
# Handle different content types
|
| 65 |
+
if isinstance(content, dict):
|
| 66 |
+
report_parts.append(self._format_dict_content(content))
|
| 67 |
+
elif isinstance(content, list):
|
| 68 |
+
report_parts.append(self._format_list_content(content))
|
| 69 |
+
else:
|
| 70 |
+
report_parts.append(str(content))
|
| 71 |
|
| 72 |
+
return "\n".join(report_parts)
|
| 73 |
+
|
| 74 |
+
except Exception as e:
|
| 75 |
+
error_msg = f"Error formatting report: {str(e)}"
|
| 76 |
+
logger.error(error_msg)
|
| 77 |
+
return f"# Error in Report Generation\n\n{error_msg}\n\nRaw content:\n{str(content)}"
|
| 78 |
+
|
| 79 |
+
def forward(self, answer: Any) -> str:
|
| 80 |
"""Process and return the final answer"""
|
| 81 |
logger.info("Formatting final research report")
|
| 82 |
try:
|
|
|
|
| 86 |
except Exception as e:
|
| 87 |
error_msg = f"Error formatting research report: {str(e)}"
|
| 88 |
logger.error(error_msg)
|
| 89 |
+
return error_msg
|
|
|
|
|
|
|
|
|
|
|
|
tools/healthcare_llm_visualizer.py
CHANGED
|
@@ -6,13 +6,13 @@ class HealthcareLLMVisualizerTool(Tool):
|
|
| 6 |
description = "Creates interactive visualizations for analyzing LLM applications in Healthcare"
|
| 7 |
inputs = {
|
| 8 |
'data': {
|
| 9 |
-
'type': 'object',
|
| 10 |
'description': 'Data for visualization in format: {"items": [{"category": "name", "value": number}]}'
|
| 11 |
}
|
| 12 |
}
|
| 13 |
-
output_type = "string"
|
| 14 |
|
| 15 |
-
def prepare_data(self, raw_data: Dict) -> List[Dict[str, Any]]:
|
| 16 |
"""Convert raw data into format suitable for visualization"""
|
| 17 |
categories = {}
|
| 18 |
|
|
|
|
| 6 |
description = "Creates interactive visualizations for analyzing LLM applications in Healthcare"
|
| 7 |
inputs = {
|
| 8 |
'data': {
|
| 9 |
+
'type': 'object', # Using 'object' for dictionary input
|
| 10 |
'description': 'Data for visualization in format: {"items": [{"category": "name", "value": number}]}'
|
| 11 |
}
|
| 12 |
}
|
| 13 |
+
output_type = "string" # Changed from 'str' to 'string'
|
| 14 |
|
| 15 |
+
def prepare_data(self, raw_data: Dict) -> Dict[str, List[Dict[str, Any]]]:
|
| 16 |
"""Convert raw data into format suitable for visualization"""
|
| 17 |
categories = {}
|
| 18 |
|
tools/visit_webpage.py
CHANGED
|
@@ -1,13 +1,19 @@
|
|
| 1 |
from typing import Any, Optional
|
| 2 |
from smolagents.tools import Tool
|
| 3 |
import requests
|
|
|
|
| 4 |
import markdownify
|
| 5 |
import smolagents
|
| 6 |
|
| 7 |
class VisitWebpageTool(Tool):
|
| 8 |
name = "visit_webpage"
|
| 9 |
description = "Visits a webpage at the given url and reads its content as a markdown string. Use this to browse webpages."
|
| 10 |
-
inputs = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
output_type = "string"
|
| 12 |
|
| 13 |
def forward(self, url: str) -> str:
|
|
@@ -15,7 +21,6 @@ class VisitWebpageTool(Tool):
|
|
| 15 |
import requests
|
| 16 |
from markdownify import markdownify
|
| 17 |
from requests.exceptions import RequestException
|
| 18 |
-
|
| 19 |
from smolagents.utils import truncate_content
|
| 20 |
except ImportError as e:
|
| 21 |
raise ImportError(
|
|
@@ -42,4 +47,4 @@ class VisitWebpageTool(Tool):
|
|
| 42 |
return f"An unexpected error occurred: {str(e)}"
|
| 43 |
|
| 44 |
def __init__(self, *args, **kwargs):
|
| 45 |
-
self.is_initialized = False
|
|
|
|
| 1 |
from typing import Any, Optional
|
| 2 |
from smolagents.tools import Tool
|
| 3 |
import requests
|
| 4 |
+
import re
|
| 5 |
import markdownify
|
| 6 |
import smolagents
|
| 7 |
|
| 8 |
class VisitWebpageTool(Tool):
|
| 9 |
name = "visit_webpage"
|
| 10 |
description = "Visits a webpage at the given url and reads its content as a markdown string. Use this to browse webpages."
|
| 11 |
+
inputs = {
|
| 12 |
+
'url': {
|
| 13 |
+
'type': 'string',
|
| 14 |
+
'description': 'The url of the webpage to visit.'
|
| 15 |
+
}
|
| 16 |
+
}
|
| 17 |
output_type = "string"
|
| 18 |
|
| 19 |
def forward(self, url: str) -> str:
|
|
|
|
| 21 |
import requests
|
| 22 |
from markdownify import markdownify
|
| 23 |
from requests.exceptions import RequestException
|
|
|
|
| 24 |
from smolagents.utils import truncate_content
|
| 25 |
except ImportError as e:
|
| 26 |
raise ImportError(
|
|
|
|
| 47 |
return f"An unexpected error occurred: {str(e)}"
|
| 48 |
|
| 49 |
def __init__(self, *args, **kwargs):
|
| 50 |
+
self.is_initialized = False
|
tools/web_search.py
CHANGED
|
@@ -1,20 +1,14 @@
|
|
| 1 |
-
from typing import Any, Optional
|
| 2 |
from smolagents.tools import Tool
|
| 3 |
import duckduckgo_search
|
| 4 |
-
import logging
|
| 5 |
-
import re
|
| 6 |
-
from datetime import datetime
|
| 7 |
-
from collections import defaultdict
|
| 8 |
-
|
| 9 |
-
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
class DuckDuckGoSearchTool(Tool):
|
| 12 |
name = "web_search"
|
| 13 |
-
description = "Performs
|
| 14 |
inputs = {
|
| 15 |
'query': {
|
| 16 |
'type': 'string',
|
| 17 |
-
'description': 'The search query to perform'
|
| 18 |
}
|
| 19 |
}
|
| 20 |
output_type = "string"
|
|
@@ -26,95 +20,24 @@ class DuckDuckGoSearchTool(Tool):
|
|
| 26 |
from duckduckgo_search import DDGS
|
| 27 |
except ImportError as e:
|
| 28 |
raise ImportError(
|
| 29 |
-
"
|
| 30 |
) from e
|
| 31 |
self.ddgs = DDGS(**kwargs)
|
| 32 |
|
| 33 |
-
def
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
]
|
| 42 |
-
|
| 43 |
-
for pattern in patterns:
|
| 44 |
-
match = re.search(pattern, text)
|
| 45 |
-
if match:
|
| 46 |
-
return match.group(0)
|
| 47 |
-
return None
|
| 48 |
-
except Exception as e:
|
| 49 |
-
logger.error(f"Error extracting date: {e}")
|
| 50 |
-
return None
|
| 51 |
-
|
| 52 |
-
def _parse_search_result(self, result: Dict[str, str]) -> Dict[str, Any]:
|
| 53 |
-
"""Parse a single search result safely"""
|
| 54 |
-
try:
|
| 55 |
-
title = result.get('title', '').strip()
|
| 56 |
-
url = result.get('link', '')
|
| 57 |
-
description = result.get('body', '').strip()
|
| 58 |
-
date = self._extract_date(description) or 'Date not found'
|
| 59 |
-
|
| 60 |
-
return {
|
| 61 |
-
'title': title,
|
| 62 |
-
'url': url,
|
| 63 |
-
'description': description,
|
| 64 |
-
'date': date
|
| 65 |
-
}
|
| 66 |
-
except Exception as e:
|
| 67 |
-
logger.error(f"Error parsing search result: {e}")
|
| 68 |
-
return {}
|
| 69 |
-
|
| 70 |
-
def _format_results(self, results: List[Dict[str, str]]) -> str:
|
| 71 |
-
"""Format search results with academic focus"""
|
| 72 |
-
if not results:
|
| 73 |
-
return "No results found. Consider refining your search terms."
|
| 74 |
-
|
| 75 |
-
formatted_output = ["## Search Results\n"]
|
| 76 |
|
|
|
|
| 77 |
for result in results:
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
f"**Summary:** {parsed['description']}\n"
|
| 85 |
-
])
|
| 86 |
-
|
| 87 |
-
return "\n".join(formatted_output)
|
| 88 |
-
|
| 89 |
-
def forward(self, query: str) -> str:
|
| 90 |
-
"""Execute search and return formatted results"""
|
| 91 |
-
logger.info(f"Performing web search for query: {query}")
|
| 92 |
-
try:
|
| 93 |
-
# Add academic focus to search if not present
|
| 94 |
-
academic_terms = ['research', 'study', 'journal', 'paper']
|
| 95 |
-
if not any(term in query.lower() for term in academic_terms):
|
| 96 |
-
query = f"{query} research study"
|
| 97 |
-
|
| 98 |
-
# Execute search with error handling
|
| 99 |
-
try:
|
| 100 |
-
results = list(self.ddgs.text(query, max_results=self.max_results))
|
| 101 |
-
if not results:
|
| 102 |
-
return "No results found. Try modifying your search terms."
|
| 103 |
-
except Exception as e:
|
| 104 |
-
logger.error(f"Search execution error: {e}")
|
| 105 |
-
return f"Error performing search: {str(e)}"
|
| 106 |
-
|
| 107 |
-
# Format results
|
| 108 |
-
formatted_output = self._format_results(results)
|
| 109 |
-
logger.info("Search completed successfully")
|
| 110 |
-
return formatted_output
|
| 111 |
-
|
| 112 |
-
except Exception as e:
|
| 113 |
-
error_msg = f"Error during web search: {str(e)}"
|
| 114 |
-
logger.error(error_msg)
|
| 115 |
-
return error_msg
|
| 116 |
-
|
| 117 |
-
def __del__(self):
|
| 118 |
-
"""Cleanup when object is destroyed"""
|
| 119 |
-
if hasattr(self, 'ddgs'):
|
| 120 |
-
self.ddgs = None
|
|
|
|
| 1 |
+
from typing import Any, Optional
|
| 2 |
from smolagents.tools import Tool
|
| 3 |
import duckduckgo_search
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
class DuckDuckGoSearchTool(Tool):
|
| 6 |
name = "web_search"
|
| 7 |
+
description = "Performs a duckduckgo web search based on your query (think a Google search) then returns the top search results."
|
| 8 |
inputs = {
|
| 9 |
'query': {
|
| 10 |
'type': 'string',
|
| 11 |
+
'description': 'The search query to perform.'
|
| 12 |
}
|
| 13 |
}
|
| 14 |
output_type = "string"
|
|
|
|
| 20 |
from duckduckgo_search import DDGS
|
| 21 |
except ImportError as e:
|
| 22 |
raise ImportError(
|
| 23 |
+
"You must install package `duckduckgo_search` to run this tool: for instance run `pip install duckduckgo-search`."
|
| 24 |
) from e
|
| 25 |
self.ddgs = DDGS(**kwargs)
|
| 26 |
|
| 27 |
+
def forward(self, query: str) -> str:
|
| 28 |
+
results = list(self.ddgs.text(query, max_results=self.max_results))
|
| 29 |
+
if len(results) == 0:
|
| 30 |
+
raise Exception("No results found! Try a less restrictive/shorter query.")
|
| 31 |
+
|
| 32 |
+
# Print result structure for debugging
|
| 33 |
+
if results:
|
| 34 |
+
print("Result keys:", results[0].keys())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
+
postprocessed_results = []
|
| 37 |
for result in results:
|
| 38 |
+
title = result.get('title', '')
|
| 39 |
+
url = result.get('link', '') or result.get('href', '')
|
| 40 |
+
body = result.get('body', '') or result.get('snippet', '')
|
| 41 |
+
postprocessed_results.append(f"[{title}]({url})\n{body}")
|
| 42 |
+
|
| 43 |
+
return "## Search Results\n\n" + "\n\n".join(postprocessed_results)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|