Daniil Bogdanov commited on
Commit
932fded
·
1 Parent(s): 9cf935f

Release v4

Browse files
Files changed (6) hide show
  1. agent.py +25 -8
  2. app.py +13 -15
  3. model.py +57 -7
  4. requirements.txt +2 -0
  5. tools.py +66 -1
  6. utils/logger.py +1 -1
agent.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Any, Optional
2
 
3
  from smolagents import CodeAgent
4
 
@@ -8,15 +8,24 @@ logger = get_logger(__name__)
8
 
9
 
10
  class Agent:
 
 
 
 
 
 
 
 
 
11
  def __init__(
12
- self, model: Any, tools: Optional[list] = None, prompt: Optional[str] = None
 
 
 
13
  ):
14
  logger.info("Initializing Agent")
15
-
16
  self.model = model
17
-
18
  self.tools = tools
19
-
20
  self.imports = [
21
  "pandas",
22
  "numpy",
@@ -28,15 +37,14 @@ class Agent:
28
  "time",
29
  "re",
30
  "openpyxl",
 
31
  ]
32
-
33
  self.agent = CodeAgent(
34
  model=self.model,
35
  tools=self.tools,
36
  add_base_tools=True,
37
  additional_authorized_imports=self.imports,
38
  )
39
-
40
  self.prompt = prompt or (
41
  """
42
  You are an advanced AI assistant specialized in solving complex, real-world tasks that require multi-step reasoning, factual accuracy, and use of external tools.
@@ -57,10 +65,19 @@ class Agent:
57
  ANSWER:
58
  """
59
  )
60
-
61
  logger.info("Agent initialized")
62
 
63
  def __call__(self, question: str, file_path: Optional[str] = None) -> str:
 
 
 
 
 
 
 
 
 
 
64
  answer = self.agent.run(
65
  self.prompt.format(question=question, context=file_path)
66
  )
 
1
+ from typing import Any, List, Optional
2
 
3
  from smolagents import CodeAgent
4
 
 
8
 
9
 
10
  class Agent:
11
+ """
12
+ Agent class that wraps a CodeAgent and provides a callable interface for answering questions.
13
+
14
+ Args:
15
+ model (Any): The language model to use.
16
+ tools (Optional[List[Any]]): List of tools to provide to the agent.
17
+ prompt (Optional[str]): Custom prompt template for the agent.
18
+ """
19
+
20
  def __init__(
21
+ self,
22
+ model: Any,
23
+ tools: Optional[List[Any]] = None,
24
+ prompt: Optional[str] = None,
25
  ):
26
  logger.info("Initializing Agent")
 
27
  self.model = model
 
28
  self.tools = tools
 
29
  self.imports = [
30
  "pandas",
31
  "numpy",
 
37
  "time",
38
  "re",
39
  "openpyxl",
40
+ "pathlib",
41
  ]
 
42
  self.agent = CodeAgent(
43
  model=self.model,
44
  tools=self.tools,
45
  add_base_tools=True,
46
  additional_authorized_imports=self.imports,
47
  )
 
48
  self.prompt = prompt or (
49
  """
50
  You are an advanced AI assistant specialized in solving complex, real-world tasks that require multi-step reasoning, factual accuracy, and use of external tools.
 
65
  ANSWER:
66
  """
67
  )
 
68
  logger.info("Agent initialized")
69
 
70
  def __call__(self, question: str, file_path: Optional[str] = None) -> str:
71
+ """
72
+ Run the agent to answer a question, optionally using a file as context.
73
+
74
+ Args:
75
+ question (str): The question to answer.
76
+ file_path (Optional[str]): Path to a file to use as context (if any).
77
+
78
+ Returns:
79
+ str: The agent's answer as a string.
80
+ """
81
  answer = self.agent.run(
82
  self.prompt.format(question=question, context=file_path)
83
  )
app.py CHANGED
@@ -1,6 +1,7 @@
1
  import inspect
2
  import os
3
  import tempfile
 
4
 
5
  import gradio as gr
6
  import pandas as pd
@@ -17,21 +18,19 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
17
 
18
  # --- Basic Agent Definition ---
19
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
20
- class BasicAgent:
21
- def __init__(self):
22
- print("BasicAgent initialized.")
23
 
24
- def __call__(self, question: str) -> str:
25
- print(f"Agent received question (first 50 chars): {question[:50]}...")
26
- fixed_answer = "This is a default answer."
27
- print(f"Agent returning fixed answer: {fixed_answer}")
28
- return fixed_answer
29
 
30
-
31
- def run_and_submit_all(profile: gr.OAuthProfile | None):
 
32
  """
33
- Fetches all questions, runs the BasicAgent on them, submits all answers,
34
- and displays the results.
 
 
 
 
 
35
  """
36
  # --- Determine HF Space Runtime URL and Repo URL ---
37
  space_id = "exsandebest/agent-course-final-assessment" # Get the SPACE_ID for sending link to the code
@@ -56,7 +55,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
56
  except Exception as e:
57
  print(f"Error instantiating agent: {e}")
58
  return f"Error initializing agent: {e}", None
59
- # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
60
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
61
  print(agent_code)
62
 
@@ -92,7 +91,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
92
  print(f"Skipping item with missing task_id or question: {item}")
93
  continue
94
  try:
95
- file_path = None
96
  try:
97
  file_response = requests.get(f"{files_url}/{task_id}", timeout=15)
98
  if file_response.status_code == 200 and file_response.content:
@@ -207,7 +206,6 @@ with gr.Blocks() as demo:
207
  status_output = gr.Textbox(
208
  label="Run Status / Submission Result", lines=5, interactive=False
209
  )
210
- # Removed max_rows=10 from DataFrame constructor
211
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
212
 
213
  run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
 
1
  import inspect
2
  import os
3
  import tempfile
4
+ from typing import Any, Optional, Tuple
5
 
6
  import gradio as gr
7
  import pandas as pd
 
18
 
19
  # --- Basic Agent Definition ---
20
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
 
 
 
21
 
 
 
 
 
 
22
 
23
+ def run_and_submit_all(
24
+ profile: Optional[gr.OAuthProfile],
25
+ ) -> Tuple[str, Optional[pd.DataFrame]]:
26
  """
27
+ Fetches all questions, runs the Agent on them, submits all answers, and displays the results.
28
+
29
+ Args:
30
+ profile (Optional[gr.OAuthProfile]): The OAuth profile of the user.
31
+
32
+ Returns:
33
+ Tuple[str, Optional[pd.DataFrame]]: Status message and DataFrame of results.
34
  """
35
  # --- Determine HF Space Runtime URL and Repo URL ---
36
  space_id = "exsandebest/agent-course-final-assessment" # Get the SPACE_ID for sending link to the code
 
55
  except Exception as e:
56
  print(f"Error instantiating agent: {e}")
57
  return f"Error initializing agent: {e}", None
58
+ # In the case of an app running as a hugging Face space, this link points toward your codebase (usefull for others so please keep it public)
59
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
60
  print(agent_code)
61
 
 
91
  print(f"Skipping item with missing task_id or question: {item}")
92
  continue
93
  try:
94
+ file_path: Optional[str] = None
95
  try:
96
  file_response = requests.get(f"{files_url}/{task_id}", timeout=15)
97
  if file_response.status_code == 200 and file_response.content:
 
206
  status_output = gr.Textbox(
207
  label="Run Status / Submission Result", lines=5, interactive=False
208
  )
 
209
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
210
 
211
  run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
model.py CHANGED
@@ -1,10 +1,20 @@
1
  import os
2
- from typing import Any
3
 
4
  from smolagents import HfApiModel, InferenceClientModel, LiteLLMModel, OpenAIServerModel
5
 
6
 
7
- def get_huggingface_api_model(model_id: str, **kwargs) -> Any:
 
 
 
 
 
 
 
 
 
 
8
  api_key = os.getenv("HUGGINGFACEHUB_API_TOKEN")
9
  if not api_key:
10
  raise ValueError("HUGGINGFACEHUB_API_TOKEN is not set")
@@ -12,7 +22,17 @@ def get_huggingface_api_model(model_id: str, **kwargs) -> Any:
12
  return HfApiModel(model_id=model_id, token=api_key, **kwargs)
13
 
14
 
15
- def get_inference_client_model(model_id: str, **kwargs) -> Any:
 
 
 
 
 
 
 
 
 
 
16
  api_key = os.getenv("HUGGINGFACEHUB_API_TOKEN")
17
  if not api_key:
18
  raise ValueError("HUGGINGFACEHUB_API_TOKEN is not set")
@@ -20,7 +40,17 @@ def get_inference_client_model(model_id: str, **kwargs) -> Any:
20
  return InferenceClientModel(model_id=model_id, token=api_key, **kwargs)
21
 
22
 
23
- def get_openai_server_model(model_id: str, **kwargs) -> Any:
 
 
 
 
 
 
 
 
 
 
24
  api_key = os.getenv("OPENAI_API_KEY")
25
  if not api_key:
26
  raise ValueError("OPENAI_API_KEY is not set")
@@ -34,13 +64,33 @@ def get_openai_server_model(model_id: str, **kwargs) -> Any:
34
  )
35
 
36
 
37
- def get_lite_llm_model(model_id: str, **kwargs) -> Any:
 
 
 
 
 
 
 
 
 
 
38
  return LiteLLMModel(model_id=model_id, **kwargs)
39
 
40
 
41
  def get_model(model_type: str, model_id: str, **kwargs) -> Any:
42
-
43
- models = {
 
 
 
 
 
 
 
 
 
 
44
  "HfApiModel": get_huggingface_api_model,
45
  "InferenceClientModel": get_inference_client_model,
46
  "OpenAIServerModel": get_openai_server_model,
 
1
  import os
2
+ from typing import Any, Callable
3
 
4
  from smolagents import HfApiModel, InferenceClientModel, LiteLLMModel, OpenAIServerModel
5
 
6
 
7
+ def get_huggingface_api_model(model_id: str, **kwargs) -> HfApiModel:
8
+ """
9
+ Returns a Hugging Face API model instance.
10
+
11
+ Args:
12
+ model_id (str): The model identifier.
13
+ **kwargs: Additional keyword arguments for the model.
14
+
15
+ Returns:
16
+ HfApiModel: Hugging Face API model instance.
17
+ """
18
  api_key = os.getenv("HUGGINGFACEHUB_API_TOKEN")
19
  if not api_key:
20
  raise ValueError("HUGGINGFACEHUB_API_TOKEN is not set")
 
22
  return HfApiModel(model_id=model_id, token=api_key, **kwargs)
23
 
24
 
25
+ def get_inference_client_model(model_id: str, **kwargs) -> InferenceClientModel:
26
+ """
27
+ Returns an Inference Client model instance.
28
+
29
+ Args:
30
+ model_id (str): The model identifier.
31
+ **kwargs: Additional keyword arguments for the model.
32
+
33
+ Returns:
34
+ InferenceClientModel: Inference client model instance.
35
+ """
36
  api_key = os.getenv("HUGGINGFACEHUB_API_TOKEN")
37
  if not api_key:
38
  raise ValueError("HUGGINGFACEHUB_API_TOKEN is not set")
 
40
  return InferenceClientModel(model_id=model_id, token=api_key, **kwargs)
41
 
42
 
43
+ def get_openai_server_model(model_id: str, **kwargs) -> OpenAIServerModel:
44
+ """
45
+ Returns an OpenAI server model instance.
46
+
47
+ Args:
48
+ model_id (str): The model identifier.
49
+ **kwargs: Additional keyword arguments for the model.
50
+
51
+ Returns:
52
+ OpenAIServerModel: OpenAI server model instance.
53
+ """
54
  api_key = os.getenv("OPENAI_API_KEY")
55
  if not api_key:
56
  raise ValueError("OPENAI_API_KEY is not set")
 
64
  )
65
 
66
 
67
+ def get_lite_llm_model(model_id: str, **kwargs) -> LiteLLMModel:
68
+ """
69
+ Returns a LiteLLM model instance.
70
+
71
+ Args:
72
+ model_id (str): The model identifier.
73
+ **kwargs: Additional keyword arguments for the model.
74
+
75
+ Returns:
76
+ LiteLLMModel: LiteLLM model instance.
77
+ """
78
  return LiteLLMModel(model_id=model_id, **kwargs)
79
 
80
 
81
  def get_model(model_type: str, model_id: str, **kwargs) -> Any:
82
+ """
83
+ Returns a model instance based on the specified type.
84
+
85
+ Args:
86
+ model_type (str): The type of the model (e.g., 'HfApiModel').
87
+ model_id (str): The model identifier.
88
+ **kwargs: Additional keyword arguments for the model.
89
+
90
+ Returns:
91
+ Any: Model instance of the specified type.
92
+ """
93
+ models: dict[str, Callable[..., Any]] = {
94
  "HfApiModel": get_huggingface_api_model,
95
  "InferenceClientModel": get_inference_client_model,
96
  "OpenAIServerModel": get_openai_server_model,
requirements.txt CHANGED
@@ -2,6 +2,8 @@ gradio
2
  numpy
3
  openpyxl
4
  pandas
 
 
5
  requests
6
  smolagents
7
  smolagents[audio]
 
2
  numpy
3
  openpyxl
4
  pandas
5
+ pillow
6
+ pytesseract
7
  requests
8
  smolagents
9
  smolagents[audio]
tools.py CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  from smolagents import (
2
  DuckDuckGoSearchTool,
3
  PythonInterpreterTool,
@@ -10,6 +14,16 @@ from youtube_transcript_api import YouTubeTranscriptApi
10
 
11
 
12
  class YouTubeTranscriptionTool(Tool):
 
 
 
 
 
 
 
 
 
 
13
  name = "youtube_transcription"
14
  description = "Fetches the transcript of a YouTube video given its URL"
15
  inputs = {
@@ -23,7 +37,56 @@ class YouTubeTranscriptionTool(Tool):
23
  return " ".join([entry["text"] for entry in transcript])
24
 
25
 
26
- def get_tools():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  tools = [
28
  DuckDuckGoSearchTool(),
29
  PythonInterpreterTool(),
@@ -31,5 +94,7 @@ def get_tools():
31
  VisitWebpageTool(),
32
  SpeechToTextTool(),
33
  YouTubeTranscriptionTool(),
 
 
34
  ]
35
  return tools
 
1
+ from typing import Any, List
2
+
3
+ import pytesseract
4
+ from PIL import Image
5
  from smolagents import (
6
  DuckDuckGoSearchTool,
7
  PythonInterpreterTool,
 
14
 
15
 
16
  class YouTubeTranscriptionTool(Tool):
17
+ """
18
+ Tool to fetch the transcript of a YouTube video given its URL.
19
+
20
+ Args:
21
+ video_url (str): YouTube video URL.
22
+
23
+ Returns:
24
+ str: Transcript of the video as a single string.
25
+ """
26
+
27
  name = "youtube_transcription"
28
  description = "Fetches the transcript of a YouTube video given its URL"
29
  inputs = {
 
37
  return " ".join([entry["text"] for entry in transcript])
38
 
39
 
40
+ class ReadFileTool(Tool):
41
+ """
42
+ Tool to read a file and return its content.
43
+
44
+ Args:
45
+ file_path (str): Path to the file to read.
46
+
47
+ Returns:
48
+ str: Content of the file or error message.
49
+ """
50
+
51
+ name = "read_file"
52
+ description = "Reads a file and returns its content"
53
+ inputs = {
54
+ "file_path": {"type": "string", "description": "Path to the file to read"},
55
+ }
56
+ output_type = "string"
57
+
58
+ def forward(self, file_path: str) -> str:
59
+ try:
60
+ with open(file_path, "r") as file:
61
+ return file.read()
62
+ except Exception as e:
63
+ return f"Error reading file: {str(e)}"
64
+
65
+
66
+ class ExtractTextFromImageTool(Tool):
67
+ name = "extract_text_from_image"
68
+ description = "Extracts text from an image using pytesseract"
69
+ inputs = {
70
+ "image_path": {"type": "string", "description": "Path to the image file"},
71
+ }
72
+ output_type = "string"
73
+
74
+ def forward(self, image_path: str) -> str:
75
+ try:
76
+ image = Image.open(image_path)
77
+ text = pytesseract.image_to_string(image)
78
+ return text
79
+ except Exception as e:
80
+ return f"Error extracting text from image: {str(e)}"
81
+
82
+
83
+ def get_tools() -> List[Tool]:
84
+ """
85
+ Returns a list of available tools for the agent.
86
+
87
+ Returns:
88
+ List[Tool]: List of initialized tool instances.
89
+ """
90
  tools = [
91
  DuckDuckGoSearchTool(),
92
  PythonInterpreterTool(),
 
94
  VisitWebpageTool(),
95
  SpeechToTextTool(),
96
  YouTubeTranscriptionTool(),
97
+ ReadFileTool(),
98
+ ExtractTextFromImageTool(),
99
  ]
100
  return tools
utils/logger.py CHANGED
@@ -3,7 +3,7 @@ import logging
3
 
4
  def get_logger(name: str = __name__) -> logging.Logger:
5
  """
6
- Create and configure a logger.
7
 
8
  Args:
9
  name (str, optional): Name of the logger. Defaults to the module name.
 
3
 
4
  def get_logger(name: str = __name__) -> logging.Logger:
5
  """
6
+ Create and configure a logger instance for the given module or name.
7
 
8
  Args:
9
  name (str, optional): Name of the logger. Defaults to the module name.