Gabandino commited on
Commit
1bf5ab8
·
verified ·
1 Parent(s): e552e13

Updates agents.py to fix llm invokation typo for final submission

Browse files
Files changed (1) hide show
  1. agents.py +325 -325
agents.py CHANGED
@@ -1,326 +1,326 @@
1
- from typing import TypedDict, Optional
2
- from langgraph.graph import StateGraph, START, END
3
- from langchain_openai import ChatOpenAI
4
- from langchain_core.messages import HumanMessage
5
- from rich.console import Console
6
- from smolagents import (
7
- CodeAgent,
8
- ToolCallingAgent,
9
- OpenAIServerModel,
10
- AgentLogger,
11
- LogLevel,
12
- Panel,
13
- Text,
14
- )
15
- from tools import (
16
- GetAttachmentTool,
17
- GoogleSearchTool,
18
- GoogleSiteSearchTool,
19
- ContentRetrieverTool,
20
- YouTubeVideoTool,
21
- SpeechRecognitionTool,
22
- ClassifierTool,
23
- ImageToChessBoardFENTool,
24
- chess_engine_locator
25
- )
26
- import openai
27
- import backoff
28
-
29
- def create_general_ai_agent(verbosity: int = LogLevel.INFO):
30
- get_attachment_tool = GetAttachmentTool()
31
- speech_recognition_tool = SpeechRecognitionTool()
32
- env_tools = [
33
- get_attachment_tool,
34
- ]
35
- model = OpenAIServerModel(model_id='gpt-4.1')
36
- console = Console(record=True)
37
- logger = AgentLogger(level=verbosity, console=console)
38
- steps_buffer = []
39
-
40
- def capture_step_log(agent) -> None:
41
- steps_buffer.append(console.export_text(clear=True))
42
-
43
- agents = {
44
- agent.name: agent
45
- for agent in [
46
- ToolCallingAgent(
47
- name='general_assistant',
48
- description='Answers questions for best of knowledge and common reasoning grounded on already known information. Can understand multimedia including audio and video files and YouTube.',
49
- model=model,
50
- tools=env_tools
51
- + [
52
- speech_recognition_tool,
53
- YouTubeVideoTool(
54
- client=model.client,
55
- speech_recognition_tool=speech_recognition_tool,
56
- frames_interval=3,
57
- chunk_duration=60,
58
- debug=True,
59
- ),
60
- ClassifierTool(
61
- client=model.client,
62
- model_id='gpt-4.1-mini',
63
- ),
64
- ],
65
- logger=logger,
66
- step_callbacks=[capture_step_log],
67
- ),
68
- ToolCallingAgent(
69
- name='web_researcher',
70
- description='Answers questions that require grounding in unknown information through search on web sites and other online resources.',
71
- tools=env_tools
72
- + [
73
- GoogleSearchTool(),
74
- GoogleSiteSearchTool(),
75
- ContentRetrieverTool(),
76
- ],
77
- model=model,
78
- planning_interval=3,
79
- max_steps=9,
80
- logger=logger,
81
- step_callbacks=[capture_step_log],
82
- ),
83
- CodeAgent(
84
- name='data_analyst',
85
- description='Data analyst with advanced skills in statistics, handling tabular data and related Python packages.',
86
- tools=env_tools,
87
- additional_authorized_imports=[
88
- 'numpy',
89
- 'pandas',
90
- 'tabulate',
91
- 'matplotlib',
92
- 'seaborn',
93
- ],
94
- model=model,
95
- logger=logger,
96
- step_callbacks=[capture_step_log],
97
- ),
98
- CodeAgent(
99
- name='chess_player',
100
- description='Chess grandmaster empowered by chess engine. Always thinks at least 100 steps ahead.',
101
- tools=env_tools
102
- + [
103
- ImageToChessBoardFENTool(client=model.client),
104
- chess_engine_locator,
105
- ],
106
- additional_authorized_imports=[
107
- 'chess',
108
- 'chess.engine',
109
- ],
110
- model=model,
111
- logger=logger,
112
- step_callbacks=[capture_step_log],
113
- ),
114
- ]
115
- }
116
-
117
- class GAIATask(TypedDict):
118
- task_id: Optional[str | None] = None
119
- question: str
120
- steps: list[str] = []
121
- agent: Optional[str | None] = None
122
- raw_answer: Optional[str | None] = None
123
- final_answer: Optional[str | None] = None
124
-
125
- llm = ChatOpenAI(model='gpt-4.1')
126
- logger=AgentLogger(level=verbosity)
127
-
128
- @backoff.on_exception(backoff.expo, openai.RateLimitError, max_time=60, max_tries=6)
129
- def llm_invoke_with_retry(messages):
130
- response = llm.invoke(messages)
131
- return response
132
-
133
- def read_question(state: GAIATask):
134
- logger.log_task(
135
- content=state['question'].strip(),
136
- subtitle=f'LangGraph with {type(llm).__name__} - {llm.model_name}',
137
- level=LogLevel.INFO,
138
- title='Final Assignment Agent for Hugging Face Agents Course',
139
- )
140
- get_attachment_tool.attachment_for(state['task_id'])
141
-
142
- return {
143
- 'steps': [],
144
- 'agent': None,
145
- 'raw_answer': None,
146
- 'final_answer': None,
147
- }
148
-
149
- def select_agent(state: GAIATask):
150
- agents_description = '\n\n'.join(
151
- [
152
- f"AGENT NAME: {a.name}\nAGENT DESCRIPTION: {a.description}"
153
- for a in agents.values()
154
- ]
155
- )
156
-
157
- prompt = f'''\
158
- You are a general AI assistant.
159
-
160
- I will provide you a question and a lsit of agents with their descriptions.
161
- Your task is to select the most appropriate agent to answer the question.
162
- You can select one of the agents or decide no agent is needed.
163
-
164
- If question has attachment only agent can answer it.
165
-
166
- QUESTION:
167
- {state['question']}
168
-
169
- {agents_description}
170
-
171
- Now, return the name of the agent you selected or "no agent needed" if you think that no agent is needed.
172
- '''
173
-
174
- response = llm.invoke_with_retry([HumanMessage(content=prompt)])
175
- agent_name = response.content.strip()
176
-
177
- if agent_name in agents:
178
- logger.log(
179
- f'Agent {agent_name} selected for solving the task.',
180
- level=LogLevel.DEBUG,
181
- )
182
- return {
183
- 'agent': agent_name,
184
- 'steps': state.get('steps', [])
185
- + [
186
- f'Agent "{agent_name}" selected for task execution.',
187
- ],
188
- }
189
- elif agent_name == 'no agent needed':
190
- logger.log(
191
- 'No appropriate agent found in the list. No agent will be used.',
192
- level=LogLevel.DEBUG,
193
- )
194
- return {
195
- 'agent': None,
196
- 'steps': state.get('steps', [])
197
- + [
198
- 'A decision is made to solve the task directly without invoking any agent.',
199
- ],
200
- }
201
- else:
202
- logger.log(
203
- f'[bold red]Warning to user: Unexpected agent name "{agent_name}" selected. No agent will be used.[/bold red]',
204
- level=LogLevel.INFO,
205
- )
206
- return {
207
- 'agent': None,
208
- 'steps': state.get('steps', [])
209
- + [
210
- f'Attempt to select non-existing agent "{agent_name}". No agent will be used.'
211
- ],
212
- }
213
-
214
- def delegate_to_agent(state: GAIATask):
215
- agent_name = state.get('agent', None)
216
- if not agent_name:
217
- raise ValueError('Agent not selected.')
218
- if agent_name not in agents:
219
- raise ValueError(f'Agent "{agent_name}" is not available.')
220
-
221
- logger.log(
222
- Panel(Text(f'Calling agent: {agent_name}.')),
223
- level=LogLevel.INFO,
224
- )
225
-
226
- agent = agents[agent_name]
227
- agent_answer = agent.run(task=state['question'])
228
- steps = [f'Agent "{agent_name}" step:\n{s}' for s in steps_buffer]
229
- steps_buffer.clear()
230
- return {
231
- 'raw_answer': agent_answer,
232
- 'steps': state.get('steps', []) + steps,
233
- }
234
-
235
- def one_shot_answering(state: GAIATask):
236
- response = llm_invoke_with_retry([HumanMessage(content=state.get('question'))])
237
- return {
238
- 'raw_answer': response.content,
239
- 'steps': state.get('steps', [])
240
- + [
241
- f'One-shot answer:\n{response.content}',
242
- ],
243
- }
244
-
245
- def refine_answer(state: GAIATask):
246
- question = state.get('question')
247
- answer = state.get('raw_answer', None)
248
- if not answer:
249
- return {'final_answer': 'No answer.'}
250
-
251
- prompt = f"""\
252
- You are a general AI assistant.
253
-
254
- I will provide you a question and correct answer to it. Answer is correct but may be too verbose or not follow the rules below.
255
- Your task is to rephrase answer according to rules below.
256
-
257
- Answer should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
258
-
259
- 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.
260
- 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.
261
- 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.
262
-
263
- If you are asked for a comma separated list, use space after comma and before next element of the list unless other directly specified in a question.
264
- Check question context to define if letters case matters. Do not change case if not prescribed by other rules or question.
265
- If you are not asked for the list, capitalize the first letter of the answer unless it changes meaning of the answer.
266
- If answer is number, use digits only not words unless otherwise directly specified in question.
267
- If answer is not full sentence, do not add period at the end.
268
-
269
- Preserve all items if the answer is a list.
270
-
271
- QUESTION:
272
- {question}
273
-
274
- ANSWER:
275
- {answer}
276
- """
277
- response = llm_invoke_with_retry([HumanMessage(contet=prompt)])
278
- refined_answer = response.content.strip()
279
- logger.log(
280
- Text(f'GAIA final answer: {refined_answer}', style='bold #d4b702'),
281
- level=LogLevel.INFO,
282
- )
283
- return {
284
- 'final_answer': refined_answer,
285
- 'steps': state.get('steps', [])
286
- + [
287
- 'Refining the answer according to GAIA benchmark rules.',
288
- f'FINAL ANSWER: {response.content}',
289
- ],
290
- }
291
-
292
- def route_task(state: GAIATask) -> str:
293
- if state.get('agent') in agents:
294
- return 'agent selected'
295
- else:
296
- return 'no agent matched'
297
-
298
- # Create the graph here
299
- gaia_graph = StateGraph(GAIATask)
300
-
301
- # Add nodes to feed them into edges
302
- gaia_graph.add_node('read_question', read_question)
303
- gaia_graph.add_node('select_agent', select_agent)
304
- gaia_graph.add_node('delegate_to_agent', delegate_to_agent)
305
- gaia_graph.add_node('one_shot_answering', one_shot_answering)
306
- gaia_graph.add_node('refine_answer', refine_answer)
307
-
308
- # Start the edges
309
- gaia_graph.add_edge(START, 'read_question')
310
- # Add edges - define the flow
311
- gaia_graph.add_edge('read_question','select_agent')
312
-
313
- # Add conditional edges/branching from select_agent
314
- gaia_graph.add_conditional_edges(
315
- 'select_agent',
316
- route_task,
317
- {'agent selected': 'delegate_to_agent', 'no agent matched': 'one_shot_answering'},
318
- )
319
-
320
- # Add the final edges
321
- gaia_graph.add_edge('delegate_to_agent', 'refine_answer')
322
- gaia_graph.add_edge('one_shot_answering', 'refine_answer')
323
- gaia_graph.add_edge('refine_answer', END)
324
-
325
- gaia = gaia_graph.compile()
326
  return gaia
 
1
+ from typing import TypedDict, Optional
2
+ from langgraph.graph import StateGraph, START, END
3
+ from langchain_openai import ChatOpenAI
4
+ from langchain_core.messages import HumanMessage
5
+ from rich.console import Console
6
+ from smolagents import (
7
+ CodeAgent,
8
+ ToolCallingAgent,
9
+ OpenAIServerModel,
10
+ AgentLogger,
11
+ LogLevel,
12
+ Panel,
13
+ Text,
14
+ )
15
+ from tools import (
16
+ GetAttachmentTool,
17
+ GoogleSearchTool,
18
+ GoogleSiteSearchTool,
19
+ ContentRetrieverTool,
20
+ YouTubeVideoTool,
21
+ SpeechRecognitionTool,
22
+ ClassifierTool,
23
+ ImageToChessBoardFENTool,
24
+ chess_engine_locator
25
+ )
26
+ import openai
27
+ import backoff
28
+
29
+ def create_general_ai_agent(verbosity: int = LogLevel.INFO):
30
+ get_attachment_tool = GetAttachmentTool()
31
+ speech_recognition_tool = SpeechRecognitionTool()
32
+ env_tools = [
33
+ get_attachment_tool,
34
+ ]
35
+ model = OpenAIServerModel(model_id='gpt-4.1')
36
+ console = Console(record=True)
37
+ logger = AgentLogger(level=verbosity, console=console)
38
+ steps_buffer = []
39
+
40
+ def capture_step_log(agent) -> None:
41
+ steps_buffer.append(console.export_text(clear=True))
42
+
43
+ agents = {
44
+ agent.name: agent
45
+ for agent in [
46
+ ToolCallingAgent(
47
+ name='general_assistant',
48
+ description='Answers questions for best of knowledge and common reasoning grounded on already known information. Can understand multimedia including audio and video files and YouTube.',
49
+ model=model,
50
+ tools=env_tools
51
+ + [
52
+ speech_recognition_tool,
53
+ YouTubeVideoTool(
54
+ client=model.client,
55
+ speech_recognition_tool=speech_recognition_tool,
56
+ frames_interval=3,
57
+ chunk_duration=60,
58
+ debug=True,
59
+ ),
60
+ ClassifierTool(
61
+ client=model.client,
62
+ model_id='gpt-4.1-mini',
63
+ ),
64
+ ],
65
+ logger=logger,
66
+ step_callbacks=[capture_step_log],
67
+ ),
68
+ ToolCallingAgent(
69
+ name='web_researcher',
70
+ description='Answers questions that require grounding in unknown information through search on web sites and other online resources.',
71
+ tools=env_tools
72
+ + [
73
+ GoogleSearchTool(),
74
+ GoogleSiteSearchTool(),
75
+ ContentRetrieverTool(),
76
+ ],
77
+ model=model,
78
+ planning_interval=3,
79
+ max_steps=9,
80
+ logger=logger,
81
+ step_callbacks=[capture_step_log],
82
+ ),
83
+ CodeAgent(
84
+ name='data_analyst',
85
+ description='Data analyst with advanced skills in statistics, handling tabular data and related Python packages.',
86
+ tools=env_tools,
87
+ additional_authorized_imports=[
88
+ 'numpy',
89
+ 'pandas',
90
+ 'tabulate',
91
+ 'matplotlib',
92
+ 'seaborn',
93
+ ],
94
+ model=model,
95
+ logger=logger,
96
+ step_callbacks=[capture_step_log],
97
+ ),
98
+ CodeAgent(
99
+ name='chess_player',
100
+ description='Chess grandmaster empowered by chess engine. Always thinks at least 100 steps ahead.',
101
+ tools=env_tools
102
+ + [
103
+ ImageToChessBoardFENTool(client=model.client),
104
+ chess_engine_locator,
105
+ ],
106
+ additional_authorized_imports=[
107
+ 'chess',
108
+ 'chess.engine',
109
+ ],
110
+ model=model,
111
+ logger=logger,
112
+ step_callbacks=[capture_step_log],
113
+ ),
114
+ ]
115
+ }
116
+
117
+ class GAIATask(TypedDict):
118
+ task_id: Optional[str | None] = None
119
+ question: str
120
+ steps: list[str] = []
121
+ agent: Optional[str | None] = None
122
+ raw_answer: Optional[str | None] = None
123
+ final_answer: Optional[str | None] = None
124
+
125
+ llm = ChatOpenAI(model='gpt-4.1')
126
+ logger=AgentLogger(level=verbosity)
127
+
128
+ @backoff.on_exception(backoff.expo, openai.RateLimitError, max_time=60, max_tries=6)
129
+ def llm_invoke_with_retry(messages):
130
+ response = llm.invoke(messages)
131
+ return response
132
+
133
+ def read_question(state: GAIATask):
134
+ logger.log_task(
135
+ content=state['question'].strip(),
136
+ subtitle=f'LangGraph with {type(llm).__name__} - {llm.model_name}',
137
+ level=LogLevel.INFO,
138
+ title='Final Assignment Agent for Hugging Face Agents Course',
139
+ )
140
+ get_attachment_tool.attachment_for(state['task_id'])
141
+
142
+ return {
143
+ 'steps': [],
144
+ 'agent': None,
145
+ 'raw_answer': None,
146
+ 'final_answer': None,
147
+ }
148
+
149
+ def select_agent(state: GAIATask):
150
+ agents_description = '\n\n'.join(
151
+ [
152
+ f"AGENT NAME: {a.name}\nAGENT DESCRIPTION: {a.description}"
153
+ for a in agents.values()
154
+ ]
155
+ )
156
+
157
+ prompt = f'''\
158
+ You are a general AI assistant.
159
+
160
+ I will provide you a question and a lsit of agents with their descriptions.
161
+ Your task is to select the most appropriate agent to answer the question.
162
+ You can select one of the agents or decide no agent is needed.
163
+
164
+ If question has attachment only agent can answer it.
165
+
166
+ QUESTION:
167
+ {state['question']}
168
+
169
+ {agents_description}
170
+
171
+ Now, return the name of the agent you selected or "no agent needed" if you think that no agent is needed.
172
+ '''
173
+
174
+ response = llm_invoke_with_retry([HumanMessage(content=prompt)])
175
+ agent_name = response.content.strip()
176
+
177
+ if agent_name in agents:
178
+ logger.log(
179
+ f'Agent {agent_name} selected for solving the task.',
180
+ level=LogLevel.DEBUG,
181
+ )
182
+ return {
183
+ 'agent': agent_name,
184
+ 'steps': state.get('steps', [])
185
+ + [
186
+ f'Agent "{agent_name}" selected for task execution.',
187
+ ],
188
+ }
189
+ elif agent_name == 'no agent needed':
190
+ logger.log(
191
+ 'No appropriate agent found in the list. No agent will be used.',
192
+ level=LogLevel.DEBUG,
193
+ )
194
+ return {
195
+ 'agent': None,
196
+ 'steps': state.get('steps', [])
197
+ + [
198
+ 'A decision is made to solve the task directly without invoking any agent.',
199
+ ],
200
+ }
201
+ else:
202
+ logger.log(
203
+ f'[bold red]Warning to user: Unexpected agent name "{agent_name}" selected. No agent will be used.[/bold red]',
204
+ level=LogLevel.INFO,
205
+ )
206
+ return {
207
+ 'agent': None,
208
+ 'steps': state.get('steps', [])
209
+ + [
210
+ f'Attempt to select non-existing agent "{agent_name}". No agent will be used.'
211
+ ],
212
+ }
213
+
214
+ def delegate_to_agent(state: GAIATask):
215
+ agent_name = state.get('agent', None)
216
+ if not agent_name:
217
+ raise ValueError('Agent not selected.')
218
+ if agent_name not in agents:
219
+ raise ValueError(f'Agent "{agent_name}" is not available.')
220
+
221
+ logger.log(
222
+ Panel(Text(f'Calling agent: {agent_name}.')),
223
+ level=LogLevel.INFO,
224
+ )
225
+
226
+ agent = agents[agent_name]
227
+ agent_answer = agent.run(task=state['question'])
228
+ steps = [f'Agent "{agent_name}" step:\n{s}' for s in steps_buffer]
229
+ steps_buffer.clear()
230
+ return {
231
+ 'raw_answer': agent_answer,
232
+ 'steps': state.get('steps', []) + steps,
233
+ }
234
+
235
+ def one_shot_answering(state: GAIATask):
236
+ response = llm_invoke_with_retry([HumanMessage(content=state.get('question'))])
237
+ return {
238
+ 'raw_answer': response.content,
239
+ 'steps': state.get('steps', [])
240
+ + [
241
+ f'One-shot answer:\n{response.content}',
242
+ ],
243
+ }
244
+
245
+ def refine_answer(state: GAIATask):
246
+ question = state.get('question')
247
+ answer = state.get('raw_answer', None)
248
+ if not answer:
249
+ return {'final_answer': 'No answer.'}
250
+
251
+ prompt = f"""\
252
+ You are a general AI assistant.
253
+
254
+ I will provide you a question and correct answer to it. Answer is correct but may be too verbose or not follow the rules below.
255
+ Your task is to rephrase answer according to rules below.
256
+
257
+ Answer should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
258
+
259
+ 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.
260
+ 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.
261
+ 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.
262
+
263
+ If you are asked for a comma separated list, use space after comma and before next element of the list unless other directly specified in a question.
264
+ Check question context to define if letters case matters. Do not change case if not prescribed by other rules or question.
265
+ If you are not asked for the list, capitalize the first letter of the answer unless it changes meaning of the answer.
266
+ If answer is number, use digits only not words unless otherwise directly specified in question.
267
+ If answer is not full sentence, do not add period at the end.
268
+
269
+ Preserve all items if the answer is a list.
270
+
271
+ QUESTION:
272
+ {question}
273
+
274
+ ANSWER:
275
+ {answer}
276
+ """
277
+ response = llm_invoke_with_retry([HumanMessage(contet=prompt)])
278
+ refined_answer = response.content.strip()
279
+ logger.log(
280
+ Text(f'GAIA final answer: {refined_answer}', style='bold #d4b702'),
281
+ level=LogLevel.INFO,
282
+ )
283
+ return {
284
+ 'final_answer': refined_answer,
285
+ 'steps': state.get('steps', [])
286
+ + [
287
+ 'Refining the answer according to GAIA benchmark rules.',
288
+ f'FINAL ANSWER: {response.content}',
289
+ ],
290
+ }
291
+
292
+ def route_task(state: GAIATask) -> str:
293
+ if state.get('agent') in agents:
294
+ return 'agent selected'
295
+ else:
296
+ return 'no agent matched'
297
+
298
+ # Create the graph here
299
+ gaia_graph = StateGraph(GAIATask)
300
+
301
+ # Add nodes to feed them into edges
302
+ gaia_graph.add_node('read_question', read_question)
303
+ gaia_graph.add_node('select_agent', select_agent)
304
+ gaia_graph.add_node('delegate_to_agent', delegate_to_agent)
305
+ gaia_graph.add_node('one_shot_answering', one_shot_answering)
306
+ gaia_graph.add_node('refine_answer', refine_answer)
307
+
308
+ # Start the edges
309
+ gaia_graph.add_edge(START, 'read_question')
310
+ # Add edges - define the flow
311
+ gaia_graph.add_edge('read_question','select_agent')
312
+
313
+ # Add conditional edges/branching from select_agent
314
+ gaia_graph.add_conditional_edges(
315
+ 'select_agent',
316
+ route_task,
317
+ {'agent selected': 'delegate_to_agent', 'no agent matched': 'one_shot_answering'},
318
+ )
319
+
320
+ # Add the final edges
321
+ gaia_graph.add_edge('delegate_to_agent', 'refine_answer')
322
+ gaia_graph.add_edge('one_shot_answering', 'refine_answer')
323
+ gaia_graph.add_edge('refine_answer', END)
324
+
325
+ gaia = gaia_graph.compile()
326
  return gaia