Spaces:
Running on Zero
Running on Zero
Deploy ArkAssistant smolagent (Hugging Face Agents Course Unit 1)
Browse files- .agents/skills/deploy-space/SKILL.md +1 -1
- AGENTS.md +10 -6
- app.py +51 -96
- prompts.yaml +2 -2
- requirements.txt +7 -7
- test_agent.py +68 -61
- tools/get_current_time_in_timezone.py +17 -20
- tools/get_weather.py +21 -24
- tools/search_wikipedia.py +28 -32
.agents/skills/deploy-space/SKILL.md
CHANGED
|
@@ -24,7 +24,7 @@ Always run these verification steps prior to deployment:
|
|
| 24 |
```
|
| 25 |
|
| 26 |
3. **Dependency Check**:
|
| 27 |
-
Ensure all imported packages in `tools/` and `app.py` (e.g., `markdownify`, `smolagents`, `ddgs`
|
| 28 |
|
| 29 |
4. **Ignore Patterns**:
|
| 30 |
Verify that sensitive files (`.env`), cache folders (`__pycache__`, `.gradio`), and the virtual environment (`.venv`) are listed in `deploy_space.py`'s `ignore_patterns`.
|
|
|
|
| 24 |
```
|
| 25 |
|
| 26 |
3. **Dependency Check**:
|
| 27 |
+
Ensure all imported packages in `tools/` and `app.py` (e.g., `markdownify`, `smolagents`, `ddgs`) are declared in `requirements.txt`.
|
| 28 |
|
| 29 |
4. **Ignore Patterns**:
|
| 30 |
Verify that sensitive files (`.env`), cache folders (`__pycache__`, `.gradio`), and the virtual environment (`.venv`) are listed in `deploy_space.py`'s `ignore_patterns`.
|
AGENTS.md
CHANGED
|
@@ -28,7 +28,16 @@ This workspace contains the complete implementation for the **Hugging Face Agent
|
|
| 28 |
|
| 29 |
## Environment & Commands
|
| 30 |
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
- **Sanity Check All Tools (Fast, No LLM)**:
|
| 34 |
```bash
|
|
@@ -40,11 +49,6 @@ This workspace contains the complete implementation for the **Hugging Face Agent
|
|
| 40 |
.venv/bin/python test_agent.py "What is the capital of Japan and what is the weather there?"
|
| 41 |
```
|
| 42 |
|
| 43 |
-
- **Run CLI Agent Query with Local Ollama (Offline)**:
|
| 44 |
-
```bash
|
| 45 |
-
USE_OLLAMA=true .venv/bin/python test_agent.py "What is the time in UTC?"
|
| 46 |
-
```
|
| 47 |
-
|
| 48 |
- **Run Gradio Web UI Locally**:
|
| 49 |
```bash
|
| 50 |
.venv/bin/python app.py
|
|
|
|
| 28 |
|
| 29 |
## Environment & Commands
|
| 30 |
|
| 31 |
+
Cloud inference only. `HF_TOKEN` and `MODEL_ID` are configured in `.env`;
|
| 32 |
+
local Ollama execution is intentionally disabled. Hugging Face Inference
|
| 33 |
+
Providers require available credits or a PRO plan; without them the API returns
|
| 34 |
+
HTTP 402 before the agent can start.
|
| 35 |
+
|
| 36 |
+
- **Create the project virtual environment**:
|
| 37 |
+
```bash
|
| 38 |
+
uv venv .venv
|
| 39 |
+
uv pip install --python .venv/bin/python -r requirements.txt
|
| 40 |
+
```
|
| 41 |
|
| 42 |
- **Sanity Check All Tools (Fast, No LLM)**:
|
| 43 |
```bash
|
|
|
|
| 49 |
.venv/bin/python test_agent.py "What is the capital of Japan and what is the weather there?"
|
| 50 |
```
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
- **Run Gradio Web UI Locally**:
|
| 53 |
```bash
|
| 54 |
.venv/bin/python app.py
|
app.py
CHANGED
|
@@ -1,127 +1,61 @@
|
|
| 1 |
-
import datetime
|
| 2 |
import os
|
| 3 |
-
|
| 4 |
-
import pytz
|
| 5 |
-
import requests
|
| 6 |
import yaml
|
| 7 |
from dotenv import load_dotenv
|
|
|
|
| 8 |
|
| 9 |
-
from
|
| 10 |
from tools.final_answer import FinalAnswerTool
|
|
|
|
|
|
|
|
|
|
| 11 |
from tools.visit_webpage import VisitWebpageTool
|
| 12 |
-
from
|
| 13 |
|
| 14 |
load_dotenv()
|
| 15 |
|
| 16 |
-
# --- Custom Tools ---
|
| 17 |
-
|
| 18 |
-
@tool
|
| 19 |
-
def get_current_time_in_timezone(timezone: str) -> str:
|
| 20 |
-
"""A tool that fetches the current local time in a specified timezone.
|
| 21 |
-
Args:
|
| 22 |
-
timezone: A string representing a valid IANA timezone (e.g., 'America/New_York', 'Europe/Paris', 'Asia/Kolkata', 'UTC').
|
| 23 |
-
"""
|
| 24 |
-
try:
|
| 25 |
-
import datetime
|
| 26 |
-
import pytz
|
| 27 |
-
tz = pytz.timezone(timezone)
|
| 28 |
-
local_time = datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
|
| 29 |
-
return f"The current local time in {timezone} is: {local_time}"
|
| 30 |
-
except Exception as e:
|
| 31 |
-
return f"Error fetching time for timezone '{timezone}': {str(e)}"
|
| 32 |
-
|
| 33 |
-
@tool
|
| 34 |
-
def get_weather(location: str) -> str:
|
| 35 |
-
"""Fetches the current live weather report and temperature for a given city or location.
|
| 36 |
-
Args:
|
| 37 |
-
location: The name of the city or location (e.g., 'Paris', 'Tokyo', 'San Francisco').
|
| 38 |
-
"""
|
| 39 |
-
try:
|
| 40 |
-
import requests
|
| 41 |
-
from urllib.parse import quote
|
| 42 |
-
encoded_loc = quote(location)
|
| 43 |
-
url = f"https://wttr.in/{encoded_loc}?format=%C+%t+(feels+like+%f)+wind:+%w"
|
| 44 |
-
headers = {"User-Agent": "HF-Agents-Course-ArkAssistant/1.0"}
|
| 45 |
-
resp = requests.get(url, headers=headers, timeout=10)
|
| 46 |
-
if resp.status_code == 200 and resp.text:
|
| 47 |
-
return f"Weather for {location}: {resp.text.strip()}"
|
| 48 |
-
return f"Could not retrieve weather for {location} (status code {resp.status_code})."
|
| 49 |
-
except Exception as e:
|
| 50 |
-
return f"Error fetching weather for '{location}': {str(e)}"
|
| 51 |
-
|
| 52 |
-
@tool
|
| 53 |
-
def search_wikipedia(query: str) -> str:
|
| 54 |
-
"""Fetches an encyclopedic summary of a topic from Wikipedia.
|
| 55 |
-
Args:
|
| 56 |
-
query: The subject or topic name to search on Wikipedia (e.g., 'Alan Turing', 'Artificial intelligence').
|
| 57 |
-
"""
|
| 58 |
-
try:
|
| 59 |
-
import requests
|
| 60 |
-
from urllib.parse import quote
|
| 61 |
-
encoded_query = quote(query)
|
| 62 |
-
url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{encoded_query}"
|
| 63 |
-
headers = {"User-Agent": "HF-Agents-Course-ArkAssistant/1.0"}
|
| 64 |
-
resp = requests.get(url, headers=headers, timeout=10)
|
| 65 |
-
if resp.status_code == 200:
|
| 66 |
-
data = resp.json()
|
| 67 |
-
extract = data.get("extract", "")
|
| 68 |
-
title = data.get("title", query)
|
| 69 |
-
if extract:
|
| 70 |
-
return f"**{title}**: {extract}"
|
| 71 |
-
return f"No extract available for '{query}'."
|
| 72 |
-
elif resp.status_code == 404:
|
| 73 |
-
return f"No Wikipedia article found for '{query}'."
|
| 74 |
-
else:
|
| 75 |
-
return f"Wikipedia query returned status code {resp.status_code}."
|
| 76 |
-
except Exception as e:
|
| 77 |
-
return f"Error querying Wikipedia for '{query}': {str(e)}"
|
| 78 |
-
|
| 79 |
# --- Core Tools & Agent Setup ---
|
| 80 |
|
| 81 |
final_answer = FinalAnswerTool()
|
| 82 |
web_search = DuckDuckGoSearchTool()
|
| 83 |
visit_webpage = VisitWebpageTool()
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
# Image generation tool from Hugging Face Hub
|
| 86 |
image_generation_tool = None
|
| 87 |
try:
|
| 88 |
-
hf_token = os.getenv("HF_TOKEN")
|
| 89 |
image_generation_tool = load_tool(
|
| 90 |
"agents-course/text-to-image",
|
| 91 |
trust_remote_code=True,
|
| 92 |
-
token=hf_token
|
| 93 |
)
|
| 94 |
except Exception as e:
|
| 95 |
print(f"Notice: text-to-image tool could not be loaded: {e}")
|
| 96 |
|
| 97 |
-
# Model
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
except ImportError:
|
| 101 |
-
from smolagents import InferenceClientModel as HfModel
|
| 102 |
-
|
| 103 |
-
hf_token = os.getenv("HF_TOKEN")
|
| 104 |
-
use_ollama = os.getenv("USE_OLLAMA", "").lower() in ("true", "1")
|
| 105 |
-
|
| 106 |
-
if use_ollama or (not hf_token and os.getenv("OLLAMA_ENABLED", "").lower() in ("true", "1")):
|
| 107 |
-
print("Using local Ollama model for agent execution...")
|
| 108 |
from smolagents import LiteLLMModel
|
|
|
|
| 109 |
model = LiteLLMModel(
|
| 110 |
-
model_id=
|
| 111 |
api_base=os.getenv("OLLAMA_API_BASE", "http://127.0.0.1:11434"),
|
| 112 |
-
num_ctx=8192
|
|
|
|
| 113 |
)
|
| 114 |
else:
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
if hf_token:
|
| 123 |
-
model_kwargs["token"] = hf_token
|
| 124 |
-
model = HfModel(**model_kwargs)
|
| 125 |
|
| 126 |
# Load prompt templates
|
| 127 |
prompts_file = os.path.join(os.path.dirname(__file__), "prompts.yaml")
|
|
@@ -137,7 +71,7 @@ research_agent = CodeAgent(
|
|
| 137 |
max_steps=5,
|
| 138 |
verbosity_level=1,
|
| 139 |
name="web_researcher",
|
| 140 |
-
description="A specialist researcher that searches DuckDuckGo, queries Wikipedia, and reads webpage content to gather accurate facts and summaries.",
|
| 141 |
prompt_templates=prompt_templates
|
| 142 |
)
|
| 143 |
|
|
@@ -148,10 +82,31 @@ local_info_agent = CodeAgent(
|
|
| 148 |
max_steps=4,
|
| 149 |
verbosity_level=1,
|
| 150 |
name="local_info_specialist",
|
| 151 |
-
description="A specialist agent that fetches real-time live weather reports and calculates current time in timezones worldwide.",
|
| 152 |
prompt_templates=prompt_templates
|
| 153 |
)
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
# Manager Agent: Coordinates specialists and synthesizes the final response
|
| 156 |
manager_tools = [final_answer]
|
| 157 |
if image_generation_tool is not None:
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
+
|
|
|
|
|
|
|
| 3 |
import yaml
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
+
from smolagents import CodeAgent, InferenceClientModel, load_tool
|
| 6 |
|
| 7 |
+
from Gradio_UI import GradioUI
|
| 8 |
from tools.final_answer import FinalAnswerTool
|
| 9 |
+
from tools.get_current_time_in_timezone import get_current_time_in_timezone
|
| 10 |
+
from tools.get_weather import get_weather
|
| 11 |
+
from tools.search_wikipedia import search_wikipedia
|
| 12 |
from tools.visit_webpage import VisitWebpageTool
|
| 13 |
+
from tools.web_search import DuckDuckGoSearchTool
|
| 14 |
|
| 15 |
load_dotenv()
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
# --- Core Tools & Agent Setup ---
|
| 18 |
|
| 19 |
final_answer = FinalAnswerTool()
|
| 20 |
web_search = DuckDuckGoSearchTool()
|
| 21 |
visit_webpage = VisitWebpageTool()
|
| 22 |
|
| 23 |
+
hf_token = os.getenv("HF_TOKEN")
|
| 24 |
+
if not hf_token:
|
| 25 |
+
raise RuntimeError(
|
| 26 |
+
"HF_TOKEN is required. Local model execution is disabled in this project."
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
# Image generation tool from Hugging Face Hub
|
| 30 |
image_generation_tool = None
|
| 31 |
try:
|
|
|
|
| 32 |
image_generation_tool = load_tool(
|
| 33 |
"agents-course/text-to-image",
|
| 34 |
trust_remote_code=True,
|
| 35 |
+
token=hf_token,
|
| 36 |
)
|
| 37 |
except Exception as e:
|
| 38 |
print(f"Notice: text-to-image tool could not be loaded: {e}")
|
| 39 |
|
| 40 |
+
# Model configuration (cloud by default; Ollama supported for local learning/testing)
|
| 41 |
+
model_id = os.getenv("MODEL_ID", "Qwen/Qwen2.5-Coder-32B-Instruct")
|
| 42 |
+
if "ollama" in model_id.lower():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
from smolagents import LiteLLMModel
|
| 44 |
+
|
| 45 |
model = LiteLLMModel(
|
| 46 |
+
model_id=model_id,
|
| 47 |
api_base=os.getenv("OLLAMA_API_BASE", "http://127.0.0.1:11434"),
|
| 48 |
+
num_ctx=int(os.getenv("OLLAMA_NUM_CTX", "8192")),
|
| 49 |
+
temperature=0.5,
|
| 50 |
)
|
| 51 |
else:
|
| 52 |
+
model = InferenceClientModel(
|
| 53 |
+
model_id=model_id,
|
| 54 |
+
token=hf_token,
|
| 55 |
+
max_tokens=2096,
|
| 56 |
+
temperature=0.5,
|
| 57 |
+
custom_role_conversions=None,
|
| 58 |
+
)
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
# Load prompt templates
|
| 61 |
prompts_file = os.path.join(os.path.dirname(__file__), "prompts.yaml")
|
|
|
|
| 71 |
max_steps=5,
|
| 72 |
verbosity_level=1,
|
| 73 |
name="web_researcher",
|
| 74 |
+
description="A specialist researcher that searches DuckDuckGo, queries Wikipedia, and reads webpage content to gather accurate facts and summaries. ALWAYS call this agent with a 'task' argument, e.g. web_researcher(task='Find the capital of Japan').",
|
| 75 |
prompt_templates=prompt_templates
|
| 76 |
)
|
| 77 |
|
|
|
|
| 82 |
max_steps=4,
|
| 83 |
verbosity_level=1,
|
| 84 |
name="local_info_specialist",
|
| 85 |
+
description="A specialist agent that fetches real-time live weather reports and calculates current time in timezones worldwide. ALWAYS call this agent with a 'task' argument, e.g. local_info_specialist(task='Get current weather in Tokyo'). Never call weather() or get_weather() directly from the manager.",
|
| 86 |
prompt_templates=prompt_templates
|
| 87 |
)
|
| 88 |
|
| 89 |
+
# Make managed agents tolerant of common argument names (query/question/etc.)
|
| 90 |
+
def _adapt_managed_agent(agent):
|
| 91 |
+
original_call = agent.__call__
|
| 92 |
+
|
| 93 |
+
def _call(task=None, **kwargs):
|
| 94 |
+
if task is None:
|
| 95 |
+
for key in ("task", "query", "question", "request", "prompt"):
|
| 96 |
+
if key in kwargs:
|
| 97 |
+
task = kwargs.pop(key)
|
| 98 |
+
break
|
| 99 |
+
if task is None and kwargs:
|
| 100 |
+
task = list(kwargs.values())[0]
|
| 101 |
+
kwargs = {}
|
| 102 |
+
return original_call(task, **kwargs)
|
| 103 |
+
|
| 104 |
+
agent.__call__ = _call
|
| 105 |
+
return agent
|
| 106 |
+
|
| 107 |
+
research_agent = _adapt_managed_agent(research_agent)
|
| 108 |
+
local_info_agent = _adapt_managed_agent(local_info_agent)
|
| 109 |
+
|
| 110 |
# Manager Agent: Coordinates specialists and synthesizes the final response
|
| 111 |
manager_tools = [final_answer]
|
| 112 |
if image_generation_tool is not None:
|
prompts.yaml
CHANGED
|
@@ -219,8 +219,8 @@
|
|
| 219 |
|
| 220 |
{%- if managed_agents and managed_agents.values() | list %}
|
| 221 |
You can also give tasks to team members.
|
| 222 |
-
Calling a team member works the same as for calling a tool: simply, the only argument you can give in the call is '
|
| 223 |
-
Given that this team member is a real human, you should be very verbose in your
|
| 224 |
Here is a list of the team members that you can call:
|
| 225 |
{%- for agent in managed_agents.values() %}
|
| 226 |
- {{ agent.name }}: {{ agent.description }}
|
|
|
|
| 219 |
|
| 220 |
{%- if managed_agents and managed_agents.values() | list %}
|
| 221 |
You can also give tasks to team members.
|
| 222 |
+
Calling a team member works the same as for calling a tool: simply, the only argument you can give in the call is 'task', a long string explaining your task.
|
| 223 |
+
Given that this team member is a real human, you should be very verbose in your task.
|
| 224 |
Here is a list of the team members that you can call:
|
| 225 |
{%- for agent in managed_agents.values() %}
|
| 226 |
- {{ agent.name }}: {{ agent.description }}
|
requirements.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
markdownify
|
| 2 |
-
smolagents
|
| 3 |
-
requests
|
| 4 |
-
duckduckgo_search
|
| 5 |
pandas
|
| 6 |
-
pytz
|
| 7 |
-
python-dotenv
|
| 8 |
pillow
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
ddgs
|
| 2 |
+
gradio>=5.23,<7
|
| 3 |
+
huggingface_hub
|
| 4 |
markdownify
|
|
|
|
|
|
|
|
|
|
| 5 |
pandas
|
|
|
|
|
|
|
| 6 |
pillow
|
| 7 |
+
python-dotenv
|
| 8 |
+
pytz
|
| 9 |
+
requests
|
| 10 |
+
smolagents>=1.26,<2
|
test_agent.py
CHANGED
|
@@ -1,94 +1,101 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
|
| 4 |
Usage:
|
| 5 |
# Run a test query against the manager agent:
|
| 6 |
.venv/bin/python test_agent.py "What is the capital of France and what is the current time there?"
|
| 7 |
|
| 8 |
-
# Run with local Ollama offline model:
|
| 9 |
-
USE_OLLAMA=true .venv/bin/python test_agent.py "What is the weather in London?"
|
| 10 |
-
|
| 11 |
# Sanity check all tools without calling the LLM:
|
| 12 |
.venv/bin/python test_agent.py --test-tools
|
|
|
|
|
|
|
| 13 |
"""
|
| 14 |
|
| 15 |
-
import os
|
| 16 |
import sys
|
| 17 |
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
print("=" * 60)
|
| 20 |
print("🔬 Running Sanity Check on ArkAssistant Tools (No LLM Calls)")
|
| 21 |
print("=" * 60)
|
| 22 |
-
|
| 23 |
-
# 1. Weather
|
| 24 |
-
try:
|
| 25 |
-
from app import get_weather
|
| 26 |
-
w = get_weather("London")
|
| 27 |
-
print(f"✅ Weather Tool: {w[:80]}...")
|
| 28 |
-
except Exception as e:
|
| 29 |
-
print(f"❌ Weather Tool Failed: {e}")
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
t = get_current_time_in_timezone("UTC")
|
| 35 |
-
print(f"✅ Timezone Tool: {t}")
|
| 36 |
-
except Exception as e:
|
| 37 |
-
print(f"❌ Timezone Tool Failed: {e}")
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
# 5. DuckDuckGo Web Search
|
| 56 |
-
try:
|
| 57 |
-
from app import web_search
|
| 58 |
-
sr = web_search("smolagents Hugging Face")
|
| 59 |
-
print(f"✅ Web Search Tool: {sr[:80]}...")
|
| 60 |
-
except Exception as e:
|
| 61 |
-
print(f"❌ Web Search Tool Failed: {e}")
|
| 62 |
|
| 63 |
print("=" * 60)
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
def main():
|
| 68 |
args = sys.argv[1:]
|
| 69 |
-
|
| 70 |
if "--test-tools" in args:
|
| 71 |
-
run_tools_sanity()
|
| 72 |
-
return
|
| 73 |
|
| 74 |
-
|
| 75 |
-
prompt = " ".join([a for a in args if not a.startswith("--")])
|
| 76 |
if not prompt:
|
| 77 |
prompt = "What is the current time in UTC and what is the weather in Paris?"
|
| 78 |
print(f"No prompt provided. Using default test prompt:\n '{prompt}'\n")
|
| 79 |
|
| 80 |
-
print(
|
| 81 |
-
from app import agent
|
| 82 |
-
|
| 83 |
-
print(f"🤖 Running Agent Task: \"{prompt}\"")
|
| 84 |
-
print("-" * 60)
|
| 85 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
response = agent.run(prompt)
|
| 87 |
print("-" * 60)
|
| 88 |
print(f"\n🎯 Agent Final Answer:\n{response}")
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
if __name__ == "__main__":
|
| 94 |
-
main()
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
+
"""CLI test runner for ArkAssistant.
|
| 3 |
+
|
| 4 |
Usage:
|
| 5 |
# Run a test query against the manager agent:
|
| 6 |
.venv/bin/python test_agent.py "What is the capital of France and what is the current time there?"
|
| 7 |
|
|
|
|
|
|
|
|
|
|
| 8 |
# Sanity check all tools without calling the LLM:
|
| 9 |
.venv/bin/python test_agent.py --test-tools
|
| 10 |
+
|
| 11 |
+
The project uses the Hugging Face Inference API and requires HF_TOKEN in .env.
|
| 12 |
"""
|
| 13 |
|
|
|
|
| 14 |
import sys
|
| 15 |
|
| 16 |
+
from tools.get_current_time_in_timezone import get_current_time_in_timezone
|
| 17 |
+
from tools.get_weather import get_weather
|
| 18 |
+
from tools.search_wikipedia import search_wikipedia
|
| 19 |
+
from tools.visit_webpage import VisitWebpageTool
|
| 20 |
+
from tools.web_search import DuckDuckGoSearchTool
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _require_result(result: str) -> str:
|
| 24 |
+
text = str(result).strip()
|
| 25 |
+
if not text:
|
| 26 |
+
raise RuntimeError("tool returned an empty result")
|
| 27 |
+
if text.lower().startswith("error"):
|
| 28 |
+
raise RuntimeError(text)
|
| 29 |
+
return text
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def run_tools_sanity() -> int:
|
| 33 |
print("=" * 60)
|
| 34 |
print("🔬 Running Sanity Check on ArkAssistant Tools (No LLM Calls)")
|
| 35 |
print("=" * 60)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
visit_webpage = VisitWebpageTool()
|
| 38 |
+
web_search = DuckDuckGoSearchTool()
|
| 39 |
+
failures: list[str] = []
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
checks = [
|
| 42 |
+
("Weather", lambda: get_weather("London")),
|
| 43 |
+
("Timezone", lambda: get_current_time_in_timezone("UTC")),
|
| 44 |
+
("Wikipedia", lambda: search_wikipedia("Alan Turing")),
|
| 45 |
+
("Visit Webpage", lambda: visit_webpage("https://httpbin.org/html")),
|
| 46 |
+
("Web Search", lambda: web_search("smolagents Hugging Face")),
|
| 47 |
+
]
|
| 48 |
|
| 49 |
+
for name, check in checks:
|
| 50 |
+
try:
|
| 51 |
+
result = _require_result(check())
|
| 52 |
+
preview = result.replace("\n", " ")[:100]
|
| 53 |
+
print(f"✅ {name} Tool: {preview}")
|
| 54 |
+
except Exception as exc:
|
| 55 |
+
failures.append(f"{name}: {exc}")
|
| 56 |
+
print(f"❌ {name} Tool Failed: {exc}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
print("=" * 60)
|
| 59 |
+
if failures:
|
| 60 |
+
print(f"❌ {len(failures)} tool check(s) failed.")
|
| 61 |
+
for failure in failures:
|
| 62 |
+
print(f" - {failure}")
|
| 63 |
+
return 1
|
| 64 |
+
print("✅ All tool sanity checks passed.")
|
| 65 |
+
return 0
|
| 66 |
+
|
| 67 |
|
| 68 |
+
def main() -> int:
|
| 69 |
args = sys.argv[1:]
|
| 70 |
+
|
| 71 |
if "--test-tools" in args:
|
| 72 |
+
return run_tools_sanity()
|
|
|
|
| 73 |
|
| 74 |
+
prompt = " ".join(arg for arg in args if not arg.startswith("--"))
|
|
|
|
| 75 |
if not prompt:
|
| 76 |
prompt = "What is the current time in UTC and what is the weather in Paris?"
|
| 77 |
print(f"No prompt provided. Using default test prompt:\n '{prompt}'\n")
|
| 78 |
|
| 79 |
+
print("🚀 Initializing ArkAssistant Agent...")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
try:
|
| 81 |
+
from app import agent
|
| 82 |
+
|
| 83 |
+
print(f"🤖 Running Agent Task: \"{prompt}\"")
|
| 84 |
+
print("-" * 60)
|
| 85 |
response = agent.run(prompt)
|
| 86 |
print("-" * 60)
|
| 87 |
print(f"\n🎯 Agent Final Answer:\n{response}")
|
| 88 |
+
return 0
|
| 89 |
+
except Exception as exc:
|
| 90 |
+
message = str(exc)
|
| 91 |
+
print(f"\n❌ Error during agent execution: {message}")
|
| 92 |
+
if "402" in message or "Payment Required" in message:
|
| 93 |
+
print(
|
| 94 |
+
"Hint: Hugging Face Inference has no available credits. "
|
| 95 |
+
"Add credits/PRO or configure another cloud provider in .env."
|
| 96 |
+
)
|
| 97 |
+
return 1
|
| 98 |
+
|
| 99 |
|
| 100 |
if __name__ == "__main__":
|
| 101 |
+
sys.exit(main())
|
tools/get_current_time_in_timezone.py
CHANGED
|
@@ -1,22 +1,19 @@
|
|
| 1 |
-
|
| 2 |
-
from typing import Any, Optional
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
description = "A tool that fetches the current local time in a specified timezone."
|
| 7 |
-
inputs = {'timezone': {'type': 'string', 'description': "A string representing a valid IANA timezone (e.g., 'America/New_York', 'Europe/Paris', 'Asia/Kolkata', 'UTC')."}}
|
| 8 |
-
output_type = "string"
|
| 9 |
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
| 1 |
+
import datetime
|
|
|
|
| 2 |
|
| 3 |
+
import pytz
|
| 4 |
+
from smolagents import tool
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
|
| 7 |
+
@tool
|
| 8 |
+
def get_current_time_in_timezone(timezone: str) -> str:
|
| 9 |
+
"""A tool that fetches the current local time in a specified timezone.
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
timezone: A valid IANA timezone (e.g., 'America/New_York', 'Europe/Paris', 'Asia/Kolkata', 'UTC').
|
| 13 |
+
"""
|
| 14 |
+
try:
|
| 15 |
+
tz = pytz.timezone(timezone)
|
| 16 |
+
local_time = datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
|
| 17 |
+
return f"The current local time in {timezone} is: {local_time}"
|
| 18 |
+
except Exception as e:
|
| 19 |
+
return f"Error fetching time for timezone '{timezone}': {str(e)}"
|
tools/get_weather.py
CHANGED
|
@@ -1,26 +1,23 @@
|
|
| 1 |
-
from
|
| 2 |
-
from typing import Any, Optional
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
description = "Fetches the current live weather report and temperature for a given city or location."
|
| 7 |
-
inputs = {'location': {'type': 'string', 'description': "The name of the city or location (e.g., 'Paris', 'Tokyo', 'San Francisco')."}}
|
| 8 |
-
output_type = "string"
|
| 9 |
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
return f"
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
| 1 |
+
from urllib.parse import quote
|
|
|
|
| 2 |
|
| 3 |
+
import requests
|
| 4 |
+
from smolagents import tool
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
|
| 7 |
+
@tool
|
| 8 |
+
def get_weather(location: str) -> str:
|
| 9 |
+
"""Fetches the current live weather report and temperature for a given city or location.
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
location: The name of the city or location (e.g., 'Paris', 'Tokyo', 'San Francisco').
|
| 13 |
+
"""
|
| 14 |
+
try:
|
| 15 |
+
encoded_loc = quote(location)
|
| 16 |
+
url = f"https://wttr.in/{encoded_loc}?format=%C+%t+(feels+like+%f)+wind:+%w"
|
| 17 |
+
headers = {"User-Agent": "HF-Agents-Course-ArkAssistant/1.0"}
|
| 18 |
+
resp = requests.get(url, headers=headers, timeout=10)
|
| 19 |
+
if resp.status_code == 200 and resp.text:
|
| 20 |
+
return f"Weather for {location}: {resp.text.strip()}"
|
| 21 |
+
return f"Could not retrieve weather for {location} (status code {resp.status_code})."
|
| 22 |
+
except Exception as e:
|
| 23 |
+
return f"Error fetching weather for '{location}': {str(e)}"
|
tools/search_wikipedia.py
CHANGED
|
@@ -1,34 +1,30 @@
|
|
| 1 |
-
from
|
| 2 |
-
from typing import Any, Optional
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
description = "Fetches an encyclopedic summary of a topic from Wikipedia."
|
| 7 |
-
inputs = {'query': {'type': 'string', 'description': "The subject or topic name to search on Wikipedia (e.g., 'Alan Turing', 'Artificial intelligence')."}}
|
| 8 |
-
output_type = "string"
|
| 9 |
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
return f"
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
| 1 |
+
from urllib.parse import quote
|
|
|
|
| 2 |
|
| 3 |
+
import requests
|
| 4 |
+
from smolagents import tool
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
|
| 7 |
+
@tool
|
| 8 |
+
def search_wikipedia(query: str) -> str:
|
| 9 |
+
"""Fetches an encyclopedic summary of a topic from Wikipedia.
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
query: The subject or topic name to search on Wikipedia (e.g., 'Alan Turing', 'Artificial intelligence').
|
| 13 |
+
"""
|
| 14 |
+
try:
|
| 15 |
+
encoded_query = quote(query)
|
| 16 |
+
url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{encoded_query}"
|
| 17 |
+
headers = {"User-Agent": "HF-Agents-Course-ArkAssistant/1.0"}
|
| 18 |
+
resp = requests.get(url, headers=headers, timeout=10)
|
| 19 |
+
if resp.status_code == 200:
|
| 20 |
+
data = resp.json()
|
| 21 |
+
extract = data.get("extract", "")
|
| 22 |
+
title = data.get("title", query)
|
| 23 |
+
if extract:
|
| 24 |
+
return f"**{title}**: {extract}"
|
| 25 |
+
return f"No extract available for '{query}'."
|
| 26 |
+
if resp.status_code == 404:
|
| 27 |
+
return f"No Wikipedia article found for '{query}'."
|
| 28 |
+
return f"Wikipedia query returned status code {resp.status_code}."
|
| 29 |
+
except Exception as e:
|
| 30 |
+
return f"Error querying Wikipedia for '{query}': {str(e)}"
|