Jean-Baptiste Pin commited on
Commit
7ce8f44
·
1 Parent(s): 32d41ae
Files changed (4) hide show
  1. README.md +107 -1
  2. app.py +65 -13
  3. prompts.yaml +9 -25
  4. requirements.txt +20 -1
README.md CHANGED
@@ -12,4 +12,110 @@ hf_oauth: true
12
  hf_oauth_expiration_minutes: 480
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  hf_oauth_expiration_minutes: 480
13
  ---
14
 
15
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
16
+
17
+ ---
18
+ QWEN 3 jinja
19
+
20
+ {%- if tools %}
21
+ {{- '<|im_start|>system\n' }}
22
+ {%- if messages[0].role == 'system' %}
23
+ {{- messages[0].content + '\n\n' }}
24
+ {%- endif %}
25
+ {{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
26
+ {%- for tool in tools %}
27
+ {{- "\n" }}
28
+ {{- tool | tojson }}
29
+ {%- endfor %}
30
+ {{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
31
+ {%- else %}
32
+ {%- if messages[0].role == 'system' %}
33
+ {{- '<|im_start|>system\n' + messages[0].content + '<|im_end|>\n' }}
34
+ {%- endif %}
35
+ {%- endif %}
36
+
37
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
38
+
39
+ {#— scan backward without using reverse filter —#}
40
+ {%- for i in range(messages|length - 1, -1, -1) %}
41
+ {%- set message = messages[i] %}
42
+ {%- set index = i %}
43
+ {%- set tool_start = "<tool_response>" %}
44
+ {%- set tool_start_length = tool_start|length %}
45
+ {%- set start_of_message = message.content[:tool_start_length] %}
46
+ {%- set tool_end = "</tool_response>" %}
47
+ {%- set tool_end_length = tool_end|length %}
48
+ {%- set start_pos = (message.content|length) - tool_end_length %}
49
+ {%- if start_pos < 0 %}
50
+ {%- set start_pos = 0 %}
51
+ {%- endif %}
52
+ {%- set end_of_message = message.content[start_pos:] %}
53
+ {%- if ns.multi_step_tool and message.role == "user" and not (start_of_message == tool_start and end_of_message == tool_end) %}
54
+ {%- set ns.multi_step_tool = false %}
55
+ {%- set ns.last_query_index = index %}
56
+ {%- endif %}
57
+ {%- endfor %}
58
+
59
+ {%- for message in messages %}
60
+ {%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
61
+ {{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
62
+ {%- elif message.role == "assistant" %}
63
+ {%- set content = message.content %}
64
+ {%- set reasoning_content = '' %}
65
+ {%- if message.reasoning_content is defined and message.reasoning_content is not none %}
66
+ {%- set reasoning_content = message.reasoning_content %}
67
+ {%- else %}
68
+ {%- if '</think>' in message.content %}
69
+ {%- set content = (message.content.split('</think>')|last).lstrip('\n') %}
70
+ {%- set reasoning_content = (message.content.split('</think>')|first).rstrip('\n') %}
71
+ {%- set reasoning_content = (reasoning_content.split('<think>')|last).lstrip('\n') %}
72
+ {%- endif %}
73
+ {%- endif %}
74
+ {%- if loop.index0 > ns.last_query_index %}
75
+ {%- if loop.last or (not loop.last and reasoning_content) %}
76
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content.strip('\n') + '\n</think>\n\n' + content.lstrip('\n') }}
77
+ {%- else %}
78
+ {{- '<|im_start|>' + message.role + '\n' + content }}
79
+ {%- endif %}
80
+ {%- else %}
81
+ {{- '<|im_start|>' + message.role + '\n' + content }}
82
+ {%- endif %}
83
+ {%- if message.tool_calls %}
84
+ {%- for tool_call in message.tool_calls %}
85
+ {%- if (loop.first and content) or (not loop.first) %}
86
+ {{- '\n' }}
87
+ {%- endif %}
88
+ {%- if tool_call.function %}
89
+ {%- set tool_call = tool_call.function %}
90
+ {%- endif %}
91
+ {{- '<tool_call>\n{"name": "' }}
92
+ {{- tool_call.name }}
93
+ {{- '", "arguments": ' }}
94
+ {%- if tool_call.arguments is string %}
95
+ {{- tool_call.arguments }}
96
+ {%- else %}
97
+ {{- tool_call.arguments | tojson }}
98
+ {%- endif %}
99
+ {{- '}\n</tool_call>' }}
100
+ {%- endfor %}
101
+ {%- endif %}
102
+ {{- '<|im_end|>\n' }}
103
+ {%- elif message.role == "tool" %}
104
+ {%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
105
+ {{- '<|im_start|>user' }}
106
+ {%- endif %}
107
+ {{- '\n<tool_response>\n' }}
108
+ {{- message.content }}
109
+ {{- '\n</tool_response>' }}
110
+ {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
111
+ {{- '<|im_end|>\n' }}
112
+ {%- endif %}
113
+ {%- endif %}
114
+ {%- endfor %}
115
+
116
+ {%- if add_generation_prompt %}
117
+ {{- '<|im_start|>assistant\n' }}
118
+ {%- if enable_thinking is defined and enable_thinking is false %}
119
+ {{- '<think>\n\n</think>\n\n' }}
120
+ {%- endif %}
121
+ {%- endif %}
app.py CHANGED
@@ -11,16 +11,61 @@ from smolagents import (
11
  FinalAnswerTool,
12
  VisitWebpageTool,
13
  LiteLLMModel,
 
14
  tool
15
  )
16
  from markdownify import markdownify
17
  from litellm import completion
18
  from qwen_vl_utils import process_vision_info
 
 
 
 
 
19
 
20
  # (Keep Constants as is)
21
  # --- Constants ---
22
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  @tool
25
  def analyze_video(url: str, question: str) -> str:
26
  """Analyze a video and answer the question.
@@ -56,7 +101,7 @@ def analyze_video(url: str, question: str) -> str:
56
  # }
57
 
58
  response = completion(
59
- api_base="http://192.168.1.82:1234/v1",
60
  model="lm_studio/qwen2.5-vl-7b-instruct",
61
  messages=messages,
62
  )
@@ -68,13 +113,13 @@ class BasicAgent:
68
  def __init__(self):
69
  with open("prompts.yaml", 'r') as stream:
70
  prompt_templates = yaml.safe_load(stream)
71
- model = LiteLLMModel(model_id="lm_studio/qwen2.5-coder-14b-instruct", api_base="http://192.168.1.82:1234/v1")
72
  self.agent = CodeAgent(
73
  model=model,
74
- additional_authorized_imports=["time", "pandas", "numpy"],
75
- tools=[DuckDuckGoSearchTool(),VisitWebpageTool(),FinalAnswerTool()], ## add your tools here (don't remove final answer)
76
- max_steps=6,
77
- verbosity_level=2,
78
  grammar=None,
79
  planning_interval=None,
80
  name=None,
@@ -82,8 +127,15 @@ class BasicAgent:
82
  prompt_templates=prompt_templates
83
  )
84
  print("BasicAgent initialized.")
85
- def __call__(self, question: str):
86
  print(f"Agent received question (first 100 chars): {question[:100]}...")
 
 
 
 
 
 
 
87
  fixed_answer = self.agent.run(question, False, True);
88
  print(f"Agent returning fixed answer: {fixed_answer}")
89
  return fixed_answer
@@ -93,7 +145,6 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
93
  Fetches all questions, runs the BasicAgent on them, submits all answers,
94
  and displays the results.
95
  """
96
-
97
  # --- Determine HF Space Runtime URL and Repo URL ---
98
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
99
 
@@ -105,8 +156,8 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
105
  return "Please Login to Hugging Face with the button.", None
106
 
107
  api_url = DEFAULT_API_URL
108
- # questions_url = f"{api_url}/questions"
109
- questions_url = f"{api_url}/random-question"
110
  submit_url = f"{api_url}/submit"
111
 
112
  # 1. Instantiate Agent ( modify this part to create your agent)
@@ -143,15 +194,16 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
143
  # 3. Run your Agent
144
  results_log = []
145
  answers_payload = []
146
- print(f"Running agent on {len([questions_data])} questions...")
147
- for item in [questions_data]:
148
  task_id = item.get("task_id")
149
  question_text = item.get("question")
 
150
  if not task_id or question_text is None:
151
  print(f"Skipping item with missing task_id or question: {item}")
152
  continue
153
  try:
154
- submitted_answer = agent(question_text)
155
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
156
  print(f"Question: {item}, Task ID: {task_id}, Submitted Answer: {submitted_answer}")
157
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
 
11
  FinalAnswerTool,
12
  VisitWebpageTool,
13
  LiteLLMModel,
14
+ WikipediaSearchTool,
15
  tool
16
  )
17
  from markdownify import markdownify
18
  from litellm import completion
19
  from qwen_vl_utils import process_vision_info
20
+ from urllib.parse import urlparse
21
+ from typing import List, Optional, Dict, Any
22
+ import tempfile
23
+ from io import BytesIO
24
+ from PIL import Image
25
 
26
  # (Keep Constants as is)
27
  # --- Constants ---
28
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
29
 
30
+ @tool
31
+ def download_file_from_url(url: str, filename: Optional[str] = None) -> str:
32
+ """
33
+ Download a file from a URL and save it to a temporary location.
34
+
35
+ Args:
36
+ url: The URL to download from
37
+ filename: Optional filename, will generate one based on URL if not provided
38
+
39
+ Returns:
40
+ Path to the downloaded file
41
+ """
42
+ try:
43
+ # Parse URL to get filename if not provided
44
+ if not filename:
45
+ path = urlparse(url).path
46
+ filename = os.path.basename(path)
47
+ if not filename:
48
+ # Generate a random name if we couldn't extract one
49
+ import uuid
50
+ filename = f"downloaded_{uuid.uuid4().hex[:8]}"
51
+
52
+ # Create temporary file
53
+ temp_dir = tempfile.gettempdir()
54
+ filepath = os.path.join(temp_dir, filename)
55
+
56
+ # Download the file
57
+ response = requests.get(url, stream=True)
58
+ response.raise_for_status()
59
+
60
+ # Save the file
61
+ with open(filepath, 'wb') as f:
62
+ for chunk in response.iter_content(chunk_size=8192):
63
+ f.write(chunk)
64
+
65
+ return f"File downloaded to {filepath}. You can now process this file."
66
+ except Exception as e:
67
+ return f"Error downloading file: {str(e)}"
68
+
69
  @tool
70
  def analyze_video(url: str, question: str) -> str:
71
  """Analyze a video and answer the question.
 
101
  # }
102
 
103
  response = completion(
104
+ api_base="http://192.168.1.183:1234/v1",
105
  model="lm_studio/qwen2.5-vl-7b-instruct",
106
  messages=messages,
107
  )
 
113
  def __init__(self):
114
  with open("prompts.yaml", 'r') as stream:
115
  prompt_templates = yaml.safe_load(stream)
116
+ model = LiteLLMModel(model_id="lm_studio/qwen2.5-coder-14b-instruct", api_base="http://192.168.1.183:1234/v1")
117
  self.agent = CodeAgent(
118
  model=model,
119
+ additional_authorized_imports=["time", "pandas", "numpy", "re", "openpyxl"],
120
+ tools=[DuckDuckGoSearchTool(),VisitWebpageTool(),WikipediaSearchTool(), download_file_from_url, FinalAnswerTool()], ## add your tools here (don't remove final answer)
121
+ max_steps=16,
122
+ verbosity_level=1,
123
  grammar=None,
124
  planning_interval=None,
125
  name=None,
 
127
  prompt_templates=prompt_templates
128
  )
129
  print("BasicAgent initialized.")
130
+ def __call__(self, question: str, file: str, taskId: str):
131
  print(f"Agent received question (first 100 chars): {question[:100]}...")
132
+ if file :
133
+ if file.endswith('png') :
134
+ images = [Image.open(BytesIO(requests.get(f"{DEFAULT_API_URL}/files/{taskId}", timeout=10).content)).convert("RGB")]
135
+ fixed_answer_pict = self.agent.run(question, False, True, images);
136
+ return fixed_answer_pict;
137
+ else:
138
+ question = question + f" You can donwload the file associated at {DEFAULT_API_URL}/files/{taskId}"
139
  fixed_answer = self.agent.run(question, False, True);
140
  print(f"Agent returning fixed answer: {fixed_answer}")
141
  return fixed_answer
 
145
  Fetches all questions, runs the BasicAgent on them, submits all answers,
146
  and displays the results.
147
  """
 
148
  # --- Determine HF Space Runtime URL and Repo URL ---
149
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
150
 
 
156
  return "Please Login to Hugging Face with the button.", None
157
 
158
  api_url = DEFAULT_API_URL
159
+ questions_url = f"{api_url}/questions"
160
+ # questions_url = f"{api_url}/random-question"
161
  submit_url = f"{api_url}/submit"
162
 
163
  # 1. Instantiate Agent ( modify this part to create your agent)
 
194
  # 3. Run your Agent
195
  results_log = []
196
  answers_payload = []
197
+ print(f"Running agent on {len(questions_data)} questions...")
198
+ for item in questions_data:
199
  task_id = item.get("task_id")
200
  question_text = item.get("question")
201
+ question_file = item.get("file_name")
202
  if not task_id or question_text is None:
203
  print(f"Skipping item with missing task_id or question: {item}")
204
  continue
205
  try:
206
+ submitted_answer = agent(question_text, question_file, task_id)
207
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
208
  print(f"Question: {item}, Task ID: {task_id}, Submitted Answer: {submitted_answer}")
209
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
prompts.yaml CHANGED
@@ -9,28 +9,7 @@ system_prompt: |-
9
  These print outputs will then appear in the 'Observation:' field, which will be available as input for the next step.
10
  In the end you have to return a final answer using the `final_answer` tool.
11
 
12
- You may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags.
13
-
14
-
15
  Here are a few examples using notional tools:
16
- ---
17
- Task: "Generate an image of the oldest person in this document."
18
-
19
- Thought: I will proceed step by step and use the following tools: `document_qa` to find the oldest person in the document, then `image_generator` to generate an image according to the answer.
20
- Code:
21
- ```py
22
- answer = document_qa(document=document, question="Who is the oldest person mentioned?")
23
- print(answer)
24
- ```<end_code>
25
- Observation: "The oldest person in the document is John Doe, a 55 year old lumberjack living in Newfoundland."
26
-
27
- Thought: I will now generate an image showcasing the oldest person.
28
- Code:
29
- ```py
30
- image = image_generator("A portrait of John Doe, a 55-year-old man living in Canada.")
31
- final_answer(image)
32
- ```<end_code>
33
-
34
  ---
35
  Task: "What is the result of the following operation: 5 + 3 + 1294.678?"
36
 
@@ -183,8 +162,7 @@ system_prompt: |-
183
  9. The state persists between code executions: so if in one step you've created variables or imported modules, these will all persist.
184
  10. Don't give up! You're in charge of solving the task, not providing directions to solve it.
185
 
186
- Report your thoughts, 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.
187
-
188
  Now Begin!
189
  planning:
190
  initial_plan: |-
@@ -225,6 +203,7 @@ planning:
225
  """
226
  {% endfor %}
227
  ```
 
228
 
229
  {%- if managed_agents and managed_agents.values() | list %}
230
  You can also give tasks to team members.
@@ -287,6 +266,7 @@ planning:
287
  {%- endfor %}"""
288
  {% endfor %}
289
  ```
 
290
 
291
  {%- if managed_agents and managed_agents.values() | list %}
292
  You can also give tasks to team members.
@@ -317,6 +297,10 @@ managed_agent:
317
  ### 2. Task outcome (extremely detailed version):
318
  ### 3. Additional context (if relevant):
319
 
 
 
 
 
320
  Put all these in your final_answer tool, everything that you do not pass as an argument to final_answer will be lost.
321
  And even if your task resolution is not successful, please return as much context as possible, so that your manager can act upon this feedback.
322
  report: |-
@@ -326,7 +310,7 @@ final_answer:
326
  pre_messages: |-
327
  An agent tried to answer a user query but it got stuck and failed to do so. You are tasked with providing an answer instead. Here is the agent's memory:
328
  post_messages: |-
 
 
329
  Based on the above, please provide an answer to the following user task:
330
  {{task}}
331
- We expect submissions to be json-line files with the following format. The first two fields are mandatory, reasoning_trace is optional:
332
- ```{"model_answer": "Answer 1 from your model", "reasoning_trace": ""}```
 
9
  These print outputs will then appear in the 'Observation:' field, which will be available as input for the next step.
10
  In the end you have to return a final answer using the `final_answer` tool.
11
 
 
 
 
12
  Here are a few examples using notional tools:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  ---
14
  Task: "What is the result of the following operation: 5 + 3 + 1294.678?"
15
 
 
162
  9. The state persists between code executions: so if in one step you've created variables or imported modules, these will all persist.
163
  10. Don't give up! You're in charge of solving the task, not providing directions to solve it.
164
 
165
+ Be careful to follow the exact submission format and instruction.
 
166
  Now Begin!
167
  planning:
168
  initial_plan: |-
 
203
  """
204
  {% endfor %}
205
  ```
206
+ You must prefer generic tools over specific one: for example prefer search or visit web page instead of wikipedia. Use wikipedia only when stated.
207
 
208
  {%- if managed_agents and managed_agents.values() | list %}
209
  You can also give tasks to team members.
 
266
  {%- endfor %}"""
267
  {% endfor %}
268
  ```
269
+ You must prefer generic tools over specific one: for example prefer search or visit web page instead of wikipedia. Use wikipedia only when stated.
270
 
271
  {%- if managed_agents and managed_agents.values() | list %}
272
  You can also give tasks to team members.
 
297
  ### 2. Task outcome (extremely detailed version):
298
  ### 3. Additional context (if relevant):
299
 
300
+ Be careful to follow the exact submission format and instruction.
301
+
302
+ Report your thoughts, 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.
303
+
304
  Put all these in your final_answer tool, everything that you do not pass as an argument to final_answer will be lost.
305
  And even if your task resolution is not successful, please return as much context as possible, so that your manager can act upon this feedback.
306
  report: |-
 
310
  pre_messages: |-
311
  An agent tried to answer a user query but it got stuck and failed to do so. You are tasked with providing an answer instead. Here is the agent's memory:
312
  post_messages: |-
313
+ Report your thoughts, 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.
314
+
315
  Based on the above, please provide an answer to the following user task:
316
  {{task}}
 
 
requirements.txt CHANGED
@@ -3,10 +3,29 @@ gradio[oauth]
3
  requests
4
  duckduckgo-search
5
  smolagents
 
6
  markdownify
7
  typing
8
  numpy
9
  pandas
10
- smolagents[litellm]
11
  numpy
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  qwen_vl_utils
 
 
 
 
 
3
  requests
4
  duckduckgo-search
5
  smolagents
6
+ smolagents[litellm]
7
  markdownify
8
  typing
9
  numpy
10
  pandas
 
11
  numpy
12
+ wikipedia-api
13
+ openpyxl
14
+ openai
15
+ yfinance
16
+ lancedb
17
+ tantivy
18
+ pypdf
19
+ exa-py
20
+ newspaper4k
21
+ lxml_html_clean
22
+ sqlalchemy
23
+ agno
24
+ beautifulsoup4
25
+ wikipedia
26
+ langchain-community
27
  qwen_vl_utils
28
+ langgraph
29
+ langchain[openai]
30
+ rizaio
31
+ google-search-results