Spaces:
Sleeping
Sleeping
Upload 8 files
Browse files- .gitignore +29 -0
- agent.py +88 -88
- config.py +7 -7
- exceptions.py +11 -11
.gitignore
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# Virtual Environments
|
7 |
+
venv/
|
8 |
+
.venv/
|
9 |
+
|
10 |
+
# Environment variables file
|
11 |
+
.env
|
12 |
+
|
13 |
+
# Logs and debug
|
14 |
+
*.log
|
15 |
+
logs/
|
16 |
+
|
17 |
+
# OS-specific files
|
18 |
+
.DS_Store
|
19 |
+
Thumbs.db
|
20 |
+
|
21 |
+
# Machine specific devcontainer file
|
22 |
+
.devcontainer/devcontainer.local.json
|
23 |
+
|
24 |
+
# Firebase Studio addition
|
25 |
+
.vscode/
|
26 |
+
|
27 |
+
# Cache
|
28 |
+
cache/
|
29 |
+
.cache/
|
agent.py
CHANGED
@@ -1,88 +1,88 @@
|
|
1 |
-
"""
|
2 |
-
agent.py – central coordinator for smolagents-powered agent.
|
3 |
-
|
4 |
-
This file exposes a single helper function `my_agent()` that returns an
|
5 |
-
object which is **callable** (i.e. implements `__call__(question:str) -> str`)
|
6 |
-
so that `app.py` can stay unchanged apart from a single import.
|
7 |
-
|
8 |
-
* Adding new tools
|
9 |
-
------------------
|
10 |
-
1. Drop the tool file inside the ``/tools`` package.
|
11 |
-
2. Import the tool class in `my_agent` and append it to the ``tools`` list.
|
12 |
-
The rest of the application will automatically pick it up.
|
13 |
-
"""
|
14 |
-
|
15 |
-
from typing import List, Sequence
|
16 |
-
|
17 |
-
try:
|
18 |
-
from smolagents import Agent, Tool # type: ignore
|
19 |
-
except ImportError as exc: # pragma: no cover
|
20 |
-
raise ImportError(
|
21 |
-
"smolagents must be in requirements.txt. "
|
22 |
-
"Add `smolagents` to your dependencies."
|
23 |
-
) from exc
|
24 |
-
|
25 |
-
# Available tools
|
26 |
-
from tools.web_search import DuckDuckGoSearchTool # noqa: E402
|
27 |
-
|
28 |
-
|
29 |
-
class SmolAgentWrapper:
|
30 |
-
"""
|
31 |
-
Thin wrapper that makes a smolagents.Agent *callable*.
|
32 |
-
|
33 |
-
The evaluation harness in app.py expects an object that can be called
|
34 |
-
directly with a single question and that returns a string. The underlying
|
35 |
-
smolagents agent is session-aware and can handle multi-turn conversations
|
36 |
-
but we keep the public interface single-turn for now.
|
37 |
-
"""
|
38 |
-
|
39 |
-
def __init__(self, tools: Sequence["Tool"] | None = None) -> None: # type: ignore[name-defined]
|
40 |
-
if tools is None:
|
41 |
-
tools = [DuckDuckGoSearchTool()]
|
42 |
-
self._agent = Agent(tools=list(tools))
|
43 |
-
|
44 |
-
# Allow the object itself to be called like a function
|
45 |
-
def __call__(self, question: str) -> str: # noqa: D401 (simple summary ok)
|
46 |
-
"""
|
47 |
-
Ask the underlying smolagents Agent a **single** question and return the answer.
|
48 |
-
|
49 |
-
Any exception is caught and surfaced as a readable string in order not
|
50 |
-
to crash the evaluation loop.
|
51 |
-
"""
|
52 |
-
try:
|
53 |
-
response = self._agent.run(question)
|
54 |
-
# smolagents may return dicts or ToolOutput objects; normalise to str
|
55 |
-
if isinstance(response, str):
|
56 |
-
return response
|
57 |
-
return str(response)
|
58 |
-
except Exception as err: # pragma: no cover
|
59 |
-
return f"ERROR: {type(err).__name__}: {err}"
|
60 |
-
|
61 |
-
|
62 |
-
# --------------------------------------------------------------------------- #
|
63 |
-
# Helper – this is what app.py will import
|
64 |
-
# --------------------------------------------------------------------------- #
|
65 |
-
|
66 |
-
def my_agent(extra_tools: Sequence["Tool"] | None = None) -> SmolAgentWrapper: # type: ignore[name-defined]
|
67 |
-
"""
|
68 |
-
Factory that returns a ready-to-go agent.
|
69 |
-
|
70 |
-
Parameters
|
71 |
-
----------
|
72 |
-
extra_tools:
|
73 |
-
Optional sequence of additional smolagents Tool objects to extend the
|
74 |
-
agent's capabilities. They are appended **after** the default search
|
75 |
-
tool so they can override it if they expose the same name.
|
76 |
-
|
77 |
-
Returns
|
78 |
-
-------
|
79 |
-
SmolAgentWrapper
|
80 |
-
A callable object compatible with the original BasicAgent.
|
81 |
-
"""
|
82 |
-
tools: List["Tool"] = [DuckDuckGoSearchTool()]
|
83 |
-
if extra_tools:
|
84 |
-
tools.extend(extra_tools)
|
85 |
-
return SmolAgentWrapper(tools=tools)
|
86 |
-
|
87 |
-
|
88 |
-
__all__ = ["my_agent", "SmolAgentWrapper"]
|
|
|
1 |
+
"""
|
2 |
+
agent.py – central coordinator for smolagents-powered agent.
|
3 |
+
|
4 |
+
This file exposes a single helper function `my_agent()` that returns an
|
5 |
+
object which is **callable** (i.e. implements `__call__(question:str) -> str`)
|
6 |
+
so that `app.py` can stay unchanged apart from a single import.
|
7 |
+
|
8 |
+
* Adding new tools
|
9 |
+
------------------
|
10 |
+
1. Drop the tool file inside the ``/tools`` package.
|
11 |
+
2. Import the tool class in `my_agent` and append it to the ``tools`` list.
|
12 |
+
The rest of the application will automatically pick it up.
|
13 |
+
"""
|
14 |
+
|
15 |
+
from typing import List, Sequence
|
16 |
+
|
17 |
+
try:
|
18 |
+
from smolagents import Agent, Tool # type: ignore
|
19 |
+
except ImportError as exc: # pragma: no cover
|
20 |
+
raise ImportError(
|
21 |
+
"smolagents must be in requirements.txt. "
|
22 |
+
"Add `smolagents` to your dependencies."
|
23 |
+
) from exc
|
24 |
+
|
25 |
+
# Available tools
|
26 |
+
from tools.web_search import DuckDuckGoSearchTool # noqa: E402
|
27 |
+
|
28 |
+
|
29 |
+
class SmolAgentWrapper:
|
30 |
+
"""
|
31 |
+
Thin wrapper that makes a smolagents.Agent *callable*.
|
32 |
+
|
33 |
+
The evaluation harness in app.py expects an object that can be called
|
34 |
+
directly with a single question and that returns a string. The underlying
|
35 |
+
smolagents agent is session-aware and can handle multi-turn conversations
|
36 |
+
but we keep the public interface single-turn for now.
|
37 |
+
"""
|
38 |
+
|
39 |
+
def __init__(self, tools: Sequence["Tool"] | None = None) -> None: # type: ignore[name-defined]
|
40 |
+
if tools is None:
|
41 |
+
tools = [DuckDuckGoSearchTool()]
|
42 |
+
self._agent = Agent(tools=list(tools))
|
43 |
+
|
44 |
+
# Allow the object itself to be called like a function
|
45 |
+
def __call__(self, question: str) -> str: # noqa: D401 (simple summary ok)
|
46 |
+
"""
|
47 |
+
Ask the underlying smolagents Agent a **single** question and return the answer.
|
48 |
+
|
49 |
+
Any exception is caught and surfaced as a readable string in order not
|
50 |
+
to crash the evaluation loop.
|
51 |
+
"""
|
52 |
+
try:
|
53 |
+
response = self._agent.run(question)
|
54 |
+
# smolagents may return dicts or ToolOutput objects; normalise to str
|
55 |
+
if isinstance(response, str):
|
56 |
+
return response
|
57 |
+
return str(response)
|
58 |
+
except Exception as err: # pragma: no cover
|
59 |
+
return f"ERROR: {type(err).__name__}: {err}"
|
60 |
+
|
61 |
+
|
62 |
+
# --------------------------------------------------------------------------- #
|
63 |
+
# Helper – this is what app.py will import
|
64 |
+
# --------------------------------------------------------------------------- #
|
65 |
+
|
66 |
+
def my_agent(extra_tools: Sequence["Tool"] | None = None) -> SmolAgentWrapper: # type: ignore[name-defined]
|
67 |
+
"""
|
68 |
+
Factory that returns a ready-to-go agent.
|
69 |
+
|
70 |
+
Parameters
|
71 |
+
----------
|
72 |
+
extra_tools:
|
73 |
+
Optional sequence of additional smolagents Tool objects to extend the
|
74 |
+
agent's capabilities. They are appended **after** the default search
|
75 |
+
tool so they can override it if they expose the same name.
|
76 |
+
|
77 |
+
Returns
|
78 |
+
-------
|
79 |
+
SmolAgentWrapper
|
80 |
+
A callable object compatible with the original BasicAgent.
|
81 |
+
"""
|
82 |
+
tools: List["Tool"] = [DuckDuckGoSearchTool()]
|
83 |
+
if extra_tools:
|
84 |
+
tools.extend(extra_tools)
|
85 |
+
return SmolAgentWrapper(tools=tools)
|
86 |
+
|
87 |
+
|
88 |
+
__all__ = ["my_agent", "SmolAgentWrapper"]
|
config.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
-
# DuckDuckGo-specific settings
|
2 |
-
DUCKDUCKGO_MAX_RESULTS = 10
|
3 |
-
DUCKDUCKGO_TIMEOUT = 15 # seconds
|
4 |
-
SEARCH_DEFAULT_ENGINE = 'duckduckgo' # Make it easy to add others
|
5 |
-
|
6 |
-
# Logging
|
7 |
-
LOG_LEVEL = "INFO"
|
|
|
1 |
+
# DuckDuckGo-specific settings
|
2 |
+
DUCKDUCKGO_MAX_RESULTS = 10
|
3 |
+
DUCKDUCKGO_TIMEOUT = 15 # seconds
|
4 |
+
SEARCH_DEFAULT_ENGINE = 'duckduckgo' # Make it easy to add others
|
5 |
+
|
6 |
+
# Logging
|
7 |
+
LOG_LEVEL = "INFO"
|
exceptions.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1 |
-
class SearchError(Exception):
|
2 |
-
"""Base exception for search errors."""
|
3 |
-
pass
|
4 |
-
|
5 |
-
class NoResultsFound(SearchError):
|
6 |
-
"""Raised when no search results are returned."""
|
7 |
-
pass
|
8 |
-
|
9 |
-
class SearchEngineUnavailable(SearchError):
|
10 |
-
"""Raised when the search engine is down or unreachable."""
|
11 |
-
pass
|
|
|
1 |
+
class SearchError(Exception):
|
2 |
+
"""Base exception for search errors."""
|
3 |
+
pass
|
4 |
+
|
5 |
+
class NoResultsFound(SearchError):
|
6 |
+
"""Raised when no search results are returned."""
|
7 |
+
pass
|
8 |
+
|
9 |
+
class SearchEngineUnavailable(SearchError):
|
10 |
+
"""Raised when the search engine is down or unreachable."""
|
11 |
+
pass
|