SerotoninRonin commited on
Commit
62ed5af
·
1 Parent(s): 81917a3

Attempting LlamaIndex approach

Browse files
Files changed (5) hide show
  1. .gitignore +3 -0
  2. agents.py +75 -0
  3. app.py +105 -39
  4. requirements.txt +5 -1
  5. tools.py +119 -0
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ .env
2
+ __pycache__/
3
+ files/
agents.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from llama_index.core.agent.workflow import ReActAgent, FunctionAgent
3
+ from llama_index.core import PromptTemplate
4
+ from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI
5
+ from tools import (
6
+ search_tool,
7
+ describe_image_tool,
8
+ parse_excel_tool,
9
+ access_webpage_tool,
10
+ string_functions_tool
11
+ )
12
+
13
+ thinking_agent = ReActAgent(
14
+ name="Thinking Agent",
15
+ description="An agent that can think and reason about tasks, and then handoff the task to Technician Agent for execution, or to Manager Agent for review.",
16
+ llm=HuggingFaceInferenceAPI(
17
+ model_name="Qwen/Qwen2.5-Coder-32B-Instruct",
18
+ provider="auto",
19
+ token=os.environ.get("HF_TOKEN")
20
+ ),
21
+ system_prompt="You are a thinking agent that can reason about tasks and communicate the necessary steps to complete them to Technician Agent, if necessary. If you believe the task is completed and the question is answered, you must handoff the answer to Manager Agent for final review.",
22
+ can_handoff_to=["Technician Agent", "Manager Agent"]
23
+ )
24
+
25
+ technician_agent = ReActAgent(
26
+ name="Technician Agent",
27
+ description="An agent that can perform various technical tasks such as searching the web, describing images, parsing Excel files, string operations, and accessing webpages.",
28
+ tools=[search_tool, describe_image_tool, parse_excel_tool, access_webpage_tool, string_functions_tool],
29
+ llm=HuggingFaceInferenceAPI(
30
+ model_name="Qwen/Qwen2.5-Coder-32B-Instruct",
31
+ provider="auto",
32
+ token=os.environ.get("HF_TOKEN")
33
+ ),
34
+ system_prompt="You are a helpful agent that answers questions based on the provided tools. Use the tools to gather information and provide accurate answers, and send those answers to Thinking Agent. If the task is too complex or requires further reasoning, handoff the task to Thinking Agent for analysis with the reasoning as why you cannot complete it. You must always handoff to Thinking Agent",
35
+ can_handoff_to=["Thinking Agent"]
36
+ )
37
+
38
+ manager_agent = ReActAgent(
39
+ name="Manager Agent",
40
+ description="A high-level agent that can manage tasks and coordinate between other agents.",
41
+ llm=HuggingFaceInferenceAPI(
42
+ model_name="Qwen/Qwen2.5-Coder-32B-Instruct",
43
+ provider="auto",
44
+ token=os.environ.get("HF_TOKEN")
45
+ ),
46
+ can_handoff_to=["Thinking Agent"],
47
+ # system_prompt="You are a manager agent that oversees tasks and coordinates between other agents. Do not include thoughts in your response. Just the answer. " \
48
+ # "You will be given a question and you need to respond with the correct answer provided by the other agents. " \
49
+ # "Your response should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. " \
50
+ # "For example, if the question is 'What color are the stars on the American flag?' Your response would be 'White'. " \
51
+ # "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. " \
52
+ # "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. " \
53
+ # "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. " \
54
+ # "Do not engage in conversation, just respond with the answer unless the question explicitly asks for a certain style of response. " \
55
+ # "If the results are questionable, you can send the task back to Thinking Agent for further analysis. If the answer can not be concluded, respond with 'I don't know'"
56
+ )
57
+
58
+ minimal_system_prompt = """
59
+ You are the Manager Agent. 💼
60
+ Respond with **only the final answer**, in as few words as possible.
61
+ Do **not** include any reasoning, thoughts, or tool calls.
62
+ If it's a number: use plain digits (no commas, %, etc.).
63
+ If it's a string: no articles, no abbreviations.
64
+ If it's a list: comma-separated, each element following the rules above.
65
+ If unsure, reply: I don't know.
66
+ Below is the current conversation consisting of interleaving human and assistant messages.
67
+ """
68
+
69
+ manager_prompt = PromptTemplate(template=minimal_system_prompt)
70
+
71
+ manager_agent.update_prompts({"react_header", manager_prompt.get_template()})
72
+
73
+ prompt_dict = manager_agent.get_prompts()
74
+ for k, v in prompt_dict.items():
75
+ print(f"Prompt: {k}\n\nValue: {v.template}")
app.py CHANGED
@@ -1,25 +1,67 @@
 
1
  import os
2
  import gradio as gr
3
  import requests
4
  import inspect
 
 
5
  import pandas as pd
6
-
 
 
 
7
  # (Keep Constants as is)
8
  # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
 
11
- # --- Basic Agent Definition ---
12
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
13
- class BasicAgent:
14
- def __init__(self):
15
- print("BasicAgent initialized.")
16
- def __call__(self, question: str) -> str:
17
- print(f"Agent received question (first 50 chars): {question[:50]}...")
18
- fixed_answer = "This is a default answer."
19
- print(f"Agent returning fixed answer: {fixed_answer}")
20
- return fixed_answer
21
-
22
- def run_and_submit_all( profile: gr.OAuthProfile | None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  """
24
  Fetches all questions, runs the BasicAgent on them, submits all answers,
25
  and displays the results.
@@ -27,20 +69,23 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
27
  # --- Determine HF Space Runtime URL and Repo URL ---
28
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
29
 
30
- if profile:
31
- username= f"{profile.username}"
32
- print(f"User logged in: {username}")
33
- else:
34
- print("User not logged in.")
35
- return "Please Login to Hugging Face with the button.", None
36
 
37
  api_url = DEFAULT_API_URL
38
  questions_url = f"{api_url}/questions"
39
  submit_url = f"{api_url}/submit"
40
 
41
- # 1. Instantiate Agent ( modify this part to create your agent)
42
  try:
43
- agent = BasicAgent()
 
 
 
 
44
  except Exception as e:
45
  print(f"Error instantiating agent: {e}")
46
  return f"Error initializing agent: {e}", None
@@ -73,14 +118,34 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
73
  results_log = []
74
  answers_payload = []
75
  print(f"Running agent on {len(questions_data)} questions...")
76
- for item in questions_data:
77
  task_id = item.get("task_id")
78
  question_text = item.get("question")
79
  if not task_id or question_text is None:
80
  print(f"Skipping item with missing task_id or question: {item}")
81
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  try:
83
- submitted_answer = agent(question_text)
 
84
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
85
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
86
  except Exception as e:
@@ -92,26 +157,27 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
92
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
93
 
94
  # 4. Prepare Submission
95
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
96
- status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
97
- print(status_update)
98
 
99
  # 5. Submit
100
  print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
101
  try:
102
- response = requests.post(submit_url, json=submission_data, timeout=60)
103
- response.raise_for_status()
104
- result_data = response.json()
105
- final_status = (
106
- f"Submission Successful!\n"
107
- f"User: {result_data.get('username')}\n"
108
- f"Overall Score: {result_data.get('score', 'N/A')}% "
109
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
110
- f"Message: {result_data.get('message', 'No message received.')}"
111
- )
112
- print("Submission successful.")
113
  results_df = pd.DataFrame(results_log)
114
- return final_status, results_df
 
115
  except requests.exceptions.HTTPError as e:
116
  error_detail = f"Server responded with status {e.response.status_code}."
117
  try:
@@ -158,7 +224,7 @@ with gr.Blocks() as demo:
158
  """
159
  )
160
 
161
- gr.LoginButton()
162
 
163
  run_button = gr.Button("Run Evaluation & Submit All Answers")
164
 
 
1
+ import base64
2
  import os
3
  import gradio as gr
4
  import requests
5
  import inspect
6
+ import dotenv
7
+ dotenv.load_dotenv() # Load environment variables from .env file
8
  import pandas as pd
9
+ from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI
10
+ from llama_index.core.agent.workflow import AgentWorkflow, AgentOutput, ToolCall, ToolCallResult, AgentInput, AgentStream
11
+ from agents import thinking_agent, technician_agent, manager_agent
12
+ import asyncio
13
  # (Keep Constants as is)
14
  # --- Constants ---
15
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
16
 
17
+ llm = HuggingFaceInferenceAPI(model_name="deepseek-ai/DeepSeek-R1", provider="auto", token=os.environ.get("HF_TOKEN"))
18
+
19
+
20
+ #def run_and_submit_all( profile: gr.OAuthProfile | None):
21
+
22
+ async def run_agent_query(agent: AgentWorkflow, question: str):
23
+ """
24
+ Runs the agent on a single question and returns the answer.
25
+ This function is intended to be used with Gradio for interactive querying.
26
+ """
27
+
28
+ handler = agent.run(user_msg=question)
29
+ current_agent = None
30
+ final_response = None
31
+ current_tool_calls = ""
32
+ async for event in handler.stream_events():
33
+ if (
34
+ hasattr(event, "current_agent_name")
35
+ and event.current_agent_name != current_agent
36
+ ):
37
+ current_agent = event.current_agent_name
38
+ print(f"\n{'='*50}")
39
+ print(f"🤖 Agent: {current_agent}")
40
+ print(f"{'='*50}\n")
41
+
42
+ if isinstance(event, AgentStream):
43
+ if event.delta:
44
+ print(event.delta, end="", flush=True)
45
+ elif isinstance(event, AgentInput):
46
+ print("📥 Input:", event.input)
47
+ elif isinstance(event, AgentOutput):
48
+ if event.response.content:
49
+ print("📤 Output:", event.response.content)
50
+ final_response = event.response.content
51
+ if event.tool_calls:
52
+ print(
53
+ "🛠️ Planning to use tools:",
54
+ [call.tool_name for call in event.tool_calls],
55
+ )
56
+ elif isinstance(event, ToolCallResult):
57
+ print(f"🔧 Tool Result ({event.tool_name}):")
58
+ print(f" Arguments: {event.tool_kwargs}")
59
+ print(f" Output: {event.tool_output}")
60
+ elif isinstance(event, ToolCall):
61
+ print(f"🔨 Calling Tool: {event.tool_name}")
62
+ print(f" With arguments: {event.tool_kwargs}")
63
+ return final_response if final_response else "No response from agent."
64
+ def run_and_submit_all():
65
  """
66
  Fetches all questions, runs the BasicAgent on them, submits all answers,
67
  and displays the results.
 
69
  # --- Determine HF Space Runtime URL and Repo URL ---
70
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
71
 
72
+ # if profile:
73
+ # username= f"{profile.username}"
74
+ # print(f"User logged in: {username}")
75
+ # else:
76
+ # print("User not logged in.")
77
+ # return "Please Login to Hugging Face with the button.", None
78
 
79
  api_url = DEFAULT_API_URL
80
  questions_url = f"{api_url}/questions"
81
  submit_url = f"{api_url}/submit"
82
 
 
83
  try:
84
+ agent = AgentWorkflow(
85
+ agents=[thinking_agent, technician_agent, manager_agent],
86
+ root_agent=manager_agent.name,
87
+ handoff_output_prompt="handoff_result: Passed to {to_agent}. Reason: {reason}. Please continue processing using the original user question."
88
+ )
89
  except Exception as e:
90
  print(f"Error instantiating agent: {e}")
91
  return f"Error initializing agent: {e}", None
 
118
  results_log = []
119
  answers_payload = []
120
  print(f"Running agent on {len(questions_data)} questions...")
121
+ for item in questions_data[:4]:
122
  task_id = item.get("task_id")
123
  question_text = item.get("question")
124
  if not task_id or question_text is None:
125
  print(f"Skipping item with missing task_id or question: {item}")
126
  continue
127
+ file_name = item.get("file_name")
128
+ if file_name:
129
+ try:
130
+ response = requests.get(f"{api_url}/files/{task_id}", timeout=15)
131
+ response.raise_for_status()
132
+ save_path = os.path.join("files", file_name)
133
+ os.makedirs("files", exist_ok=True)
134
+ with open(save_path, "wb") as f:
135
+ f.write(response.content)
136
+ question_text += f" (File: {save_path})"
137
+ except requests.exceptions.RequestException as e:
138
+ print(f"Error fetching file for task {task_id}: {e}")
139
+ return f"Error fetching file for task {task_id}: {e}", None
140
+ except requests.exceptions.JSONDecodeError as e:
141
+ print(f"Error decoding JSON response for file {file_name}: {e}")
142
+ return f"Error decoding JSON response for file {file_name}: {e}", None
143
+ except Exception as e:
144
+ print(f"An unexpected error occurred fetching file {file_name}: {e}")
145
+ return f"An unexpected error occurred fetching file {file_name}: {e}", None
146
  try:
147
+ print(f"Running agent on task {task_id} with question: {question_text}")
148
+ submitted_answer = asyncio.run(run_agent_query(agent, question_text))
149
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
150
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
151
  except Exception as e:
 
157
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
158
 
159
  # 4. Prepare Submission
160
+ # submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
161
+ # status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
162
+ # print(status_update)
163
 
164
  # 5. Submit
165
  print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
166
  try:
167
+ # response = requests.post(submit_url, json=submission_data, timeout=60)
168
+ # response.raise_for_status()
169
+ # result_data = response.json()
170
+ # final_status = (
171
+ # f"Submission Successful!\n"
172
+ # f"User: {result_data.get('username')}\n"
173
+ # f"Overall Score: {result_data.get('score', 'N/A')}% "
174
+ # f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
175
+ # f"Message: {result_data.get('message', 'No message received.')}"
176
+ # )
177
+ # print("Submission successful.")
178
  results_df = pd.DataFrame(results_log)
179
+ return "Nothing submitted", results_df
180
+ # return final_status, results_df
181
  except requests.exceptions.HTTPError as e:
182
  error_detail = f"Server responded with status {e.response.status_code}."
183
  try:
 
224
  """
225
  )
226
 
227
+ # gr.LoginButton()
228
 
229
  run_button = gr.Button("Run Evaluation & Submit All Answers")
230
 
requirements.txt CHANGED
@@ -1,2 +1,6 @@
1
  gradio
2
- requests
 
 
 
 
 
1
  gradio
2
+ requests
3
+ llama-index-multi-modal-llms-huggingface
4
+ llama-index
5
+ llama-index-llms-huggingface-api
6
+ llama-index-readers-whisper
tools.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from llama_index.core.tools import FunctionTool
2
+ from llama_index.tools.tavily_research import TavilyToolSpec
3
+ from llama_index.core.schema import ImageDocument
4
+ from llama_index.readers.whisper import WhisperReader
5
+
6
+ import os
7
+
8
+ tool_spec = TavilyToolSpec(
9
+ api_key=os.environ.get("TAVILY_API_KEY"),
10
+ )
11
+
12
+ search_tool = FunctionTool.from_defaults(tool_spec.search)
13
+
14
+ from llama_index.multi_modal_llms.huggingface import HuggingFaceMultiModal
15
+
16
+ def describe_image(image_path: str, prompt: str = "Describe the following image:") -> str:
17
+ """
18
+ Function to describe an image using a multi-modal LLM.
19
+ :param image_path: Path to the image file or base64 encoded image data.
20
+ :param prompt: Prompt to use for the description. Defaults to "Describe the following image:".
21
+ :return: Description of the image.
22
+ """
23
+ image = ImageDocument(image_path=image_path)
24
+ try:
25
+ llm = HuggingFaceMultiModal.from_model_name("Qwen/Qwen2-VL-2B-Instruct", use_fast=True)
26
+ return llm.complete(
27
+ prompt=f"{prompt}",
28
+ image_documents=[image]
29
+ ).text
30
+ except Exception as e:
31
+ return f"Error describing image: {e}"
32
+
33
+ describe_image_tool = FunctionTool.from_defaults(describe_image)
34
+
35
+ # Tool to parse xls/xlsx files
36
+ def parse_excel(file_path: str) -> str:
37
+ """
38
+ Function to parse an Excel file and return its content as a string.
39
+ :param file_path: Path to the Excel file (xls or xlsx).
40
+ :return: Content of the Excel file as a string.
41
+ """
42
+ import pandas as pd
43
+
44
+ df = pd.read_excel(io=file_path)
45
+
46
+ # Convert DataFrame to string
47
+ return df.to_string() if not df.empty else "The Excel file is empty."
48
+
49
+ parse_excel_tool = FunctionTool.from_defaults(parse_excel)
50
+
51
+ def access_webpage(url: str) -> str:
52
+ """
53
+ Function to access a webpage and return its content.
54
+ :param url: URL of the webpage to access.
55
+ :return: Content of the webpage.
56
+ """
57
+ import requests
58
+ try:
59
+ print(f"Accessing webpage: {url}")
60
+ response = requests.get(url)
61
+ print(f"Response status code: {response.status_code}")
62
+ response.raise_for_status() # Raise an error for bad responses
63
+ return response.text
64
+ except requests.RequestException as e:
65
+ return f"Error accessing {url}: {e}"
66
+ except Exception as e:
67
+ return f"An unexpected error occurred: {e}"
68
+
69
+ access_webpage_tool = FunctionTool.from_defaults(access_webpage)
70
+
71
+ def string_functions(input_string: str, operation: str) -> str:
72
+ """
73
+ Function to perform string operations.
74
+ :param input_string: The input string to operate on.
75
+ :param operation: The operation to perform (e.g., "uppercase", "lowercase", "reverse", "length", "count_vowels", "count_consonants", "count_words", "count_sentences").
76
+ :return: Result of the string operation.
77
+ """
78
+ if operation == "uppercase":
79
+ return input_string.upper()
80
+ elif operation == "lowercase":
81
+ return input_string.lower()
82
+ elif operation == "reverse":
83
+ return input_string[::-1]
84
+ elif operation == "length":
85
+ return str(len(input_string))
86
+ elif operation == "count_vowels":
87
+ vowels = "aeiouAEIOU"
88
+ return str(sum(1 for char in input_string if char in vowels))
89
+ elif operation == "count_consonants":
90
+ vowels = "aeiouAEIOU"
91
+ return str(sum(1 for char in input_string if char.isalpha() and char not in vowels))
92
+ elif operation == "count_words":
93
+ return str(len(input_string.split()))
94
+ elif operation == "count_sentences":
95
+ import re
96
+ sentences = re.split(r'[.!?]+', input_string)
97
+ return str(len([s for s in sentences if s.strip()]))
98
+ else:
99
+ return "Invalid operation. Supported operations: uppercase, lowercase, reverse."
100
+
101
+ string_functions_tool = FunctionTool.from_defaults(string_functions)
102
+
103
+ def transcribe_audio(audio_path: str) -> str:
104
+ """
105
+ Function to transcribe audio using a multi-modal LLM.
106
+ :param audio_path: Path to the audio file.
107
+ :return: Transcription of the audio.
108
+ """
109
+ try:
110
+ reader = WhisperReader(api_key=os.environ.get("OPENAI_API_KEY"))
111
+ documents = reader.load_data(file=audio_path)
112
+ if not documents:
113
+ return "No audio content found."
114
+ # Assuming the first document contains the transcription
115
+ return documents[0].text if documents else "No transcription available."
116
+ except Exception as e:
117
+ return f"Error transcribing audio: {e}"
118
+
119
+ transcribe_audio_tool = FunctionTool.from_defaults(transcribe_audio)