Spaces:
Sleeping
Sleeping
import os | |
import re | |
import asyncio | |
from tavily import AsyncTavilyClient | |
from llama_index.core.tools import FunctionTool | |
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI | |
from llama_index.tools.duckduckgo import DuckDuckGoSearchToolSpec | |
from llama_index.tools.wikipedia import WikipediaToolSpec | |
from langfuse.llama_index import LlamaIndexInstrumentor | |
from llama_index.llms.ollama import Ollama | |
from llama_index.llms.google_genai import GoogleGenAI | |
from llama_index.core.agent.workflow import FunctionAgent, AgentWorkflow | |
from llama_index.core.agent.workflow import ( | |
AgentOutput, | |
ToolCall, | |
ToolCallResult, | |
) | |
from multimodality_tools import get_image_qa_tool, get_transcription_tool, \ | |
get_excel_analysis_tool, get_excel_tool, get_csv_analysis_tool, get_csv_tool, _get_file | |
class BasicAgent: | |
def __init__(self, ollama=False, langfuse=False): | |
if not ollama: | |
llm = GoogleGenAI(model="gemini-2.0-flash", api_key=os.getenv("GEMINI_API_KEY")) | |
# llm = HuggingFaceInferenceAPI(model_name="Qwen/Qwen3-32B") #"Qwen/Qwen2.5-Coder-32B-Instruct") | |
else: | |
llm = Ollama(model="mistral:latest", request_timeout=120.0) | |
# Langfuse | |
self.langfuse = langfuse | |
if self.langfuse: | |
self.instrumentor = LlamaIndexInstrumentor() | |
self.instrumentor.start() | |
# Initialize sub-agents | |
main_agent = FunctionAgent( | |
name="MainAgent", | |
description="Can organize and delegate work to different agents and can compile a final answer to a question from other agents' outputs.", | |
system_prompt=( | |
"You are a general AI assistant. I will ask you a question. " | |
"Report your thoughts, delegate work to other agents if necessary, and" | |
"finish your answer with the following template: " | |
"FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number " | |
"OR as few words as possible OR a comma separated list of numbers and/or " | |
"strings. 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." | |
), | |
llm=llm, | |
tools=[], | |
can_handoff_to=["WikiAgent", "WebAgent", "StatsAgent", "AudioAgent", "ImageAgent"], | |
) | |
# TODO Wikipedia tool does not return the tables from the page... | |
wiki_spec = WikipediaToolSpec() | |
wiki_search_tool = wiki_spec.to_tool_list()[1] | |
wiki_agent = FunctionAgent( | |
name="WikiAgent", | |
description="Uses wikipedia to answer a question.", | |
system_prompt=( | |
"You are a Wikipedia agent that can search Wikipedia for information and extract the relevant information to answer a question. " | |
"You only give concise answers and if you don't find an answer to the given query on Wikipedia, " | |
"you communicate this clearly. Always hand off your answer to MainAgent." | |
), | |
llm=llm, | |
tools=[wiki_search_tool], | |
can_handoff_to=["MainAgent"], | |
) | |
tool_spec = DuckDuckGoSearchToolSpec() | |
search_tool = FunctionTool.from_defaults(tool_spec.duckduckgo_full_search) | |
# In case DuckDuckGo is not good enough | |
async def search_web(query: str) -> str: | |
"""Searches the web to answer questions.""" | |
client = AsyncTavilyClient(api_key=os.getenv("TAVILY")) | |
return str(await client.search(query)) | |
web_search_agent = FunctionAgent( | |
name="WebAgent", | |
description="Uses the web to answer a question.", | |
system_prompt=( | |
"You are a Web agent that can search the Web and extract the relevant information to answer a question. " | |
"You only give concise answers and if you don't find an answer to the given query with your tool, " | |
"you communicate this clearly. Always hand off your answer to MainAgent." | |
), | |
llm=llm, | |
tools=[search_web], | |
can_handoff_to=["MainAgent"], | |
) | |
audio_agent = FunctionAgent( | |
name="AudioAgent", | |
description="Uses transcription tools to analyze audio files. This agent needs a file id and an optional question as input", | |
system_prompt=( | |
"You are an audio agent that can transcribe an audio file identified by its id and answer questions about the transcript. " | |
"You only give concise answers and if you cannot answer the given query using your tool, " | |
"you communicate this clearly. Always hand off your answer to MainAgent." | |
), | |
llm=llm, | |
tools=[get_transcription_tool()], | |
can_handoff_to=["MainAgent"], | |
) | |
image_agent = FunctionAgent( | |
name="ImageAgent", | |
description="Can respond to questions involving image understanding. This agent needs a file id and a question as an input.", | |
system_prompt=( | |
"You are an agent that can read images from a file identified by its id and answer questions about it. " | |
"Give concise answers and only include the relevant information in you response." | |
"If you cannot answer the given query using your tool, you communicate this clearly. " | |
"Always hand off your answer to MainAgent." | |
), | |
llm=llm, | |
tools=[get_image_qa_tool()], | |
can_handoff_to=["MainAgent"], | |
) | |
stats_agent = FunctionAgent( | |
name="StatsAgent", | |
description="Uses statistical tools to read and analyse excel and csv files. This agent needs a file id and an optional question as an input", | |
system_prompt=( | |
"You are an agent that can read excel and csv files and run simple statistical analysis on them. " | |
"You can use this information or the loaded file to answer questions about it. " | |
"You only give concise answers and if you cannot answer the given query using your tool, " | |
"you communicate this clearly. Always hand off your answer to MainAgent." | |
), | |
llm=llm, | |
tools=[get_csv_analysis_tool(), get_csv_tool(), | |
get_excel_analysis_tool(), get_excel_tool()], | |
can_handoff_to=["MainAgent"], | |
) | |
# Main AgentWorkflow | |
self.agent = AgentWorkflow( | |
agents=[main_agent, wiki_agent, web_search_agent, | |
audio_agent, image_agent, stats_agent], | |
root_agent=main_agent.name, | |
) | |
async def __call__(self, question: str, task_id: str = None) -> str: | |
file_str = "" | |
if file_exists(task_id): | |
file_str = f'\nIf you need to load a file, do so by providing the id "{task_id}".' | |
msg = f"{question}{file_str}" | |
# Stream events | |
handler = self.agent.run(user_msg=msg) | |
current_agent = None | |
current_tool_calls = "" | |
async for event in handler.stream_events(): | |
if ( | |
hasattr(event, "current_agent_name") | |
and event.current_agent_name != current_agent | |
): | |
current_agent = event.current_agent_name | |
print(f"\n{'='*50}") | |
print(f"🤖 Agent: {current_agent}") | |
print(f"{'='*50}\n") | |
elif isinstance(event, AgentOutput): | |
if event.response.content: | |
print("📤 Output:", event.response.content) | |
if event.tool_calls: | |
print( | |
"🛠️ Planning to use tools:", | |
[call.tool_name for call in event.tool_calls], | |
) | |
elif isinstance(event, ToolCallResult): | |
print(f"🔧 Tool Result ({event.tool_name}):") | |
print(f" Arguments: {event.tool_kwargs}") | |
print(f" Output: {event.tool_output}") | |
elif isinstance(event, ToolCall): | |
print(f"🔨 Calling Tool: {event.tool_name}") | |
print(f" With arguments: {event.tool_kwargs}") | |
# Avoid ratelimits - 15 requests per minute | |
await asyncio.sleep(4.1) | |
if self.langfuse: | |
self.instrumentor.flush() | |
try: | |
res = await handler | |
res = res.response.content | |
res = re.sub(r'^.*?FINAL ANSWER:', '', res, flags=re.DOTALL).strip() | |
return res | |
except: | |
return "Error occured. No valid agent response could be determined." | |
def file_exists(task_id: str) -> bool: | |
try: | |
file = _get_file(task_id) | |
except: | |
return False | |
del file | |
return True |