HFswapnil commited on
Commit
2fb3630
·
verified ·
1 Parent(s): d3e3048

Upload 6 files

Browse files
Files changed (6) hide show
  1. src/app.py +130 -0
  2. src/deep_research.py +296 -0
  3. src/model.py +19 -0
  4. src/rag_utils.py +54 -0
  5. src/run_model.py +21 -0
  6. src/web_search.py +76 -0
src/app.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from run_model import generate_response, generate_RAG_response
3
+ from web_search import search_web
4
+ from deep_research import perform_deep_research
5
+ import tempfile
6
+
7
+ st.set_page_config(layout="wide")
8
+
9
+ def main():
10
+ st.title("💬 Chat with Gemma")
11
+
12
+
13
+ with st.sidebar:
14
+ st.title("Tools")
15
+ st.markdown("For source code of this website, visit : <some_link>")
16
+
17
+ option = st.selectbox(
18
+ "Choose tools",
19
+ ("Simple Chat", "Web Search", "Upload PDF", "Deep Web Search"),
20
+ )
21
+
22
+ temperature = st.slider(
23
+ label="Temperature (controls randomness)",
24
+ min_value=0.0,
25
+ max_value=2.0,
26
+ value=1.0,
27
+ step=0.01,
28
+ help="Lower = more deterministic, Higher = more random"
29
+ )
30
+ # Top-k sampling
31
+ top_k = st.slider(
32
+ label="Top-k (limits to top K tokens by probability)",
33
+ min_value=0,
34
+ max_value=100,
35
+ value=50,
36
+ step=1,
37
+ help="0 = disable top-k filtering"
38
+ )
39
+
40
+ # Top-p (nucleus sampling)
41
+ top_p = st.slider(
42
+ label="Top-p (nucleus sampling cutoff)",
43
+ min_value=0.0,
44
+ max_value=1.0,
45
+ value=0.9,
46
+ step=0.01,
47
+ help="0.0 = conservative, 1.0 = more random"
48
+ )
49
+
50
+ if option == "Upload PDF":
51
+ file = st.file_uploader(label="Uploaded file will provide context to LLM", type="pdf")
52
+
53
+
54
+ if option == "Web Search":
55
+ st.write("Web Search Enabled for next query")
56
+ # WB_SEARCH = True
57
+
58
+ if option == "None":
59
+ st.warning("You are not using any tool")
60
+
61
+ if option == "Deep Web Search":
62
+ st.write("Deep Web Research Enabled for next query")
63
+
64
+
65
+
66
+ # col1, col2 = st.columns([6, 1], gap="small")
67
+
68
+ # column 1
69
+ # with col1:
70
+ if "messages" not in st.session_state:
71
+ st.session_state.messages = []
72
+
73
+ for msg in st.session_state.messages:
74
+ with st.chat_message(msg["role"]):
75
+ st.markdown(msg["content"])
76
+
77
+ # prompt = st.chat_input("Say something...")
78
+
79
+ if prompt:= st.chat_input("Say something..."):
80
+ # Display user message
81
+ st.chat_message("user").markdown(prompt)
82
+ st.session_state.messages.append({"role": "user", "content": prompt})
83
+ if option == "Simple Chat":
84
+
85
+ response = generate_response(history=st.session_state.messages, query=prompt, temperature=temperature, top_k=top_k, top_p=top_p)
86
+ st.chat_message("assistant").markdown(response)
87
+ # st.session_state.messages.append({"role": "assistant", "content": response})
88
+
89
+ if option == "Web Search":
90
+ st.session_state.messages.append([{"role": "user", "content" : prompt}])
91
+
92
+ response, sources = search_web(prompt)
93
+ # asnswer = response
94
+ # st.chat_message("assistant").markdown(f"{response}\n\n###Sources\n{'\n'.join([source for source in sources])}")
95
+ with st.chat_message("assistant"):
96
+ st.markdown(f"{response}\n\n### Sources\n" + "\n".join(sources))
97
+
98
+ if option == "Deep Web Search":
99
+ st.session_state.messages.append([{"role": "user", "content" : prompt}])
100
+
101
+ response = perform_deep_research(prompt)
102
+ # asnswer = response
103
+ # st.chat_message("assistant").markdown(f"{response}\n\n###Sources\n{'\n'.join([source for source in sources])}")
104
+ with st.chat_message("assistant"):
105
+ st.markdown(response)
106
+
107
+ if option == "Upload PDF":
108
+ st.session_state.messages.append([{"role": "user", "content" : prompt}])
109
+
110
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
111
+ tmp_file.write(file.read())
112
+ tmp_path = tmp_file.name
113
+ print(tmp_path)
114
+ response = generate_RAG_response(prompt, tmp_path, st.session_state.messages)
115
+ # print(file)
116
+ with st.chat_message("assistant"):
117
+ st.markdown(response)
118
+
119
+ st.session_state.messages.append({"role": "assistant", "content": response})
120
+
121
+
122
+ # # Slider in column 2
123
+ # with col2:
124
+ # temperature = st.slider("Temperature", 0.0, 1.0, 0.0)
125
+ # top_k = st.slider("Top k", 0.0, 100.0, 40.0)
126
+ # top_p = st.slider("Top p", 0.0, 1.0, 0.95)
127
+
128
+
129
+ if __name__ == "__main__":
130
+ main()
src/deep_research.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_google_genai import ChatGoogleGenerativeAI
2
+ import os
3
+ from typing import Annotated, Dict, Any, List, Union, Optional
4
+ from typing_extensions import TypedDict, Annotated, Literal
5
+ import operator
6
+ from dataclasses import dataclass, field
7
+ from re import T
8
+ import json
9
+ from langchain_core.runnables import RunnableConfig
10
+ from langgraph.graph import START, END, StateGraph
11
+ from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
12
+ import time
13
+ from tavily import TavilyClient
14
+ import re
15
+ from model import get_gemma
16
+
17
+ from api_key import TAVILY_API_KEY
18
+
19
+ gemma_model = get_gemma()
20
+
21
+ tavily_client = TavilyClient(api_key=TAVILY_API_KEY)
22
+
23
+ max_web_research_loops: int = 4
24
+
25
+
26
+ @dataclass(kw_only=True)
27
+ class SummaryState:
28
+ research_topic: str = field(default=None)
29
+ search_query: str = field(default=None)
30
+ web_search_results: Annotated[list, operator.add] = field(default_factory=list)
31
+ sources_gathered: Annotated[list, operator.add] = field(default=list)
32
+ research_loop_count: int = 0
33
+ running_summary: str = None
34
+
35
+
36
+ @dataclass(kw_only=True)
37
+ class SummaryStateInput:
38
+ research_topic: str = None
39
+
40
+ @dataclass(kw_only=True)
41
+ class SummaryStateOutput:
42
+ running_summary: str = None
43
+
44
+
45
+ # Query Writer
46
+ query_writer_instructions = """Your gola is to generate web search query.
47
+ The query will gather information about specific topic.
48
+
49
+ Topic: {research_topic}
50
+
51
+ Return your query as JSON object:
52
+ {{
53
+ "query": "string",
54
+ "aspect" : "string",
55
+ "rationale" : "string"
56
+ }}
57
+ """
58
+
59
+ # Summerizer Instructions
60
+ summerizer_instructions = """Your goal is to generate high-quality summary of the web search results.
61
+ when EXTENDING an existing summary:
62
+ 1. Seamlessly integrate new information without repeating what's already covered.
63
+ 2. Maintain consistancy with existing content's style.
64
+ 3. Only add new and non-redudant information.
65
+ 4. Ensure smooth transition between existing and new content.
66
+
67
+ when creating a NEW summary:
68
+ 1. Highlight the most relevant information from each source.
69
+ 2. Provide concise overview of the key points related to each report topic.
70
+ 3. Emphasize on significant findings or insights.
71
+ 4. Ensure coherent flow of information.
72
+
73
+ In both cases:
74
+ 1. Focus on factual & objective information
75
+ 2. Maintain consistat technical depth
76
+ 3. Avoid repetition & redundancy
77
+ 4. DON'T use phrases like "based on new results"
78
+ 5. DON'T add preamble like "Here is an extended summary ...", instead just provide summary directly
79
+ 6. DON'T add references or works cited section.
80
+ 7. You will generate tables using markdown when user asks you to do.
81
+ """
82
+
83
+ # Reflection Instructions
84
+ reflection_summary = """You are an expert research assistant analyzing summary about {research_topic}.
85
+
86
+ Your Tasks :
87
+ 1. Identify knowledge gaps or areas the need further exploration.
88
+ 2. Generate a follow-up question that would help in expanding the understanding.
89
+ 3. Focus on technical details, implementation specifics.
90
+
91
+ Ensure follow-up question is self-contained and includes necessary context for web search.
92
+
93
+ Return response as JSON object:
94
+ {{
95
+ "knowledge_gap" : "string",
96
+ "follow_up_query" : "string"
97
+ }}
98
+ """
99
+
100
+
101
+ def generate_query(state: SummaryState):
102
+ # To generate query for web search
103
+
104
+ system_message_for_query_writer = query_writer_instructions.format(research_topic=state.research_topic)
105
+
106
+ result = gemma_model.invoke(
107
+ [
108
+ HumanMessage(content=f"IMPORTANT INSTRUCTIONS:\n{system_message_for_query_writer}\n\nGenerate a query for web search")
109
+ ]
110
+ )
111
+
112
+ # print(f"[FUN] GENERATE_QUERY:\nType: {type(result)}\nContent: {result}")
113
+ raw_content = result.content.strip()
114
+ match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", raw_content, re.DOTALL)
115
+ if match:
116
+ json_str = match.group(1)
117
+ else:
118
+ raise ValueError(f"Failed to extract JSON from model response: {raw_content}")
119
+
120
+ query = json.loads(json_str)
121
+ return {"search_query" : query["query"]}
122
+
123
+
124
+ def deduplicate_and_format_sources(
125
+ search_response: Union[Dict[str, Any], List[Dict[str, Any]]],
126
+ max_tokens_per_source: int,
127
+ fetch_full_page: bool = False
128
+ ) -> str:
129
+ """
130
+ Format and deduplicate search responses from various search APIs.
131
+
132
+ Takes either a single search response or list of responses from search APIs,
133
+ deduplicates them by URL, and formats them into a structured string.
134
+
135
+ Args:
136
+ search_response (Union[Dict[str, Any], List[Dict[str, Any]]]): Either:
137
+ - A dict with a 'results' key containing a list of search results
138
+ - A list of dicts, each containing search results
139
+ max_tokens_per_source (int): Maximum number of tokens to include for each source's content
140
+ fetch_full_page (bool, optional): Whether to include the full page content. Defaults to False.
141
+
142
+ Returns:
143
+ str: Formatted string with deduplicated sources
144
+
145
+ Raises:
146
+ ValueError: If input is neither a dict with 'results' key nor a list of search results
147
+ """
148
+ # Convert input to list of results
149
+ if isinstance(search_response, dict):
150
+ sources_list = search_response['results']
151
+ elif isinstance(search_response, list):
152
+ sources_list = []
153
+ for response in search_response:
154
+ if isinstance(response, dict) and 'results' in response:
155
+ sources_list.extend(response['results'])
156
+ else:
157
+ sources_list.extend(response)
158
+ else:
159
+ raise ValueError("Input must be either a dict with 'results' or a list of search results")
160
+
161
+ # Deduplicate by URL
162
+ unique_sources = {}
163
+ for source in sources_list:
164
+ if source['url'] not in unique_sources:
165
+ unique_sources[source['url']] = source
166
+
167
+ # Format output
168
+ formatted_text = "Sources:\n\n"
169
+ for i, source in enumerate(unique_sources.values(), 1):
170
+ formatted_text += f"Source: {source['title']}\n===\n"
171
+ formatted_text += f"URL: {source['url']}\n===\n"
172
+ formatted_text += f"Most relevant content from source: {source['content']}\n===\n"
173
+ if fetch_full_page:
174
+ # Using rough estimate of 4 characters per token
175
+ char_limit = max_tokens_per_source * 4
176
+ # Handle None raw_content
177
+ raw_content = source.get('raw_content', '')
178
+ if raw_content is None:
179
+ raw_content = ''
180
+ print(f"Warning: No raw_content found for source {source['url']}")
181
+ if len(raw_content) > char_limit:
182
+ raw_content = raw_content[:char_limit] + "... [truncated]"
183
+ formatted_text += f"Full source content limited to {max_tokens_per_source} tokens: {raw_content}\n\n"
184
+
185
+ return formatted_text.strip()
186
+
187
+
188
+ def format_sources(search_results: Dict[str, Any]) -> str:
189
+ """
190
+ Format search results into a bullet-point list of sources with URLs.
191
+
192
+ Creates a simple bulleted list of search results with title and URL for each source.
193
+
194
+ Args:
195
+ search_results (Dict[str, Any]): Search response containing a 'results' key with
196
+ a list of search result objects
197
+
198
+ Returns:
199
+ str: Formatted string with sources as bullet points in the format "* title : url"
200
+ """
201
+ return '\n'.join(
202
+ f"* {source['title']} : {source['url']}"
203
+ for source in search_results['results']
204
+ )
205
+
206
+
207
+ def web_research(state: SummaryState):
208
+ search_results = tavily_client.search(state.search_query, include_raw_content=True, max_results=1)
209
+ search_str = deduplicate_and_format_sources(search_results, max_tokens_per_source=1000)
210
+ return {
211
+ "sources_gathered" : [format_sources(search_results)],
212
+ "research_loop_count" : state.research_loop_count + 1,
213
+ "web_search_results" : [search_str]
214
+ }
215
+
216
+
217
+ def summarize_sources(state: SummaryState):
218
+ existing_summary = state.running_summary
219
+ print(state.web_search_results)
220
+ most_recent_web_search = state.web_search_results[-1]
221
+
222
+ if existing_summary:
223
+ human_message = (
224
+ f"IMPORTANT INSTRUCTIONS:\n{summerizer_instructions}\n\n"
225
+ f"Extend the existing summary: {existing_summary}\n\n"
226
+ f"Include new search results: {most_recent_web_search}"
227
+ f"That addresses the following topic: {state.research_topic}"
228
+ )
229
+ else:
230
+ human_message = (
231
+ f"IMPORTANT INSTRUCTIONS:\n{summerizer_instructions}\n\n"
232
+ f"Generate summary of these search results: {most_recent_web_search}"
233
+ f"That addresses the following topic: {state.research_topic}"
234
+ )
235
+
236
+ result = gemma_model.invoke([HumanMessage(content=human_message)])
237
+
238
+ return {"running_summary" : result.content}
239
+
240
+
241
+ def reflect_on_summary(state: SummaryState):
242
+ result = gemma_model([
243
+ HumanMessage(content=f"IMPORTANT INSTRUCTIONS:\n{reflection_summary.format(research_topic=state.research_topic)}\n\nIdentify a knowledge gap and generate a follow-up web search query based on existing knowledge: {state.running_summary}")
244
+ ])
245
+ # print(f">> [FUN] REFLECT ON SUMMARY:\n{result.content}")
246
+ raw_content = result.content.strip()
247
+ match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", raw_content, re.DOTALL)
248
+ if match:
249
+ json_str = match.group(1)
250
+ else:
251
+ raise ValueError(f"Failed to extract JSON from model response: {raw_content}")
252
+
253
+ query = json.loads(json_str)
254
+ print(query)
255
+ return {"search_query" : query["follow_up_query"]}
256
+
257
+
258
+ def finalize_summary(state: SummaryState):
259
+ all_sources = "\n".join(source for source in state.sources_gathered)
260
+ print(f"All Sources: {all_sources}")
261
+ running_summary = f"## Summary\n\n{state.running_summary}\n\nSources:\n{all_sources}"
262
+ return {"running_summary" : running_summary}
263
+
264
+ def route_research(state: SummaryState):
265
+ if state.research_loop_count <= max_web_research_loops:
266
+ return "web_research"
267
+ else:
268
+ return "finalize_summary"
269
+
270
+
271
+ def perform_deep_research(query):
272
+
273
+ builder = StateGraph(SummaryState, input_schema=SummaryStateInput, output_schema=SummaryStateOutput)
274
+
275
+ builder.add_node("generate_query", generate_query)
276
+ builder.add_node("web_research", web_research)
277
+ builder.add_node("summarize_sources", summarize_sources)
278
+ builder.add_node("reflect_on_summary", reflect_on_summary)
279
+ builder.add_node("finalize_summary", finalize_summary)
280
+
281
+ # Add edges
282
+ builder.add_edge(START, "generate_query")
283
+ builder.add_edge("generate_query", "web_research")
284
+ builder.add_edge("web_research", "summarize_sources")
285
+ builder.add_edge("summarize_sources", "reflect_on_summary")
286
+ builder.add_conditional_edges("reflect_on_summary", route_research)
287
+ builder.add_edge("finalize_summary", END)
288
+
289
+
290
+ graph = builder.compile()
291
+
292
+ research_input = SummaryStateInput(research_topic=query)
293
+
294
+ research_output = graph.invoke(research_input)
295
+
296
+ return research_output["running_summary"]
src/model.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from api_key import GOOGLE_API_KEY
2
+ from langchain_google_genai import ChatGoogleGenerativeAI
3
+
4
+
5
+
6
+ def get_gemma(temperature=0, top_k=40, top_p=0.95):
7
+
8
+ gemma_model = ChatGoogleGenerativeAI(
9
+ model="gemma-3-12b-it",
10
+ temperature=temperature,
11
+ max_tokens=2048,
12
+ timeout=None,
13
+ max_retries=2,
14
+ google_api_key=GOOGLE_API_KEY,
15
+ top_k=top_k,
16
+ top_p=top_p
17
+ )
18
+
19
+ return gemma_model
src/rag_utils.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
2
+ from langchain_community.document_loaders import PyPDFLoader
3
+ from langchain_chroma import Chroma
4
+ from langchain_huggingface import HuggingFaceEmbeddings
5
+
6
+
7
+ embedding_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
8
+
9
+ def load_pdf_document(file_path):
10
+ document_loader = PyPDFLoader(file_path)
11
+ return document_loader.load()
12
+
13
+ def chunk_documents(raw_documents):
14
+ text_processor = RecursiveCharacterTextSplitter(
15
+ chunk_size = 1000,
16
+ chunk_overlap = 200,
17
+ add_start_index = True
18
+ )
19
+ return text_processor.split_documents(raw_documents)
20
+
21
+ def find_related_documents(query, vector_database):
22
+ # return vector_database.similarity_search(query, k=2)
23
+ return vector_database.max_marginal_relevance_search(query, k=2, fetch_k=5, lambda_mult=0.6)
24
+
25
+
26
+ def ProcessDocuments(document_path: str) -> str:
27
+
28
+ loaded_doc = load_pdf_document(document_path)
29
+ chunked_doc = chunk_documents(loaded_doc)
30
+
31
+
32
+ vector_database = Chroma(
33
+ persist_directory=f"./chroma_store/{document_path.split("\\")[-1].split(".")[0]}",
34
+ embedding_function=embedding_model
35
+ )
36
+
37
+ vector_database.add_documents(chunked_doc)
38
+
39
+
40
+ def generate_context(query: str, file: str):
41
+
42
+ ProcessDocuments(file)
43
+
44
+
45
+ vector_database = Chroma(
46
+ persist_directory=f"./chroma_store/{file.split("\\")[-1].split(".")[0]}",
47
+ embedding_function=embedding_model
48
+ )
49
+
50
+ relevant_docs = find_related_documents(query, vector_database)
51
+ context_text = "\n".join([doc.page_content for doc in relevant_docs])
52
+
53
+ return query, context_text
54
+
src/run_model.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from model import get_gemma
2
+ from rag_utils import generate_context
3
+
4
+
5
+ def generate_response(history=[], temperature: float=0.0, top_k=None, top_p=None):
6
+
7
+ gemma_model = get_gemma(temperature=temperature, top_k=top_k, top_p=top_p)
8
+
9
+ response = gemma_model.invoke(history).content
10
+
11
+ return response
12
+
13
+ def generate_RAG_response(query: str, file_path, history=[]):
14
+ gemma_model = get_gemma()
15
+ query, context = generate_context(query, file_path)
16
+
17
+ history[-1] = {"role" : "user", "content" : f"INSTRUCTION: Answer the query with given context in mind.\nQUERY: {query}\n\nCONTEXT : {context}"}
18
+
19
+ response = gemma_model.invoke(history).content
20
+
21
+ return response
src/web_search.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.agents import AgentExecutor, create_react_agent
2
+ from langchain.agents import load_tools, Tool
3
+ # from langchain.tools import DuckDuckGoSearchResults
4
+ from tavily import TavilyClient
5
+ from langchain.prompts import PromptTemplate
6
+ from model import get_gemma
7
+ from api_key import TAVILY_API_KEY
8
+
9
+ gemma_model = get_gemma()
10
+
11
+
12
+ # Create the ReAct template
13
+ react_template = """Answer the following questions as best you can. You have access to the following tools:
14
+
15
+ {tools}
16
+
17
+ Use the following format:
18
+
19
+ Question: the input question you must answer
20
+ Thought: you should always think about what to do
21
+ Action: the action to take, should be one of [{tool_names}]
22
+ Action Input: the input to the action
23
+ Observation: the result of the action
24
+ ... (this Thought/Action/Action Input/Observation can repeat N times)
25
+ Thought: I now know the final answer
26
+ Final Answer: the final answer to the original input question
27
+
28
+ Begin!
29
+
30
+ Question: {input}
31
+ Thought:{agent_scratchpad}"""
32
+
33
+ prompt = PromptTemplate(
34
+ template=react_template,
35
+ input_variables=["tools", "tool_names", "input", "agent_scratchpad"]
36
+ )
37
+
38
+
39
+ tavily_client = TavilyClient(api_key=TAVILY_API_KEY)
40
+
41
+ tavily_search_tool = Tool(
42
+ name="tavily search",
43
+ description = "A web search engine. Use this to as a search engine for general queries.",
44
+ func = lambda x: tavily_client.search(x, max_results=1)
45
+ )
46
+
47
+ # Prepare tools
48
+ tools = load_tools(["llm-math"], llm=gemma_model)
49
+ tools.append(tavily_search_tool)
50
+
51
+
52
+ # Construct the ReAct agent
53
+ agent = create_react_agent(gemma_model, tools, prompt)
54
+ agent_executor = AgentExecutor(
55
+ agent=agent,
56
+ tools=tools,
57
+ verbose=True,
58
+ handle_parsing_errors=True,
59
+ return_intermediate_steps=True
60
+ )
61
+
62
+ def get_urls_from_response(response):
63
+ urls = []
64
+ for step in response["intermediate_steps"]:
65
+ urls.append(step[1]["results"][0]["url"])
66
+ return urls
67
+
68
+
69
+ def search_web(query):
70
+ response = agent_executor.invoke({"input" : query})
71
+
72
+ output = response["output"]
73
+ sources = get_urls_from_response(response)
74
+
75
+ return output, sources
76
+