Sandiago21 commited on
Commit
0c02b91
·
verified ·
1 Parent(s): 5834810

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +275 -24
app.py CHANGED
@@ -44,9 +44,10 @@ class Config(object):
44
  self.temperature = 0.1
45
  self.DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
46
  self.model_name = "mistralai/Mistral-7B-Instruct-v0.2"
 
47
  # self.reasoning_model_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
48
  # self.reasoning_model_name = "Qwen/Qwen2.5-7B-Instruct"
49
- self.reasoning_model_name = "mistralai/Mistral-7B-Instruct-v0.2"
50
 
51
 
52
  config = Config()
@@ -193,6 +194,149 @@ def visit_webpage(url: str) -> str:
193
  return (text[:1000], )
194
 
195
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  def web_search(query: str, num_results: int = 10):
197
  """
198
  Search the internet for the query provided
@@ -212,6 +356,107 @@ def web_search(query: str, num_results: int = 10):
212
  soup = BeautifulSoup(response.text, "html.parser")
213
  return [a.get("href") for a in soup.select(".result__a")[:num_results]]
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  def planner_node(state: AgentState):
216
  """
217
  Planning node for a tool-using LLM agent.
@@ -312,6 +557,7 @@ User request:
312
 
313
  return state
314
 
 
315
  def planner_node(state: AgentState):
316
  """
317
  Planning node for a tool-using LLM agent.
@@ -411,6 +657,7 @@ User request:
411
  state["proposed_action"] = data
412
 
413
  return state
 
414
 
415
  def safety_node(state: AgentState):
416
  """
@@ -423,8 +670,8 @@ def safety_node(state: AgentState):
423
  prompt = f"""
424
  You are a response agent.
425
 
426
- You must reason over the user request and the provided information and output the answer to the user's request.
427
-
428
  You MUST return EXACTLY one line in the following format:
429
  Response: <answer>
430
 
@@ -614,7 +861,7 @@ def tool_executor(state: AgentState):
614
  responsible for translating structured LLM intent into real system actions.
615
  """
616
 
617
- web_page_result = ""
618
  action = Action.model_validate(state["proposed_action"])
619
 
620
  best_query_webpage_information_similarity_score = -1.0
@@ -639,36 +886,40 @@ def tool_executor(state: AgentState):
639
 
640
  for result in results:
641
  try:
642
- web_page_results = visit_webpage(result)
 
643
 
644
- for web_page_result in web_page_results:
645
- query_embeddings = sentence_transformer_model.encode_query(state["messages"][-1].content).reshape(1, -1)
646
- webpage_information_embeddings = sentence_transformer_model.encode_query(web_page_result).reshape(1, -1)
647
- query_webpage_information_similarity_score = float(cosine_similarity(query_embeddings, webpage_information_embeddings)[0][0])
648
-
649
- # logger.info(f"Webpage Information and Similarity Score: {web_page_result} - {query_webpage_information_similarity_score}")
650
-
651
- if query_webpage_information_similarity_score > 0.60:
652
- webpage_information_complete += web_page_result
653
- webpage_information_complete += " \n "
654
- webpage_information_complete += " \n "
655
-
656
- if query_webpage_information_similarity_score > best_query_webpage_information_similarity_score:
657
- best_query_webpage_information_similarity_score = query_webpage_information_similarity_score
658
- best_webpage_information = web_page_result
659
 
660
  except Exception as e:
661
  logger.info(f"Tool Executor - Exception: {e}")
662
 
663
  elif action.tool == "visit_webpage":
664
  try:
665
- web_page_result = visit_webpage(**action.args)
666
  except:
667
  pass
668
  else:
669
  result = "Unknown tool"
670
 
671
- state["information"] = webpage_information_complete
 
 
 
672
  state["best_query_webpage_information_similarity_score"] = best_query_webpage_information_similarity_score
673
 
674
  logger.info(f"Information: {state['information']}")
@@ -719,8 +970,8 @@ class BasicAgent:
719
 
720
 
721
  # if question == "Given this table defining * on the set S = {a, b, c, d, e}\n\n|*|a|b|c|d|e|\n|---|---|---|---|---|---|\n|a|a|b|c|b|d|\n|b|b|c|a|e|c|\n|c|c|a|b|b|a|\n|d|b|e|b|e|d|\n|e|d|b|a|d|c|\n\nprovide the subset of S involved in any possible counter-examples that prove * is not commutative. Provide your answer as a comma separated list of the elements in the set in alphabetical order.":
722
- if " image " not in question and " video " not in question:
723
- # if question == "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?":
724
  state = {
725
  "messages": question,
726
  }
 
44
  self.temperature = 0.1
45
  self.DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
46
  self.model_name = "mistralai/Mistral-7B-Instruct-v0.2"
47
+ # self.model_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
48
  # self.reasoning_model_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
49
  # self.reasoning_model_name = "Qwen/Qwen2.5-7B-Instruct"
50
+ # self.reasoning_model_name = "mistralai/Mistral-7B-Instruct-v0.2"
51
 
52
 
53
  config = Config()
 
194
  return (text[:1000], )
195
 
196
 
197
+ def visit_webpage(url: str) -> str:
198
+ headers = {
199
+ "User-Agent": "Mozilla/5.0"
200
+ }
201
+
202
+ response = requests.get(url, headers=headers, timeout=10)
203
+ response.raise_for_status()
204
+
205
+ soup = BeautifulSoup(response.text, "html.parser")
206
+
207
+ # Remove scripts/styles
208
+ for tag in soup(["script", "style"]):
209
+ tag.extract()
210
+
211
+ content = soup.find("div", {"id": "mw-content-text"})
212
+
213
+ texts = []
214
+
215
+ # 1. Paragraphs
216
+ for p in content.find_all("p"):
217
+ texts.append(p.get_text(strip=False))
218
+
219
+ # 2. Definition lists
220
+ for dd in content.find_all("dd"):
221
+ texts.append(dd.get_text(strip=False))
222
+
223
+ # 3. Tables (IMPORTANT)
224
+ for table in content.find_all("table", {"class": "wikitable"}):
225
+ for row in table.find_all("tr"):
226
+ cols = [c.get_text(strip=True) for c in row.find_all(["td", "th"])]
227
+ if cols:
228
+ texts.append(" | ".join(cols))
229
+
230
+ return (" \n ".join(texts)[:1000], )
231
+
232
+
233
+ def visit_webpage(url: str) -> str:
234
+ headers = {
235
+ "User-Agent": "Mozilla/5.0"
236
+ }
237
+
238
+ response = requests.get(url, headers=headers, timeout=10)
239
+ response.raise_for_status()
240
+
241
+ soup = BeautifulSoup(response.text, "html.parser")
242
+
243
+ # Remove scripts/styles
244
+ for tag in soup(["script", "style"]):
245
+ tag.extract()
246
+
247
+ content = soup.find("div", {"id": "mw-content-text"})
248
+
249
+ # Extract more elements (not just <p>)
250
+ elements = soup.find_all(["p", "dd"])
251
+
252
+ main_text = " \n ".join(el.get_text(strip=False) for el in elements)
253
+
254
+ # 3. Tables (IMPORTANT)
255
+ table_texts = []
256
+ for table in content.find_all("table", {"class": "wikitable"}):
257
+ for row in table.find_all("tr"):
258
+ cols = [c.get_text(strip=True) for c in row.find_all(["td", "th"])]
259
+ if cols:
260
+ table_texts.append(" | ".join(cols))
261
+
262
+ if len(table_texts) > 0:
263
+ return [main_text[:1000], " \n ".join(table_texts),]
264
+ else:
265
+ return [main_text[:1000],]
266
+
267
+
268
+ def visit_webpage(url: str) -> str:
269
+ headers = {
270
+ "User-Agent": "Mozilla/5.0"
271
+ }
272
+
273
+ response = requests.get(url, headers=headers, timeout=10)
274
+ response.raise_for_status()
275
+
276
+ soup = BeautifulSoup(response.text, "html.parser")
277
+
278
+ # Remove scripts/styles
279
+ for tag in soup(["script", "style"]):
280
+ tag.extract()
281
+
282
+ content = soup.find("div", {"id": "mw-content-text"})
283
+
284
+ # Extract more elements (not just <p>)
285
+ elements = soup.find_all(["p", "dd"])
286
+
287
+ main_text = " \n ".join(el.get_text(strip=False) for el in elements)
288
+
289
+ # 3. Tables (IMPORTANT)
290
+ table_texts = []
291
+ if content is not None:
292
+ for table in content.find_all("table", {"class": "wikitable"}):
293
+ for row in table.find_all("tr"):
294
+ cols = [c.get_text(strip=True) for c in row.find_all(["td", "th"])]
295
+ if cols:
296
+ table_texts.append(" | ".join(cols))
297
+
298
+ if len(table_texts) > 0:
299
+ return [main_text[:1000], " \n ".join(table_texts),]
300
+ else:
301
+ return [main_text[:1000],]
302
+
303
+
304
+ def visit_webpage(url: str) -> str:
305
+ headers = {
306
+ "User-Agent": "Mozilla/5.0"
307
+ }
308
+
309
+ response = requests.get(url, headers=headers, timeout=10)
310
+ response.raise_for_status()
311
+
312
+ soup = BeautifulSoup(response.text, "html.parser")
313
+
314
+ # Remove scripts/styles
315
+ for tag in soup(["script", "style"]):
316
+ tag.extract()
317
+
318
+ content = soup.find("div", {"id": "mw-content-text"})
319
+
320
+ # Extract more elements (not just <p>)
321
+ elements = soup.find_all(["p", "dd"])
322
+
323
+ main_text = " \n ".join(el.get_text(strip=False) for el in elements)
324
+
325
+ # 3. Tables (IMPORTANT)
326
+ table_texts = []
327
+ if content is not None:
328
+ for table in content.find_all("table", {"class": "wikitable"}):
329
+ for row in table.find_all("tr"):
330
+ cols = [c.get_text(strip=True) for c in row.find_all(["td", "th"])]
331
+ if cols:
332
+ table_texts.append(" | ".join(cols))
333
+
334
+ if len(table_texts) > 0:
335
+ return [main_text[:1000], " \n ".join(table_texts)[:5000],]
336
+ else:
337
+ return [main_text[:1000],]
338
+
339
+
340
  def web_search(query: str, num_results: int = 10):
341
  """
342
  Search the internet for the query provided
 
356
  soup = BeautifulSoup(response.text, "html.parser")
357
  return [a.get("href") for a in soup.select(".result__a")[:num_results]]
358
 
359
+
360
+ def planner_node(state: AgentState):
361
+ """
362
+ Planning node for a tool-using LLM agent.
363
+
364
+ The planner enforces:
365
+ - Strict JSON-only output
366
+ - Tool selection constrained to predefined tools
367
+ - Argument generation limited to user-provided information
368
+
369
+ Parameters
370
+ ----------
371
+ state : dict
372
+ Agent state dictionary containing:
373
+ - "messages" (str): The user's natural language request.
374
+
375
+ Returns
376
+ -------
377
+ dict
378
+ Updated state dictionary with additional keys:
379
+ - "proposed_action" (dict): Parsed JSON tool call in the form:
380
+ {
381
+ "tool": "<tool_name>",
382
+ "args": {...}
383
+ }
384
+ - "risk_score" (float): Initialized risk score (default 0.0).
385
+ - "decision" (str): Initial decision ("allow" by default).
386
+
387
+ Behavior
388
+ --------
389
+ 1. Constructs a planning prompt including:
390
+ - Available tools and allowed arguments
391
+ - Strict JSON formatting requirements
392
+ - Example of valid output
393
+ 2. Calls the language model via `generate()`.
394
+ 3. Attempts to extract valid JSON from the model output.
395
+ 4. Repairs malformed JSON using `repair_json`.
396
+ 5. Stores the parsed action into the agent state.
397
+
398
+ Security Notes
399
+ --------------
400
+ - This node does not enforce tool-level authorization.
401
+ - It does not validate hallucinated tools.
402
+ - It does not perform risk scoring beyond initializing values.
403
+ - Downstream nodes must implement:
404
+ * Tool whitelist validation
405
+ * Argument validation
406
+ * Risk scoring and mitigation
407
+ * Execution authorization
408
+
409
+ Intended Usage
410
+ --------------
411
+ Designed for multi-agent or LangGraph-style workflows where:
412
+ Planner → Risk Assessment → Tool Executor → Logger
413
+
414
+ This node represents the *planning layer* of the agent architecture.
415
+ """
416
+
417
+ user_input = state["messages"][-1].content
418
+
419
+ prompt = f"""
420
+ You are a planning agent.
421
+
422
+ You MUST return ONLY valid JSON as per the tools specs below ONLY.
423
+ No extra text.
424
+ DO NOT invent anything additional beyond the user request provided. Keep it strict to the user request information provided. The question and the query should be fully relevant to the user request provided, no deviation and hallucination. If possible and makes sense then the query should be exactly the user request.
425
+
426
+ The available tools and their respective arguments are: {{
427
+ "web_search": ["query"],
428
+ "visit_webpage": ["url"],
429
+ }}
430
+
431
+ Return exactly the following format:
432
+ Response:
433
+ {{
434
+ "tool": "...",
435
+ "args": {{...}}
436
+ }}
437
+
438
+ User request: Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?. Example of valid JSON expected:
439
+ Response:
440
+ {{"tool": "web_search",
441
+ "args": {{"query": "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?",
442
+ }}
443
+ }}
444
+
445
+ Return only one Response!
446
+
447
+ User request:
448
+ {user_input}
449
+ """
450
+
451
+ output = generate(prompt)
452
+
453
+ state["proposed_action"] = output.split("Response:")[-1]
454
+ fixed = repair_json(state["proposed_action"])
455
+ data = json.loads(fixed)
456
+ state["proposed_action"] = data
457
+
458
+ return state
459
+
460
  def planner_node(state: AgentState):
461
  """
462
  Planning node for a tool-using LLM agent.
 
557
 
558
  return state
559
 
560
+
561
  def planner_node(state: AgentState):
562
  """
563
  Planning node for a tool-using LLM agent.
 
657
  state["proposed_action"] = data
658
 
659
  return state
660
+
661
 
662
  def safety_node(state: AgentState):
663
  """
 
670
  prompt = f"""
671
  You are a response agent.
672
 
673
+ You must reason over the user request and the provided information and output the answer to the user's request. Reason well over the information provided, if any, and output the answer that satisfies the user's question exactly.
674
+
675
  You MUST return EXACTLY one line in the following format:
676
  Response: <answer>
677
 
 
861
  responsible for translating structured LLM intent into real system actions.
862
  """
863
 
864
+ webpage_result = ""
865
  action = Action.model_validate(state["proposed_action"])
866
 
867
  best_query_webpage_information_similarity_score = -1.0
 
886
 
887
  for result in results:
888
  try:
889
+ webpage_results = visit_webpage(result)
890
+ webpage_result = " \n ".join(webpage_results)
891
 
892
+ # for webpage_result in webpage_results:
893
+ query_embeddings = sentence_transformer_model.encode_query(state["messages"][-1].content).reshape(1, -1)
894
+ webpage_information_embeddings = sentence_transformer_model.encode_query(webpage_result).reshape(1, -1)
895
+ query_webpage_information_similarity_score = float(cosine_similarity(query_embeddings, webpage_information_embeddings)[0][0])
896
+
897
+ # logger.info(f"Webpage Information and Similarity Score: {result} - {webpage_result} - {query_webpage_information_similarity_score}")
898
+
899
+ if query_webpage_information_similarity_score > 0.65:
900
+ webpage_information_complete += webpage_result
901
+ webpage_information_complete += " \n "
902
+ webpage_information_complete += " \n "
903
+
904
+ if query_webpage_information_similarity_score > best_query_webpage_information_similarity_score:
905
+ best_query_webpage_information_similarity_score = query_webpage_information_similarity_score
906
+ best_webpage_information = webpage_result
907
 
908
  except Exception as e:
909
  logger.info(f"Tool Executor - Exception: {e}")
910
 
911
  elif action.tool == "visit_webpage":
912
  try:
913
+ webpage_result = visit_webpage(**action.args)
914
  except:
915
  pass
916
  else:
917
  result = "Unknown tool"
918
 
919
+ if webpage_information_complete == "":
920
+ webpage_information_complete = best_webpage_information
921
+
922
+ state["information"] = webpage_information_complete[:3000]
923
  state["best_query_webpage_information_similarity_score"] = best_query_webpage_information_similarity_score
924
 
925
  logger.info(f"Information: {state['information']}")
 
970
 
971
 
972
  # if question == "Given this table defining * on the set S = {a, b, c, d, e}\n\n|*|a|b|c|d|e|\n|---|---|---|---|---|---|\n|a|a|b|c|b|d|\n|b|b|c|a|e|c|\n|c|c|a|b|b|a|\n|d|b|e|b|e|d|\n|e|d|b|a|d|c|\n\nprovide the subset of S involved in any possible counter-examples that prove * is not commutative. Provide your answer as a comma separated list of the elements in the set in alphabetical order.":
973
+ # if " image " not in question and " video " not in question:
974
+ if question == "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?":
975
  state = {
976
  "messages": question,
977
  }