ddewaele commited on
Commit
218536f
·
1 Parent(s): ccd8abb
Files changed (4) hide show
  1. app.py +29 -16
  2. gradio_ui.py +296 -0
  3. prompts.yaml +1 -0
  4. requirements.txt +5 -0
app.py CHANGED
@@ -5,28 +5,29 @@ from smolagents import (
5
  load_tool,
6
  CodeAgent,
7
  HfApiModel,
8
- GradioUI,
9
  VisitWebpageTool,
10
  )
11
 
12
- # from opentelemetry import trace
13
- # from opentelemetry.sdk.trace import TracerProvider
14
- # from opentelemetry.sdk.trace.export import BatchSpanProcessor
15
- #
16
- # from openinference.instrumentation.smolagents import SmolagentsInstrumentor
17
- # from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
18
- # from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
19
- #
20
- # endpoint = "http://0.0.0.0:6006/v1/traces"
21
- # trace_provider = TracerProvider()
22
- # trace_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter(endpoint)))
23
- #
24
- # SmolagentsInstrumentor().instrument(tracer_provider=trace_provider)
25
 
26
- model_id='Qwen/Qwen2.5-Coder-32B-Instruct'
 
 
 
 
 
 
 
 
 
 
27
 
 
 
28
  model = HfApiModel(
29
- max_tokens=2096,
30
  temperature=0.5,
31
  model_id=model_id,
32
  custom_role_conversions=None,
@@ -46,7 +47,12 @@ with open("prompts.yaml", 'r') as stream:
46
 
47
  examples = [
48
  [{"text": "what are AI agents ?"}],
 
 
49
  [{"text": "What do you know about ReAct ?"}],
 
 
 
50
  [{"text": "What are some of the topics covered in the course ?"}],
51
  ]
52
 
@@ -54,6 +60,13 @@ examples = [
54
  agent = CodeAgent(tools=[visitWebpageTool, image_generation_tool], model=model, prompt_templates=prompt_templates)
55
 
56
  gr = GradioUI(agent).launch()
 
 
 
 
 
 
 
57
 
58
  # messages = gr.State([])
59
  # messages.append(gr.ChatMessage(role="assistant", content="testing"))
 
5
  load_tool,
6
  CodeAgent,
7
  HfApiModel,
 
8
  VisitWebpageTool,
9
  )
10
 
11
+ from opentelemetry import trace
12
+ from opentelemetry.sdk.trace import TracerProvider
13
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
 
 
 
 
 
 
 
 
 
 
14
 
15
+ from openinference.instrumentation.smolagents import SmolagentsInstrumentor
16
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
17
+ from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
18
+
19
+ from gradio_ui import GradioUI
20
+
21
+ endpoint = "http://0.0.0.0:6006/v1/traces"
22
+ trace_provider = TracerProvider()
23
+ trace_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter(endpoint)))
24
+
25
+ SmolagentsInstrumentor().instrument(tracer_provider=trace_provider)
26
 
27
+ model_id='Qwen/Qwen2.5-Coder-32B-Instruct'
28
+ # model_id='meta-llama/Llama-3.3-70B-Instruct'
29
  model = HfApiModel(
30
+ max_tokens=200,
31
  temperature=0.5,
32
  model_id=model_id,
33
  custom_role_conversions=None,
 
47
 
48
  examples = [
49
  [{"text": "what are AI agents ?"}],
50
+ [{"text": "how are tools used and how do they fit in ?"}],
51
+ [{"text": "What do you know about tokenization"}],
52
  [{"text": "What do you know about ReAct ?"}],
53
+ [{"text": "What are special tokens ?"}],
54
+ [{"text": "What are actions ?"}],
55
+ [{"text": "What is the role of an LLM ?"}],
56
  [{"text": "What are some of the topics covered in the course ?"}],
57
  ]
58
 
 
60
  agent = CodeAgent(tools=[visitWebpageTool, image_generation_tool], model=model, prompt_templates=prompt_templates)
61
 
62
  gr = GradioUI(agent).launch()
63
+ # Loop over each example and call agent.run() with the text
64
+ # for prompt in prompts:
65
+ # text = prompt[0]["text"]
66
+ # response = agent.run(text)
67
+ # print(f"Input: {text}\nResponse: {response}\n")
68
+
69
+
70
 
71
  # messages = gr.State([])
72
  # messages.append(gr.ChatMessage(role="assistant", content="testing"))
gradio_ui.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
3
+ # Copyright 2024 The HuggingFace Inc. team. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ import mimetypes
17
+ import os
18
+ import re
19
+ import shutil
20
+ from typing import Optional
21
+
22
+ from smolagents.agent_types import AgentAudio, AgentImage, AgentText, handle_agent_output_types
23
+ from smolagents.agents import ActionStep, MultiStepAgent
24
+ from smolagents.memory import MemoryStep
25
+ from smolagents.utils import _is_package_available
26
+
27
+
28
+ def pull_messages_from_step(
29
+ step_log: MemoryStep,
30
+ ):
31
+ """Extract ChatMessage objects from agent steps with proper nesting"""
32
+ import gradio as gr
33
+
34
+ if isinstance(step_log, ActionStep):
35
+ # Output the step number
36
+ step_number = f"Step {step_log.step_number}" if step_log.step_number is not None else ""
37
+ yield gr.ChatMessage(role="assistant", content=f"**{step_number}**")
38
+
39
+ # First yield the thought/reasoning from the LLM
40
+ if hasattr(step_log, "model_output") and step_log.model_output is not None:
41
+ # Clean up the LLM output
42
+ model_output = step_log.model_output.strip()
43
+ # Remove any trailing <end_code> and extra backticks, handling multiple possible formats
44
+ model_output = re.sub(r"```\s*<end_code>", "```", model_output) # handles ```<end_code>
45
+ model_output = re.sub(r"<end_code>\s*```", "```", model_output) # handles <end_code>```
46
+ model_output = re.sub(r"```\s*\n\s*<end_code>", "```", model_output) # handles ```\n<end_code>
47
+ model_output = model_output.strip()
48
+ yield gr.ChatMessage(role="assistant", content=model_output)
49
+
50
+ # For tool calls, create a parent message
51
+ if hasattr(step_log, "tool_calls") and step_log.tool_calls is not None:
52
+ first_tool_call = step_log.tool_calls[0]
53
+ used_code = first_tool_call.name == "python_interpreter"
54
+ parent_id = f"call_{len(step_log.tool_calls)}"
55
+
56
+ # Tool call becomes the parent message with timing info
57
+ # First we will handle arguments based on type
58
+ args = first_tool_call.arguments
59
+ if isinstance(args, dict):
60
+ content = str(args.get("answer", str(args)))
61
+ else:
62
+ content = str(args).strip()
63
+
64
+ if used_code:
65
+ # Clean up the content by removing any end code tags
66
+ content = re.sub(r"```.*?\n", "", content) # Remove existing code blocks
67
+ content = re.sub(r"\s*<end_code>\s*", "", content) # Remove end_code tags
68
+ content = content.strip()
69
+ if not content.startswith("```python"):
70
+ content = f"```python\n{content}\n```"
71
+
72
+ parent_message_tool = gr.ChatMessage(
73
+ role="assistant",
74
+ content=content,
75
+ metadata={
76
+ "title": f"🛠️ Used tool {first_tool_call.name}",
77
+ "id": parent_id,
78
+ "status": "pending",
79
+ },
80
+ )
81
+ yield parent_message_tool
82
+
83
+ # Nesting execution logs under the tool call if they exist
84
+ if hasattr(step_log, "observations") and (
85
+ step_log.observations is not None and step_log.observations.strip()
86
+ ): # Only yield execution logs if there's actual content
87
+ log_content = step_log.observations.strip()
88
+ if log_content:
89
+ log_content = re.sub(r"^Execution logs:\s*", "", log_content)
90
+ yield gr.ChatMessage(
91
+ role="assistant",
92
+ content=f"{log_content}",
93
+ metadata={"title": "📝 Execution Logs", "parent_id": parent_id, "status": "done"},
94
+ )
95
+
96
+ # Nesting any errors under the tool call
97
+ if hasattr(step_log, "error") and step_log.error is not None:
98
+ yield gr.ChatMessage(
99
+ role="assistant",
100
+ content=str(step_log.error),
101
+ metadata={"title": "💥 Error", "parent_id": parent_id, "status": "done"},
102
+ )
103
+
104
+ # Update parent message metadata to done status without yielding a new message
105
+ parent_message_tool.metadata["status"] = "done"
106
+
107
+ # Handle standalone errors but not from tool calls
108
+ elif hasattr(step_log, "error") and step_log.error is not None:
109
+ yield gr.ChatMessage(role="assistant", content=str(step_log.error), metadata={"title": "💥 Error"})
110
+
111
+ # Calculate duration and token information
112
+ step_footnote = f"{step_number}"
113
+ if hasattr(step_log, "input_token_count") and hasattr(step_log, "output_token_count"):
114
+ token_str = (
115
+ f" | Input-tokens:{step_log.input_token_count:,} | Output-tokens:{step_log.output_token_count:,}"
116
+ )
117
+ step_footnote += token_str
118
+ if hasattr(step_log, "duration"):
119
+ step_duration = f" | Duration: {round(float(step_log.duration), 2)}" if step_log.duration else None
120
+ step_footnote += step_duration
121
+ step_footnote = f"""<span style="color: #bbbbc2; font-size: 12px;">{step_footnote}</span> """
122
+ yield gr.ChatMessage(role="assistant", content=f"{step_footnote}")
123
+ yield gr.ChatMessage(role="assistant", content="-----")
124
+
125
+
126
+ def stream_to_gradio(
127
+ agent,
128
+ task: str,
129
+ reset_agent_memory: bool = False,
130
+ additional_args: Optional[dict] = None,
131
+ ):
132
+ """Runs an agent with the given task and streams the messages from the agent as gradio ChatMessages."""
133
+ if not _is_package_available("gradio"):
134
+ raise ModuleNotFoundError(
135
+ "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
136
+ )
137
+ import gradio as gr
138
+
139
+ total_input_tokens = 0
140
+ total_output_tokens = 0
141
+
142
+ for step_log in agent.run(task, stream=True, reset=reset_agent_memory, additional_args=additional_args):
143
+ # Track tokens if model provides them
144
+ if getattr(agent.model, "last_input_token_count", None) is not None:
145
+ total_input_tokens += agent.model.last_input_token_count
146
+ total_output_tokens += agent.model.last_output_token_count
147
+ if isinstance(step_log, ActionStep):
148
+ step_log.input_token_count = agent.model.last_input_token_count
149
+ step_log.output_token_count = agent.model.last_output_token_count
150
+
151
+ for message in pull_messages_from_step(
152
+ step_log,
153
+ ):
154
+ yield message
155
+
156
+ final_answer = step_log # Last log is the run's final_answer
157
+ final_answer = handle_agent_output_types(final_answer)
158
+
159
+ if isinstance(final_answer, AgentText):
160
+ yield gr.ChatMessage(
161
+ role="assistant",
162
+ content=f"**Final answer:**\n{final_answer.to_string()}\n",
163
+ )
164
+ elif isinstance(final_answer, AgentImage):
165
+ yield gr.ChatMessage(
166
+ role="assistant",
167
+ content={"path": final_answer.to_string(), "mime_type": "image/png"},
168
+ )
169
+ elif isinstance(final_answer, AgentAudio):
170
+ yield gr.ChatMessage(
171
+ role="assistant",
172
+ content={"path": final_answer.to_string(), "mime_type": "audio/wav"},
173
+ )
174
+ else:
175
+ yield gr.ChatMessage(role="assistant", content=f"**Final answer:** {str(final_answer)}")
176
+
177
+
178
+ class GradioUI:
179
+ """A one-line interface to launch your agent in Gradio"""
180
+
181
+ def __init__(self, agent: MultiStepAgent, file_upload_folder: str | None = None):
182
+ if not _is_package_available("gradio"):
183
+ raise ModuleNotFoundError(
184
+ "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
185
+ )
186
+ self.agent = agent
187
+ self.file_upload_folder = file_upload_folder
188
+ if self.file_upload_folder is not None:
189
+ if not os.path.exists(file_upload_folder):
190
+ os.mkdir(file_upload_folder)
191
+
192
+ def interact_with_agent(self, prompt, messages):
193
+ import gradio as gr
194
+
195
+ messages.append(gr.ChatMessage(role="user", content=prompt))
196
+ yield messages
197
+ for msg in stream_to_gradio(self.agent, task=prompt, reset_agent_memory=True):
198
+ messages.append(msg)
199
+ yield messages
200
+ yield messages
201
+
202
+ def upload_file(
203
+ self,
204
+ file,
205
+ file_uploads_log,
206
+ allowed_file_types=[
207
+ "application/pdf",
208
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
209
+ "text/plain",
210
+ ],
211
+ ):
212
+ """
213
+ Handle file uploads, default allowed types are .pdf, .docx, and .txt
214
+ """
215
+ import gradio as gr
216
+
217
+ if file is None:
218
+ return gr.Textbox("No file uploaded", visible=True), file_uploads_log
219
+
220
+ try:
221
+ mime_type, _ = mimetypes.guess_type(file.name)
222
+ except Exception as e:
223
+ return gr.Textbox(f"Error: {e}", visible=True), file_uploads_log
224
+
225
+ if mime_type not in allowed_file_types:
226
+ return gr.Textbox("File type disallowed", visible=True), file_uploads_log
227
+
228
+ # Sanitize file name
229
+ original_name = os.path.basename(file.name)
230
+ sanitized_name = re.sub(
231
+ r"[^\w\-.]", "_", original_name
232
+ ) # Replace any non-alphanumeric, non-dash, or non-dot characters with underscores
233
+
234
+ type_to_ext = {}
235
+ for ext, t in mimetypes.types_map.items():
236
+ if t not in type_to_ext:
237
+ type_to_ext[t] = ext
238
+
239
+ # Ensure the extension correlates to the mime type
240
+ sanitized_name = sanitized_name.split(".")[:-1]
241
+ sanitized_name.append("" + type_to_ext[mime_type])
242
+ sanitized_name = "".join(sanitized_name)
243
+
244
+ # Save the uploaded file to the specified folder
245
+ file_path = os.path.join(self.file_upload_folder, os.path.basename(sanitized_name))
246
+ shutil.copy(file.name, file_path)
247
+
248
+ return gr.Textbox(f"File uploaded: {file_path}", visible=True), file_uploads_log + [file_path]
249
+
250
+ def log_user_message(self, text_input, file_uploads_log):
251
+ return (
252
+ text_input
253
+ + (
254
+ f"\nYou have been provided with these files, which might be helpful or not: {file_uploads_log}"
255
+ if len(file_uploads_log) > 0
256
+ else ""
257
+ ),
258
+ "",
259
+ )
260
+
261
+ def launch(self, share: bool = False, **kwargs):
262
+ import gradio as gr
263
+
264
+ with gr.Blocks(fill_height=True) as demo:
265
+ stored_messages = gr.State([])
266
+ file_uploads_log = gr.State([])
267
+ chatbot = gr.Chatbot(
268
+ label="Agent",
269
+ type="messages",
270
+ avatar_images=(
271
+ None,
272
+ "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png",
273
+ ),
274
+ resizeable=True,
275
+ scale=1,
276
+ )
277
+ # If an upload folder is provided, enable the upload feature
278
+ if self.file_upload_folder is not None:
279
+ upload_file = gr.File(label="Upload a file")
280
+ upload_status = gr.Textbox(label="Upload Status", interactive=False, visible=False)
281
+ upload_file.change(
282
+ self.upload_file,
283
+ [upload_file, file_uploads_log],
284
+ [upload_status, file_uploads_log],
285
+ )
286
+ text_input = gr.Textbox(lines=1, label="Chat Message")
287
+ text_input.submit(
288
+ self.log_user_message,
289
+ [text_input, file_uploads_log],
290
+ [stored_messages, text_input],
291
+ ).then(self.interact_with_agent, [stored_messages, chatbot], [chatbot])
292
+
293
+ demo.launch(debug=True, share=share, **kwargs)
294
+
295
+
296
+ __all__ = ["stream_to_gradio", "GradioUI"]
prompts.yaml CHANGED
@@ -28,6 +28,7 @@
28
  5. In every response you are also forced to come up with 5 multiple choice questions related to the topic.
29
  6. These 5 multiple choice questions should be appended to your final answer in a human readable form (no JSON)
30
  7. For every question you will also highlight the answer and explain why this is the correct answer.
 
31
 
32
 
33
  You will be given a task to solve as best you can.
 
28
  5. In every response you are also forced to come up with 5 multiple choice questions related to the topic.
29
  6. These 5 multiple choice questions should be appended to your final answer in a human readable form (no JSON)
30
  7. For every question you will also highlight the answer and explain why this is the correct answer.
31
+ 8. The multiple choice questions, the answer and explanation all becomes pare of the `final_answer`
32
 
33
 
34
  You will be given a task to solve as best you can.
requirements.txt CHANGED
@@ -1 +1,6 @@
 
1
  smolagents
 
 
 
 
 
1
+ huggingface_hub
2
  smolagents
3
+ arize-phoenix
4
+ opentelemetry-sdk
5
+ opentelemetry-exporter-otlp
6
+ openinference-instrumentation-smolagents