David de la Iglesia Castro Nathan Brake commited on
Commit
14653c6
·
unverified ·
1 Parent(s): a8cb892

Initial setup (#2)

Browse files

* boilerplate setup

* Add smolagents and tracing code by

@njbrake
.

Minor tweaks.

Co-authored-by: Nathan Brake <[email protected]>

* enh(DEFAULT_PROMPT): Add missing hours word.

* Add test_google_maps_tool

* Update mkdocs.yml

* Update src/surf_spot_finder/cli.py

---------

Co-authored-by: Nathan Brake <[email protected]>

.github/setup.sh CHANGED
@@ -1,3 +1,2 @@
1
  python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
2
  python -m pip install -e .
3
- python -m pip install --upgrade streamlit
 
1
  python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
2
  python -m pip install -e .
 
.gitignore CHANGED
@@ -162,3 +162,5 @@ cython_debug/
162
 
163
  .idea/
164
  .vscode/
 
 
 
162
 
163
  .idea/
164
  .vscode/
165
+
166
+ telemetry_output
README.md CHANGED
@@ -13,7 +13,7 @@ This blueprint guides you to ...
13
 
14
  📘 To explore this project further and discover other Blueprints, visit the [**Blueprints Hub**](https://developer-hub.mozilla.ai/).
15
 
16
- 👉 📖 For more detailed guidance on using this project, please visit our [**Docs here**](https://mozilla-ai.github.io/Blueprint-template/)
17
 
18
  ### Built with
19
  - Python 3.10+
 
13
 
14
  📘 To explore this project further and discover other Blueprints, visit the [**Blueprints Hub**](https://developer-hub.mozilla.ai/).
15
 
16
+ 👉 📖 For more detailed guidance on using this project, please visit our [**Docs here**](https://mozilla-ai.github.io/surf-spot-finder/)
17
 
18
  ### Built with
19
  - Python 3.10+
demo/app.py CHANGED
@@ -1,7 +0,0 @@
1
- import streamlit as st
2
-
3
- from blueprint.hello import hello
4
-
5
- st.title("Blueprint Demo")
6
-
7
- st.write(hello())
 
 
 
 
 
 
 
 
demo/run.sh DELETED
@@ -1,26 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Adapted from https://docs.streamlit.io/deploy/tutorials/kubernetes
4
-
5
- APP_PID=
6
- stopRunningProcess() {
7
- # Based on https://linuxconfig.org/how-to-propagate-a-signal-to-child-processes-from-a-bash-script
8
- if test ! "${APP_PID}" = '' && ps -p ${APP_PID} > /dev/null ; then
9
- > /proc/1/fd/1 echo "Stopping ${COMMAND_PATH} which is running with process ID ${APP_PID}"
10
-
11
- kill -TERM ${APP_PID}
12
- > /proc/1/fd/1 echo "Waiting for ${COMMAND_PATH} to process SIGTERM signal"
13
-
14
- wait ${APP_PID}
15
- > /proc/1/fd/1 echo "All processes have stopped running"
16
- else
17
- > /proc/1/fd/1 echo "${COMMAND_PATH} was not started when the signal was sent or it has already been stopped"
18
- fi
19
- }
20
-
21
- trap stopRunningProcess EXIT TERM
22
-
23
- streamlit run ${HOME}/document-to-podcast/demo/app.py &
24
- APP_ID=${!}
25
-
26
- wait ${APP_ID}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docs/api.md CHANGED
@@ -1,3 +1,7 @@
1
  # API Reference
2
 
3
- "::: blueprint.hello"
 
 
 
 
 
1
  # API Reference
2
 
3
+ ::: surf_spot_finder.config.Config
4
+
5
+ ::: surf_spot_finder.agents.smolagents
6
+
7
+ ::: surf_spot_finder.tracing
docs/future-features-contributions.md CHANGED
@@ -7,21 +7,21 @@ This Blueprint is an evolving project designed to grow with the help of the open
7
  ## 🌟 **How You Can Contribute**
8
 
9
  ### 🛠️ **Enhance the Blueprint**
10
- - Check the [Issues](https://github.com/mozilla-ai/blueprint-template/issues) page to see if there are feature requests you'd like to implement
11
- - Refer to our [Contribution Guide](https://github.com/mozilla-ai/blueprint-template/blob/main/CONTRIBUTING.md) for more details on contributions
12
 
13
  ### 🎨 **Extensibility Ideas**
14
 
15
  This Blueprint is designed to be a foundation you can build upon. By extending its capabilities, you can open the door to new applications, improve user experience, and adapt the Blueprint to address other use cases. Here are a few ideas for how you can expand its potential:
16
 
17
 
18
- We’d love to see how you can enhance this Blueprint! If you create improvements or extend its capabilities, consider contributing them back to the project so others in the community can benefit from your work. Check out our [Contributions Guide](https://github.com/mozilla-ai/blueprint-template/blob/main/CONTRIBUTING.md) to get started!
19
 
20
  ### 💡 **Share Your Ideas**
21
- Got an idea for how this Blueprint could be improved? You can share your suggestions through [GitHub Discussions](https://github.com/mozilla-ai/blueprint-template/discussions).
22
 
23
  ### 🌍 **Build New Blueprints**
24
- This project is part of a larger initiative to create a collection of reusable starter code solutions that use open-source AI tools. If you’re inspired to create your own Blueprint, you can use the [Blueprint-template](https://github.com/new?template_name=Blueprint-template&template_owner=mozilla-ai) to get started.
25
 
26
  ---
27
 
 
7
  ## 🌟 **How You Can Contribute**
8
 
9
  ### 🛠️ **Enhance the Blueprint**
10
+ - Check the [Issues](https://github.com/mozilla-ai/surf-spot-finder/issues) page to see if there are feature requests you'd like to implement
11
+ - Refer to our [Contribution Guide](https://github.com/mozilla-ai/surf-spot-finder/blob/main/CONTRIBUTING.md) for more details on contributions
12
 
13
  ### 🎨 **Extensibility Ideas**
14
 
15
  This Blueprint is designed to be a foundation you can build upon. By extending its capabilities, you can open the door to new applications, improve user experience, and adapt the Blueprint to address other use cases. Here are a few ideas for how you can expand its potential:
16
 
17
 
18
+ We’d love to see how you can enhance this Blueprint! If you create improvements or extend its capabilities, consider contributing them back to the project so others in the community can benefit from your work. Check out our [Contributions Guide](https://github.com/mozilla-ai/surf-spot-finder/blob/main/CONTRIBUTING.md) to get started!
19
 
20
  ### 💡 **Share Your Ideas**
21
+ Got an idea for how this Blueprint could be improved? You can share your suggestions through [GitHub Discussions](https://github.com/mozilla-ai/surf-spot-finder/discussions).
22
 
23
  ### 🌍 **Build New Blueprints**
24
+ This project is part of a larger initiative to create a collection of reusable starter code solutions that use open-source AI tools. If you’re inspired to create your own Blueprint, you can use the [surf-spot-finder](https://github.com/new?template_name=surf-spot-finder&template_owner=mozilla-ai) to get started.
25
 
26
  ---
27
 
mkdocs.yml CHANGED
@@ -1,6 +1,7 @@
1
- site_name: Guidance Docs
2
- repo_url: https://github.com/mozilla-ai/blueprint-template
3
- repo_name: blueprint-template
 
4
 
5
  nav:
6
  - Home: index.md
 
1
+ site_name: Surf Spot Finder
2
+
3
+ repo_url: https://github.com/mozilla-ai/surf-spot-finder
4
+ repo_name: surf-spot-finder
5
 
6
  nav:
7
  - Home: index.md
pyproject.toml CHANGED
@@ -3,17 +3,26 @@ requires = ["setuptools>=48", "setuptools_scm[toml]>=6.3.1"]
3
  build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
- name = "blueprint"
7
  readme = "README.md"
8
  license = {text = "Apache-2.0"}
9
  requires-python = ">=3.10"
10
  dynamic = ["version"]
11
  dependencies = [
 
 
12
  "loguru",
13
- "streamlit",
 
 
14
  ]
15
 
16
  [project.optional-dependencies]
 
 
 
 
 
17
  docs = [
18
  "mkdocs",
19
  "mkdocs-material",
@@ -26,9 +35,9 @@ tests = [
26
  ]
27
 
28
  [project.urls]
29
- Documentation = "https://mozilla-ai.github.io/Blueprint-template/"
30
- Issues = "https://github.com/mozilla-ai/Blueprint-template/issues"
31
- Source = "https://github.com/mozilla-ai/Blueprint-template"
32
 
33
  [tool.setuptools.packages.find]
34
  exclude = ["tests", "tests.*"]
@@ -36,3 +45,6 @@ where = ["src"]
36
  namespaces = false
37
 
38
  [tool.setuptools_scm]
 
 
 
 
3
  build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
+ name = "surf-spot-finder"
7
  readme = "README.md"
8
  license = {text = "Apache-2.0"}
9
  requires-python = ">=3.10"
10
  dynamic = ["version"]
11
  dependencies = [
12
+ "arize-phoenix>=8.12.1",
13
+ "fire",
14
  "loguru",
15
+ "mcp>=1.3.0",
16
+ "pydantic",
17
+ "smolagents[litellm,mcp,telemetry]>=1.10.0",
18
  ]
19
 
20
  [project.optional-dependencies]
21
+ demo = [
22
+ "gradio",
23
+ "spaces"
24
+ ]
25
+
26
  docs = [
27
  "mkdocs",
28
  "mkdocs-material",
 
35
  ]
36
 
37
  [project.urls]
38
+ Documentation = "https://mozilla-ai.github.io/surf-spot-finder/"
39
+ Issues = "https://github.com/mozilla-ai/surf-spot-finder/issues"
40
+ Source = "https://github.com/mozilla-ai/surf-spot-finder"
41
 
42
  [tool.setuptools.packages.find]
43
  exclude = ["tests", "tests.*"]
 
45
  namespaces = false
46
 
47
  [tool.setuptools_scm]
48
+
49
+ [project.scripts]
50
+ find-surf-spot = "surf_spot_finder.cli:main"
src/blueprint/hello.py DELETED
@@ -1,8 +0,0 @@
1
- def hello() -> str:
2
- """
3
- Greets the world
4
-
5
- Returns:
6
- str: "Hello, world!"
7
- """
8
- return "Hello, world!"
 
 
 
 
 
 
 
 
 
src/{blueprint → surf_spot_finder}/__init__.py RENAMED
File without changes
src/surf_spot_finder/agents/__init__.py ADDED
File without changes
src/surf_spot_finder/agents/smolagents.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Optional, TYPE_CHECKING
3
+
4
+ from loguru import logger
5
+
6
+ if TYPE_CHECKING:
7
+ from smolagents import CodeAgent
8
+
9
+
10
+ @logger.catch(reraise=True)
11
+ def load_smolagent(model_id: str, api_key_var: Optional[str]) -> "CodeAgent":
12
+ """ """
13
+ from smolagents import (
14
+ CodeAgent,
15
+ ToolCollection,
16
+ DuckDuckGoSearchTool,
17
+ VisitWebpageTool,
18
+ LiteLLMModel,
19
+ )
20
+ from mcp import StdioServerParameters
21
+
22
+ model = LiteLLMModel(
23
+ model_id=model_id,
24
+ api_key_var=os.environ[api_key_var] if api_key_var else None,
25
+ )
26
+
27
+ if "GOOGLE_MAPS_API_KEY" in os.environ:
28
+ # We could easily use any of the MCPs at https://github.com/modelcontextprotocol/servers
29
+ # or at https://glama.ai/mcp/servers
30
+ # or at https://smithery.ai/
31
+ # https://github.com/modelcontextprotocol/servers/tree/main/src/google-maps
32
+ server_parameters = StdioServerParameters(
33
+ command="npx",
34
+ args=["@modelcontextprotocol/server-google-maps"],
35
+ env={**os.environ},
36
+ )
37
+ # https://huggingface.co/docs/smolagents/v1.10.0/en/reference/tools#smolagents.ToolCollection.from_mcp
38
+ with ToolCollection.from_mcp(server_parameters) as tool_collection:
39
+ agent = CodeAgent(
40
+ tools=[
41
+ *tool_collection.tools,
42
+ DuckDuckGoSearchTool(),
43
+ VisitWebpageTool(),
44
+ ],
45
+ model=model,
46
+ add_base_tools=True,
47
+ additional_authorized_imports=["json"],
48
+ )
49
+ else:
50
+ logger.debug(
51
+ "GOOGLE_MAPS_api_key_var not set, running without Google Maps tool"
52
+ )
53
+ agent = CodeAgent(
54
+ tools=[DuckDuckGoSearchTool(), VisitWebpageTool()],
55
+ model=model,
56
+ add_base_tools=True,
57
+ additional_authorized_imports=["json"],
58
+ )
59
+
60
+ return agent
src/surf_spot_finder/cli.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ from fire import Fire
4
+ from loguru import logger
5
+
6
+ from surf_spot_finder.config import (
7
+ Config,
8
+ DEFAULT_PROMPT,
9
+ )
10
+ from surf_spot_finder.agents.smolagents import load_smolagent
11
+ from surf_spot_finder.tracing import setup_tracing
12
+
13
+
14
+ @logger.catch(reraise=True)
15
+ def find_surf_spot(
16
+ location: str,
17
+ date: str,
18
+ max_driving_hours: int,
19
+ model_id: str,
20
+ api_key_var: Optional[str] = None,
21
+ prompt: str = DEFAULT_PROMPT,
22
+ json_tracer: bool = True,
23
+ ):
24
+ logger.info("Loading config")
25
+ config = Config(
26
+ location=location,
27
+ date=date,
28
+ max_driving_hours=max_driving_hours,
29
+ model_id=model_id,
30
+ api_key_var=api_key_var,
31
+ prompt=prompt,
32
+ json_tracer=json_tracer,
33
+ )
34
+
35
+ logger.info("Loading agent")
36
+ agent = load_smolagent(config.model_id, config.api_key_var)
37
+
38
+ logger.info("Setting up tracing")
39
+ setup_tracing(project_name="find-surf-spot", json_tracer=config.json_tracer)
40
+
41
+ logger.info("Running agent")
42
+ agent.run(
43
+ config.prompt.format(
44
+ LOCATION=config.location,
45
+ MAX_DRIVING_HOURS=config.max_driving_hours,
46
+ DATE=config.date,
47
+ )
48
+ )
49
+
50
+
51
+ def main():
52
+ Fire(find_surf_spot)
src/surf_spot_finder/config.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated, Optional
2
+ from pydantic import AfterValidator, BaseModel, FutureDatetime, PositiveInt
3
+
4
+
5
+ DEFAULT_PROMPT = (
6
+ "What will be the best surf spot around {LOCATION}"
7
+ ", in a radio of {MAX_DRIVING_HOURS} hours driving"
8
+ ", at {DATE}?"
9
+ )
10
+
11
+
12
+ def validate_prompt(value):
13
+ for placeholder in ("{LOCATION}", "{MAX_DRIVING_HOURS}"):
14
+ if placeholder not in value:
15
+ raise ValueError(f"prompt must contain {placeholder}")
16
+ return value
17
+
18
+
19
+ class Config(BaseModel):
20
+ prompt: str = Annotated[str, AfterValidator(validate_prompt)]
21
+ location: str
22
+ max_driving_hours: PositiveInt
23
+ date: FutureDatetime
24
+ model_id: str
25
+ api_key_var: Optional[str] = None
26
+ json_tracer: bool = True
src/surf_spot_finder/tracing.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ import os
3
+
4
+ from opentelemetry import trace
5
+ from openinference.instrumentation.smolagents import SmolagentsInstrumentor
6
+ from opentelemetry.sdk.trace import TracerProvider
7
+ from opentelemetry.sdk.trace.export import SimpleSpanProcessor
8
+ from opentelemetry.sdk.trace.export import SpanExporter
9
+ from phoenix.otel import register
10
+
11
+
12
+ class JsonFileSpanExporter(SpanExporter):
13
+ def __init__(self, file_name: str):
14
+ self.file_name = file_name
15
+
16
+ def export(self, spans) -> None:
17
+ with open(self.file_name, "a") as f:
18
+ for span in spans:
19
+ f.write(
20
+ span.to_json() + "\n"
21
+ ) # Ensure to_json() method is properly implemented
22
+
23
+ def shutdown(self):
24
+ pass
25
+
26
+
27
+ def setup_tracing(project_name: str, json_tracer: bool = True) -> TracerProvider:
28
+ """
29
+ Set up tracing configuration based on the selected mode.
30
+
31
+ Args:
32
+ project_name: Name of the project for tracing
33
+ json_tracer: Whether to use the custom JSON file exporter (True) or Phoenix (False)
34
+
35
+ Returns:
36
+ TracerProvider: The configured tracer provider
37
+ """
38
+ if json_tracer:
39
+ local_folder: str = "telemetry_output"
40
+ if not os.path.exists(local_folder):
41
+ os.makedirs(local_folder)
42
+ timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
43
+
44
+ tracer_provider = TracerProvider()
45
+ trace.set_tracer_provider(tracer_provider)
46
+
47
+ json_file_exporter = JsonFileSpanExporter(
48
+ file_name=f"{local_folder}/{project_name}-{timestamp}.json"
49
+ )
50
+ span_processor = SimpleSpanProcessor(json_file_exporter)
51
+ tracer_provider.add_span_processor(span_processor)
52
+ else:
53
+ tracer_provider = register(project_name=project_name)
54
+
55
+ SmolagentsInstrumentor().instrument(tracer_provider=tracer_provider)
56
+
57
+ return tracer_provider
tests/unit/agents/test_load_smolagents.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from surf_spot_finder.agents.smolagents import load_smolagent
2
+
3
+
4
+ def test_google_maps_tool(monkeypatch):
5
+ monkeypatch.setenv("GEMINI_API_KEY", "FOO")
6
+
7
+ no_google_maps_agent = load_smolagent("gemini/gemini-2.0-flash", "GEMINI_API_KEY")
8
+ assert sorted(list(no_google_maps_agent.tools.keys())) == [
9
+ "final_answer",
10
+ "visit_webpage",
11
+ "web_search",
12
+ ]
13
+
14
+ monkeypatch.setenv("GOOGLE_MAPS_API_KEY", "BAR")
15
+ google_maps_agent = load_smolagent("gemini/gemini-2.0-flash", "GEMINI_API_KEY")
16
+ assert sorted(list(google_maps_agent.tools.keys())) == [
17
+ "final_answer",
18
+ "maps_directions",
19
+ "maps_distance_matrix",
20
+ "maps_elevation",
21
+ "maps_geocode",
22
+ "maps_place_details",
23
+ "maps_reverse_geocode",
24
+ "maps_search_places",
25
+ "visit_webpage",
26
+ "web_search",
27
+ ]
tests/unit/test_hello.py DELETED
@@ -1,5 +0,0 @@
1
- from blueprint.hello import hello
2
-
3
-
4
- def test_hello():
5
- assert hello() == "Hello, world!"