semioz commited on
Commit
13ba8fa
·
1 Parent(s): 81917a3
Files changed (8) hide show
  1. .gitignore +34 -0
  2. agent.py +103 -0
  3. app.py +5 -10
  4. pyproject.toml +30 -0
  5. requirements.txt +0 -2
  6. system_prompt.txt +9 -0
  7. tools.py +196 -0
  8. uv.lock +0 -0
.gitignore ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+
3
+
4
+ .Python
5
+ build/
6
+ venv/
7
+ ENV/
8
+ env/
9
+ .env
10
+ .venv
11
+ env.bak/
12
+ venv.bak/
13
+ .python-version
14
+
15
+ # IPython
16
+ profile_default/
17
+ ipython_config.py
18
+
19
+ # Logs
20
+ *.log
21
+ logs/
22
+ log/
23
+
24
+
25
+ .DS_Store
26
+ .project
27
+ .pydevproject
28
+
29
+ *.db
30
+ *.rdb
31
+
32
+ .env
33
+
34
+ .ruff_cache
agent.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+
3
+ from langchain_core.messages import HumanMessage, SystemMessage
4
+ from langchain_groq import ChatGroq
5
+ from langchain_huggingface import (
6
+ ChatHuggingFace,
7
+ HuggingFaceEmbeddings,
8
+ HuggingFaceEndpoint,
9
+ )
10
+ from langgraph.graph import START, MessagesState, StateGraph
11
+ from langgraph.prebuilt import ToolNode, tools_condition
12
+
13
+ from tools import tools
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ # ----- Initializing vector store and retriever tool -------
19
+
20
+ with open("system_prompt.txt", encoding="utf-8") as f:
21
+ system_prompt = f.read()
22
+ print(system_prompt)
23
+
24
+ sys_msg = SystemMessage(content=system_prompt)
25
+
26
+ embeddings = HuggingFaceEmbeddings(
27
+ model_name="sentence-transformers/all-mpnet-base-v2"
28
+ ) # dim=768
29
+
30
+ '''
31
+ supabase: Client = create_client(
32
+ os.environ.get("SUPABASE_URL"), os.environ.get("SUPABASE_SERVICE_ROLE_KEY")
33
+ )
34
+ vector_store = SupabaseVectorStore(
35
+ client=supabase,
36
+ embedding=embeddings,
37
+ table_name="documents2",
38
+ query_name="match_documents_2",
39
+ )
40
+ create_retriever_tool = create_retriever_tool(
41
+ retriever=vector_store.as_retriever(),
42
+ name="Question Search",
43
+ description="A tool to retrieve similar questions from a vector store.",
44
+ )
45
+ '''
46
+
47
+ def build_graph(provider: str = "groq"):
48
+ """Build the graph"""
49
+ if provider == "groq":
50
+ llm = ChatGroq(model="qwen/qwen3-32b", temperature=0)
51
+ elif provider == "huggingface":
52
+ llm = ChatHuggingFace(
53
+ llm=HuggingFaceEndpoint(
54
+ repo_id="TinyLlama/TinyLlama-1.1B-Chat-v1.0",
55
+ task="text-generation",
56
+ max_new_tokens=1024,
57
+ temperature=0,
58
+ ),
59
+ verbose=True,
60
+ )
61
+ else:
62
+ raise ValueError("Invalid provider. Choose 'groq' or 'huggingface'.")
63
+ llm_with_tools = llm.bind_tools(tools)
64
+
65
+ # Node
66
+ def assistant(state: MessagesState):
67
+ """Assistant node"""
68
+ return {"messages": [llm_with_tools.invoke(state["messages"])]}
69
+
70
+ def retriever(state: MessagesState):
71
+ """Retriever node"""
72
+ similar_question = vector_store.similarity_search(state["messages"][0].content)
73
+
74
+ if similar_question:
75
+ example_msg = HumanMessage(
76
+ content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
77
+ )
78
+ return {"messages": [sys_msg] + state["messages"] + [example_msg]}
79
+ # no similar questions are found
80
+ return {"messages": [sys_msg] + state["messages"]}
81
+
82
+ builder = StateGraph(MessagesState)
83
+ builder.add_node("retriever", retriever)
84
+ builder.add_node("assistant", assistant)
85
+ builder.add_node("tools", ToolNode(tools))
86
+ builder.add_edge(START, "retriever")
87
+ builder.add_edge("retriever", "assistant")
88
+ builder.add_conditional_edges(
89
+ "assistant",
90
+ tools_condition,
91
+ )
92
+ builder.add_edge("tools", "assistant")
93
+
94
+ return builder.compile()
95
+
96
+
97
+ if __name__ == "__main__":
98
+ question = "If Ada Lovelace was born in 1815 and Charles Babbage died in 1871, how old was she when he died?"
99
+ graph = build_graph(provider="groq")
100
+ messages = [HumanMessage(content=question)]
101
+ messages = graph.invoke({"messages": messages})
102
+ for m in messages["messages"]:
103
+ m.pretty_print()
app.py CHANGED
@@ -1,15 +1,12 @@
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.")
@@ -91,7 +88,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
91
  print("Agent did not produce any answers to submit.")
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)
@@ -140,7 +137,6 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
140
  return status_message, results_df
141
 
142
 
143
- # --- Build Gradio Interface using Blocks ---
144
  with gr.Blocks() as demo:
145
  gr.Markdown("# Basic Agent Evaluation Runner")
146
  gr.Markdown(
@@ -163,7 +159,6 @@ with gr.Blocks() as demo:
163
  run_button = gr.Button("Run Evaluation & Submit All Answers")
164
 
165
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
166
- # Removed max_rows=10 from DataFrame constructor
167
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
168
 
169
  run_button.click(
@@ -193,4 +188,4 @@ if __name__ == "__main__":
193
  print("-"*(60 + len(" App Starting ")) + "\n")
194
 
195
  print("Launching Gradio Interface for Basic Agent Evaluation...")
196
- demo.launch(debug=True, share=False)
 
1
  import os
2
+
3
  import gradio as gr
 
 
4
  import pandas as pd
5
+ import requests
6
 
 
 
7
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
8
 
9
+ # --- Agent Definition ---
 
10
  class BasicAgent:
11
  def __init__(self):
12
  print("BasicAgent initialized.")
 
88
  print("Agent did not produce any answers to submit.")
89
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
90
 
91
+ # 4. Prepare Submission
92
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
93
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
94
  print(status_update)
 
137
  return status_message, results_df
138
 
139
 
 
140
  with gr.Blocks() as demo:
141
  gr.Markdown("# Basic Agent Evaluation Runner")
142
  gr.Markdown(
 
159
  run_button = gr.Button("Run Evaluation & Submit All Answers")
160
 
161
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
 
162
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
163
 
164
  run_button.click(
 
188
  print("-"*(60 + len(" App Starting ")) + "\n")
189
 
190
  print("Launching Gradio Interface for Basic Agent Evaluation...")
191
+ demo.launch(debug=True, share=False)
pyproject.toml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "infersense"
3
+ version = "0.1.0"
4
+ authors = [{ name = "Semih Berkay Ozturk" }]
5
+ dependencies = [
6
+ "gradio>=5.38.1",
7
+ "langchain-community>=0.3.27",
8
+ "langchain-groq>=0.3.6",
9
+ "langchain-huggingface>=0.3.1",
10
+ "langgraph>=0.5.4",
11
+ "polars>=1.31.0",
12
+ "pytesseract>=0.3.13",
13
+ ]
14
+
15
+ [tool.ruff.lint]
16
+ extend-select = [
17
+ "F", # Pyflakes rules
18
+ "W", # PyCodeStyle warnings
19
+ "E", # PyCodeStyle errors
20
+ "I", # Sort imports properly
21
+ "UP", # Warn if certain things can changed due to newer Python versions
22
+ "C4", # Catch incorrect use of comprehensions, dict, list, etc
23
+ "FA", # Enforce from __future__ import annotations
24
+ "ISC", # Good use of string concatenation
25
+ "ICN", # Use common import conventions
26
+ "RET", # Good return practices
27
+ "SIM", # Common simplification rules
28
+ "TID", # Some good import practices
29
+ "TC", # Enforce importing certain types in a TYPE_CHECKING block
30
+ ]
requirements.txt DELETED
@@ -1,2 +0,0 @@
1
- gradio
2
- requests
 
 
 
system_prompt.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ You are an helpful assistant tasked with answering questions precisely and concisely by reasoning and, when needed, using available tools to find information.
2
+ When given a question, think carefully, decide if a tool call is necessary, and use tools to gather information before answering.
3
+ Once you have enough information, respond with only the final answer in this format:
4
+ FINAL ANSWER: [YOUR FINAL ANSWER]
5
+ Your FINAL ANSWER should be a number OR a few words OR a comma-separated list of numbers and/or words.
6
+ If a number is requested, do not include commas, currency symbols, or units unless explicitly asked.
7
+ If a string is requested, avoid articles and abbreviations, and write digits as plain text unless specified otherwise.
8
+ For lists, apply these rules to each element accordingly.
9
+ Only output the line starting with "FINAL ANSWER:" followed immediately by your answer, nothing else.
tools.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import polars as pl
2
+ import pytesseract
3
+ from langchain_community.document_loaders import ArxivLoader, WikipediaLoader
4
+ from langchain_community.tools.tavily_search import TavilySearchResults
5
+ from langchain_core.tools import tool
6
+ from PIL import Image
7
+
8
+ # --------- Basic Math tools ---------
9
+
10
+ @tool
11
+ def add(a: float, b: float) -> float:
12
+ """
13
+ Add two numbers.
14
+ Args:
15
+ a (float): the first number
16
+ b (float): the second number
17
+ """
18
+ return a + b
19
+
20
+ @tool
21
+ def subtract(a: float, b: float) -> int:
22
+ """
23
+ Subtract two numbers.
24
+ Args:
25
+ a (float): the first number
26
+ b (float): the second number
27
+ """
28
+ return a - b
29
+
30
+ @tool
31
+ def multiply(a: float, b: float) -> float:
32
+ """
33
+ Multiplies two numbers.
34
+ Args:
35
+ a (float): the first number
36
+ b (float): the second number
37
+ """
38
+ return a * b
39
+
40
+ @tool
41
+ def divide(a: float, b: float) -> float:
42
+ """
43
+ Divides two numbers.
44
+ Args:
45
+ a (float): the first float number
46
+ b (float): the second float number
47
+ """
48
+ if b == 0:
49
+ raise ValueError("Cannot divided by zero.")
50
+ return a / b
51
+
52
+
53
+ @tool
54
+ def modulus(a: int, b: int) -> int:
55
+ """
56
+ Get the modulus of two numbers.
57
+ Args:
58
+ a (int): the first number
59
+ b (int): the second number
60
+ """
61
+ return a % b
62
+
63
+
64
+ @tool
65
+ def power(a: float, b: float) -> float:
66
+ """
67
+ Get the power of two numbers.
68
+ Args:
69
+ a (float): the first number
70
+ b (float): the second number
71
+ """
72
+ return a**b
73
+
74
+ # ------- Search Tools -------
75
+
76
+ @tool
77
+ def arxiv_search(query: str) -> str:
78
+ """Search Arxiv for a query and return maximum 3 result.
79
+ Args:
80
+ query: The search query."""
81
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
82
+ formatted_search_docs = "\n\n---\n\n".join(
83
+ [
84
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
85
+ for doc in search_docs
86
+ ]
87
+ )
88
+ return {"arxiv_results": formatted_search_docs}
89
+
90
+ @tool
91
+ def web_search(query: str) -> str:
92
+ """Search the Web via Tavily for a query and return 3 results in maximum.
93
+ Args:
94
+ query: The search query."""
95
+ search_docs = TavilySearchResults(max_results=3).invoke(query)
96
+ formatted_search_docs = "\n\n---\n\n".join(
97
+ [
98
+ f'<Document source="{doc.metadata.get("url", "")}" title="{doc.get("title", "")}"/>\n{doc.get("content", "")}\n</Document>'
99
+ for doc in search_docs
100
+ ]
101
+ )
102
+ return {"web_results": formatted_search_docs}
103
+
104
+ @tool
105
+ def wikipedia_search(query: str) -> str:
106
+ """Search Wikipedia for a query and return maximum 3 results.
107
+ Args:
108
+ query: The search query."""
109
+ search_docs = WikipediaLoader(query=query, load_max_docs=3).load()
110
+ formatted_search_docs = "\n\n---\n\n".join(
111
+ [
112
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
113
+ for doc in search_docs
114
+ ])
115
+ return {"wiki_results": formatted_search_docs}
116
+
117
+ # ------ Document Processing Tools ------
118
+
119
+ @tool
120
+ def extract_text_from_image(image_path: str) -> str:
121
+ """
122
+ Extract text from an image by using pytesseract via OCR.
123
+ Args:
124
+ image_path (str): the path to the image file.
125
+ """
126
+ try:
127
+ image = Image.open(image_path)
128
+ text = pytesseract.image_to_string(image)
129
+
130
+ return f"Extracted the text from image:\n\n{text}"
131
+ except Exception as e:
132
+ return f"Error extracting text from image: {str(e)}"
133
+
134
+
135
+ @tool
136
+ def analyze_csv_file(file_path: str, query: str) -> str:
137
+ """
138
+ Analyze a CSV file by using Polars and answer a question about it.
139
+ Args:
140
+ file_path (str): the path to the CSV file.
141
+ query (str): Question about the data
142
+ """
143
+ try:
144
+ df = pl.read_csv(file_path)
145
+
146
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
147
+ result += f"Columns: {', '.join(df.columns)}\n\n"
148
+
149
+ result += "Summary statistics:\n"
150
+ result += str(df.describe())
151
+
152
+ return result
153
+
154
+ except Exception as e:
155
+ return f"Error occured analyzing CSV file: {str(e)}"
156
+
157
+
158
+ @tool
159
+ def analyze_excel_file(file_path: str, query: str) -> str:
160
+ """
161
+ Analyze an Excel file using Polars and answer a question about it.
162
+ Args:
163
+ file_path (str): the path to the Excel file.
164
+ query (str): Question about the data
165
+ """
166
+ try:
167
+ df = pl.read_excel(file_path)
168
+
169
+ result = (
170
+ f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
171
+ )
172
+ result += f"Columns: {', '.join(df.columns)}\n\n"
173
+
174
+ result += "Summary statistics:\n"
175
+ result += str(df.describe())
176
+
177
+ return result
178
+
179
+ except Exception as e:
180
+ return f"Error occured analyzing Excel file: {str(e)}"
181
+
182
+
183
+ tools = [
184
+ multiply,
185
+ add,
186
+ subtract,
187
+ divide,
188
+ modulus,
189
+ power,
190
+ web_search,
191
+ wikipedia_search,
192
+ arxiv_search,
193
+ extract_text_from_image,
194
+ analyze_csv_file,
195
+ analyze_excel_file,
196
+ ]
uv.lock ADDED
The diff for this file is too large to render. See raw diff