Hong0518 commited on
Commit
cc64171
·
1 Parent(s): 81917a3

final version

Browse files
Files changed (7) hide show
  1. .gitignore +5 -0
  2. agent.py +202 -0
  3. app.py +13 -6
  4. metadata.jsonl +0 -0
  5. supabase_docs.csv +0 -0
  6. system_prompt.txt +24 -0
  7. test.ipynb +675 -0
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .python-version
2
+ pyproject.toml
3
+ poetry.lock
4
+ .venv
5
+ .env
agent.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from langgraph.graph import START, StateGraph, MessagesState
4
+ from langgraph.prebuilt import tools_condition, ToolNode
5
+ from langchain_google_genai import ChatGoogleGenerativeAI
6
+ from langchain_groq import ChatGroq
7
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint, HuggingFaceEmbeddings
8
+ from langchain_community.tools.tavily_search import TavilySearchResults
9
+ from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
10
+ from langchain_community.vectorstores import SupabaseVectorStore
11
+ from langchain_core.messages import SystemMessage, HumanMessage
12
+ from langchain_core.tools import tool
13
+ from langchain.tools.retriever import create_retriever_tool
14
+ from supabase.client import Client, create_client
15
+
16
+ load_dotenv()
17
+
18
+ @tool
19
+ def multiply(a: int, b: int) -> int:
20
+ """
21
+ Multiplies two numbers.
22
+
23
+ Args:
24
+ a (int): The first number.
25
+ b (int): The second number.
26
+ Returns:
27
+ int: The product of the two numbers.
28
+ Example:
29
+ >>> multiply(2, 3)
30
+ 6
31
+ """
32
+ return a * b
33
+
34
+ @tool
35
+ def add(a: int, b: int) -> int:
36
+ """
37
+ Adds two numbers.
38
+
39
+ Args:
40
+ a (int): The first number.
41
+ b (int): The second number.
42
+ Returns:
43
+ int: The sum of the two numbers.
44
+ Example:
45
+ >>> add(2, 3)
46
+ 5
47
+ """
48
+ return a + b
49
+
50
+ @tool
51
+ def subtract(a: int, b: int) -> int:
52
+ """
53
+ Subtracts two numbers.
54
+
55
+ Args:
56
+ a (int): The first number.
57
+ b (int): The second number.
58
+ Returns:
59
+ int: The difference of the two numbers.
60
+ Example:
61
+ >>> subtract(5, 3)
62
+ 2
63
+ """
64
+ return a - b
65
+
66
+ @tool
67
+ def divide(a: int, b: int) -> float:
68
+ """
69
+ Divides two numbers.
70
+
71
+ Args:
72
+ a (int): The first number.
73
+ b (int): The second number.
74
+ Returns:
75
+ float: The quotient of the two numbers.
76
+ Example:
77
+ >>> divide(6, 3)
78
+ 2.0
79
+ """
80
+ if b == 0:
81
+ raise ValueError("Cannot divide by zero.")
82
+ return a / b
83
+
84
+ @tool
85
+ def modulus(a: int, b: int) -> int:
86
+ """
87
+ Calculates the modulus of two numbers.
88
+
89
+ Args:
90
+ a (int): The first number.
91
+ b (int): The second number.
92
+ Returns:
93
+ int: The modulus of the two numbers.
94
+ Example:
95
+ >>> modulus(5, 3)
96
+ 2
97
+ """
98
+ return a % b
99
+
100
+ @tool
101
+ def wiki_search(query: str) -> str:
102
+ """
103
+ Searches Wikipedia for a given query and returns maximum 3 results.
104
+
105
+ Args:
106
+ query (str): The search query.
107
+ """
108
+ search_docs = WikipediaLoader(max_results=3).invoke(query)
109
+ formatted_search_docs = "\n\n---\n\n".join([
110
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
111
+ for doc in search_docs
112
+ ])
113
+ return {"web_results": formatted_search_docs}
114
+
115
+ @tool
116
+ def arxiv_search(query: str) -> str:
117
+ """
118
+ Searches Arxiv for a given query and returns maximum 3 results.
119
+
120
+ Args:
121
+ query (str): The search query.
122
+ """
123
+ search_docs = ArxivLoader(max_results=3, load_max_docs=3).load()
124
+ formatted_search_docs = "\n\n---\n\n".join([
125
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
126
+ for doc in search_docs
127
+ ])
128
+ return {"arvix_results": formatted_search_docs}
129
+
130
+ with open("system_prompt.txt", "r", encoding='utf8') as f:
131
+ system_prompt = f.read()
132
+
133
+ sys_msg = SystemMessage(content=system_prompt)
134
+
135
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
136
+ supabase: Client = create_client(
137
+ os.environ.get("SUPABASE_URL"),
138
+ os.environ.get("SUPABASE_SERVICE_KEY")
139
+ )
140
+
141
+ vector_store = SupabaseVectorStore(
142
+ client=supabase,
143
+ embedding= embeddings,
144
+ table_name="documents",
145
+ query_name="match_documents_langchain",
146
+ )
147
+ create_retriever_tool = create_retriever_tool(
148
+ retriever=vector_store.as_retriever(),
149
+ name="Question Search",
150
+ description="A tool to retrieve similar questions from a vector store."
151
+ )
152
+
153
+ tools = [multiply, add, subtract, divide, modulus, wiki_search, arxiv_search, create_retriever_tool]
154
+
155
+ def build_graph(provider: str = 'groq'):
156
+ """Builds the graph for the agent."""
157
+ if provider == 'google':
158
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
159
+ elif provider == 'groq':
160
+ llm = ChatGroq(model="qwen-qwq-32b", temperature=0)
161
+ elif provider == 'huggingface':
162
+ llm = ChatHuggingFace(
163
+ llm=HuggingFaceEndpoint(
164
+ url="https://api-inference.huggingface.co/models/Meta-DeepLearning/llama-2-7b-chat-hf",
165
+ temperature=0,
166
+ ),
167
+ )
168
+ else:
169
+ raise ValueError("Invalid provider. Choose 'google', 'groq', or 'huggingface'.")
170
+
171
+ llm_with_tools = llm.bind_tools(tools)
172
+
173
+ def assistant(state: MessagesState):
174
+ """Assistant node"""
175
+ return {"messages": [llm_with_tools.invoke(state["messages"])]}
176
+
177
+ def retriever(state: MessagesState):
178
+ """Retriever node"""
179
+ similar_questions = vector_store.similarity_search(state["messages"][0].content)
180
+ example_msg = HumanMessage(
181
+ content=f"Here I provide a similar question to answer for reference: \n\n{similar_questions[0].page_content}"
182
+ )
183
+ return {"messages": [sys_msg] + state["messages"] + [example_msg]}
184
+
185
+ builder = StateGraph(MessagesState)
186
+ builder.add_node("retriever", retriever)
187
+ builder.add_node("assistant", assistant)
188
+ builder.add_node("tools", ToolNode(tools))
189
+ builder.add_edge(START, "retriever")
190
+ builder.add_edge("retriever", "assistant")
191
+ builder.add_conditional_edges("assistant", tools_condition)
192
+ builder.add_edge("tools", "assistant")
193
+
194
+ return builder.compile()
195
+
196
+ if __name__ == "__main__":
197
+ question = "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect?"
198
+ graph = build_graph(provider='groq')
199
+ messages = [HumanMessage(content=question)]
200
+ messages = graph.invoke({"messages": messages})
201
+ for m in messages["messages"]:
202
+ m.pretty_print()
app.py CHANGED
@@ -1,8 +1,13 @@
 
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 ---
@@ -11,13 +16,17 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
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.")
 
16
  def __call__(self, question: str) -> str:
17
  print(f"Agent received question (first 50 chars): {question[:50]}...")
18
- fixed_answer = "This is a default answer."
19
- print(f"Agent returning fixed answer: {fixed_answer}")
20
- return fixed_answer
 
 
21
 
22
  def run_and_submit_all( profile: gr.OAuthProfile | None):
23
  """
@@ -146,11 +155,9 @@ with gr.Blocks() as demo:
146
  gr.Markdown(
147
  """
148
  **Instructions:**
149
-
150
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
151
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
152
  3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
153
-
154
  ---
155
  **Disclaimers:**
156
  Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
 
1
+ """ Basic Agent Evaluation Runner"""
2
  import os
3
+ import inspect
4
  import gradio as gr
5
  import requests
 
6
  import pandas as pd
7
+ from langchain_core.messages import HumanMessage
8
+ from agent import build_graph
9
+
10
+
11
 
12
  # (Keep Constants as is)
13
  # --- Constants ---
 
16
  # --- Basic Agent Definition ---
17
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
18
  class BasicAgent:
19
+ """A langgraph agent."""
20
  def __init__(self):
21
  print("BasicAgent initialized.")
22
+ self.graph = build_graph()
23
  def __call__(self, question: str) -> str:
24
  print(f"Agent received question (first 50 chars): {question[:50]}...")
25
+ # Wrap the question in a HumanMessage from langchain_core
26
+ messages = [HumanMessage(content=question)]
27
+ messages = self.graph.invoke({"messages": messages})
28
+ answer = messages['messages'][-1].content
29
+ return answer[14:]
30
 
31
  def run_and_submit_all( profile: gr.OAuthProfile | None):
32
  """
 
155
  gr.Markdown(
156
  """
157
  **Instructions:**
 
158
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
159
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
160
  3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
 
161
  ---
162
  **Disclaimers:**
163
  Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
metadata.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
supabase_docs.csv ADDED
The diff for this file is too large to render. See raw diff
 
system_prompt.txt ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ You are a helpful assistant tasked with answering questions using a set of tools.
3
+ If the tool is not available, you can try to find the information online. You can also use your own knowledge to answer the question.
4
+ You need to provide a step-by-step explanation of how you arrived at the answer.
5
+ ==========================
6
+ Here is a few examples showing you how to answer the question step by step.
7
+
8
+ Question 1: In the year 2022, and before December, what does "R" stand for in the three core policies of the type of content that was violated in the public logs on the Legume Wikipedia page?
9
+ Steps:
10
+ 1. Searched "legume wikipedia" on Google.
11
+ 2. Opened "Legume" on Wikipedia.
12
+ 3. Clicked "View history".
13
+ 4. Clicked "View logs for this page".
14
+ 5. Checked all types of logs.
15
+ 6. Set the date to November 2022.
16
+ 7. Followed the BLP link of the violation.
17
+ 8. Noted the meaning of "R".
18
+ Tools:
19
+ 1. Web browser
20
+ 2. Search engine
21
+ Final Answer: research
22
+
23
+ ==========================
24
+ Now, please answer the following question step by step.
test.ipynb ADDED
@@ -0,0 +1,675 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "d0cc4adf",
6
+ "metadata": {},
7
+ "source": [
8
+ "### Question data"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": 23,
14
+ "id": "14e3f417",
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "# Load metadata.jsonl\n",
19
+ "import json\n",
20
+ "# Load the metadata.jsonl file\n",
21
+ "with open('metadata.jsonl', 'r') as jsonl_file:\n",
22
+ " json_list = list(jsonl_file)\n",
23
+ "\n",
24
+ "json_QA = []\n",
25
+ "for json_str in json_list:\n",
26
+ " json_data = json.loads(json_str)\n",
27
+ " json_QA.append(json_data)"
28
+ ]
29
+ },
30
+ {
31
+ "cell_type": "code",
32
+ "execution_count": 24,
33
+ "id": "5e2da6fc",
34
+ "metadata": {},
35
+ "outputs": [
36
+ {
37
+ "name": "stdout",
38
+ "output_type": "stream",
39
+ "text": [
40
+ "==================================================\n",
41
+ "Task ID: 935e2cff-ae78-4218-b3f5-115589b19dae\n",
42
+ "Question: In the year 2022, and before December, what does \"R\" stand for in the three core policies of the type of content that was violated in the public logs on the Legume Wikipedia page?\n",
43
+ "Level: 1\n",
44
+ "Final Answer: research\n",
45
+ "Annotator Metadata: \n",
46
+ " ├── Steps: \n",
47
+ " │ ├── 1. Searched \"legume wikipedia\" on Google.\n",
48
+ " │ ├── 2. Opened \"Legume\" on Wikipedia.\n",
49
+ " │ ├── 3. Clicked \"View history\".\n",
50
+ " │ ├── 4. Clicked \"View logs for this page\".\n",
51
+ " │ ├── 5. Checked all types of logs.\n",
52
+ " │ ├── 6. Set the date to November 2022.\n",
53
+ " │ ├── 7. Followed the BLP link of the violation.\n",
54
+ " │ ├── 8. Noted the meaning of \"R\".\n",
55
+ " ├── Number of steps: 8\n",
56
+ " ├── How long did this take?: 10 minutes\n",
57
+ " ├── Tools:\n",
58
+ " │ ├── 1. Web browser\n",
59
+ " │ ├── 2. Search engine\n",
60
+ " └── Number of tools: 2\n",
61
+ "==================================================\n"
62
+ ]
63
+ }
64
+ ],
65
+ "source": [
66
+ "# randomly select 3 samples\n",
67
+ "# {\"task_id\": \"c61d22de-5f6c-4958-a7f6-5e9707bd3466\", \"Question\": \"A paper about AI regulation that was originally submitted to arXiv.org in June 2022 shows a figure with three axes, where each axis has a label word at both ends. Which of these words is used to describe a type of society in a Physics and Society article submitted to arXiv.org on August 11, 2016?\", \"Level\": 2, \"Final answer\": \"egalitarian\", \"file_name\": \"\", \"Annotator Metadata\": {\"Steps\": \"1. Go to arxiv.org and navigate to the Advanced Search page.\\n2. Enter \\\"AI regulation\\\" in the search box and select \\\"All fields\\\" from the dropdown.\\n3. Enter 2022-06-01 and 2022-07-01 into the date inputs, select \\\"Submission date (original)\\\", and submit the search.\\n4. Go through the search results to find the article that has a figure with three axes and labels on each end of the axes, titled \\\"Fairness in Agreement With European Values: An Interdisciplinary Perspective on AI Regulation\\\".\\n5. Note the six words used as labels: deontological, egalitarian, localized, standardized, utilitarian, and consequential.\\n6. Go back to arxiv.org\\n7. Find \\\"Physics and Society\\\" and go to the page for the \\\"Physics and Society\\\" category.\\n8. Note that the tag for this category is \\\"physics.soc-ph\\\".\\n9. Go to the Advanced Search page.\\n10. Enter \\\"physics.soc-ph\\\" in the search box and select \\\"All fields\\\" from the dropdown.\\n11. Enter 2016-08-11 and 2016-08-12 into the date inputs, select \\\"Submission date (original)\\\", and submit the search.\\n12. Search for instances of the six words in the results to find the paper titled \\\"Phase transition from egalitarian to hierarchical societies driven by competition between cognitive and social constraints\\\", indicating that \\\"egalitarian\\\" is the correct answer.\", \"Number of steps\": \"12\", \"How long did this take?\": \"8 minutes\", \"Tools\": \"1. Web browser\\n2. Image recognition tools (to identify and parse a figure with three axes)\", \"Number of tools\": \"2\"}}\n",
68
+ "\n",
69
+ "import random\n",
70
+ "# random.seed(42)\n",
71
+ "random_samples = random.sample(json_QA, 1)\n",
72
+ "for sample in random_samples:\n",
73
+ " print(\"=\" * 50)\n",
74
+ " print(f\"Task ID: {sample['task_id']}\")\n",
75
+ " print(f\"Question: {sample['Question']}\")\n",
76
+ " print(f\"Level: {sample['Level']}\")\n",
77
+ " print(f\"Final Answer: {sample['Final answer']}\")\n",
78
+ " print(f\"Annotator Metadata: \")\n",
79
+ " print(f\" ├── Steps: \")\n",
80
+ " for step in sample['Annotator Metadata']['Steps'].split('\\n'):\n",
81
+ " print(f\" │ ├── {step}\")\n",
82
+ " print(f\" ├── Number of steps: {sample['Annotator Metadata']['Number of steps']}\")\n",
83
+ " print(f\" ├── How long did this take?: {sample['Annotator Metadata']['How long did this take?']}\")\n",
84
+ " print(f\" ├── Tools:\")\n",
85
+ " for tool in sample['Annotator Metadata']['Tools'].split('\\n'):\n",
86
+ " print(f\" │ ├── {tool}\")\n",
87
+ " print(f\" └── Number of tools: {sample['Annotator Metadata']['Number of tools']}\")\n",
88
+ "print(\"=\" * 50)"
89
+ ]
90
+ },
91
+ {
92
+ "cell_type": "code",
93
+ "execution_count": 25,
94
+ "id": "4bb02420",
95
+ "metadata": {},
96
+ "outputs": [],
97
+ "source": [
98
+ "### build a vector database based on the metadata.jsonl\n",
99
+ "# https://python.langchain.com/docs/integrations/vectorstores/supabase/\n",
100
+ "import os\n",
101
+ "from dotenv import load_dotenv\n",
102
+ "from langchain_huggingface import HuggingFaceEmbeddings\n",
103
+ "from langchain_community.vectorstores import SupabaseVectorStore\n",
104
+ "from supabase.client import Client, create_client\n",
105
+ "\n",
106
+ "\n",
107
+ "load_dotenv()\n",
108
+ "embeddings = HuggingFaceEmbeddings(model_name=\"sentence-transformers/all-mpnet-base-v2\") # dim=768\n",
109
+ "\n",
110
+ "supabase_url = os.environ.get(\"SUPABASE_URL\")\n",
111
+ "supabase_key = os.environ.get(\"SUPABASE_SERVICE_KEY\")\n",
112
+ "supabase: Client = create_client(supabase_url, supabase_key)"
113
+ ]
114
+ },
115
+ {
116
+ "cell_type": "code",
117
+ "execution_count": 26,
118
+ "id": "a070b955",
119
+ "metadata": {},
120
+ "outputs": [
121
+ {
122
+ "name": "stdout",
123
+ "output_type": "stream",
124
+ "text": [
125
+ "Error inserting data into Supabase: {'code': '42501', 'details': None, 'hint': None, 'message': 'new row violates row-level security policy for table \"documents\"'}\n"
126
+ ]
127
+ }
128
+ ],
129
+ "source": [
130
+ "# wrap the metadata.jsonl's questions and answers into a list of document\n",
131
+ "from langchain.schema import Document\n",
132
+ "docs = []\n",
133
+ "for sample in json_QA:\n",
134
+ " content = f\"Question : {sample['Question']}\\n\\nFinal answer : {sample['Final answer']}\"\n",
135
+ " doc = {\n",
136
+ " \"content\" : content,\n",
137
+ " \"metadata\" : { # meatadata的格式必须时source键,否则会报错\n",
138
+ " \"source\" : sample['task_id']\n",
139
+ " },\n",
140
+ " \"embedding\" : embeddings.embed_query(content),\n",
141
+ " }\n",
142
+ " docs.append(doc)\n",
143
+ "\n",
144
+ "# upload the documents to the vector database\n",
145
+ "try:\n",
146
+ " response = (\n",
147
+ " supabase.table(\"documents\")\n",
148
+ " .insert(docs)\n",
149
+ " .execute()\n",
150
+ " )\n",
151
+ "except Exception as exception:\n",
152
+ " print(\"Error inserting data into Supabase:\", exception)\n",
153
+ "\n",
154
+ "# ALTERNATIVE : Save the documents (a list of dict) into a csv file, and manually upload it to Supabase\n",
155
+ "# import pandas as pd\n",
156
+ "# df = pd.DataFrame(docs)\n",
157
+ "# df.to_csv('supabase_docs.csv', index=False)"
158
+ ]
159
+ },
160
+ {
161
+ "cell_type": "code",
162
+ "execution_count": 27,
163
+ "id": "77fb9dbb",
164
+ "metadata": {},
165
+ "outputs": [],
166
+ "source": [
167
+ "# add items to vector database\n",
168
+ "vector_store = SupabaseVectorStore(\n",
169
+ " client=supabase,\n",
170
+ " embedding= embeddings,\n",
171
+ " table_name=\"documents\",\n",
172
+ " query_name=\"match_documents_langchain\",\n",
173
+ ")\n",
174
+ "retriever = vector_store.as_retriever()"
175
+ ]
176
+ },
177
+ {
178
+ "cell_type": "code",
179
+ "execution_count": 28,
180
+ "id": "12a05971",
181
+ "metadata": {},
182
+ "outputs": [],
183
+ "source": [
184
+ "query = \"On June 6, 2023, an article by Carolyn Collins Petersen was published in Universe Today. This article mentions a team that produced a paper about their observations, linked at the bottom of the article. Find this paper. Under what NASA award number was the work performed by R. G. Arendt supported by?\"\n",
185
+ "matched_docs = vector_store.similarity_search(query, 2)\n",
186
+ "# docs = retriever.invoke(query)\n",
187
+ "# docs[0]"
188
+ ]
189
+ },
190
+ {
191
+ "cell_type": "code",
192
+ "execution_count": 29,
193
+ "id": "1eae5ba4",
194
+ "metadata": {},
195
+ "outputs": [
196
+ {
197
+ "name": "stdout",
198
+ "output_type": "stream",
199
+ "text": [
200
+ "List of tools used in all samples:\n",
201
+ "Total number of tools used: 83\n",
202
+ " ├── web browser: 107\n",
203
+ " ├── image recognition tools (to identify and parse a figure with three axes): 1\n",
204
+ " ├── search engine: 101\n",
205
+ " ├── calculator: 34\n",
206
+ " ├── unlambda compiler (optional): 1\n",
207
+ " ├── a web browser.: 2\n",
208
+ " ├── a search engine.: 2\n",
209
+ " ├── a calculator.: 1\n",
210
+ " ├── microsoft excel: 5\n",
211
+ " ├── google search: 1\n",
212
+ " ├── ne: 9\n",
213
+ " ├── pdf access: 7\n",
214
+ " ├── file handling: 2\n",
215
+ " ├── python: 3\n",
216
+ " ├── image recognition tools: 12\n",
217
+ " ├── jsonld file access: 1\n",
218
+ " ├── video parsing: 1\n",
219
+ " ├── python compiler: 1\n",
220
+ " ├── video recognition tools: 3\n",
221
+ " ├── pdf viewer: 7\n",
222
+ " ├── microsoft excel / google sheets: 3\n",
223
+ " ├── word document access: 1\n",
224
+ " ├── tool to extract text from images: 1\n",
225
+ " ├── a word reversal tool / script: 1\n",
226
+ " ├── counter: 1\n",
227
+ " ├── excel: 3\n",
228
+ " ├── image recognition: 5\n",
229
+ " ├── color recognition: 3\n",
230
+ " ├── excel file access: 3\n",
231
+ " ├── xml file access: 1\n",
232
+ " ├── access to the internet archive, web.archive.org: 1\n",
233
+ " ├── text processing/diff tool: 1\n",
234
+ " ├── gif parsing tools: 1\n",
235
+ " ├── a web browser: 7\n",
236
+ " ├── a search engine: 7\n",
237
+ " ├── a speech-to-text tool: 2\n",
238
+ " ├── code/data analysis tools: 1\n",
239
+ " ├── audio capability: 2\n",
240
+ " ├── pdf reader: 1\n",
241
+ " ├── markdown: 1\n",
242
+ " ├── a calculator: 5\n",
243
+ " ├── access to wikipedia: 3\n",
244
+ " ├── image recognition/ocr: 3\n",
245
+ " ├── google translate access: 1\n",
246
+ " ├── ocr: 4\n",
247
+ " ├── bass note data: 1\n",
248
+ " ├── text editor: 1\n",
249
+ " ├── xlsx file access: 1\n",
250
+ " ├── powerpoint viewer: 1\n",
251
+ " ├── csv file access: 1\n",
252
+ " ├── calculator (or use excel): 1\n",
253
+ " ├── computer algebra system: 1\n",
254
+ " ├── video processing software: 1\n",
255
+ " ├── audio processing software: 1\n",
256
+ " ├── computer vision: 1\n",
257
+ " ├── google maps: 1\n",
258
+ " ├── access to excel files: 1\n",
259
+ " ├── calculator (or ability to count): 1\n",
260
+ " ├── a file interface: 3\n",
261
+ " ├── a python ide: 1\n",
262
+ " ├── spreadsheet editor: 1\n",
263
+ " ├── tools required: 1\n",
264
+ " ├── b browser: 1\n",
265
+ " ├── image recognition and processing tools: 1\n",
266
+ " ├── computer vision or ocr: 1\n",
267
+ " ├── c++ compiler: 1\n",
268
+ " ├── access to google maps: 1\n",
269
+ " ├── youtube player: 1\n",
270
+ " ├── natural language processor: 1\n",
271
+ " ├── graph interaction tools: 1\n",
272
+ " ├── bablyonian cuniform -> arabic legend: 1\n",
273
+ " ├── access to youtube: 1\n",
274
+ " ├── image search tools: 1\n",
275
+ " ├── calculator or counting function: 1\n",
276
+ " ├── a speech-to-text audio processing tool: 1\n",
277
+ " ├── access to academic journal websites: 1\n",
278
+ " ├── pdf reader/extracter: 1\n",
279
+ " ├── rubik's cube model: 1\n",
280
+ " ├── wikipedia: 1\n",
281
+ " ├── video capability: 1\n",
282
+ " ├── image processing tools: 1\n",
283
+ " ├── age recognition software: 1\n",
284
+ " ├── youtube: 1\n"
285
+ ]
286
+ }
287
+ ],
288
+ "source": [
289
+ "# list of the tools used in all the samples\n",
290
+ "from collections import Counter, OrderedDict\n",
291
+ "\n",
292
+ "tools = []\n",
293
+ "for sample in json_QA:\n",
294
+ " for tool in sample['Annotator Metadata']['Tools'].split('\\n'):\n",
295
+ " tool = tool[2:].strip().lower()\n",
296
+ " if tool.startswith(\"(\"):\n",
297
+ " tool = tool[11:].strip()\n",
298
+ " tools.append(tool)\n",
299
+ "tools_counter = OrderedDict(Counter(tools))\n",
300
+ "print(\"List of tools used in all samples:\")\n",
301
+ "print(\"Total number of tools used:\", len(tools_counter))\n",
302
+ "for tool, count in tools_counter.items():\n",
303
+ " print(f\" ├── {tool}: {count}\")"
304
+ ]
305
+ },
306
+ {
307
+ "cell_type": "markdown",
308
+ "id": "5efee12a",
309
+ "metadata": {},
310
+ "source": [
311
+ "#### Graph"
312
+ ]
313
+ },
314
+ {
315
+ "cell_type": "code",
316
+ "execution_count": 30,
317
+ "id": "7fe573cc",
318
+ "metadata": {},
319
+ "outputs": [],
320
+ "source": [
321
+ "system_prompt = \"\"\"\n",
322
+ "You are a helpful assistant tasked with answering questions using a set of tools.\n",
323
+ "If the tool is not available, you can try to find the information online. You can also use your own knowledge to answer the question. \n",
324
+ "You need to provide a step-by-step explanation of how you arrived at the answer.\n",
325
+ "==========================\n",
326
+ "Here is a few examples showing you how to answer the question step by step.\n",
327
+ "\"\"\"\n",
328
+ "for i, samples in enumerate(random_samples):\n",
329
+ " system_prompt += f\"\\nQuestion {i+1}: {samples['Question']}\\nSteps:\\n{samples['Annotator Metadata']['Steps']}\\nTools:\\n{samples['Annotator Metadata']['Tools']}\\nFinal Answer: {samples['Final answer']}\\n\"\n",
330
+ "system_prompt += \"\\n==========================\\n\"\n",
331
+ "system_prompt += \"Now, please answer the following question step by step.\\n\"\n",
332
+ "\n",
333
+ "# save the system_prompt to a file\n",
334
+ "with open('system_prompt.txt', 'w') as f:\n",
335
+ " f.write(system_prompt)"
336
+ ]
337
+ },
338
+ {
339
+ "cell_type": "code",
340
+ "execution_count": 31,
341
+ "id": "d6beb0da",
342
+ "metadata": {},
343
+ "outputs": [
344
+ {
345
+ "name": "stdout",
346
+ "output_type": "stream",
347
+ "text": [
348
+ "\n",
349
+ "You are a helpful assistant tasked with answering questions using a set of tools.\n",
350
+ "If the tool is not available, you can try to find the information online. You can also use your own knowledge to answer the question. \n",
351
+ "You need to provide a step-by-step explanation of how you arrived at the answer.\n",
352
+ "==========================\n",
353
+ "Here is a few examples showing you how to answer the question step by step.\n",
354
+ "\n",
355
+ "Question 1: In the year 2022, and before December, what does \"R\" stand for in the three core policies of the type of content that was violated in the public logs on the Legume Wikipedia page?\n",
356
+ "Steps:\n",
357
+ "1. Searched \"legume wikipedia\" on Google.\n",
358
+ "2. Opened \"Legume\" on Wikipedia.\n",
359
+ "3. Clicked \"View history\".\n",
360
+ "4. Clicked \"View logs for this page\".\n",
361
+ "5. Checked all types of logs.\n",
362
+ "6. Set the date to November 2022.\n",
363
+ "7. Followed the BLP link of the violation.\n",
364
+ "8. Noted the meaning of \"R\".\n",
365
+ "Tools:\n",
366
+ "1. Web browser\n",
367
+ "2. Search engine\n",
368
+ "Final Answer: research\n",
369
+ "\n",
370
+ "==========================\n",
371
+ "Now, please answer the following question step by step.\n",
372
+ "\n"
373
+ ]
374
+ }
375
+ ],
376
+ "source": [
377
+ "# load the system prompt from the file\n",
378
+ "with open('system_prompt.txt', 'r') as f:\n",
379
+ " system_prompt = f.read()\n",
380
+ "print(system_prompt)"
381
+ ]
382
+ },
383
+ {
384
+ "cell_type": "code",
385
+ "execution_count": 32,
386
+ "id": "42fde0f8",
387
+ "metadata": {},
388
+ "outputs": [
389
+ {
390
+ "ename": "DefaultCredentialsError",
391
+ "evalue": "Your default credentials were not found. To set up Application Default Credentials, see https://cloud.google.com/docs/authentication/external/set-up-adc for more information.",
392
+ "output_type": "error",
393
+ "traceback": [
394
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
395
+ "\u001b[31mDefaultCredentialsError\u001b[39m Traceback (most recent call last)",
396
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[32]\u001b[39m\u001b[32m, line 156\u001b[39m\n\u001b[32m 142\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m {\u001b[33m\"\u001b[39m\u001b[33msimilar_questions\u001b[39m\u001b[33m\"\u001b[39m: formatted_search_docs}\n\u001b[32m 144\u001b[39m tools = [\n\u001b[32m 145\u001b[39m multiply,\n\u001b[32m 146\u001b[39m add,\n\u001b[32m (...)\u001b[39m\u001b[32m 153\u001b[39m question_retrieve_tool\n\u001b[32m 154\u001b[39m ]\n\u001b[32m--> \u001b[39m\u001b[32m156\u001b[39m llm = \u001b[43mChatGoogleGenerativeAI\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mgemini-2.0-flash\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 157\u001b[39m llm_with_tools = llm.bind_tools(tools)\n",
397
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Programming/RobotPai/.venv/lib/python3.13/site-packages/langchain_google_genai/chat_models.py:1053\u001b[39m, in \u001b[36mChatGoogleGenerativeAI.__init__\u001b[39m\u001b[34m(self, **kwargs)\u001b[39m\n\u001b[32m 1046\u001b[39m suggestion = (\n\u001b[32m 1047\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m Did you mean: \u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00msuggestions[\u001b[32m0\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m?\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m suggestions \u001b[38;5;28;01melse\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 1048\u001b[39m )\n\u001b[32m 1049\u001b[39m logger.warning(\n\u001b[32m 1050\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mUnexpected argument \u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00marg\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 1051\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mprovided to ChatGoogleGenerativeAI.\u001b[39m\u001b[38;5;132;01m{\u001b[39;00msuggestion\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 1052\u001b[39m )\n\u001b[32m-> \u001b[39m\u001b[32m1053\u001b[39m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[34;43m__init__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
398
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Programming/RobotPai/.venv/lib/python3.13/site-packages/langchain_core/load/serializable.py:130\u001b[39m, in \u001b[36mSerializable.__init__\u001b[39m\u001b[34m(self, *args, **kwargs)\u001b[39m\n\u001b[32m 128\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, *args: Any, **kwargs: Any) -> \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 129\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"\"\"\"\u001b[39;00m \u001b[38;5;66;03m# noqa: D419\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m130\u001b[39m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[34;43m__init__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
399
+ " \u001b[31m[... skipping hidden 1 frame]\u001b[39m\n",
400
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Programming/RobotPai/.venv/lib/python3.13/site-packages/langchain_google_genai/chat_models.py:1114\u001b[39m, in \u001b[36mChatGoogleGenerativeAI.validate_environment\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1112\u001b[39m google_api_key = \u001b[38;5;28mself\u001b[39m.google_api_key\n\u001b[32m 1113\u001b[39m transport: Optional[\u001b[38;5;28mstr\u001b[39m] = \u001b[38;5;28mself\u001b[39m.transport\n\u001b[32m-> \u001b[39m\u001b[32m1114\u001b[39m \u001b[38;5;28mself\u001b[39m.client = \u001b[43mgenaix\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbuild_generative_service\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 1115\u001b[39m \u001b[43m \u001b[49m\u001b[43mcredentials\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mcredentials\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1116\u001b[39m \u001b[43m \u001b[49m\u001b[43mapi_key\u001b[49m\u001b[43m=\u001b[49m\u001b[43mgoogle_api_key\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1117\u001b[39m \u001b[43m \u001b[49m\u001b[43mclient_info\u001b[49m\u001b[43m=\u001b[49m\u001b[43mclient_info\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1118\u001b[39m \u001b[43m \u001b[49m\u001b[43mclient_options\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mclient_options\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1119\u001b[39m \u001b[43m \u001b[49m\u001b[43mtransport\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtransport\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1120\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1121\u001b[39m \u001b[38;5;28mself\u001b[39m.async_client_running = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 1122\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\n",
401
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Programming/RobotPai/.venv/lib/python3.13/site-packages/langchain_google_genai/_genai_extension.py:276\u001b[39m, in \u001b[36mbuild_generative_service\u001b[39m\u001b[34m(credentials, api_key, client_options, client_info, transport)\u001b[39m\n\u001b[32m 262\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mbuild_generative_service\u001b[39m(\n\u001b[32m 263\u001b[39m credentials: Optional[credentials.Credentials] = \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 264\u001b[39m api_key: Optional[\u001b[38;5;28mstr\u001b[39m] = \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m (...)\u001b[39m\u001b[32m 267\u001b[39m transport: Optional[\u001b[38;5;28mstr\u001b[39m] = \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 268\u001b[39m ) -> v1betaGenerativeServiceClient:\n\u001b[32m 269\u001b[39m config = _prepare_config(\n\u001b[32m 270\u001b[39m credentials=credentials,\n\u001b[32m 271\u001b[39m api_key=api_key,\n\u001b[32m (...)\u001b[39m\u001b[32m 274\u001b[39m client_info=client_info,\n\u001b[32m 275\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m276\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mv1betaGenerativeServiceClient\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\n",
402
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Programming/RobotPai/.venv/lib/python3.13/site-packages/google/ai/generativelanguage_v1beta/services/generative_service/client.py:697\u001b[39m, in \u001b[36mGenerativeServiceClient.__init__\u001b[39m\u001b[34m(self, credentials, transport, client_options, client_info)\u001b[39m\n\u001b[32m 688\u001b[39m transport_init: Union[\n\u001b[32m 689\u001b[39m Type[GenerativeServiceTransport],\n\u001b[32m 690\u001b[39m Callable[..., GenerativeServiceTransport],\n\u001b[32m (...)\u001b[39m\u001b[32m 694\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m cast(Callable[..., GenerativeServiceTransport], transport)\n\u001b[32m 695\u001b[39m )\n\u001b[32m 696\u001b[39m \u001b[38;5;66;03m# initialize with the provided callable or the passed in class\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m697\u001b[39m \u001b[38;5;28mself\u001b[39m._transport = \u001b[43mtransport_init\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 698\u001b[39m \u001b[43m \u001b[49m\u001b[43mcredentials\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcredentials\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 699\u001b[39m \u001b[43m \u001b[49m\u001b[43mcredentials_file\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_client_options\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcredentials_file\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 700\u001b[39m \u001b[43m \u001b[49m\u001b[43mhost\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_api_endpoint\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 701\u001b[39m \u001b[43m \u001b[49m\u001b[43mscopes\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_client_options\u001b[49m\u001b[43m.\u001b[49m\u001b[43mscopes\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 702\u001b[39m \u001b[43m \u001b[49m\u001b[43mclient_cert_source_for_mtls\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_client_cert_source\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 703\u001b[39m \u001b[43m \u001b[49m\u001b[43mquota_project_id\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_client_options\u001b[49m\u001b[43m.\u001b[49m\u001b[43mquota_project_id\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 704\u001b[39m \u001b[43m \u001b[49m\u001b[43mclient_info\u001b[49m\u001b[43m=\u001b[49m\u001b[43mclient_info\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 705\u001b[39m \u001b[43m \u001b[49m\u001b[43malways_use_jwt_access\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 706\u001b[39m \u001b[43m \u001b[49m\u001b[43mapi_audience\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_client_options\u001b[49m\u001b[43m.\u001b[49m\u001b[43mapi_audience\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 707\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 709\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33masync\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mstr\u001b[39m(\u001b[38;5;28mself\u001b[39m._transport):\n\u001b[32m 710\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m CLIENT_LOGGING_SUPPORTED \u001b[38;5;129;01mand\u001b[39;00m _LOGGER.isEnabledFor(\n\u001b[32m 711\u001b[39m std_logging.DEBUG\n\u001b[32m 712\u001b[39m ): \u001b[38;5;66;03m# pragma: NO COVER\u001b[39;00m\n",
403
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Programming/RobotPai/.venv/lib/python3.13/site-packages/google/ai/generativelanguage_v1beta/services/generative_service/transports/grpc.py:234\u001b[39m, in \u001b[36mGenerativeServiceGrpcTransport.__init__\u001b[39m\u001b[34m(self, host, credentials, credentials_file, scopes, channel, api_mtls_endpoint, client_cert_source, ssl_channel_credentials, client_cert_source_for_mtls, quota_project_id, client_info, always_use_jwt_access, api_audience)\u001b[39m\n\u001b[32m 229\u001b[39m \u001b[38;5;28mself\u001b[39m._ssl_channel_credentials = grpc.ssl_channel_credentials(\n\u001b[32m 230\u001b[39m certificate_chain=cert, private_key=key\n\u001b[32m 231\u001b[39m )\n\u001b[32m 233\u001b[39m \u001b[38;5;66;03m# The base transport sets the host, credentials and scopes\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m234\u001b[39m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[34;43m__init__\u001b[39;49m\u001b[43m(\u001b[49m\n\u001b[32m 235\u001b[39m \u001b[43m \u001b[49m\u001b[43mhost\u001b[49m\u001b[43m=\u001b[49m\u001b[43mhost\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 236\u001b[39m \u001b[43m \u001b[49m\u001b[43mcredentials\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcredentials\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 237\u001b[39m \u001b[43m \u001b[49m\u001b[43mcredentials_file\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcredentials_file\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 238\u001b[39m \u001b[43m \u001b[49m\u001b[43mscopes\u001b[49m\u001b[43m=\u001b[49m\u001b[43mscopes\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 239\u001b[39m \u001b[43m \u001b[49m\u001b[43mquota_project_id\u001b[49m\u001b[43m=\u001b[49m\u001b[43mquota_project_id\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 240\u001b[39m \u001b[43m \u001b[49m\u001b[43mclient_info\u001b[49m\u001b[43m=\u001b[49m\u001b[43mclient_info\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 241\u001b[39m \u001b[43m \u001b[49m\u001b[43malways_use_jwt_access\u001b[49m\u001b[43m=\u001b[49m\u001b[43malways_use_jwt_access\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 242\u001b[39m \u001b[43m \u001b[49m\u001b[43mapi_audience\u001b[49m\u001b[43m=\u001b[49m\u001b[43mapi_audience\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 243\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 245\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m._grpc_channel:\n\u001b[32m 246\u001b[39m \u001b[38;5;66;03m# initialize with the provided callable or the default channel\u001b[39;00m\n\u001b[32m 247\u001b[39m channel_init = channel \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mtype\u001b[39m(\u001b[38;5;28mself\u001b[39m).create_channel\n",
404
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Programming/RobotPai/.venv/lib/python3.13/site-packages/google/ai/generativelanguage_v1beta/services/generative_service/transports/base.py:100\u001b[39m, in \u001b[36mGenerativeServiceTransport.__init__\u001b[39m\u001b[34m(self, host, credentials, credentials_file, scopes, quota_project_id, client_info, always_use_jwt_access, api_audience, **kwargs)\u001b[39m\n\u001b[32m 96\u001b[39m credentials, _ = google.auth.load_credentials_from_file(\n\u001b[32m 97\u001b[39m credentials_file, **scopes_kwargs, quota_project_id=quota_project_id\n\u001b[32m 98\u001b[39m )\n\u001b[32m 99\u001b[39m \u001b[38;5;28;01melif\u001b[39;00m credentials \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m._ignore_credentials:\n\u001b[32m--> \u001b[39m\u001b[32m100\u001b[39m credentials, _ = \u001b[43mgoogle\u001b[49m\u001b[43m.\u001b[49m\u001b[43mauth\u001b[49m\u001b[43m.\u001b[49m\u001b[43mdefault\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 101\u001b[39m \u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mscopes_kwargs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mquota_project_id\u001b[49m\u001b[43m=\u001b[49m\u001b[43mquota_project_id\u001b[49m\n\u001b[32m 102\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 103\u001b[39m \u001b[38;5;66;03m# Don't apply audience if the credentials file passed from user.\u001b[39;00m\n\u001b[32m 104\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(credentials, \u001b[33m\"\u001b[39m\u001b[33mwith_gdch_audience\u001b[39m\u001b[33m\"\u001b[39m):\n",
405
+ "\u001b[36mFile \u001b[39m\u001b[32m~/Programming/RobotPai/.venv/lib/python3.13/site-packages/google/auth/_default.py:685\u001b[39m, in \u001b[36mdefault\u001b[39m\u001b[34m(scopes, request, quota_project_id, default_scopes)\u001b[39m\n\u001b[32m 677\u001b[39m _LOGGER.warning(\n\u001b[32m 678\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mNo project ID could be determined. Consider running \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 679\u001b[39m \u001b[33m\"\u001b[39m\u001b[33m`gcloud config set project` or setting the \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 680\u001b[39m \u001b[33m\"\u001b[39m\u001b[33menvironment variable\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 681\u001b[39m environment_vars.PROJECT,\n\u001b[32m 682\u001b[39m )\n\u001b[32m 683\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m credentials, effective_project_id\n\u001b[32m--> \u001b[39m\u001b[32m685\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m exceptions.DefaultCredentialsError(_CLOUD_SDK_MISSING_CREDENTIALS)\n",
406
+ "\u001b[31mDefaultCredentialsError\u001b[39m: Your default credentials were not found. To set up Application Default Credentials, see https://cloud.google.com/docs/authentication/external/set-up-adc for more information."
407
+ ]
408
+ }
409
+ ],
410
+ "source": [
411
+ "import dotenv\n",
412
+ "from langgraph.graph import MessagesState, START, StateGraph\n",
413
+ "from langgraph.prebuilt import tools_condition\n",
414
+ "from langgraph.prebuilt import ToolNode\n",
415
+ "from langchain_google_genai import ChatGoogleGenerativeAI\n",
416
+ "from langchain_huggingface import HuggingFaceEmbeddings\n",
417
+ "from langchain_community.tools.tavily_search import TavilySearchResults\n",
418
+ "from langchain_community.document_loaders import WikipediaLoader\n",
419
+ "from langchain_community.document_loaders import ArxivLoader\n",
420
+ "from langchain_community.vectorstores import SupabaseVectorStore\n",
421
+ "from langchain.tools.retriever import create_retriever_tool\n",
422
+ "from langchain_core.messages import HumanMessage, SystemMessage\n",
423
+ "from langchain_core.tools import tool\n",
424
+ "from supabase.client import Client, create_client\n",
425
+ "\n",
426
+ "# Define the retriever from supabase\n",
427
+ "load_dotenv()\n",
428
+ "embeddings = HuggingFaceEmbeddings(model_name=\"sentence-transformers/all-mpnet-base-v2\") # dim=768\n",
429
+ "\n",
430
+ "supabase_url = os.environ.get(\"SUPABASE_URL\")\n",
431
+ "supabase_key = os.environ.get(\"SUPABASE_SERVICE_KEY\")\n",
432
+ "supabase: Client = create_client(supabase_url, supabase_key)\n",
433
+ "vector_store = SupabaseVectorStore(\n",
434
+ " client=supabase,\n",
435
+ " embedding= embeddings,\n",
436
+ " table_name=\"documents\",\n",
437
+ " query_name=\"match_documents_langchain\",\n",
438
+ ")\n",
439
+ "\n",
440
+ "question_retrieve_tool = create_retriever_tool(\n",
441
+ " vector_store.as_retriever(),\n",
442
+ " \"Question Retriever\",\n",
443
+ " \"Find similar questions in the vector database for the given question.\",\n",
444
+ ")\n",
445
+ "\n",
446
+ "@tool\n",
447
+ "def multiply(a: int, b: int) -> int:\n",
448
+ " \"\"\"Multiply two numbers.\n",
449
+ "\n",
450
+ " Args:\n",
451
+ " a: first int\n",
452
+ " b: second int\n",
453
+ " \"\"\"\n",
454
+ " return a * b\n",
455
+ "\n",
456
+ "@tool\n",
457
+ "def add(a: int, b: int) -> int:\n",
458
+ " \"\"\"Add two numbers.\n",
459
+ " \n",
460
+ " Args:\n",
461
+ " a: first int\n",
462
+ " b: second int\n",
463
+ " \"\"\"\n",
464
+ " return a + b\n",
465
+ "\n",
466
+ "@tool\n",
467
+ "def subtract(a: int, b: int) -> int:\n",
468
+ " \"\"\"Subtract two numbers.\n",
469
+ " \n",
470
+ " Args:\n",
471
+ " a: first int\n",
472
+ " b: second int\n",
473
+ " \"\"\"\n",
474
+ " return a - b\n",
475
+ "\n",
476
+ "@tool\n",
477
+ "def divide(a: int, b: int) -> int:\n",
478
+ " \"\"\"Divide two numbers.\n",
479
+ " \n",
480
+ " Args:\n",
481
+ " a: first int\n",
482
+ " b: second int\n",
483
+ " \"\"\"\n",
484
+ " if b == 0:\n",
485
+ " raise ValueError(\"Cannot divide by zero.\")\n",
486
+ " return a / b\n",
487
+ "\n",
488
+ "@tool\n",
489
+ "def modulus(a: int, b: int) -> int:\n",
490
+ " \"\"\"Get the modulus of two numbers.\n",
491
+ " \n",
492
+ " Args:\n",
493
+ " a: first int\n",
494
+ " b: second int\n",
495
+ " \"\"\"\n",
496
+ " return a % b\n",
497
+ "\n",
498
+ "@tool\n",
499
+ "def wiki_search(query: str) -> str:\n",
500
+ " \"\"\"Search Wikipedia for a query and return maximum 2 results.\n",
501
+ " \n",
502
+ " Args:\n",
503
+ " query: The search query.\"\"\"\n",
504
+ " search_docs = WikipediaLoader(query=query, load_max_docs=2).load()\n",
505
+ " formatted_search_docs = \"\\n\\n---\\n\\n\".join(\n",
506
+ " [\n",
507
+ " f'<Document source=\"{doc.metadata[\"source\"]}\" page=\"{doc.metadata.get(\"page\", \"\")}\"/>\\n{doc.page_content}\\n</Document>'\n",
508
+ " for doc in search_docs\n",
509
+ " ])\n",
510
+ " return {\"wiki_results\": formatted_search_docs}\n",
511
+ "\n",
512
+ "@tool\n",
513
+ "def web_search(query: str) -> str:\n",
514
+ " \"\"\"Search Tavily for a query and return maximum 3 results.\n",
515
+ " \n",
516
+ " Args:\n",
517
+ " query: The search query.\"\"\"\n",
518
+ " search_docs = TavilySearchResults(max_results=3).invoke(query=query)\n",
519
+ " formatted_search_docs = \"\\n\\n---\\n\\n\".join(\n",
520
+ " [\n",
521
+ " f'<Document source=\"{doc.metadata[\"source\"]}\" page=\"{doc.metadata.get(\"page\", \"\")}\"/>\\n{doc.page_content}\\n</Document>'\n",
522
+ " for doc in search_docs\n",
523
+ " ])\n",
524
+ " return {\"web_results\": formatted_search_docs}\n",
525
+ "\n",
526
+ "@tool\n",
527
+ "def arvix_search(query: str) -> str:\n",
528
+ " \"\"\"Search Arxiv for a query and return maximum 3 result.\n",
529
+ " \n",
530
+ " Args:\n",
531
+ " query: The search query.\"\"\"\n",
532
+ " search_docs = ArxivLoader(query=query, load_max_docs=3).load()\n",
533
+ " formatted_search_docs = \"\\n\\n---\\n\\n\".join(\n",
534
+ " [\n",
535
+ " f'<Document source=\"{doc.metadata[\"source\"]}\" page=\"{doc.metadata.get(\"page\", \"\")}\"/>\\n{doc.page_content[:1000]}\\n</Document>'\n",
536
+ " for doc in search_docs\n",
537
+ " ])\n",
538
+ " return {\"arvix_results\": formatted_search_docs}\n",
539
+ "\n",
540
+ "@tool\n",
541
+ "def similar_question_search(question: str) -> str:\n",
542
+ " \"\"\"Search the vector database for similar questions and return the first results.\n",
543
+ " \n",
544
+ " Args:\n",
545
+ " question: the question human provided.\"\"\"\n",
546
+ " matched_docs = vector_store.similarity_search(query, 3)\n",
547
+ " formatted_search_docs = \"\\n\\n---\\n\\n\".join(\n",
548
+ " [\n",
549
+ " f'<Document source=\"{doc.metadata[\"source\"]}\" page=\"{doc.metadata.get(\"page\", \"\")}\"/>\\n{doc.page_content[:1000]}\\n</Document>'\n",
550
+ " for doc in matched_docs\n",
551
+ " ])\n",
552
+ " return {\"similar_questions\": formatted_search_docs}\n",
553
+ "\n",
554
+ "tools = [\n",
555
+ " multiply,\n",
556
+ " add,\n",
557
+ " subtract,\n",
558
+ " divide,\n",
559
+ " modulus,\n",
560
+ " wiki_search,\n",
561
+ " web_search,\n",
562
+ " arvix_search,\n",
563
+ " question_retrieve_tool\n",
564
+ "]\n",
565
+ "\n",
566
+ "llm = ChatGoogleGenerativeAI(model=\"gemini-2.0-flash\")\n",
567
+ "llm_with_tools = llm.bind_tools(tools)"
568
+ ]
569
+ },
570
+ {
571
+ "cell_type": "code",
572
+ "execution_count": null,
573
+ "id": "7dd0716c",
574
+ "metadata": {},
575
+ "outputs": [],
576
+ "source": [
577
+ "# load the system prompt from the file\n",
578
+ "with open('system_prompt.txt', 'r') as f:\n",
579
+ " system_prompt = f.read()\n",
580
+ "\n",
581
+ "\n",
582
+ "# System message\n",
583
+ "sys_msg = SystemMessage(content=system_prompt)\n",
584
+ "\n",
585
+ "# Node\n",
586
+ "def assistant(state: MessagesState):\n",
587
+ " \"\"\"Assistant node\"\"\"\n",
588
+ " return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}\n",
589
+ "\n",
590
+ "# Build graph\n",
591
+ "builder = StateGraph(MessagesState)\n",
592
+ "builder.add_node(\"assistant\", assistant)\n",
593
+ "builder.add_node(\"tools\", ToolNode(tools))\n",
594
+ "builder.add_edge(START, \"assistant\")\n",
595
+ "builder.add_conditional_edges(\n",
596
+ " \"assistant\",\n",
597
+ " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n",
598
+ " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n",
599
+ " tools_condition,\n",
600
+ ")\n",
601
+ "builder.add_edge(\"tools\", \"assistant\")\n",
602
+ "\n",
603
+ "# Compile graph\n",
604
+ "graph = builder.compile()\n"
605
+ ]
606
+ },
607
+ {
608
+ "cell_type": "code",
609
+ "execution_count": null,
610
+ "id": "f4e77216",
611
+ "metadata": {},
612
+ "outputs": [
613
+ {
614
+ "data": {
615
+ "image/png": "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",
616
+ "text/plain": [
617
+ "<IPython.core.display.Image object>"
618
+ ]
619
+ },
620
+ "metadata": {},
621
+ "output_type": "display_data"
622
+ }
623
+ ],
624
+ "source": [
625
+ "from IPython.display import Image, display\n",
626
+ "\n",
627
+ "display(Image(graph.get_graph(xray=True).draw_mermaid_png()))"
628
+ ]
629
+ },
630
+ {
631
+ "cell_type": "code",
632
+ "execution_count": null,
633
+ "id": "5987d58c",
634
+ "metadata": {},
635
+ "outputs": [],
636
+ "source": [
637
+ "question = \"\"\n",
638
+ "messages = [HumanMessage(content=question)]\n",
639
+ "messages = graph.invoke({\"messages\": messages})"
640
+ ]
641
+ },
642
+ {
643
+ "cell_type": "code",
644
+ "execution_count": null,
645
+ "id": "330cbf17",
646
+ "metadata": {},
647
+ "outputs": [],
648
+ "source": [
649
+ "for m in messages['messages']:\n",
650
+ " m.pretty_print()"
651
+ ]
652
+ }
653
+ ],
654
+ "metadata": {
655
+ "kernelspec": {
656
+ "display_name": ".venv",
657
+ "language": "python",
658
+ "name": "python3"
659
+ },
660
+ "language_info": {
661
+ "codemirror_mode": {
662
+ "name": "ipython",
663
+ "version": 3
664
+ },
665
+ "file_extension": ".py",
666
+ "mimetype": "text/x-python",
667
+ "name": "python",
668
+ "nbconvert_exporter": "python",
669
+ "pygments_lexer": "ipython3",
670
+ "version": "3.13.3"
671
+ }
672
+ },
673
+ "nbformat": 4,
674
+ "nbformat_minor": 5
675
+ }