Benny97 commited on
Commit
3a7b7ae
·
verified ·
1 Parent(s): e6bc1d6

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -1,12 +1,159 @@
1
  ---
2
- title: SearchOps Assistant
3
- emoji: 🌍
4
- colorFrom: gray
5
- colorTo: indigo
6
  sdk: gradio
7
  sdk_version: 5.29.1
8
- app_file: app.py
9
- pinned: false
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: SearchOps_Assistant
3
+ app_file: app.py
 
 
4
  sdk: gradio
5
  sdk_version: 5.29.1
 
 
6
  ---
7
 
8
+ # Search Personal Co-Worker
9
+
10
+ An intelligent search assistant built on LangGraph that can answer questions, perform web searches, record query history, and support personalized interactions.
11
+
12
+ ## Project Overview
13
+
14
+ This project creates a smart search agent capable of finding information, answering questions, and maintaining search history records. Built using the LangGraph framework, it combines various tools to provide a rich search experience.
15
+
16
+ ## Key Features
17
+
18
+ - **Intelligent Search**: Answer complex questions like "Where to find the best Chinese food in New York" or "What are the strongest AI technologies in 2025"
19
+ - **Search History Management**: Automatically saves all search records, allowing users to query their history using natural language (e.g., "give me all the search history")
20
+ - **Multi-Tool Integration**: Combines web search, Wikipedia, Python computation and other tools to provide comprehensive answers
21
+ - **Personalized Experience**: Recognizes users by username and saves individual search histories
22
+ - **Evaluation Mechanism**: Supports custom success criteria to evaluate search result quality
23
+ - **Automated Execution**: Uses Docker for automated program running and web scraping capabilities
24
+ - **Mobile Notifications**: Integrates with Pushover API to send real-time notifications to mobile devices
25
+ - **Web Scraping**: Employs Pydantic for structured data extraction from websites
26
+
27
+ ## Project Structure
28
+
29
+ The project consists of three main files:
30
+
31
+ - **app.py**: Frontend interface responsible for user interaction
32
+ - **search.py**: LangGraph core backend that manages the search process and graph nodes
33
+ - **search_tools.py**: Defines all tools and agent functionalities
34
+
35
+ ## LangGraph Architecture
36
+
37
+ The search flow consists of the following nodes:
38
+
39
+ - **StartNode**: Initializes the search process
40
+ - **WorkerNode**: Handles the main search logic
41
+ - **ToolsNode**: Manages and executes various external tools
42
+ - **EvaluatorNode**: Evaluates whether search results meet success criteria
43
+ - **SQLFormatterNode**: Processes database-related queries
44
+ - **EndNode**: Completes the search process and returns results
45
+
46
+ ## Integrated Tools
47
+
48
+ This project integrates several powerful tools:
49
+
50
+ 1. **Search Tool**: Online web search functionality
51
+ 2. **History Query Tool**: Retrieves user's past search history
52
+ 3. **Wikipedia Tool**: Directly queries Wikipedia content
53
+ 4. **Python REPL**: Executes Python code for calculations or data processing
54
+ 5. **Push Notification Tool**: Sends push notifications to mobile devices via Pushover API
55
+ 6. **File Tools**: File read/write operations
56
+ 7. **Docker Execution**: Securely runs code in isolated containers
57
+ 8. **Web Scraping**: Uses Pydantic models for structured web data extraction
58
+
59
+ ## Installation & Setup
60
+
61
+ ### Requirements
62
+
63
+ - Python 3.8+
64
+ - Docker (for automated program execution)
65
+ - Required dependencies (see requirements.txt)
66
+ - Pushover account and API key (for mobile notifications)
67
+
68
+ ### Installation Steps
69
+
70
+ 1. Clone the repository:
71
+ ```bash
72
+ git clone [repository-url]
73
+ cd [project-folder]
74
+ ```
75
+
76
+ 2. Install dependencies:
77
+ ```bash
78
+ pip install -r requirements.txt
79
+ playwright install chromium
80
+ ```
81
+
82
+ 3. Set up environment variables:
83
+ ```bash
84
+ # Required for Pushover notifications
85
+ export PUSHOVER_USER_KEY=your_user_key
86
+ export PUSHOVER_API_TOKEN=your_api_token
87
+ ```
88
+
89
+ 4. Install Docker (if not already installed):
90
+ ```bash
91
+ # For Ubuntu
92
+ sudo apt-get update
93
+ sudo apt-get install docker-ce docker-ce-cli containerd.io
94
+
95
+ # For macOS and Windows, download and install Docker Desktop
96
+ ```
97
+
98
+ ### Running the Application
99
+
100
+ ```bash
101
+ python app.py
102
+ ```
103
+
104
+ The application will start locally. Access the URL displayed in the terminal (typically http://localhost:7860) to use the search assistant.
105
+
106
+ ## Usage Guide
107
+
108
+ 1. Enter your username (used to record search history)
109
+ 2. Type your question in the search box
110
+ 3. Specify success criteria (optional, used to evaluate result quality)
111
+ 4. Click the "Search" button
112
+ 5. View your search results
113
+ 6. Query your history by entering commands like "give me all the search history"
114
+ 7. Receive push notifications on your mobile device for important alerts
115
+
116
+ ## Deploying to Hugging Face
117
+
118
+ To deploy this project to Hugging Face Spaces:
119
+
120
+ 1. Create a setup.sh file with the following content:
121
+ ```bash
122
+ #!/bin/bash
123
+ pip install -r requirements.txt
124
+ playwright install chromium
125
+ ```
126
+
127
+ 2. Create a .hf directory with a requirements.txt file containing:
128
+ ```
129
+ execute_setup_sh==True
130
+ ```
131
+
132
+ 3. Set up repository secrets in Hugging Face for sensitive API keys:
133
+ - PUSHOVER_USER_KEY
134
+ - PUSHOVER_API_TOKEN
135
+
136
+ 4. Ensure your code runs in headless mode for Playwright:
137
+ ```python
138
+ browser = playwright.chromium.launch(headless=True)
139
+ ```
140
+
141
+ 5. Note: Docker execution features may have limited functionality in the Hugging Face environment
142
+
143
+ ## Troubleshooting
144
+
145
+ If you encounter issues during deployment, check:
146
+ - Browser binary installation (Playwright requires additional setup)
147
+ - Environment differences between local and deployment
148
+ - Path configurations and dependencies
149
+ - API key configuration for Pushover notifications
150
+ - Docker availability and permissions in your deployment environment
151
+
152
+ ## Contributing
153
+
154
+ Contributions to improve the project are welcome. Please feel free to submit a pull request or open an issue.
155
+
156
+ ## License
157
+
158
+ Benny
159
+
SearchOps_Assistant/.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
SearchOps_Assistant/.hf/requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.29.1
2
+ langchain==0.3.25
3
+ langchain_community==0.3.24
4
+ langchain_core==0.3.60
5
+ langchain_experimental==0.3.4
6
+ langchain_openai==0.3.17
7
+ langgraph==0.4.5
8
+ playwright==1.52.0
9
+ pydantic==2.11.4
10
+ python-dotenv==1.1.0
11
+ Requests==2.32.3
12
+ typing_extensions==4.13.2
13
+
14
+ execute_setup_sh==True
SearchOps_Assistant/README.md ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Search Personal Co-Worker
2
+
3
+ An intelligent search assistant built on LangGraph that can answer questions, perform web searches, record query history, and support personalized interactions.
4
+
5
+ ## Project Overview
6
+
7
+ This project creates a smart search agent capable of finding information, answering questions, and maintaining search history records. Built using the LangGraph framework, it combines various tools to provide a rich search experience.
8
+
9
+ ## Key Features
10
+
11
+ - **Intelligent Search**: Answer complex questions like "Where to find the best Chinese food in New York" or "What are the strongest AI technologies in 2025"
12
+ - **Search History Management**: Automatically saves all search records, allowing users to query their history using natural language (e.g., "give me all the search history")
13
+ - **Multi-Tool Integration**: Combines web search, Wikipedia, Python computation and other tools to provide comprehensive answers
14
+ - **Personalized Experience**: Recognizes users by username and saves individual search histories
15
+ - **Evaluation Mechanism**: Supports custom success criteria to evaluate search result quality
16
+ - **Automated Execution**: Uses Docker for automated program running and web scraping capabilities
17
+ - **Mobile Notifications**: Integrates with Pushover API to send real-time notifications to mobile devices
18
+ - **Web Scraping**: Employs Pydantic for structured data extraction from websites
19
+
20
+ ## Project Structure
21
+
22
+ The project consists of three main files:
23
+
24
+ - **app.py**: Frontend interface responsible for user interaction
25
+ - **search.py**: LangGraph core backend that manages the search process and graph nodes
26
+ - **search_tools.py**: Defines all tools and agent functionalities
27
+
28
+ ## LangGraph Architecture
29
+
30
+ The search flow consists of the following nodes:
31
+
32
+ - **StartNode**: Initializes the search process
33
+ - **WorkerNode**: Handles the main search logic
34
+ - **ToolsNode**: Manages and executes various external tools
35
+ - **EvaluatorNode**: Evaluates whether search results meet success criteria
36
+ - **SQLFormatterNode**: Processes database-related queries
37
+ - **EndNode**: Completes the search process and returns results
38
+
39
+ ## Integrated Tools
40
+
41
+ This project integrates several powerful tools:
42
+
43
+ 1. **Search Tool**: Online web search functionality
44
+ 2. **History Query Tool**: Retrieves user's past search history
45
+ 3. **Wikipedia Tool**: Directly queries Wikipedia content
46
+ 4. **Python REPL**: Executes Python code for calculations or data processing
47
+ 5. **Push Notification Tool**: Sends push notifications to mobile devices via Pushover API
48
+ 6. **File Tools**: File read/write operations
49
+ 7. **Docker Execution**: Securely runs code in isolated containers
50
+ 8. **Web Scraping**: Uses Pydantic models for structured web data extraction
51
+
52
+ ## Installation & Setup
53
+
54
+ ### Requirements
55
+
56
+ - Python 3.8+
57
+ - Docker (for automated program execution)
58
+ - Required dependencies (see requirements.txt)
59
+ - Pushover account and API key (for mobile notifications)
60
+
61
+ ### Installation Steps
62
+
63
+ 1. Clone the repository:
64
+ ```bash
65
+ git clone [repository-url]
66
+ cd [project-folder]
67
+ ```
68
+
69
+ 2. Install dependencies:
70
+ ```bash
71
+ pip install -r requirements.txt
72
+ playwright install chromium
73
+ ```
74
+
75
+ 3. Set up environment variables:
76
+ ```bash
77
+ # Required for Pushover notifications
78
+ export PUSHOVER_USER_KEY=your_user_key
79
+ export PUSHOVER_API_TOKEN=your_api_token
80
+ ```
81
+
82
+ 4. Install Docker (if not already installed):
83
+ ```bash
84
+ # For Ubuntu
85
+ sudo apt-get update
86
+ sudo apt-get install docker-ce docker-ce-cli containerd.io
87
+
88
+ # For macOS and Windows, download and install Docker Desktop
89
+ ```
90
+
91
+ ### Running the Application
92
+
93
+ ```bash
94
+ python app.py
95
+ ```
96
+
97
+ The application will start locally. Access the URL displayed in the terminal (typically http://localhost:7860) to use the search assistant.
98
+
99
+ ## Usage Guide
100
+
101
+ 1. Enter your username (used to record search history)
102
+ 2. Type your question in the search box
103
+ 3. Specify success criteria (optional, used to evaluate result quality)
104
+ 4. Click the "Search" button
105
+ 5. View your search results
106
+ 6. Query your history by entering commands like "give me all the search history"
107
+ 7. Receive push notifications on your mobile device for important alerts
108
+
109
+ ## Deploying to Hugging Face
110
+
111
+ To deploy this project to Hugging Face Spaces:
112
+
113
+ 1. Create a setup.sh file with the following content:
114
+ ```bash
115
+ #!/bin/bash
116
+ pip install -r requirements.txt
117
+ playwright install chromium
118
+ ```
119
+
120
+ 2. Create a .hf directory with a requirements.txt file containing:
121
+ ```
122
+ execute_setup_sh==True
123
+ ```
124
+
125
+ 3. Set up repository secrets in Hugging Face for sensitive API keys:
126
+ - PUSHOVER_USER_KEY
127
+ - PUSHOVER_API_TOKEN
128
+
129
+ 4. Ensure your code runs in headless mode for Playwright:
130
+ ```python
131
+ browser = playwright.chromium.launch(headless=True)
132
+ ```
133
+
134
+ 5. Note: Docker execution features may have limited functionality in the Hugging Face environment
135
+
136
+ ## Troubleshooting
137
+
138
+ If you encounter issues during deployment, check:
139
+ - Browser binary installation (Playwright requires additional setup)
140
+ - Environment differences between local and deployment
141
+ - Path configurations and dependencies
142
+ - API key configuration for Pushover notifications
143
+ - Docker availability and permissions in your deployment environment
144
+
145
+ ## Contributing
146
+
147
+ Contributions to improve the project are welcome. Please feel free to submit a pull request or open an issue.
148
+
149
+ ## License
150
+
151
+ [Add your license information here]
152
+
153
+ ---
154
+ title: SearchOps_Assistant
155
+ app_file: app.py
156
+ sdk: gradio
157
+ sdk_version: 5.29.1
158
+ ---
SearchOps_Assistant/__pycache__/search.cpython-312.pyc ADDED
Binary file (18.4 kB). View file
 
SearchOps_Assistant/__pycache__/searcher_tools.cpython-312.pyc ADDED
Binary file (4.81 kB). View file
 
SearchOps_Assistant/app.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from search import Search
3
+
4
+
5
+ async def setup():
6
+ search = Search()
7
+ await search.setup()
8
+ return search
9
+
10
+ async def process_message(search, username, message, success_criteria, history):
11
+ results = await search.run_superstep(message, username, success_criteria, history)
12
+ return results, search
13
+
14
+ async def reset():
15
+ new_search = Search()
16
+ await new_search.setup()
17
+ return "", "","", None, new_search
18
+
19
+ def free_resources(search):
20
+ print("Cleaning up")
21
+ try:
22
+ if search:
23
+ search.free_resources()
24
+ except Exception as e:
25
+ print(f"Exception during cleanup: {e}")
26
+
27
+
28
+ with gr.Blocks(title="Search", theme=gr.themes.Default(primary_hue="emerald")) as ui:
29
+ gr.Markdown("## Search Personal Co-Worker")
30
+ search = gr.State(delete_callback=free_resources)
31
+
32
+ with gr.Row():
33
+ chatbot = gr.Chatbot(label="Search", height=300, type="messages")
34
+ with gr.Group():
35
+ with gr.Row():
36
+ username = gr.Textbox(show_label=False, placeholder="Enter your username")
37
+ with gr.Row():
38
+ message = gr.Textbox(show_label=False, placeholder="Your request to the Search")
39
+ with gr.Row():
40
+ success_criteria = gr.Textbox(show_label=False, placeholder="What are your success critiera?")
41
+ with gr.Row():
42
+ reset_button = gr.Button("Reset", variant="stop")
43
+ go_button = gr.Button("Go!", variant="primary")
44
+
45
+ ui.load(setup, [], [search])
46
+ message.submit(process_message, [search,username, message, success_criteria, chatbot], [chatbot, search])
47
+ success_criteria.submit(process_message, [search, username, message, success_criteria, chatbot], [chatbot, search])
48
+ go_button.click(process_message, [search, username, message, success_criteria, chatbot], [chatbot, search])
49
+ reset_button.click(reset, [], [username, message, success_criteria, chatbot, search])
50
+
51
+
52
+ ui.launch(inbrowser=True)
SearchOps_Assistant/db.ipynb ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "from langchain.agents.agent_toolkits import SQLDatabaseToolkit\n",
10
+ "from langchain.sql_database import SQLDatabase\n",
11
+ "from langchain.agents import Tool"
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "code",
16
+ "execution_count": 4,
17
+ "metadata": {},
18
+ "outputs": [
19
+ {
20
+ "ename": "AttributeError",
21
+ "evalue": "'SQLDatabase' object has no attribute 'close'",
22
+ "output_type": "error",
23
+ "traceback": [
24
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
25
+ "\u001b[31mAttributeError\u001b[39m Traceback (most recent call last)",
26
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[4]\u001b[39m\u001b[32m, line 5\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# 初始化数据库连接\u001b[39;00m\n\u001b[32m 2\u001b[39m db = SQLDatabase.from_uri(\u001b[33m\"\u001b[39m\u001b[33msqlite:///example.db\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m----> \u001b[39m\u001b[32m5\u001b[39m \u001b[43mdb\u001b[49m\u001b[43m.\u001b[49m\u001b[43mclose\u001b[49m()\n",
27
+ "\u001b[31mAttributeError\u001b[39m: 'SQLDatabase' object has no attribute 'close'"
28
+ ]
29
+ }
30
+ ],
31
+ "source": [
32
+ "\n",
33
+ "# 初始化数据库连接\n",
34
+ "db = SQLDatabase.from_uri(\"sqlite:///example.db\")\n",
35
+ "\n",
36
+ "\n",
37
+ "db.close()"
38
+ ]
39
+ },
40
+ {
41
+ "cell_type": "code",
42
+ "execution_count": 7,
43
+ "metadata": {},
44
+ "outputs": [],
45
+ "source": [
46
+ "import sqlite3\n",
47
+ "\n",
48
+ "conn = sqlite3.connect(\"query_log.db\")\n",
49
+ "cursor = conn.cursor()\n",
50
+ "\n",
51
+ "cursor.execute('''\n",
52
+ "CREATE TABLE IF NOT EXISTS search_history (\n",
53
+ " id TEXT PRIMARY KEY,\n",
54
+ " username TEXT,\n",
55
+ " feedback TEXT,\n",
56
+ " reply TEXT,\n",
57
+ " timestamp DATETIME DEFAULT CURRENT_TIMESTAMP\n",
58
+ ")\n",
59
+ "''')\n",
60
+ "\n",
61
+ "conn.commit()\n",
62
+ "conn.close()\n"
63
+ ]
64
+ },
65
+ {
66
+ "cell_type": "code",
67
+ "execution_count": 9,
68
+ "metadata": {},
69
+ "outputs": [
70
+ {
71
+ "name": "stdout",
72
+ "output_type": "stream",
73
+ "text": [
74
+ "('benny', 'b43cb216-d36d-4ba3-87f7-75fdf5790487', \"The assistant has indicated an inability to retrieve the search history, which means it did not fulfill the user's request to provide a table of the search history. Additionally, while the assistant asked the user if they have specific search queries, this does not meet the requirement for providing the requested information. Therefore, the response does not meet the success criteria.\", \"It seems that I'm unable to retrieve the search history at this moment. If you have any specific search queries or topics you're interested in, please let me know!\", '2025-05-16 08:53:20')\n"
75
+ ]
76
+ }
77
+ ],
78
+ "source": [
79
+ "conn = sqlite3.connect(\"query_log.db\")\n",
80
+ "cursor = conn.cursor()\n",
81
+ "\n",
82
+ "cursor.execute('''\n",
83
+ "select * from search_history\n",
84
+ "''')\n",
85
+ "results = cursor.fetchall()\n",
86
+ "\n",
87
+ "for row in results:\n",
88
+ " print(row) \n",
89
+ "conn.commit()\n",
90
+ "conn.close()"
91
+ ]
92
+ },
93
+ {
94
+ "cell_type": "code",
95
+ "execution_count": null,
96
+ "metadata": {},
97
+ "outputs": [],
98
+ "source": []
99
+ }
100
+ ],
101
+ "metadata": {
102
+ "kernelspec": {
103
+ "display_name": ".venv",
104
+ "language": "python",
105
+ "name": "python3"
106
+ },
107
+ "language_info": {
108
+ "codemirror_mode": {
109
+ "name": "ipython",
110
+ "version": 3
111
+ },
112
+ "file_extension": ".py",
113
+ "mimetype": "text/x-python",
114
+ "name": "python",
115
+ "nbconvert_exporter": "python",
116
+ "pygments_lexer": "ipython3",
117
+ "version": "3.12.7"
118
+ }
119
+ },
120
+ "nbformat": 4,
121
+ "nbformat_minor": 2
122
+ }
SearchOps_Assistant/example.db ADDED
File without changes
SearchOps_Assistant/query_log.db ADDED
Binary file (49.2 kB). View file
 
SearchOps_Assistant/requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.29.1
2
+ langchain==0.3.25
3
+ langchain_community==0.3.24
4
+ langchain_core==0.3.60
5
+ langchain_experimental==0.3.4
6
+ langchain_openai==0.3.17
7
+ langgraph==0.4.5
8
+ playwright==1.52.0
9
+ pydantic==2.11.4
10
+ python-dotenv==1.1.0
11
+ Requests==2.32.3
12
+ typing_extensions==4.13.2
SearchOps_Assistant/search.py ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated
2
+ from typing_extensions import TypedDict
3
+ from langgraph.graph import StateGraph, START, END
4
+ from langgraph.graph.message import add_messages
5
+ from dotenv import load_dotenv
6
+ from langgraph.prebuilt import ToolNode
7
+ from langchain_openai import ChatOpenAI
8
+ from langgraph.checkpoint.memory import MemorySaver
9
+ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
10
+ from typing import List, Any, Optional, Dict
11
+ from pydantic import BaseModel, Field
12
+ from searcher_tools import playwright_tools, other_tools
13
+ import uuid
14
+ import asyncio
15
+ from datetime import datetime
16
+ import sqlite3
17
+ import json
18
+ load_dotenv(override=True)
19
+
20
+ class State(TypedDict):
21
+ messages: Annotated[List[Any], add_messages]
22
+ username: str
23
+ success_criteria: str
24
+ feedback_on_work: Optional[str]
25
+ success_criteria_met: bool
26
+ user_input_needed: bool
27
+ tool_name: Optional[str]
28
+
29
+ class EvaluatorOutput(BaseModel):
30
+ feedback: str = Field(description="Feedback on the assistant's response")
31
+ success_criteria_met: bool = Field(description="Whether the success criteria have been met")
32
+ user_input_needed: bool = Field(description="True if more input is needed from the user, or clarifications, or the assistant is stuck")
33
+
34
+
35
+ class Search:
36
+ def __init__(self):
37
+ self.worker_llm_with_tools = None
38
+ self.evaluator_llm_with_output = None
39
+ self.tools = None
40
+ self.llm_with_tools = None
41
+ self.graph = None
42
+ self.search_id = str(uuid.uuid4())
43
+ self.memory = MemorySaver()
44
+ self.browser = None
45
+ self.playwright = None
46
+ self.formatting_llm = None
47
+ async def setup(self):
48
+ self.tools, self.browser, self.playwright = await playwright_tools()
49
+ self.tools += await other_tools()
50
+ worker_llm = ChatOpenAI(model="gpt-4o-mini")
51
+ self.worker_llm_with_tools = worker_llm.bind_tools(self.tools)
52
+ evaluator_llm = ChatOpenAI(model="gpt-4o-mini")
53
+ self.evaluator_llm_with_output = evaluator_llm.with_structured_output(EvaluatorOutput)
54
+ self.formatting_llm = ChatOpenAI(model="gpt-4o-mini")
55
+ await self.build_graph()
56
+
57
+ def worker(self, state: State) -> Dict[str, Any]:
58
+
59
+ if not state.get("username") or state["username"].strip() == "":
60
+ # Username not provided, return response asking for username
61
+ response = AIMessage(content="Please provide a username before search or accessing search history.")
62
+ new_state = dict(state)
63
+ new_state["messages"] = [response]
64
+ new_state["tool_name"] = None
65
+ new_state["user_input_needed"] = True # Signal that we need user input
66
+ return new_state
67
+
68
+ system_message = f"""You are a helpful assistant that can use tools to complete tasks.
69
+ You keep working on a task until either you have a question or clarification for the user, or the success criteria is met.
70
+ You have many tools to help you, including tools to browse the internet, navigating and retrieving web pages.
71
+ You have a tool to run python code, but note that you would need to include a print() statement if you wanted to receive output.
72
+ The username is {state['username']}The current date and time is {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
73
+
74
+ This is the success criteria:
75
+ {state['success_criteria']}
76
+ You should reply either with a question for the user about this assignment, or with your final response.
77
+ If you have a question for the user, you need to reply by clearly stating your question. An example might be:
78
+
79
+ Question: please clarify whether you want a summary or a detailed answer
80
+
81
+ If you've finished, reply with the final answer, and don't ask a question; simply reply with the answer.
82
+ """
83
+
84
+ if state.get("feedback_on_work"):
85
+ system_message += f"""
86
+ Previously you thought you completed the assignment, but your reply was rejected because the success criteria was not met.
87
+ Here is the feedback on why this was rejected:
88
+ {state['feedback_on_work']}
89
+ With this feedback, please continue the assignment, ensuring that you meet the success criteria or have a question for the user."""
90
+
91
+ # Add in the system message
92
+
93
+ found_system_message = False
94
+ messages = state["messages"]
95
+ for message in messages:
96
+ if isinstance(message, SystemMessage):
97
+ message.content = system_message
98
+ found_system_message = True
99
+
100
+ if not found_system_message:
101
+ messages = [SystemMessage(content=system_message)] + messages
102
+
103
+ # Invoke the LLM with tools
104
+ response = self.worker_llm_with_tools.invoke(messages)
105
+ tool_name = None
106
+ if hasattr(response, "tool_calls") and response.tool_calls:
107
+ tool_name = response.tool_calls[0]["name"]
108
+ print(f"Next tool to run: {tool_name}")
109
+ # Return updated state
110
+ new_state = dict(state)
111
+ new_state["messages"] = [response]
112
+ new_state["tool_name"] = tool_name
113
+
114
+ return new_state
115
+
116
+
117
+ def worker_router(self, state: State) -> str:
118
+ last_message = state["messages"][-1]
119
+
120
+ if hasattr(last_message, "tool_calls") and last_message.tool_calls:
121
+ return "tools"
122
+ else:
123
+ return "evaluator"
124
+
125
+
126
+ def format_conversation(self, messages: List[Any]) -> str:
127
+ conversation = "Conversation history:\n\n"
128
+ for message in messages:
129
+ if isinstance(message, HumanMessage):
130
+ conversation += f"User: {message.content}\n"
131
+ elif isinstance(message, AIMessage):
132
+ text = message.content or "[Tools use]"
133
+ conversation += f"Assistant: {text}\n"
134
+ return conversation
135
+
136
+ def evaluator(self, state: State) -> State:
137
+ last_response = state["messages"][-1].content
138
+
139
+ system_message = f"""You are an evaluator that determines if a task has been completed successfully by an Assistant.
140
+ Assess the Assistant's last response based on the given criteria. Respond with your feedback, and with your decision on whether the success criteria has been met,
141
+ and whether more input is needed from the user."""
142
+
143
+ user_message = f"""You are evaluating a conversation between the User and Assistant. You decide what action to take based on the last response from the Assistant.
144
+
145
+ The entire conversation with the assistant, with the user's original request and all replies, is:
146
+ {self.format_conversation(state['messages'])}
147
+
148
+ The success criteria for this assignment is:
149
+ {state['success_criteria']}
150
+
151
+ And the final response from the Assistant that you are evaluating is:
152
+ {last_response}
153
+
154
+ Respond with your feedback, and decide if the success criteria is met by this response.
155
+ Also, decide if more user input is required, either because the assistant has a question, needs clarification, or seems to be stuck and unable to answer without help.
156
+
157
+ The Assistant has access to tools, including a tool to retrieve search history. If the Assistant correctly uses this tool, regardless of the result (which depends on permissions),
158
+ then consider the success criteria met if the Assistant did its job correctly and communicated results or limitations properly.
159
+ """
160
+ if state["feedback_on_work"]:
161
+ user_message += f"Also, note that in a prior attempt from the Assistant, you provided this feedback: {state['feedback_on_work']}\n"
162
+ user_message += "If you're seeing the Assistant repeating the same mistakes, then consider responding that user input is required."
163
+
164
+ evaluator_messages = [SystemMessage(content=system_message), HumanMessage(content=user_message)]
165
+
166
+ eval_result = self.evaluator_llm_with_output.invoke(evaluator_messages)
167
+ new_state = {
168
+ "messages": [{"role": "assistant", "content": f"Evaluator Feedback on this answer: {eval_result.feedback}"}],
169
+ "feedback_on_work": eval_result.feedback,
170
+ "success_criteria_met": eval_result.success_criteria_met,
171
+ "user_input_needed": eval_result.user_input_needed
172
+ }
173
+ return new_state
174
+
175
+ def route_based_on_evaluation(self, state: State) -> str:
176
+ is_admin = state['username'] == 'admin'
177
+
178
+ if state["success_criteria_met"] or state["user_input_needed"]:
179
+ # Log search for non-admin users immediately
180
+ if not is_admin:
181
+ self.log_search(
182
+ state['username'],
183
+ self.search_id,
184
+ state['feedback_on_work'],
185
+ state["messages"][-1].content
186
+ )
187
+ return "END"
188
+ else:
189
+ return "worker"
190
+
191
+ def sqlformator(self, state: State) -> Dict[str, Any]:
192
+ """
193
+ A node that completely replaces the state with just the formatted table
194
+ """
195
+ print("SQL Formatter activated, completely replacing history with formatted table")
196
+
197
+ # Get the last message which should contain the JSON data
198
+ last_message = state["messages"][-1]
199
+ original_message = None
200
+
201
+ # Find the original user request for search history
202
+ for msg in state["messages"]:
203
+ if isinstance(msg, HumanMessage) and "search history" in msg.content.lower():
204
+ original_message = msg
205
+ break
206
+
207
+ # If no request found, use a generic one
208
+ if not original_message:
209
+ original_message = HumanMessage(content="give me the search history")
210
+
211
+ # Format the JSON data
212
+ try:
213
+ # Parse JSON to ensure we have valid data
214
+ import json
215
+ json_data = json.loads(last_message.content)
216
+
217
+ # Create system message for the formatting LLM
218
+ system_message = """You are a data formatting specialist. Format the provided JSON data as a clean ASCII table.
219
+ ONLY return the formatted table, nothing else. Do not reference or include the original JSON."""
220
+
221
+ # Create formatting prompt
222
+ user_message = f"""Format this JSON data as a readable ASCII table:
223
+
224
+ {last_message.content}
225
+
226
+ Create a table with these columns: ID, Thread ID, Username, Timestamp
227
+ Truncate long values for readability.
228
+ ONLY return the formatted table, nothing else."""
229
+
230
+ # Create a new LLM instance for formatting
231
+ formatting_llm = ChatOpenAI(model="gpt-4o-mini")
232
+
233
+ # Get formatted response
234
+ formatting_response = formatting_llm.invoke([
235
+ SystemMessage(content=system_message),
236
+ HumanMessage(content=user_message)
237
+ ])
238
+
239
+ # Create an enhanced formatted response
240
+ formatted_table = "# Search History Results\n\n" + formatting_response.content
241
+
242
+ except Exception as e:
243
+ print(f"Error formatting table: {e}")
244
+ formatted_table = f"Error formatting search history: {str(e)}"
245
+
246
+ # Create completely new state with minimal message history
247
+ # This is the key part - we're replacing the entire message history
248
+ new_state = {
249
+ "messages": [
250
+ SystemMessage(content="SQL assistant."),
251
+ original_message, # Original user request
252
+ AIMessage(content=formatted_table) # Only the formatted table
253
+ ],
254
+ "username": state["username"],
255
+ "success_criteria": state["success_criteria"],
256
+ "feedback_on_work": None, # Reset feedback
257
+ "success_criteria_met": True, # Mark as completed
258
+ "user_input_needed": False,
259
+ "tool_name": None # Reset tool name
260
+ }
261
+
262
+ return new_state
263
+
264
+ def route_based_on_tools(self, state: State) -> str:
265
+ last_message = state["messages"][-1]
266
+ print("Tool output:", last_message.content)
267
+
268
+ print("State keys:", list(state.keys()))
269
+
270
+ tool_name = state.get("tool_name")
271
+ print("Tool name detected:", tool_name)
272
+
273
+ if tool_name == "get_user_history":
274
+ if not state.get("username") or state["username"].strip() == "":
275
+ print("Username not provided, search history access denied")
276
+ # Add a message to state explaining why access is denied
277
+ state["messages"].append(AIMessage(content="Error: Username is required to access search history. Please provide a username."))
278
+ return "END" # End the flow with error message
279
+ print("Detected get_user_history tool, routing to SQL formatter")
280
+ return "sqlformator"
281
+ else:
282
+ print("Routing to worker")
283
+ return "worker"
284
+
285
+
286
+
287
+ def log_search(self,username:str,thread_id: str, feedback: str, reply: str):
288
+ conn = sqlite3.connect("query_log.db")
289
+ cursor = conn.cursor()
290
+ cursor.execute('''
291
+ INSERT INTO search_history (thread_id, username, feedback, reply)
292
+ VALUES (?, ?, ?, ?)
293
+ ''', (thread_id,username, feedback, reply))
294
+ conn.commit()
295
+ conn.close()
296
+
297
+ async def build_graph(self):
298
+ # Set up Graph Builder with State
299
+ graph_builder = StateGraph(State)
300
+
301
+ # Add nodes
302
+ graph_builder.add_node("worker", self.worker)
303
+ graph_builder.add_node("tools", ToolNode(tools=self.tools))
304
+ graph_builder.add_node("evaluator", self.evaluator)
305
+ graph_builder.add_node("sqlformator", self.sqlformator)
306
+
307
+ # Add edges
308
+ graph_builder.add_conditional_edges("worker", self.worker_router, {"tools": "tools", "evaluator": "evaluator"})
309
+ graph_builder.add_conditional_edges("tools", self.route_based_on_tools, {"worker": "worker", "sqlformator": 'sqlformator'})
310
+ graph_builder.add_edge("sqlformator", END)
311
+ graph_builder.add_conditional_edges("evaluator", self.route_based_on_evaluation, {"worker": "worker", "END": END})
312
+
313
+ graph_builder.add_edge(START, "worker")
314
+
315
+ # Compile the graph
316
+ self.graph = graph_builder.compile(checkpointer=self.memory)
317
+
318
+ async def run_superstep(self, message, username, success_criteria, history):
319
+ config = {"configurable": {"thread_id": self.search_id}}
320
+
321
+ state = {
322
+ "messages": message,
323
+ "username": username,
324
+ "success_criteria": success_criteria or "The answer should be clear and accurate",
325
+ "feedback_on_work": None,
326
+ "success_criteria_met": False,
327
+ "user_input_needed": False,
328
+ "tool_name": None
329
+ }
330
+ result = await self.graph.ainvoke(state, config=config)
331
+
332
+
333
+
334
+ user = {"role": "user", "content": message}
335
+
336
+ # For search history requests, find the formatted table response
337
+ if "search history" in message.lower():
338
+ formatted_table = None
339
+ for msg in reversed(result["messages"]):
340
+ content = msg.content if hasattr(msg, "content") else msg.get("content", "")
341
+
342
+ # Look for a message that contains table formatting indicators
343
+ if any(marker in content for marker in ["|", "+-", "ID", "Thread ID", "Username", "Timestamp"]):
344
+ formatted_table = content
345
+ break
346
+
347
+ if formatted_table:
348
+ reply = {"role": "assistant", "content": formatted_table}
349
+ else:
350
+ # Fallback to last message if no table found
351
+ reply = {"role": "assistant", "content": result["messages"][-1].content}
352
+ else:
353
+ # For non-search history requests, use normal processing
354
+ reply = {"role": "assistant", "content": result["messages"][-2].content}
355
+
356
+ reply = {"role": "assistant", "content": result["messages"][-2].content}
357
+ feedback = {"role": "assistant", "content": result["messages"][-1].content}
358
+
359
+
360
+ return history + [user, reply, feedback]
361
+
362
+
363
+
364
+
365
+
366
+
367
+ def cleanup(self):
368
+ if self.browser:
369
+ try:
370
+ loop = asyncio.get_running_loop()
371
+ loop.create_task(self.browser.close())
372
+ if self.playwright:
373
+ loop.create_task(self.playwright.stop())
374
+ except RuntimeError:
375
+ # If no loop is running, do a direct run
376
+ asyncio.run(self.browser.close())
377
+ if self.playwright:
378
+ asyncio.run(self.playwright.stop())
SearchOps_Assistant/searcher_tools.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from playwright.async_api import async_playwright
2
+ from langchain_community.agent_toolkits import PlayWrightBrowserToolkit
3
+ from dotenv import load_dotenv
4
+ import os
5
+ import requests
6
+ from langchain.agents import Tool
7
+ from langchain_community.agent_toolkits import FileManagementToolkit
8
+ from langchain_community.tools.wikipedia.tool import WikipediaQueryRun
9
+ from langchain_experimental.tools import PythonREPLTool
10
+ from langchain_community.utilities import GoogleSerperAPIWrapper
11
+ from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
12
+ from langchain.agents.agent_toolkits import SQLDatabaseToolkit
13
+ from langchain.sql_database import SQLDatabase
14
+ import uuid
15
+ import sqlite3
16
+ load_dotenv(override=True)
17
+ pushover_token = os.getenv("PUSHOVER_TOKEN")
18
+ pushover_user = os.getenv("PUSHOVER_USER")
19
+ pushover_url = "https://api.pushover.net/1/messages.json"
20
+ serper = GoogleSerperAPIWrapper()
21
+
22
+ async def playwright_tools():
23
+ playwright = await async_playwright().start()
24
+ browser = await playwright.chromium.launch(headless=False)
25
+ toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=browser)
26
+ return toolkit.get_tools(), browser, playwright
27
+
28
+
29
+ def push(text: str):
30
+ """Send a push notification to the user"""
31
+ requests.post(pushover_url, data = {"token": pushover_token, "user": pushover_user, "message": text})
32
+ return "success"
33
+
34
+
35
+ def get_file_tools():
36
+ toolkit = FileManagementToolkit(root_dir="sandbox")
37
+ return toolkit.get_tools()
38
+
39
+
40
+
41
+
42
+ def get_user_history(username: str):
43
+ """
44
+ Retrieves search history from the database for the given username only.
45
+ Users can only view their own search records.
46
+ """
47
+ try:
48
+ conn = sqlite3.connect("query_log.db")
49
+ cursor = conn.cursor()
50
+
51
+ # Ensure table exists
52
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='search_history'")
53
+ if not cursor.fetchone():
54
+ print("[ERROR] search_history table does not exist")
55
+ return []
56
+
57
+ # Query the user's own records
58
+ query = '''
59
+ SELECT * FROM search_history
60
+ WHERE username = ?
61
+ ORDER BY timestamp DESC
62
+ '''
63
+ cursor.execute(query, (username,))
64
+ results = cursor.fetchall()
65
+
66
+ # Format the results into dictionaries
67
+ formatted_results = []
68
+ for row in results:
69
+ record = {}
70
+ for i, col in enumerate(cursor.description):
71
+ record[col[0]] = row[i]
72
+ formatted_results.append(record)
73
+
74
+ conn.close()
75
+ return formatted_results
76
+
77
+ except Exception as e:
78
+ print(f"[ERROR] Database error: {str(e)}")
79
+ return []
80
+
81
+ async def other_tools():
82
+ push_tool = Tool(name="send_push_notification", func=push, description="Use this tool when you want to send a push notification")
83
+ file_tools = get_file_tools()
84
+
85
+ tool_search =Tool(
86
+ name="search",
87
+ func=serper.run,
88
+ description="Use this tool when you want to get the results of an online web search"
89
+ )
90
+
91
+ get_history_tool = Tool(
92
+ name="get_user_history",
93
+ func=get_user_history,
94
+ description="Use this tool when user wants to view their own search history"
95
+ )
96
+ wikipedia = WikipediaAPIWrapper()
97
+ wiki_tool = WikipediaQueryRun(api_wrapper=wikipedia)
98
+
99
+ python_repl = PythonREPLTool()
100
+
101
+ return file_tools + [push_tool, tool_search, python_repl, wiki_tool, get_history_tool]
102
+
SearchOps_Assistant/setup.sh ADDED
File without changes
__pycache__/search.cpython-312.pyc ADDED
Binary file (18.4 kB). View file
 
__pycache__/searcher_tools.cpython-312.pyc ADDED
Binary file (4.81 kB). View file
 
app.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from search import Search
3
+
4
+
5
+ async def setup():
6
+ search = Search()
7
+ await search.setup()
8
+ return search
9
+
10
+ async def process_message(search, username, message, success_criteria, history):
11
+ results = await search.run_superstep(message, username, success_criteria, history)
12
+ return results, search
13
+
14
+ async def reset():
15
+ new_search = Search()
16
+ await new_search.setup()
17
+ return "", "","", None, new_search
18
+
19
+ def free_resources(search):
20
+ print("Cleaning up")
21
+ try:
22
+ if search:
23
+ search.free_resources()
24
+ except Exception as e:
25
+ print(f"Exception during cleanup: {e}")
26
+
27
+
28
+ with gr.Blocks(title="Search", theme=gr.themes.Default(primary_hue="emerald")) as ui:
29
+ gr.Markdown("## Search Personal Co-Worker")
30
+ search = gr.State(delete_callback=free_resources)
31
+
32
+ with gr.Row():
33
+ chatbot = gr.Chatbot(label="Search", height=300, type="messages")
34
+ with gr.Group():
35
+ with gr.Row():
36
+ username = gr.Textbox(show_label=False, placeholder="Enter your username")
37
+ with gr.Row():
38
+ message = gr.Textbox(show_label=False, placeholder="Your request to the Search")
39
+ with gr.Row():
40
+ success_criteria = gr.Textbox(show_label=False, placeholder="What are your success critiera?")
41
+ with gr.Row():
42
+ reset_button = gr.Button("Reset", variant="stop")
43
+ go_button = gr.Button("Go!", variant="primary")
44
+
45
+ ui.load(setup, [], [search])
46
+ message.submit(process_message, [search,username, message, success_criteria, chatbot], [chatbot, search])
47
+ success_criteria.submit(process_message, [search, username, message, success_criteria, chatbot], [chatbot, search])
48
+ go_button.click(process_message, [search, username, message, success_criteria, chatbot], [chatbot, search])
49
+ reset_button.click(reset, [], [username, message, success_criteria, chatbot, search])
50
+
51
+
52
+ ui.launch(inbrowser=True)
db.ipynb ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "from langchain.agents.agent_toolkits import SQLDatabaseToolkit\n",
10
+ "from langchain.sql_database import SQLDatabase\n",
11
+ "from langchain.agents import Tool"
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "code",
16
+ "execution_count": 11,
17
+ "metadata": {},
18
+ "outputs": [],
19
+ "source": [
20
+ "import sqlite3\n",
21
+ "\n",
22
+ "conn = sqlite3.connect(\"query_log.db\")\n",
23
+ "cursor = conn.cursor()\n",
24
+ "\n",
25
+ "cursor.execute('''\n",
26
+ "CREATE TABLE IF NOT EXISTS search_history (\n",
27
+ " id INTEGER PRIMARY KEY AUTOINCREMENT,\n",
28
+ " thread_id TEXT,\n",
29
+ " username TEXT,\n",
30
+ " feedback TEXT,\n",
31
+ " reply TEXT,\n",
32
+ " timestamp DATETIME DEFAULT CURRENT_TIMESTAMP\n",
33
+ ")\n",
34
+ "''')\n",
35
+ "\n",
36
+ "conn.commit()\n",
37
+ "conn.close()\n"
38
+ ]
39
+ },
40
+ {
41
+ "cell_type": "code",
42
+ "execution_count": null,
43
+ "metadata": {},
44
+ "outputs": [],
45
+ "source": []
46
+ }
47
+ ],
48
+ "metadata": {
49
+ "kernelspec": {
50
+ "display_name": ".venv",
51
+ "language": "python",
52
+ "name": "python3"
53
+ },
54
+ "language_info": {
55
+ "codemirror_mode": {
56
+ "name": "ipython",
57
+ "version": 3
58
+ },
59
+ "file_extension": ".py",
60
+ "mimetype": "text/x-python",
61
+ "name": "python",
62
+ "nbconvert_exporter": "python",
63
+ "pygments_lexer": "ipython3",
64
+ "version": "3.12.7"
65
+ }
66
+ },
67
+ "nbformat": 4,
68
+ "nbformat_minor": 2
69
+ }
example.db ADDED
File without changes
query_log.db ADDED
Binary file (12.3 kB). View file
 
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.29.1
2
+ langchain==0.3.25
3
+ langchain_community==0.3.24
4
+ langchain_core==0.3.60
5
+ langchain_experimental==0.3.4
6
+ langchain_openai==0.3.17
7
+ langgraph==0.4.5
8
+ playwright==1.52.0
9
+ pydantic==2.11.4
10
+ python-dotenv==1.1.0
11
+ Requests==2.32.3
12
+ typing_extensions==4.13.2
13
+
search.py ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated
2
+ from typing_extensions import TypedDict
3
+ from langgraph.graph import StateGraph, START, END
4
+ from langgraph.graph.message import add_messages
5
+ from dotenv import load_dotenv
6
+ from langgraph.prebuilt import ToolNode
7
+ from langchain_openai import ChatOpenAI
8
+ from langgraph.checkpoint.memory import MemorySaver
9
+ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
10
+ from typing import List, Any, Optional, Dict
11
+ from pydantic import BaseModel, Field
12
+ from searcher_tools import playwright_tools, other_tools
13
+ import uuid
14
+ import asyncio
15
+ from datetime import datetime
16
+ import sqlite3
17
+ import json
18
+ load_dotenv(override=True)
19
+
20
+ class State(TypedDict):
21
+ messages: Annotated[List[Any], add_messages]
22
+ username: str
23
+ success_criteria: str
24
+ feedback_on_work: Optional[str]
25
+ success_criteria_met: bool
26
+ user_input_needed: bool
27
+ tool_name: Optional[str]
28
+
29
+ class EvaluatorOutput(BaseModel):
30
+ feedback: str = Field(description="Feedback on the assistant's response")
31
+ success_criteria_met: bool = Field(description="Whether the success criteria have been met")
32
+ user_input_needed: bool = Field(description="True if more input is needed from the user, or clarifications, or the assistant is stuck")
33
+
34
+
35
+ class Search:
36
+ def __init__(self):
37
+ self.worker_llm_with_tools = None
38
+ self.evaluator_llm_with_output = None
39
+ self.tools = None
40
+ self.llm_with_tools = None
41
+ self.graph = None
42
+ self.search_id = str(uuid.uuid4())
43
+ self.memory = MemorySaver()
44
+ self.browser = None
45
+ self.playwright = None
46
+ self.formatting_llm = None
47
+ async def setup(self):
48
+ self.tools, self.browser, self.playwright = await playwright_tools()
49
+ self.tools += await other_tools()
50
+ worker_llm = ChatOpenAI(model="gpt-4o-mini")
51
+ self.worker_llm_with_tools = worker_llm.bind_tools(self.tools)
52
+ evaluator_llm = ChatOpenAI(model="gpt-4o-mini")
53
+ self.evaluator_llm_with_output = evaluator_llm.with_structured_output(EvaluatorOutput)
54
+ self.formatting_llm = ChatOpenAI(model="gpt-4o-mini")
55
+ await self.build_graph()
56
+
57
+ def worker(self, state: State) -> Dict[str, Any]:
58
+
59
+ if not state.get("username") or state["username"].strip() == "":
60
+ # Username not provided, return response asking for username
61
+ response = AIMessage(content="Please provide a username before search or accessing search history.")
62
+ new_state = dict(state)
63
+ new_state["messages"] = [response]
64
+ new_state["tool_name"] = None
65
+ new_state["user_input_needed"] = True # Signal that we need user input
66
+ return new_state
67
+
68
+ system_message = f"""You are a helpful assistant that can use tools to complete tasks.
69
+ You keep working on a task until either you have a question or clarification for the user, or the success criteria is met.
70
+ You have many tools to help you, including tools to browse the internet, navigating and retrieving web pages.
71
+ You have a tool to run python code, but note that you would need to include a print() statement if you wanted to receive output.
72
+ The username is {state['username']}The current date and time is {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
73
+
74
+ This is the success criteria:
75
+ {state['success_criteria']}
76
+ You should reply either with a question for the user about this assignment, or with your final response.
77
+ If you have a question for the user, you need to reply by clearly stating your question. An example might be:
78
+
79
+ Question: please clarify whether you want a summary or a detailed answer
80
+
81
+ If you've finished, reply with the final answer, and don't ask a question; simply reply with the answer.
82
+ """
83
+
84
+ if state.get("feedback_on_work"):
85
+ system_message += f"""
86
+ Previously you thought you completed the assignment, but your reply was rejected because the success criteria was not met.
87
+ Here is the feedback on why this was rejected:
88
+ {state['feedback_on_work']}
89
+ With this feedback, please continue the assignment, ensuring that you meet the success criteria or have a question for the user."""
90
+
91
+ # Add in the system message
92
+
93
+ found_system_message = False
94
+ messages = state["messages"]
95
+ for message in messages:
96
+ if isinstance(message, SystemMessage):
97
+ message.content = system_message
98
+ found_system_message = True
99
+
100
+ if not found_system_message:
101
+ messages = [SystemMessage(content=system_message)] + messages
102
+
103
+ # Invoke the LLM with tools
104
+ response = self.worker_llm_with_tools.invoke(messages)
105
+ tool_name = None
106
+ if hasattr(response, "tool_calls") and response.tool_calls:
107
+ tool_name = response.tool_calls[0]["name"]
108
+ print(f"Next tool to run: {tool_name}")
109
+ # Return updated state
110
+ new_state = dict(state)
111
+ new_state["messages"] = [response]
112
+ new_state["tool_name"] = tool_name
113
+
114
+ return new_state
115
+
116
+
117
+ def worker_router(self, state: State) -> str:
118
+ last_message = state["messages"][-1]
119
+
120
+ if hasattr(last_message, "tool_calls") and last_message.tool_calls:
121
+ return "tools"
122
+ else:
123
+ return "evaluator"
124
+
125
+
126
+ def format_conversation(self, messages: List[Any]) -> str:
127
+ conversation = "Conversation history:\n\n"
128
+ for message in messages:
129
+ if isinstance(message, HumanMessage):
130
+ conversation += f"User: {message.content}\n"
131
+ elif isinstance(message, AIMessage):
132
+ text = message.content or "[Tools use]"
133
+ conversation += f"Assistant: {text}\n"
134
+ return conversation
135
+
136
+ def evaluator(self, state: State) -> State:
137
+ last_response = state["messages"][-1].content
138
+
139
+ system_message = f"""You are an evaluator that determines if a task has been completed successfully by an Assistant.
140
+ Assess the Assistant's last response based on the given criteria. Respond with your feedback, and with your decision on whether the success criteria has been met,
141
+ and whether more input is needed from the user."""
142
+
143
+ user_message = f"""You are evaluating a conversation between the User and Assistant. You decide what action to take based on the last response from the Assistant.
144
+
145
+ The entire conversation with the assistant, with the user's original request and all replies, is:
146
+ {self.format_conversation(state['messages'])}
147
+
148
+ The success criteria for this assignment is:
149
+ {state['success_criteria']}
150
+
151
+ And the final response from the Assistant that you are evaluating is:
152
+ {last_response}
153
+
154
+ Respond with your feedback, and decide if the success criteria is met by this response.
155
+ Also, decide if more user input is required, either because the assistant has a question, needs clarification, or seems to be stuck and unable to answer without help.
156
+
157
+ The Assistant has access to tools, including a tool to retrieve search history. If the Assistant correctly uses this tool, regardless of the result (which depends on permissions),
158
+ then consider the success criteria met if the Assistant did its job correctly and communicated results or limitations properly.
159
+ """
160
+ if state["feedback_on_work"]:
161
+ user_message += f"Also, note that in a prior attempt from the Assistant, you provided this feedback: {state['feedback_on_work']}\n"
162
+ user_message += "If you're seeing the Assistant repeating the same mistakes, then consider responding that user input is required."
163
+
164
+ evaluator_messages = [SystemMessage(content=system_message), HumanMessage(content=user_message)]
165
+
166
+ eval_result = self.evaluator_llm_with_output.invoke(evaluator_messages)
167
+ new_state = {
168
+ "messages": [{"role": "assistant", "content": f"Evaluator Feedback on this answer: {eval_result.feedback}"}],
169
+ "feedback_on_work": eval_result.feedback,
170
+ "success_criteria_met": eval_result.success_criteria_met,
171
+ "user_input_needed": eval_result.user_input_needed
172
+ }
173
+ return new_state
174
+
175
+ def route_based_on_evaluation(self, state: State) -> str:
176
+ is_admin = state['username'] == 'admin'
177
+
178
+ if state["success_criteria_met"] or state["user_input_needed"]:
179
+ # Log search for non-admin users immediately
180
+ if not is_admin:
181
+ self.log_search(
182
+ state['username'],
183
+ self.search_id,
184
+ state['feedback_on_work'],
185
+ state["messages"][-1].content
186
+ )
187
+ return "END"
188
+ else:
189
+ return "worker"
190
+
191
+ def sqlformator(self, state: State) -> Dict[str, Any]:
192
+ """
193
+ A node that completely replaces the state with just the formatted table
194
+ """
195
+ print("SQL Formatter activated, completely replacing history with formatted table")
196
+
197
+ # Get the last message which should contain the JSON data
198
+ last_message = state["messages"][-1]
199
+ original_message = None
200
+
201
+ # Find the original user request for search history
202
+ for msg in state["messages"]:
203
+ if isinstance(msg, HumanMessage) and "search history" in msg.content.lower():
204
+ original_message = msg
205
+ break
206
+
207
+ # If no request found, use a generic one
208
+ if not original_message:
209
+ original_message = HumanMessage(content="give me the search history")
210
+
211
+ # Format the JSON data
212
+ try:
213
+ # Parse JSON to ensure we have valid data
214
+ import json
215
+ json_data = json.loads(last_message.content)
216
+
217
+ # Create system message for the formatting LLM
218
+ system_message = """You are a data formatting specialist. Format the provided JSON data as a clean ASCII table.
219
+ ONLY return the formatted table, nothing else. Do not reference or include the original JSON."""
220
+
221
+ # Create formatting prompt
222
+ user_message = f"""Format this JSON data as a readable ASCII table:
223
+
224
+ {last_message.content}
225
+
226
+ Create a table with these columns: ID, Thread ID, Username, Timestamp
227
+ Truncate long values for readability.
228
+ ONLY return the formatted table, nothing else."""
229
+
230
+ # Create a new LLM instance for formatting
231
+ formatting_llm = ChatOpenAI(model="gpt-4o-mini")
232
+
233
+ # Get formatted response
234
+ formatting_response = formatting_llm.invoke([
235
+ SystemMessage(content=system_message),
236
+ HumanMessage(content=user_message)
237
+ ])
238
+
239
+ # Create an enhanced formatted response
240
+ formatted_table = "# Search History Results\n\n" + formatting_response.content
241
+
242
+ except Exception as e:
243
+ print(f"Error formatting table: {e}")
244
+ formatted_table = f"Error formatting search history: {str(e)}"
245
+
246
+ # Create completely new state with minimal message history
247
+ # This is the key part - we're replacing the entire message history
248
+ new_state = {
249
+ "messages": [
250
+ SystemMessage(content="SQL assistant."),
251
+ original_message, # Original user request
252
+ AIMessage(content=formatted_table) # Only the formatted table
253
+ ],
254
+ "username": state["username"],
255
+ "success_criteria": state["success_criteria"],
256
+ "feedback_on_work": None, # Reset feedback
257
+ "success_criteria_met": True, # Mark as completed
258
+ "user_input_needed": False,
259
+ "tool_name": None # Reset tool name
260
+ }
261
+
262
+ return new_state
263
+
264
+ def route_based_on_tools(self, state: State) -> str:
265
+ last_message = state["messages"][-1]
266
+ print("Tool output:", last_message.content)
267
+
268
+ print("State keys:", list(state.keys()))
269
+
270
+ tool_name = state.get("tool_name")
271
+ print("Tool name detected:", tool_name)
272
+
273
+ if tool_name == "get_user_history":
274
+ if not state.get("username") or state["username"].strip() == "":
275
+ print("Username not provided, search history access denied")
276
+ # Add a message to state explaining why access is denied
277
+ state["messages"].append(AIMessage(content="Error: Username is required to access search history. Please provide a username."))
278
+ return "END" # End the flow with error message
279
+ print("Detected get_user_history tool, routing to SQL formatter")
280
+ return "sqlformator"
281
+ else:
282
+ print("Routing to worker")
283
+ return "worker"
284
+
285
+
286
+
287
+ def log_search(self,username:str,thread_id: str, feedback: str, reply: str):
288
+ conn = sqlite3.connect("query_log.db")
289
+ cursor = conn.cursor()
290
+ cursor.execute('''
291
+ INSERT INTO search_history (thread_id, username, feedback, reply)
292
+ VALUES (?, ?, ?, ?)
293
+ ''', (thread_id,username, feedback, reply))
294
+ conn.commit()
295
+ conn.close()
296
+
297
+ async def build_graph(self):
298
+ # Set up Graph Builder with State
299
+ graph_builder = StateGraph(State)
300
+
301
+ # Add nodes
302
+ graph_builder.add_node("worker", self.worker)
303
+ graph_builder.add_node("tools", ToolNode(tools=self.tools))
304
+ graph_builder.add_node("evaluator", self.evaluator)
305
+ graph_builder.add_node("sqlformator", self.sqlformator)
306
+
307
+ # Add edges
308
+ graph_builder.add_conditional_edges("worker", self.worker_router, {"tools": "tools", "evaluator": "evaluator"})
309
+ graph_builder.add_conditional_edges("tools", self.route_based_on_tools, {"worker": "worker", "sqlformator": 'sqlformator'})
310
+ graph_builder.add_edge("sqlformator", END)
311
+ graph_builder.add_conditional_edges("evaluator", self.route_based_on_evaluation, {"worker": "worker", "END": END})
312
+
313
+ graph_builder.add_edge(START, "worker")
314
+
315
+ # Compile the graph
316
+ self.graph = graph_builder.compile(checkpointer=self.memory)
317
+
318
+ async def run_superstep(self, message, username, success_criteria, history):
319
+ config = {"configurable": {"thread_id": self.search_id}}
320
+
321
+ state = {
322
+ "messages": message,
323
+ "username": username,
324
+ "success_criteria": success_criteria or "The answer should be clear and accurate",
325
+ "feedback_on_work": None,
326
+ "success_criteria_met": False,
327
+ "user_input_needed": False,
328
+ "tool_name": None
329
+ }
330
+ result = await self.graph.ainvoke(state, config=config)
331
+
332
+
333
+
334
+ user = {"role": "user", "content": message}
335
+
336
+ # For search history requests, find the formatted table response
337
+ if "search history" in message.lower():
338
+ formatted_table = None
339
+ for msg in reversed(result["messages"]):
340
+ content = msg.content if hasattr(msg, "content") else msg.get("content", "")
341
+
342
+ # Look for a message that contains table formatting indicators
343
+ if any(marker in content for marker in ["|", "+-", "ID", "Thread ID", "Username", "Timestamp"]):
344
+ formatted_table = content
345
+ break
346
+
347
+ if formatted_table:
348
+ reply = {"role": "assistant", "content": formatted_table}
349
+ else:
350
+ # Fallback to last message if no table found
351
+ reply = {"role": "assistant", "content": result["messages"][-1].content}
352
+ else:
353
+ # For non-search history requests, use normal processing
354
+ reply = {"role": "assistant", "content": result["messages"][-2].content}
355
+
356
+ reply = {"role": "assistant", "content": result["messages"][-2].content}
357
+ feedback = {"role": "assistant", "content": result["messages"][-1].content}
358
+
359
+
360
+ return history + [user, reply, feedback]
361
+
362
+
363
+
364
+
365
+
366
+
367
+ def cleanup(self):
368
+ if self.browser:
369
+ try:
370
+ loop = asyncio.get_running_loop()
371
+ loop.create_task(self.browser.close())
372
+ if self.playwright:
373
+ loop.create_task(self.playwright.stop())
374
+ except RuntimeError:
375
+ # If no loop is running, do a direct run
376
+ asyncio.run(self.browser.close())
377
+ if self.playwright:
378
+ asyncio.run(self.playwright.stop())
searcher_tools.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from playwright.async_api import async_playwright
2
+ from langchain_community.agent_toolkits import PlayWrightBrowserToolkit
3
+ from dotenv import load_dotenv
4
+ import os
5
+ import requests
6
+ from langchain.agents import Tool
7
+ from langchain_community.agent_toolkits import FileManagementToolkit
8
+ from langchain_community.tools.wikipedia.tool import WikipediaQueryRun
9
+ from langchain_experimental.tools import PythonREPLTool
10
+ from langchain_community.utilities import GoogleSerperAPIWrapper
11
+ from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
12
+ from langchain.agents.agent_toolkits import SQLDatabaseToolkit
13
+ from langchain.sql_database import SQLDatabase
14
+ import uuid
15
+ import sqlite3
16
+ load_dotenv(override=True)
17
+ pushover_token = os.getenv("PUSHOVER_TOKEN")
18
+ pushover_user = os.getenv("PUSHOVER_USER")
19
+ pushover_url = "https://api.pushover.net/1/messages.json"
20
+ serper = GoogleSerperAPIWrapper()
21
+
22
+ async def playwright_tools():
23
+ playwright = await async_playwright().start()
24
+ browser = await playwright.chromium.launch(headless=False)
25
+ toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=browser)
26
+ return toolkit.get_tools(), browser, playwright
27
+
28
+
29
+ def push(text: str):
30
+ """Send a push notification to the user"""
31
+ requests.post(pushover_url, data = {"token": pushover_token, "user": pushover_user, "message": text})
32
+ return "success"
33
+
34
+
35
+ def get_file_tools():
36
+ toolkit = FileManagementToolkit(root_dir="sandbox")
37
+ return toolkit.get_tools()
38
+
39
+
40
+
41
+
42
+ def get_user_history(username: str):
43
+ """
44
+ Retrieves search history from the database for the given username only.
45
+ Users can only view their own search records.
46
+ """
47
+ try:
48
+ conn = sqlite3.connect("query_log.db")
49
+ cursor = conn.cursor()
50
+
51
+ # Ensure table exists
52
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='search_history'")
53
+ if not cursor.fetchone():
54
+ print("[ERROR] search_history table does not exist")
55
+ return []
56
+
57
+ # Query the user's own records
58
+ query = '''
59
+ SELECT * FROM search_history
60
+ WHERE username = ?
61
+ ORDER BY timestamp DESC
62
+ '''
63
+ cursor.execute(query, (username,))
64
+ results = cursor.fetchall()
65
+
66
+ # Format the results into dictionaries
67
+ formatted_results = []
68
+ for row in results:
69
+ record = {}
70
+ for i, col in enumerate(cursor.description):
71
+ record[col[0]] = row[i]
72
+ formatted_results.append(record)
73
+
74
+ conn.close()
75
+ return formatted_results
76
+
77
+ except Exception as e:
78
+ print(f"[ERROR] Database error: {str(e)}")
79
+ return []
80
+
81
+ async def other_tools():
82
+ push_tool = Tool(name="send_push_notification", func=push, description="Use this tool when you want to send a push notification")
83
+ file_tools = get_file_tools()
84
+
85
+ tool_search =Tool(
86
+ name="search",
87
+ func=serper.run,
88
+ description="Use this tool when you want to get the results of an online web search"
89
+ )
90
+
91
+ get_history_tool = Tool(
92
+ name="get_user_history",
93
+ func=get_user_history,
94
+ description="Use this tool when user wants to view their own search history"
95
+ )
96
+ wikipedia = WikipediaAPIWrapper()
97
+ wiki_tool = WikipediaQueryRun(api_wrapper=wikipedia)
98
+
99
+ python_repl = PythonREPLTool()
100
+
101
+ return file_tools + [push_tool, tool_search, python_repl, wiki_tool, get_history_tool]
102
+
setup.sh ADDED
File without changes