ptoloudis commited on
Commit
968807e
·
verified ·
1 Parent(s): aa975e2

Upload 5 files

Browse files
Files changed (4) hide show
  1. agent.py +47 -99
  2. app.py +4 -4
  3. requirements.txt +2 -6
  4. system_prompt.txt +1 -3
agent.py CHANGED
@@ -1,23 +1,31 @@
1
  """LangGraph Agent"""
2
  import os
 
3
  from dotenv import load_dotenv
4
  from langgraph.graph import START, StateGraph, MessagesState
5
  from langgraph.prebuilt import tools_condition
6
  from langgraph.prebuilt import ToolNode
7
  from langchain_google_genai import ChatGoogleGenerativeAI
8
  from langchain_groq import ChatGroq
9
- from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint, HuggingFaceEmbeddings
10
- from langchain_community.tools.tavily_search import TavilySearchResults
11
  from langchain_community.document_loaders import WikipediaLoader
12
  from langchain_community.document_loaders import ArxivLoader
13
- from langchain_community.vectorstores import SupabaseVectorStore
14
- from langchain_core.messages import SystemMessage, HumanMessage
15
  from langchain_core.tools import tool
16
- from langchain.tools.retriever import create_retriever_tool
17
- from supabase.client import Client, create_client
18
 
19
  load_dotenv()
20
 
 
 
 
 
 
 
 
 
 
 
21
  @tool
22
  def multiply(a: int, b: int) -> int:
23
  """Multiply two numbers.
@@ -30,7 +38,7 @@ def multiply(a: int, b: int) -> int:
30
  @tool
31
  def add(a: int, b: int) -> int:
32
  """Add two numbers.
33
-
34
  Args:
35
  a: first int
36
  b: second int
@@ -40,7 +48,7 @@ def add(a: int, b: int) -> int:
40
  @tool
41
  def subtract(a: int, b: int) -> int:
42
  """Subtract two numbers.
43
-
44
  Args:
45
  a: first int
46
  b: second int
@@ -48,9 +56,9 @@ def subtract(a: int, b: int) -> int:
48
  return a - b
49
 
50
  @tool
51
- def divide(a: int, b: int) -> int:
52
  """Divide two numbers.
53
-
54
  Args:
55
  a: first int
56
  b: second int
@@ -62,7 +70,7 @@ def divide(a: int, b: int) -> int:
62
  @tool
63
  def modulus(a: int, b: int) -> int:
64
  """Get the modulus of two numbers.
65
-
66
  Args:
67
  a: first int
68
  b: second int
@@ -72,45 +80,34 @@ def modulus(a: int, b: int) -> int:
72
  @tool
73
  def wiki_search(query: str) -> str:
74
  """Search Wikipedia for a query and return maximum 2 results.
75
-
76
  Args:
77
  query: The search query."""
78
  search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
79
- formatted_search_docs = "\n\n---\n\n".join(
80
- [
81
- f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
82
- for doc in search_docs
83
- ])
84
- return {"wiki_results": formatted_search_docs}
85
 
86
  @tool
87
  def web_search(query: str) -> str:
88
- """Search Tavily for a query and return maximum 3 results.
89
-
90
  Args:
91
  query: The search query."""
92
- search_docs = TavilySearchResults(max_results=3).invoke(query=query)
93
- formatted_search_docs = "\n\n---\n\n".join(
94
- [
95
- f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
96
- for doc in search_docs
97
- ])
98
- return {"web_results": formatted_search_docs}
99
 
100
  @tool
101
  def arvix_search(query: str) -> str:
102
  """Search Arxiv for a query and return maximum 3 result.
103
-
104
  Args:
105
  query: The search query."""
106
  search_docs = ArxivLoader(query=query, load_max_docs=3).load()
107
- formatted_search_docs = "\n\n---\n\n".join(
108
- [
109
- f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
110
- for doc in search_docs
111
- ])
112
- return {"arvix_results": formatted_search_docs}
113
-
114
 
115
 
116
  # load the system prompt from the file
@@ -120,25 +117,6 @@ with open("system_prompt.txt", "r", encoding="utf-8") as f:
120
  # System message
121
  sys_msg = SystemMessage(content=system_prompt)
122
 
123
- # build a retriever
124
- embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") # dim=768
125
- supabase: Client = create_client(
126
- os.environ.get("SUPABASE_URL"),
127
- os.environ.get("SUPABASE_SERVICE_KEY"))
128
- vector_store = SupabaseVectorStore(
129
- client=supabase,
130
- embedding= embeddings,
131
- table_name="documents",
132
- query_name="match_documents_langchain",
133
- )
134
- create_retriever_tool = create_retriever_tool(
135
- retriever=vector_store.as_retriever(),
136
- name="Question Search",
137
- description="A tool to retrieve similar questions from a vector store.",
138
- )
139
-
140
-
141
-
142
  tools = [
143
  multiply,
144
  add,
@@ -153,19 +131,23 @@ tools = [
153
  # Build graph function
154
  def build_graph(provider: str = "google"):
155
  """Build the graph"""
156
- # Load environment variables from .env file
157
  if provider == "google":
158
- # Google Gemini
159
  llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
160
  elif provider == "groq":
161
- # Groq https://console.groq.com/docs/models
162
- llm = ChatGroq(model="qwen-qwq-32b", temperature=0) # optional : qwen-qwq-32b gemma2-9b-it
163
  elif provider == "huggingface":
164
- # TODO: Add huggingface endpoint
 
 
165
  llm = ChatHuggingFace(
166
  llm=HuggingFaceEndpoint(
167
- url="https://api-inference.huggingface.co/models/Meta-DeepLearning/llama-2-7b-chat-hf",
 
 
168
  temperature=0,
 
169
  ),
170
  )
171
  else:
@@ -177,47 +159,13 @@ def build_graph(provider: str = "google"):
177
  def assistant(state: MessagesState):
178
  """Assistant node"""
179
  return {"messages": [llm_with_tools.invoke(state["messages"])]}
180
-
181
- # def retriever(state: MessagesState):
182
- # """Retriever node"""
183
- # similar_question = vector_store.similarity_search(state["messages"][0].content)
184
- #example_msg = HumanMessage(
185
- # content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
186
- # )
187
- # return {"messages": [sys_msg] + state["messages"] + [example_msg]}
188
-
189
- from langchain_core.messages import AIMessage
190
-
191
- def retriever(state: MessagesState):
192
- query = state["messages"][-1].content
193
- similar_doc = vector_store.similarity_search(query, k=1)[0]
194
-
195
- content = similar_doc.page_content
196
- if "Final answer :" in content:
197
- answer = content.split("Final answer :")[-1].strip()
198
- else:
199
- answer = content.strip()
200
-
201
- return {"messages": [AIMessage(content=answer)]}
202
-
203
- # builder = StateGraph(MessagesState)
204
- #builder.add_node("retriever", retriever)
205
- #builder.add_node("assistant", assistant)
206
- #builder.add_node("tools", ToolNode(tools))
207
- #builder.add_edge(START, "retriever")
208
- #builder.add_edge("retriever", "assistant")
209
- #builder.add_conditional_edges(
210
- # "assistant",
211
- # tools_condition,
212
- #)
213
- #builder.add_edge("tools", "assistant")
214
 
215
  builder = StateGraph(MessagesState)
216
- builder.add_node("retriever", retriever)
217
-
218
- # Retriever ist Start und Endpunkt
219
- builder.set_entry_point("retriever")
220
- builder.set_finish_point("retriever")
221
 
222
  # Compile graph
223
  return builder.compile()
 
1
  """LangGraph Agent"""
2
  import os
3
+ import re
4
  from dotenv import load_dotenv
5
  from langgraph.graph import START, StateGraph, MessagesState
6
  from langgraph.prebuilt import tools_condition
7
  from langgraph.prebuilt import ToolNode
8
  from langchain_google_genai import ChatGoogleGenerativeAI
9
  from langchain_groq import ChatGroq
10
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
11
+ from langchain_community.tools import DuckDuckGoSearchRun
12
  from langchain_community.document_loaders import WikipediaLoader
13
  from langchain_community.document_loaders import ArxivLoader
14
+ from langchain_core.messages import SystemMessage
 
15
  from langchain_core.tools import tool
 
 
16
 
17
  load_dotenv()
18
 
19
+ _FINAL_ANSWER_RE = re.compile(r"final answer\s*:\s*", re.IGNORECASE)
20
+
21
+
22
+ def extract_final_answer(text: str) -> str:
23
+ """Strip the `FINAL ANSWER:` prefix the system prompt asks the model for."""
24
+ match = _FINAL_ANSWER_RE.search(text)
25
+ answer = text[match.end():] if match else text
26
+ return answer.strip()
27
+
28
+
29
  @tool
30
  def multiply(a: int, b: int) -> int:
31
  """Multiply two numbers.
 
38
  @tool
39
  def add(a: int, b: int) -> int:
40
  """Add two numbers.
41
+
42
  Args:
43
  a: first int
44
  b: second int
 
48
  @tool
49
  def subtract(a: int, b: int) -> int:
50
  """Subtract two numbers.
51
+
52
  Args:
53
  a: first int
54
  b: second int
 
56
  return a - b
57
 
58
  @tool
59
+ def divide(a: int, b: int) -> float:
60
  """Divide two numbers.
61
+
62
  Args:
63
  a: first int
64
  b: second int
 
70
  @tool
71
  def modulus(a: int, b: int) -> int:
72
  """Get the modulus of two numbers.
73
+
74
  Args:
75
  a: first int
76
  b: second int
 
80
  @tool
81
  def wiki_search(query: str) -> str:
82
  """Search Wikipedia for a query and return maximum 2 results.
83
+
84
  Args:
85
  query: The search query."""
86
  search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
87
+ return "\n\n---\n\n".join(
88
+ f'<Document source="{doc.metadata.get("source", "")}"/>\n{doc.page_content}\n</Document>'
89
+ for doc in search_docs
90
+ )
 
 
91
 
92
  @tool
93
  def web_search(query: str) -> str:
94
+ """Search the web (DuckDuckGo) for a query and return a few results.
95
+
96
  Args:
97
  query: The search query."""
98
+ return DuckDuckGoSearchRun().invoke(query)
 
 
 
 
 
 
99
 
100
  @tool
101
  def arvix_search(query: str) -> str:
102
  """Search Arxiv for a query and return maximum 3 result.
103
+
104
  Args:
105
  query: The search query."""
106
  search_docs = ArxivLoader(query=query, load_max_docs=3).load()
107
+ return "\n\n---\n\n".join(
108
+ f'<Document source="{doc.metadata.get("source", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
109
+ for doc in search_docs
110
+ )
 
 
 
111
 
112
 
113
  # load the system prompt from the file
 
117
  # System message
118
  sys_msg = SystemMessage(content=system_prompt)
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  tools = [
121
  multiply,
122
  add,
 
131
  # Build graph function
132
  def build_graph(provider: str = "google"):
133
  """Build the graph"""
 
134
  if provider == "google":
135
+ # Google Gemini - needs GOOGLE_API_KEY
136
  llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
137
  elif provider == "groq":
138
+ # Groq https://console.groq.com/docs/models - needs GROQ_API_KEY
139
+ llm = ChatGroq(model="llama-3.3-70b-versatile", temperature=0)
140
  elif provider == "huggingface":
141
+ # HF Inference Providers - needs HF_TOKEN. "auto" provider selection can land on a
142
+ # backend that doesn't support tool calling, so pin one that does (override via
143
+ # HF_AGENT_PROVIDER if it doesn't happen to host HF_AGENT_MODEL).
144
  llm = ChatHuggingFace(
145
  llm=HuggingFaceEndpoint(
146
+ repo_id=os.getenv("HF_AGENT_MODEL", "meta-llama/Llama-3.1-8B-Instruct"),
147
+ provider=os.getenv("HF_AGENT_PROVIDER", "deepinfra"),
148
+ huggingfacehub_api_token=os.getenv("HF_TOKEN"),
149
  temperature=0,
150
+ max_new_tokens=1024,
151
  ),
152
  )
153
  else:
 
159
  def assistant(state: MessagesState):
160
  """Assistant node"""
161
  return {"messages": [llm_with_tools.invoke(state["messages"])]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  builder = StateGraph(MessagesState)
164
+ builder.add_node("assistant", assistant)
165
+ builder.add_node("tools", ToolNode(tools))
166
+ builder.add_edge(START, "assistant")
167
+ builder.add_conditional_edges("assistant", tools_condition)
168
+ builder.add_edge("tools", "assistant")
169
 
170
  # Compile graph
171
  return builder.compile()
app.py CHANGED
@@ -5,7 +5,7 @@ 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
 
@@ -25,10 +25,10 @@ class BasicAgent:
25
 
26
  def __call__(self, question: str) -> str:
27
  print(f"Agent received question (first 50 chars): {question[:50]}...")
28
- messages = [HumanMessage(content=question)]
29
  result = self.graph.invoke({"messages": messages})
30
- answer = result['messages'][-1].content
31
- return answer # kein [14:] mehr nötig!
32
 
33
 
34
 
 
5
  import requests
6
  import pandas as pd
7
  from langchain_core.messages import HumanMessage
8
+ from agent import build_graph, sys_msg, extract_final_answer
9
 
10
 
11
 
 
25
 
26
  def __call__(self, question: str) -> str:
27
  print(f"Agent received question (first 50 chars): {question[:50]}...")
28
+ messages = [sys_msg, HumanMessage(content=question)]
29
  result = self.graph.invoke({"messages": messages})
30
+ answer = extract_final_answer(result['messages'][-1].content)
31
+ return answer
32
 
33
 
34
 
requirements.txt CHANGED
@@ -1,18 +1,14 @@
1
  gradio
2
  requests
3
- langchain
4
  langchain-community
5
  langchain-core
6
  langchain-google-genai
7
  langchain-huggingface
8
  langchain-groq
9
- langchain-tavily
10
- langchain-chroma
11
  langgraph
12
  huggingface_hub
13
- supabase
14
  arxiv
15
- pymupdf
16
  wikipedia
17
- pgvector
18
  python-dotenv
 
1
  gradio
2
  requests
3
+ pandas
4
  langchain-community
5
  langchain-core
6
  langchain-google-genai
7
  langchain-huggingface
8
  langchain-groq
 
 
9
  langgraph
10
  huggingface_hub
11
+ duckduckgo-search
12
  arxiv
 
13
  wikipedia
 
14
  python-dotenv
system_prompt.txt CHANGED
@@ -5,9 +5,7 @@ FINAL ANSWER: [ANSWER]
5
 
6
  Only write the answer in that exact format. Do not explain anything. Do not include any other text.
7
 
8
- If you are provided with a similar question and its final answer, and the current question is **exactly the same**, then simply return the same final answer without using any tools.
9
-
10
- Only use tools if the current question is different from the similar one.
11
 
12
  Examples:
13
  - FINAL ANSWER: FunkMonk
 
5
 
6
  Only write the answer in that exact format. Do not explain anything. Do not include any other text.
7
 
8
+ Use the available tools whenever you need external information or computation to answer.
 
 
9
 
10
  Examples:
11
  - FINAL ANSWER: FunkMonk