Source code for camel.toolkits.wolfram_alpha_toolkit
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import os
import xml.etree.ElementTree as ET
from typing import Any, Dict, List, Union
import requests
from camel.toolkits.base import BaseToolkit
from camel.toolkits.function_tool import FunctionTool
from camel.utils import MCPServer, api_keys_required, dependencies_required
[docs]
@MCPServer()
class WolframAlphaToolkit(BaseToolkit):
r"""A class representing a toolkit for WolframAlpha.
Wolfram|Alpha is an answer engine developed by Wolfram Research.
It is offered as an online service that answers factual queries
by computing answers from externally sourced data.
"""
[docs]
@api_keys_required(
[
(None, "WOLFRAMALPHA_APP_ID"),
]
)
@dependencies_required("wolframalpha")
def query_wolfram_alpha(
self, query: str, is_detailed: bool = False
) -> Union[str, Dict[str, Any]]:
r"""Queries Wolfram|Alpha and returns the result.
Args:
query (str): The query to send to Wolfram Alpha.
is_detailed (bool): Whether to include additional details
including step by step information in the result.
(default: :obj:`False`)
Returns:
Union[str, Dict[str, Any]]: The result from Wolfram Alpha.
Returns a string if `is_detailed` is False, otherwise returns
a dictionary with detailed information.
"""
import wolframalpha
WOLFRAMALPHA_APP_ID = os.environ.get("WOLFRAMALPHA_APP_ID", "")
try:
client = wolframalpha.Client(WOLFRAMALPHA_APP_ID)
res = client.query(query)
except Exception as e:
return f"Wolfram Alpha wasn't able to answer it. Error: {e}"
pased_result = self._parse_wolfram_result(res)
if is_detailed:
step_info = self._get_wolframalpha_step_by_step_solution(
WOLFRAMALPHA_APP_ID, query
)
pased_result["steps"] = step_info
return pased_result
return pased_result["final_answer"]
[docs]
@api_keys_required(
[
(None, "WOLFRAMALPHA_APP_ID"),
]
)
def query_wolfram_llm(self, query: str) -> str:
r"""Sends a query to the Wolfram|Alpha API optimized for language
model usage.
Args:
query (str): The query to send to Wolfram Alpha LLM.
Returns:
str: The result from Wolfram Alpha as a string.
"""
WOLFRAMALPHA_APP_ID = os.environ.get("WOLFRAMALPHA_APP_ID", "")
try:
url = "https://www.wolframalpha.com/api/v1/llm-api"
params = {"input": query, "appid": WOLFRAMALPHA_APP_ID}
response = requests.get(url, params=params)
response.raise_for_status()
return response.text
except Exception as e:
return f"Wolfram Alpha wasn't able to answer it. Error: {e}"
def _parse_wolfram_result(self, result) -> Dict[str, Any]:
r"""Parses a Wolfram Alpha API result into a structured dictionary
format.
Args:
result: The API result returned from a Wolfram Alpha
query, structured with multiple pods, each containing specific
information related to the query.
Returns:
Dict[str, Any]: A structured dictionary with the original query
and the final answer.
"""
# Extract the original query
query = result.get("@inputstring", "")
# Initialize a dictionary to hold structured output
output = {"query": query, "pod_info": [], "final_answer": None}
# Loop through each pod to extract the details
for pod in result.get("pod", []):
# Handle the case where subpod might be a list
subpod_data = pod.get("subpod", {})
if isinstance(subpod_data, list):
# If it's a list, get the first item for 'plaintext' and 'img'
description, image_url = next(
(
(data["plaintext"], data["img"])
for data in subpod_data
if "plaintext" in data and "img" in data
),
("", ""),
)
else:
# Otherwise, handle it as a dictionary
description = subpod_data.get("plaintext", "")
image_url = subpod_data.get("img", {}).get("@src", "")
pod_info = {
"title": pod.get("@title", ""),
"description": description,
"image_url": image_url,
}
# For Results pod, collect all plaintext values from subpods
if pod.get("@title") == "Results":
results_text = []
if isinstance(subpod_data, list):
for subpod in subpod_data:
if subpod.get("plaintext"):
results_text.append(subpod["plaintext"])
else:
if description:
results_text.append(description)
pod_info["description"] = "\n".join(results_text)
# Add to steps list
output["pod_info"].append(pod_info)
# Get final answer
if pod.get("@primary", False):
output["final_answer"] = description
return output
def _get_wolframalpha_step_by_step_solution(
self, app_id: str, query: str
) -> dict:
r"""Retrieve a step-by-step solution from the Wolfram Alpha API for a
given query.
Args:
app_id (str): Your Wolfram Alpha API application ID.
query (str): The mathematical or computational query to solve.
Returns:
dict: The step-by-step solution response text from the Wolfram
Alpha API.
"""
# Define the base URL
url = "https://api.wolframalpha.com/v2/query"
# Set up the query parameters
params = {
"appid": app_id,
"input": query,
"podstate": ["Result__Step-by-step solution", "Show all steps"],
"format": "plaintext",
}
# Send the request
response = requests.get(url, params=params)
root = ET.fromstring(response.text)
# Extracting step-by-step steps, including 'SBSStep' and 'SBSHintStep'
steps = []
# Find all subpods within the 'Results' pod
for subpod in root.findall(".//pod[@title='Results']//subpod"):
# Check if the subpod has the desired stepbystepcontenttype
content_type = subpod.find("stepbystepcontenttype")
if content_type is not None and content_type.text in [
"SBSStep",
"SBSHintStep",
]:
plaintext = subpod.find("plaintext")
if plaintext is not None and plaintext.text:
step_text = plaintext.text.strip()
cleaned_step = step_text.replace(
"Hint: |", ""
).strip() # Remove 'Hint: |' if present
steps.append(cleaned_step)
# Structuring the steps into a dictionary
structured_steps = {}
for i, step in enumerate(steps, start=1):
structured_steps[f"step{i}"] = step
return structured_steps
[docs]
def get_tools(self) -> List[FunctionTool]:
r"""Returns a list of FunctionTool objects representing the
functions in the toolkit.
Returns:
List[FunctionTool]: A list of FunctionTool objects
representing the functions in the toolkit.
"""
return [
FunctionTool(self.query_wolfram_alpha),
FunctionTool(self.query_wolfram_llm),
]