Spaces:
Sleeping
Sleeping
import base64 | |
import logging | |
import os | |
from io import BytesIO | |
from typing import Any | |
from smolagents import ( | |
CodeAgent, | |
DuckDuckGoSearchTool, | |
OpenAIServerModel, | |
VisitWebpageTool, | |
WikipediaSearchTool, | |
tool, | |
) | |
system_prompt = """You are an AI Agent that is tasked to answer questions in a concise and accurate manner. | |
I will ask you a question and provide you with additional context if available. | |
Context can be in the form of Data(data), Code(code), Audio(audio), or Images(image_url). | |
Context is provided by specifying the content type followed by the content itself. | |
For example: code: print("Hello World") or Data: [1, 2, 3, 4, 5] or audio: [base64 encoded audio] or image_url: [base64 encoded image]. | |
YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. | |
DO NOT use formatting such as bold, italics, or code blocks in your final answer. | |
DO NOT use sources, references, or abbreviations in your final answer. | |
If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. | |
If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. | |
If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string. | |
If you are asked for a specific number format, follow the instructions carefully. | |
If you are asked for a number only answer with the number itself, without any additional text or formatting. | |
If you are asked for a string only answer with the string itself, without any additional text or formatting. | |
If you are asked for a list only answer with the list itself, without any additional text or formatting. | |
Think step by step. Report your thoughts. | |
Finish your answer with the following template: | |
FINAL ANSWER: [YOUR FINAL ANSWER]. | |
For example, if the question is "What is the capital of France?", you should answer: | |
FINAL ANSWER: Paris | |
If the question is "What is 2 + 2?", you should answer: | |
FINAL ANSWER: 4 | |
If the question is "What is 1 divided by 2, answer with 2 digits after the decimal point?", you should answer: | |
FINAL ANSWER: 0.50 | |
If the quesion is "What is 10 * 10 with four digits after the decimal point?", you should answer: | |
FINAL ANSWER: 100.0000 | |
""" | |
# def is_correct_format(answer: str, _) -> bool: | |
# """Check if the answer contains a final answer in the correct format. | |
# Args: | |
# answer: The answer to check. | |
# Returns: | |
# True if the answer contains a final answer, False otherwise. | |
# This ensures the final output is in the correct format. | |
# """ | |
# return ( | |
# "ANSWER:" in answer | |
# or "FINAL ANSWER:" in answer | |
# or "Answer:" in answer | |
# or "Final Answer:" in answer | |
# or "answer:" in answer | |
# or "final answer:" in answer | |
# or "answer:" in answer.lower() | |
# or "final answer:" in answer.lower() | |
# ) | |
def wikipedia_suggested_page(query: str) -> str: | |
"""Search Wikipedia for suggested pages based on the query. | |
Args: | |
query: The search query. The query should be coarse and not provide too many details. | |
E.g. "Python programming" or "Artificial Intelligence". | |
Returns: | |
A list of suggested page titles. Pages are \n separated. | |
""" | |
from wikipedia import suggest | |
try: | |
return suggest(query) | |
except Exception as e: | |
logging.error(f"Error fetching Wikipedia suggestions for '{query}': {e}") | |
return f"Error fetching suggestions: {e}" | |
def wikipedia_page(title: str) -> str: | |
"""Search Wikipedia for a page based on the title. | |
Args: | |
title: The title of the Wikipedia page to search for. | |
Returns: | |
The content of the Wikipedia page. | |
""" | |
from wikipedia import page | |
try: | |
return page(title, auto_suggest=True).content | |
except Exception as e: | |
logging.error(f"Error fetching Wikipedia page for '{title}': {e}") | |
return f"Error fetching page: {e}" | |
class BasicAgent: | |
def __init__(self): | |
model = OpenAIServerModel( | |
model_id="gpt-4o-mini", | |
api_key=os.getenv("OPENAI_API_KEY"), | |
temperature=0.0, | |
) | |
search = DuckDuckGoSearchTool(max_results=5) | |
# speech_to_text = SpeechToTextTool() | |
visitor = VisitWebpageTool(max_output_length=4000) | |
wiki_search = WikipediaSearchTool() | |
self.agent = CodeAgent( | |
max_steps=10, | |
verbosity_level=0, | |
tools=[ | |
search, | |
# speech_to_text, | |
visitor, | |
wiki_search, | |
wikipedia_suggested_page, | |
wikipedia_page, | |
], | |
model=model, | |
instructions=system_prompt, | |
additional_authorized_imports=["pandas", "numpy"], | |
use_structured_outputs_internally=True, | |
add_base_tools=True, | |
) | |
logging.info( | |
f"System prompt set for BasicAgent: {self.agent.memory.system_prompt}" | |
) | |
def __call__(self, question: str, content, content_type) -> Any: | |
match content_type: | |
case "xlsx": | |
additional_args = {"data": content} | |
case "py": | |
additional_args = {"code": content} | |
case "audio": | |
additional_args = {"audio": content} | |
case "png": | |
buffer = BytesIO() | |
content.save(buffer, format="PNG") | |
buffer.seek(0) | |
image_content = ( | |
"data:image/png;base64," | |
+ base64.b64encode(buffer.getvalue()).decode("utf-8") | |
) | |
additional_args = {"image_url": image_content} | |
case _: | |
additional_args = None | |
response = self.agent.run( | |
question, | |
additional_args=additional_args, | |
images=[content] if content_type == "png" else None, | |
reset=True, | |
) | |
return response | |
def formatting(answer: str) -> str: | |
"""Extract the final answer from the response.""" | |
if "FINAL ANSWER:" in answer: | |
answer = answer.split("FINAL ANSWER:")[-1].strip() | |
if "ANSWER:" in answer: | |
answer = answer.split("ANSWER:")[-1].strip() | |
if "Answer:" in answer: | |
answer = answer.split("Answer:")[-1].strip() | |
if "Final Answer:" in answer: | |
answer = answer.split("Final Answer:")[-1].strip() | |
if "answer:" in answer.lower(): | |
answer = answer.split("answer:")[-1].strip() | |
if "final answer:" in answer.lower(): | |
answer = answer.split("final answer:")[-1].strip() | |
if "answer is:" in answer.lower(): | |
answer = answer.split("answer is:")[-1].strip() | |
if "is:" in answer.lower(): | |
answer = answer.split("is:")[-1].strip() | |
if "**" in answer: | |
answer = answer.split("**")[-1].strip().replace("**", "") | |
if "```" in answer: | |
answer = answer.split("```")[-1].strip().replace("```", "") | |
if "```python" in answer: | |
answer = answer.split("```python")[-1].strip().replace("```", "") | |
if "```json" in answer: | |
answer = answer.split("```json")[-1].strip().replace("```", "") | |
if "```yaml" in answer: | |
answer = answer.split("```yaml")[-1].strip().replace("```", "") | |
if "```txt" in answer: | |
answer = answer.split("```txt")[-1].strip().replace("```", "") | |
answer = answer.capitalize() | |
answer = answer.replace('"', '').strip() | |
answer = answer.replace("'", "").strip() | |
answer = answer.replace("[", "").replace("]", "").strip() | |
return answer.strip() # Fallback to return the whole answer if no specific format found |