GabrielJuan349 commited on
Commit
c6cd0dd
·
1 Parent(s): 81917a3

First version of Final-assigment from AI agents course

Browse files
Files changed (7) hide show
  1. .gitignore +115 -0
  2. agent.py +99 -0
  3. app.py +7 -3
  4. example.env +8 -0
  5. requirements.txt +16 -1
  6. system_prompt.txt +5 -0
  7. tools.py +94 -0
.gitignore ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+
7
+ # Distribution / packaging
8
+ .Python
9
+ build/
10
+ develop-eggs/
11
+ dist/
12
+ downloads/
13
+ eggs/
14
+ .eggs/
15
+ lib/
16
+ lib64/
17
+ parts/
18
+ sdist/
19
+ var/
20
+ wheels/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+
25
+ # Virtual environments
26
+ venv/
27
+ ENV/
28
+ env/
29
+ .env
30
+ .venv
31
+ env.bak/
32
+ venv.bak/
33
+ .python-version
34
+
35
+ # Unit test / coverage reports
36
+ htmlcov/
37
+ .tox/
38
+ .nox/
39
+ .coverage
40
+ .coverage.*
41
+ .cache
42
+ nosetests.xml
43
+ coverage.xml
44
+ *.cover
45
+ .hypothesis/
46
+ .pytest_cache/
47
+ pytest-*.xml
48
+
49
+ # Jupyter Notebook
50
+ .ipynb_checkpoints
51
+
52
+ # IPython
53
+ profile_default/
54
+ ipython_config.py
55
+
56
+ # Logs
57
+ *.log
58
+ logs/
59
+ log/
60
+
61
+ # IDE specific files
62
+ .idea/
63
+ .vscode/
64
+ *.swp
65
+ *.swo
66
+ *~
67
+ .DS_Store
68
+ .project
69
+ .pydevproject
70
+ .settings/
71
+ .vs/
72
+ *.sublime-project
73
+ *.sublime-workspace
74
+
75
+ # Database
76
+ *.db
77
+ *.rdb
78
+ *.sqlite
79
+ *.sqlite3
80
+
81
+ # Environment variables
82
+ .env
83
+ .env.local
84
+ .env.development.local
85
+ .env.test.local
86
+ .env.production.local
87
+
88
+ # macOS specific
89
+ .DS_Store
90
+ .AppleDouble
91
+ .LSOverride
92
+ Icon
93
+ ._*
94
+ .DocumentRevisions-V100
95
+ .fseventsd
96
+ .Spotlight-V100
97
+ .TemporaryItems
98
+ .Trashes
99
+ .VolumeIcon.icns
100
+ .com.apple.timemachine.donotpresent
101
+
102
+ # AI/model files
103
+ *.h5
104
+ *.pb
105
+ *.onnx
106
+ *.tflite
107
+ *.pt
108
+ *.pth
109
+ *.weights
110
+
111
+ # Temporary files
112
+ tmp/
113
+ temp/
114
+ .tmp
115
+ *.tmp
agent.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_core.messages import SystemMessage, HumanMessage
6
+ from langchain.tools.retriever import create_retriever_tool
7
+ from langchain_community.vectorstores import Qdrant
8
+ from qdrant_client import QdrantClient
9
+ from langchain_google_genai import ChatGoogleGenerativeAI
10
+ from langchain_groq import ChatGroq
11
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint, HuggingFaceEmbeddings
12
+ from tools import multiply,add,subtract,divide,modulus,wiki_search,duckduckgo_search,arvix_search
13
+
14
+
15
+ load_dotenv()
16
+
17
+ with open("system_prompt.txt", "r", encoding="utf-8") as f:
18
+ system_prompt = f.read()
19
+
20
+ # System message
21
+ sys_msg = SystemMessage(content=system_prompt)
22
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/static-similarity-mrl-multilingual-v1", model_kwargs={'device': 'cpu'})
23
+ qdrant = QdrantClient(
24
+ url=os.environ.get("QDRANT_URL"),
25
+ api_key=os.environ.get("QDRANT_SERVICE_KEY")
26
+ )
27
+
28
+ vector_store = Qdrant(
29
+ client=qdrant,
30
+ embedding_function=embeddings,
31
+ collection_name="documents",
32
+ query_name="match_documents_langchain"
33
+ )
34
+ create_retriever_tool = create_retriever_tool(
35
+ retriever=vector_store.as_retriever(),
36
+ name="Question Search",
37
+ description="A tool to retrieve similar questions from a vector store.",
38
+ )
39
+ tools = [
40
+ multiply,
41
+ add,
42
+ subtract,
43
+ divide,
44
+ modulus,
45
+ wiki_search,
46
+ duckduckgo_search,
47
+ arvix_search,
48
+ ]
49
+
50
+ def build_graph(provider: str = "groq"):
51
+ """Build the graph"""
52
+ # Load environment variables from .env file
53
+ model=""
54
+ if provider == "google":
55
+ # Google Gemini
56
+ model = os.environ.get("GEMINI_MODEL")
57
+ llm = ChatGoogleGenerativeAI(model=model, temperature=0)
58
+ elif provider == "groq":
59
+ # Groq https://console.groq.com/docs/models
60
+ model = os.environ.get("GROQ_MODEL")
61
+ llm = ChatGroq(model=model, temperature=0)
62
+ elif provider == "huggingface":
63
+ model = os.environ.get("HUGGINGFACEHUB_URL")
64
+ llm = ChatHuggingFace(
65
+ llm=HuggingFaceEndpoint(
66
+ url=model,
67
+ temperature=0,
68
+ ),
69
+ )
70
+ else:
71
+ raise ValueError("Invalid provider. Choose 'google', 'groq' or 'huggingface'.")
72
+ # Bind tools to LLM
73
+ llm_with_tools = llm.bind_tools(tools)
74
+
75
+ def assistant(state: MessagesState):
76
+ """Assistant node"""
77
+ return {"messages": [llm_with_tools.invoke(state["messages"])]}
78
+
79
+ def retriever(state: MessagesState):
80
+ """Retriever node"""
81
+ similar_question = vector_store.similarity_search(state["messages"][0].content)
82
+ example_msg = HumanMessage(
83
+ content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
84
+ )
85
+ return {"messages": [sys_msg] + state["messages"] + [example_msg]}
86
+
87
+ builder = StateGraph(MessagesState)
88
+ builder.add_node("retriever", retriever)
89
+ builder.add_node("assistant", assistant)
90
+ builder.add_node("tools", ToolNode(tools))
91
+ builder.add_edge(START, "retriever")
92
+ builder.add_edge("retriever", "assistant")
93
+ builder.add_conditional_edges(
94
+ "assistant",
95
+ tools_condition,
96
+ )
97
+ builder.add_edge("tools", "assistant")
98
+
99
+ return builder.compile()
app.py CHANGED
@@ -3,6 +3,8 @@ import gradio as gr
3
  import requests
4
  import inspect
5
  import pandas as pd
 
 
6
 
7
  # (Keep Constants as is)
8
  # --- Constants ---
@@ -13,11 +15,13 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
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
  """
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ from langchain_core.messages import HumanMessage
7
+ from agent import build_graph
8
 
9
  # (Keep Constants as is)
10
  # --- Constants ---
 
15
  class BasicAgent:
16
  def __init__(self):
17
  print("BasicAgent initialized.")
18
+ self.graph = build_graph()
19
  def __call__(self, question: str) -> str:
20
  print(f"Agent received question (first 50 chars): {question[:50]}...")
21
+ messages = [HumanMessage(content=question)]
22
+ messages = self.graph.invoke({"messages": messages})
23
+ answer = messages['messages'][-1].content
24
+ return answer[14:]
25
 
26
  def run_and_submit_all( profile: gr.OAuthProfile | None):
27
  """
example.env ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ QDRANT_URL='my-qdrant-url'
2
+ QDRANT_SERVICE_KEY='my-qdrant-service-key'
3
+ GEMINI_API_KEY='my-gemini-api-key'
4
+ GEMINI_MODEL='gemini-1.5-flash'
5
+ GROQ_API_KEY='my-groq-api-key'
6
+ GROQ_MODEL='groq-llama-3-70b-instruct'
7
+ HUGGINGFACEHUB_API_TOKEN='my-huggingfacehub-api-token'
8
+ HUGGINGFACEHUB_URL='my-huggingfacehub-url'
requirements.txt CHANGED
@@ -1,2 +1,17 @@
1
  gradio
2
- requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  gradio
2
+ requests
3
+ langchain
4
+ langchain-community
5
+ langchain-core
6
+ langchain-google-genai
7
+ langchain-huggingface
8
+ langchain-groq
9
+ langgraph
10
+ huggingface_hub
11
+ qdrant-client
12
+ arxiv
13
+ pymupdf
14
+ wikipedia
15
+ python-dotenv
16
+ duckduckgo-search
17
+ sentence-transformers
system_prompt.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ You are a helpful assistant tasked with answering questions using a set of tools.
2
+ Now, I will ask you a question. Report your thoughts, and finish your answer with the following template:
3
+ FINAL ANSWER: [YOUR FINAL ANSWER].
4
+ YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. 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. 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. 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.
5
+ Your answer should only start with "FINAL ANSWER: ", then follows with the answer.
tools.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ from langchain_community.tools import DuckDuckGoSearchResults
3
+ from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
4
+
5
+ @tool
6
+ def multiply(a: int, b: int) -> int:
7
+ """Multiply two numbers.
8
+ Args:
9
+ a: first int
10
+ b: second int
11
+ """
12
+ return a * b
13
+
14
+ @tool
15
+ def add(a: int, b: int) -> int:
16
+ """Add two numbers.
17
+
18
+ Args:
19
+ a: first int
20
+ b: second int
21
+ """
22
+ return a + b
23
+
24
+ @tool
25
+ def subtract(a: int, b: int) -> int:
26
+ """Subtract two numbers.
27
+
28
+ Args:
29
+ a: first int
30
+ b: second int
31
+ """
32
+ return a - b
33
+
34
+ @tool
35
+ def divide(a: int, b: int) -> int:
36
+ """Divide two numbers.
37
+
38
+ Args:
39
+ a: first int
40
+ b: second int
41
+ """
42
+ if b == 0:
43
+ raise ValueError("Cannot divide by zero.")
44
+ return a / b
45
+
46
+ @tool
47
+ def modulus(a: int, b: int) -> int:
48
+ """Get the modulus of two numbers.
49
+
50
+ Args:
51
+ a: first int
52
+ b: second int
53
+ """
54
+ return a % b
55
+
56
+ @tool
57
+ def wiki_search(query: str) -> str:
58
+ """Search Wikipedia for a query and return maximum 2 results.
59
+
60
+ Args:
61
+ query: The search query."""
62
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
63
+ formatted_search_docs = "\n\n---\n\n".join(
64
+ [
65
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
66
+ for doc in search_docs
67
+ ])
68
+ return {"wiki_results": formatted_search_docs}
69
+
70
+ @tool
71
+ def arvix_search(query: str) -> str:
72
+ """Search Arxiv for a query and return maximum 3 result.
73
+
74
+ Args:
75
+ query: The search query."""
76
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
77
+ formatted_search_docs = "\n\n---\n\n".join(
78
+ [
79
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
80
+ for doc in search_docs
81
+ ])
82
+ return {"arvix_results": formatted_search_docs}
83
+
84
+ @tool
85
+ def duckduckgo_search(query: str) -> str:
86
+ """Search DuckDuckGo for a query and return maximum 3 results.
87
+
88
+ Args:
89
+ query: The search query."""
90
+ search_results = DuckDuckGoSearchResults(max_results=3).invoke(query)
91
+ formatted_search_results = "\n\n---\n\n".join(
92
+ [f'<Document source="{result["href"]}"/>\n{result["body"]}\n</Document>' for result in search_results]
93
+ )
94
+ return {"duckduckgo_results": formatted_search_results}