tool_use #1
							
								
								
									
										20
									
								
								.gitea/workflows/datadog-sca.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.gitea/workflows/datadog-sca.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| on: [push] | ||||
|  | ||||
| name: Datadog Software Composition Analysis | ||||
|  | ||||
| jobs: | ||||
|   software-composition-analysis: | ||||
|     runs-on: ubuntu-latest | ||||
|     name: Datadog SBOM Generation and Upload | ||||
|     steps: | ||||
|     - name: Checkout | ||||
|       uses: actions/checkout@v3 | ||||
|     - name: Check imported libraries are secure and compliant | ||||
|       id: datadog-software-composition-analysis | ||||
|       uses: DataDog/datadog-sca-github-action@main | ||||
|       with: | ||||
|         dd_api_key: ${{ secrets.DD_API_KEY }} | ||||
|         dd_app_key: ${{ secrets.DD_APP_KEY }} | ||||
|         dd_service: jarvis | ||||
|         dd_env: ci | ||||
|         dd_site: us5.datadoghq.com | ||||
							
								
								
									
										21
									
								
								.gitea/workflows/datadog-static-analysis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.gitea/workflows/datadog-static-analysis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| on: [push] | ||||
|  | ||||
| name: Datadog Static Analysis | ||||
|  | ||||
| jobs: | ||||
|   static-analysis: | ||||
|     runs-on: ubuntu-latest | ||||
|     name: Datadog Static Analyzer | ||||
|     steps: | ||||
|     - name: Checkout | ||||
|       uses: actions/checkout@v3 | ||||
|     - name: Check code meets quality and security standards | ||||
|       id: datadog-static-analysis | ||||
|       uses: DataDog/datadog-static-analyzer-github-action@v1 | ||||
|       with: | ||||
|         dd_api_key: ${{ secrets.DD_API_KEY }} | ||||
|         dd_app_key: ${{ secrets.DD_APP_KEY }} | ||||
|         dd_service: jarvis | ||||
|         dd_env: ci | ||||
|         dd_site: us5.datadoghq.com | ||||
|         cpu_count: 2 | ||||
							
								
								
									
										20
									
								
								.github/workflows/datadog-sca.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/datadog-sca.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| on: [push] | ||||
|  | ||||
| name: Datadog Software Composition Analysis | ||||
|  | ||||
| jobs: | ||||
|   software-composition-analysis: | ||||
|     runs-on: ubuntu-latest | ||||
|     name: Datadog SBOM Generation and Upload | ||||
|     steps: | ||||
|     - name: Checkout | ||||
|       uses: actions/checkout@v3 | ||||
|     - name: Check imported libraries are secure and compliant | ||||
|       id: datadog-software-composition-analysis | ||||
|       uses: DataDog/datadog-sca-github-action@main | ||||
|       with: | ||||
|         dd_api_key: ${{ secrets.DD_API_KEY }} | ||||
|         dd_app_key: ${{ secrets.DD_APP_KEY }} | ||||
|         dd_service: jarvis | ||||
|         dd_env: ci | ||||
|         dd_site: us5.datadoghq.com | ||||
							
								
								
									
										21
									
								
								.github/workflows/datadog-static-analysis.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.github/workflows/datadog-static-analysis.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| on: [push] | ||||
|  | ||||
| name: Datadog Static Analysis | ||||
|  | ||||
| jobs: | ||||
|   static-analysis: | ||||
|     runs-on: ubuntu-latest | ||||
|     name: Datadog Static Analyzer | ||||
|     steps: | ||||
|     - name: Checkout | ||||
|       uses: actions/checkout@v3 | ||||
|     - name: Check code meets quality and security standards | ||||
|       id: datadog-static-analysis | ||||
|       uses: DataDog/datadog-static-analyzer-github-action@v1 | ||||
|       with: | ||||
|         dd_api_key: ${{ secrets.DD_API_KEY }} | ||||
|         dd_app_key: ${{ secrets.DD_APP_KEY }} | ||||
|         dd_service: jarvis | ||||
|         dd_env: ci | ||||
|         dd_site: us5.datadoghq.com | ||||
|         cpu_count: 2 | ||||
							
								
								
									
										43
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -174,3 +174,46 @@ cython_debug/ | ||||
| pyvenv.cfg | ||||
| .venv | ||||
| pip-selfcheck.json | ||||
|  | ||||
|  | ||||
| # Logs | ||||
| logs | ||||
| *.log | ||||
| npm-debug.log* | ||||
|  | ||||
| # Runtime data | ||||
| pids | ||||
| *.pid | ||||
| *.seed | ||||
|  | ||||
| # Directory for instrumented libs generated by jscoverage/JSCover | ||||
| lib-cov | ||||
|  | ||||
| # Coverage directory used by tools like istanbul | ||||
| coverage | ||||
|  | ||||
| # nyc test coverage | ||||
| .nyc_output | ||||
|  | ||||
| # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||||
| .grunt | ||||
|  | ||||
| # node-waf configuration | ||||
| .lock-wscript | ||||
|  | ||||
| # Compiled binary addons (http://nodejs.org/api/addons.html) | ||||
| build/Release | ||||
|  | ||||
| # Dependency directories | ||||
| node_modules | ||||
| jspm_packages | ||||
|  | ||||
| # Optional npm cache directory | ||||
| .npm | ||||
|  | ||||
| # Optional REPL history | ||||
| .node_repl_history | ||||
| .next | ||||
|  | ||||
| config.ini | ||||
| *.db | ||||
							
								
								
									
										20
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| # Use an official Python runtime as a parent image | ||||
| FROM python:3.9-slim | ||||
|  | ||||
| # Set the working directory in the container | ||||
| WORKDIR /app | ||||
|  | ||||
| # Copy the current directory contents into the container at /app | ||||
| COPY . /app | ||||
|  | ||||
| # Install any needed packages specified in requirements.txt | ||||
| RUN pip install --no-cache-dir -r requirements.txt | ||||
|  | ||||
| # Make port 5001 available to the world outside this container | ||||
| EXPOSE 5001 | ||||
|  | ||||
| # Define environment variable | ||||
| ENV FLASK_APP=main.py | ||||
|  | ||||
| # Run app.py when the container launches | ||||
| CMD ["python", "main.py"] | ||||
							
								
								
									
										138
									
								
								client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								client.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| import time | ||||
|  | ||||
| import requests | ||||
|  | ||||
|  | ||||
| class LLMChatClient: | ||||
|     def __init__(self, base_url, api_key): | ||||
|         self.base_url = base_url.rstrip("/") | ||||
|         self.api_key = api_key | ||||
|         self.headers = {"X-API-Key": api_key, "Content-Type": "application/json"} | ||||
|  | ||||
|     def submit_query(self, message): | ||||
|         """ | ||||
|         Submit a query to the LLM Chat Server. | ||||
|  | ||||
|         Args: | ||||
|             message (str): The message to send to the server. | ||||
|  | ||||
|         Returns: | ||||
|             str: The query ID for the submitted query. | ||||
|  | ||||
|         Raises: | ||||
|             requests.RequestException: If the request fails. | ||||
|  | ||||
|         Example: | ||||
|             client = LLMChatClient('http://localhost:5001', 'your-api-key') | ||||
|             query_id = client.submit_query('What is the capital of France?') | ||||
|             print(f"Query ID: {query_id}") | ||||
|  | ||||
|         cURL equivalent: | ||||
|             curl -X POST http://localhost:5001/api/v1/query \ | ||||
|                  -H "Content-Type: application/json" \ | ||||
|                  -H "X-API-Key: your-api-key" \ | ||||
|                  -d '{"message": "What is the capital of France?"}' | ||||
|         """ | ||||
|         url = f"{self.base_url}/api/v1/query" | ||||
|         data = {"message": message} | ||||
|         response = requests.post(url, json=data, headers=self.headers) | ||||
|         response.raise_for_status() | ||||
|         return response.json()["query_id"] | ||||
|  | ||||
|     def get_query_status(self, query_id): | ||||
|         """ | ||||
|         Get the status of a submitted query. | ||||
|  | ||||
|         Args: | ||||
|             query_id (str): The ID of the query to check. | ||||
|  | ||||
|         Returns: | ||||
|             dict: A dictionary containing the status and conversation history (if completed). | ||||
|  | ||||
|         Raises: | ||||
|             requests.RequestException: If the request fails. | ||||
|  | ||||
|         Example: | ||||
|             client = LLMChatClient('http://localhost:5001', 'your-api-key') | ||||
|             status = client.get_query_status('query-id-here') | ||||
|             print(f"Query status: {status['status']}") | ||||
|             if status['status'] == 'completed': | ||||
|                 print(f"Conversation history: {status['conversation_history']}") | ||||
|  | ||||
|         cURL equivalent: | ||||
|             curl -X GET http://localhost:5001/api/v1/query_status/query-id-here \ | ||||
|                  -H "X-API-Key: your-api-key" | ||||
|         """ | ||||
|         url = f"{self.base_url}/api/v1/query_status/{query_id}" | ||||
|         response = requests.get(url, headers=self.headers) | ||||
|         response.raise_for_status() | ||||
|         return response.json() | ||||
|  | ||||
|     def submit_query_and_wait(self, message, max_wait_time=300, poll_interval=2): | ||||
|         """ | ||||
|         Submit a query and wait for the result. | ||||
|  | ||||
|         Args: | ||||
|             message (str): The message to send to the server. | ||||
|             max_wait_time (int): Maximum time to wait for the result in seconds. | ||||
|             poll_interval (int): Time between status checks in seconds. | ||||
|  | ||||
|         Returns: | ||||
|             dict: The completed conversation history. | ||||
|  | ||||
|         Raises: | ||||
|             requests.RequestException: If the request fails. | ||||
|             TimeoutError: If the query doesn't complete within max_wait_time. | ||||
|  | ||||
|         Example: | ||||
|             client = LLMChatClient('http://localhost:5001', 'your-api-key') | ||||
|             result = client.submit_query_and_wait('What is the capital of France?') | ||||
|             print(f"Conversation history: {result}") | ||||
|         """ | ||||
|         query_id = self.submit_query(message) | ||||
|         start_time = time.time() | ||||
|  | ||||
|         while time.time() - start_time < max_wait_time: | ||||
|             status = self.get_query_status(query_id) | ||||
|             if status["status"] == "completed": | ||||
|                 return status["conversation_history"] | ||||
|             time.sleep(poll_interval) | ||||
|  | ||||
|         raise TimeoutError(f"Query did not complete within {max_wait_time} seconds") | ||||
|  | ||||
|  | ||||
| class LLMChatAdminClient: | ||||
|     def __init__(self, base_url, admin_key): | ||||
|         self.base_url = base_url.rstrip("/") | ||||
|         self.admin_key = admin_key | ||||
|         self.headers = {"X-Admin-Key": admin_key, "Content-Type": "application/json"} | ||||
|  | ||||
|     def generate_api_key(self, username): | ||||
|         """ | ||||
|         Generate a new API key for a user. | ||||
|  | ||||
|         Args: | ||||
|             username (str): The username to generate the API key for. | ||||
|  | ||||
|         Returns: | ||||
|             dict: A dictionary containing the username and generated API key. | ||||
|  | ||||
|         Raises: | ||||
|             requests.RequestException: If the request fails. | ||||
|  | ||||
|         Example: | ||||
|             admin_client = LLMChatAdminClient('http://localhost:5001', 'your-admin-key') | ||||
|             result = admin_client.generate_api_key('new_user') | ||||
|             print(f"Generated API key for {result['username']}: {result['api_key']}") | ||||
|  | ||||
|         cURL equivalent: | ||||
|             curl -X POST http://localhost:5001/admin/generate_key \ | ||||
|                  -H "Content-Type: application/json" \ | ||||
|                  -H "X-Admin-Key: your-admin-key" \ | ||||
|                  -d '{"username": "new_user"}' | ||||
|         """ | ||||
|         url = f"{self.base_url}/admin/generate_key" | ||||
|         data = {"username": username} | ||||
|         response = requests.post(url, json=data, headers=self.headers) | ||||
|         response.raise_for_status() | ||||
|         return response.json() | ||||
							
								
								
									
										16
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| version: '3.8' | ||||
|  | ||||
| services: | ||||
|   llm-chat-server: | ||||
|     build: . | ||||
|     ports: | ||||
|       - "5001:5001" | ||||
|     volumes: | ||||
|       - ./llm_chat_server.db:/app/llm_chat_server.db | ||||
|       - ./config.ini:/app/config.ini | ||||
|     environment: | ||||
|       - FLASK_ENV=production | ||||
|     restart: unless-stopped | ||||
|  | ||||
| volumes: | ||||
|   llm_chat_server_db: | ||||
							
								
								
									
										403
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										403
									
								
								index.html
									
									
									
									
									
								
							| @@ -9,6 +9,8 @@ | ||||
|     <script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script> | ||||
|     <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | ||||
|     <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@1.0.1/dist/chartjs-adapter-moment.min.js"></script> | ||||
|     <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script> | ||||
|     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css"> | ||||
|     <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@400;700&display=swap" rel="stylesheet"> | ||||
|     <style> | ||||
|         body { | ||||
| @@ -86,13 +88,19 @@ | ||||
|             color: #888; | ||||
|         } | ||||
|         .thought-summary { | ||||
|             cursor: pointer; | ||||
|             color: #fff; | ||||
|             margin-bottom: 5px; | ||||
|             font-weight: bold; | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             margin-bottom: 5px; | ||||
|             padding: 5px; | ||||
|             border-radius: 3px; | ||||
|         } | ||||
|         .thought-summary.plan { background-color: #2c3e50; } | ||||
|         .thought-summary.decision { background-color: #34495e; } | ||||
|         .thought-summary.tool_call { background-color: #16a085; } | ||||
|         .thought-summary.tool_result { background-color: #27ae60; } | ||||
|         .thought-summary.think_more { background-color: #2980b9; } | ||||
|         .thought-summary.answer { background-color: #8e44ad; } | ||||
|         .thought-summary.reply { background-color: #f39c12; } | ||||
|         .thought-summary.thoughts { background-color: #f39c12; } | ||||
|         .thought-details { | ||||
|             display: none; | ||||
|             margin-left: 20px; | ||||
| @@ -238,11 +246,111 @@ | ||||
|                 transform: translateX(0); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         .conversation-history-container { | ||||
|             margin-top: 20px; | ||||
|             background-color: #222; | ||||
|             border-radius: 5px; | ||||
|             padding: 10px; | ||||
|         } | ||||
|  | ||||
|         #conversation-history { | ||||
|             color: #fff; | ||||
|             font-family: 'Noto Sans Mono', monospace; | ||||
|             font-size: 12px; | ||||
|         } | ||||
|         .history-card { | ||||
|             background-color: #2c3e50; | ||||
|             border-radius: 5px; | ||||
|             padding: 10px; | ||||
|             margin-bottom: 10px; | ||||
|         } | ||||
|         .history-role { | ||||
|             font-weight: bold; | ||||
|             margin-bottom: 5px; | ||||
|         } | ||||
|         .history-content { | ||||
|             white-space: pre-wrap; | ||||
|             word-break: break-word; | ||||
|         } | ||||
|  | ||||
|         .error-message { | ||||
|             background-color: #ff6b6b; | ||||
|             color: #fff; | ||||
|             padding: 10px; | ||||
|             border-radius: 5px; | ||||
|             margin-bottom: 10px; | ||||
|         } | ||||
|  | ||||
|         .retrying { | ||||
|             background-color: #feca57; | ||||
|             color: #333; | ||||
|         } | ||||
|  | ||||
|         #clear-history-button { | ||||
|             background-color: #e74c3c; | ||||
|             color: white; | ||||
|             border: none; | ||||
|             padding: 10px; | ||||
|             margin-bottom: 10px; | ||||
|             cursor: pointer; | ||||
|             font-family: 'Noto Sans Mono', monospace; | ||||
|             font-size: 14px; | ||||
|             border-radius: 5px; | ||||
|         } | ||||
|  | ||||
|         #clear-history-button:hover { | ||||
|             background-color: #c0392b; | ||||
|         } | ||||
|  | ||||
|         #chat-tabs { | ||||
|             display: flex; | ||||
|             background-color: #222; | ||||
|             padding: 10px 10px 0 10px; | ||||
|         } | ||||
|          | ||||
|         .chat-tab { | ||||
|             background-color: #444; | ||||
|             color: #fff; | ||||
|             border: none; | ||||
|             padding: 10px 20px; | ||||
|             margin-right: 5px; | ||||
|             cursor: pointer; | ||||
|             border-top-left-radius: 5px; | ||||
|             border-top-right-radius: 5px; | ||||
|         } | ||||
|          | ||||
|         .chat-tab.active { | ||||
|             background-color: #666; | ||||
|         } | ||||
|          | ||||
|         #new-chat-button { | ||||
|             background-color: #27ae60; | ||||
|             color: #fff; | ||||
|             border: none; | ||||
|             padding: 10px 20px; | ||||
|             cursor: pointer; | ||||
|             border-top-left-radius: 5px; | ||||
|             border-top-right-radius: 5px; | ||||
|         } | ||||
|          | ||||
|         .close-tab { | ||||
|             margin-left: 10px; | ||||
|             color: #ff6b6b; | ||||
|             cursor: pointer; | ||||
|         } | ||||
|          | ||||
|         .thinking-section { | ||||
|             margin-bottom: 20px; | ||||
|             border-left: 2px solid #444; | ||||
|             padding-left: 10px; | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
| <body> | ||||
|     <div id="main-container"> | ||||
|         <div id="chat-area"> | ||||
|             <div id="chat-tabs"></div> | ||||
|             <div id="chat-container"></div> | ||||
|             <div id="input-container" class="pdp-panel"> | ||||
|                 <div class="pdp-label">INPUT:</div> | ||||
| @@ -272,6 +380,12 @@ | ||||
|                 <div class="graph-title">GPU Memory</div> | ||||
|                 <canvas id="gpuMemoryChart"></canvas> | ||||
|             </div> | ||||
|              | ||||
|             <!-- Add this new section for conversation history --> | ||||
|             <div class="conversation-history-container"> | ||||
|                 <div class="graph-title">Conversation History</div> | ||||
|                 <div id="conversation-history"></div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
| @@ -280,10 +394,80 @@ | ||||
|         const chatContainer = document.getElementById('chat-container'); | ||||
|         const userInput = document.getElementById('user-input'); | ||||
|         const sendButton = document.getElementById('send-button'); | ||||
|         const chatTabs = document.getElementById('chat-tabs'); | ||||
|  | ||||
|         let thinkingElement = null; | ||||
|         let thinkingDetails = null; | ||||
|         let thinkingStartTime = null; | ||||
|         let currentChatId = null; | ||||
|         let chats = {}; | ||||
|  | ||||
|         function createNewChat() { | ||||
|             const chatId = Date.now().toString(); | ||||
|             chats[chatId] = { | ||||
|                 messages: [], | ||||
|                 thinkingSections: [] | ||||
|             }; | ||||
|             addChatTab(chatId); | ||||
|             switchToChat(chatId); | ||||
|             saveChats(); | ||||
|         } | ||||
|  | ||||
|         function addChatTab(chatId) { | ||||
|             const tab = document.createElement('button'); | ||||
|             tab.classList.add('chat-tab'); | ||||
|             tab.textContent = `Chat ${Object.keys(chats).length}`; | ||||
|             tab.onclick = () => switchToChat(chatId); | ||||
|  | ||||
|             const closeButton = document.createElement('span'); | ||||
|             closeButton.classList.add('close-tab'); | ||||
|             closeButton.textContent = '×'; | ||||
|             closeButton.onclick = (e) => { | ||||
|                 e.stopPropagation(); | ||||
|                 closeChat(chatId); | ||||
|             }; | ||||
|  | ||||
|             tab.appendChild(closeButton); | ||||
|             chatTabs.insertBefore(tab, chatTabs.lastElementChild); | ||||
|         } | ||||
|  | ||||
|         function switchToChat(chatId) { | ||||
|             currentChatId = chatId; | ||||
|             document.querySelectorAll('.chat-tab').forEach(tab => tab.classList.remove('active')); | ||||
|             document.querySelector(`.chat-tab:nth-child(${Object.keys(chats).indexOf(chatId) + 1})`).classList.add('active'); | ||||
|             renderChat(chatId); | ||||
|         } | ||||
|  | ||||
|         function closeChat(chatId) { | ||||
|             delete chats[chatId]; | ||||
|             saveChats(); | ||||
|             const tabToRemove = Array.from(chatTabs.children).find(tab => tab.textContent.includes(`Chat ${Object.keys(chats).indexOf(chatId) + 1}`)); | ||||
|             if (tabToRemove) { | ||||
|                 chatTabs.removeChild(tabToRemove); | ||||
|             } | ||||
|             if (currentChatId === chatId) { | ||||
|                 const remainingChatIds = Object.keys(chats); | ||||
|                 if (remainingChatIds.length > 0) { | ||||
|                     switchToChat(remainingChatIds[0]); | ||||
|                 } else { | ||||
|                     createNewChat(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function renderChat(chatId) { | ||||
|             chatContainer.innerHTML = ''; | ||||
|             const chat = chats[chatId]; | ||||
|             chat.messages.forEach(message => addMessage(message.content, message.isUser)); | ||||
|             chat.thinkingSections.forEach(section => { | ||||
|                 const thinkingSection = createThinkingSection(); | ||||
|                 section.thoughts.forEach(thought => addThought(thought.type, thought.content, thought.details, thinkingSection)); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         function createThinkingSection() { | ||||
|             const section = document.createElement('div'); | ||||
|             section.classList.add('thinking-section'); | ||||
|             chatContainer.appendChild(section); | ||||
|             return section; | ||||
|         } | ||||
|  | ||||
|         function addMessage(message, isUser) { | ||||
|             const messageElement = document.createElement('div'); | ||||
| @@ -292,65 +476,40 @@ | ||||
|             messageElement.innerHTML = isUser ? message : marked.parse(message); | ||||
|             chatContainer.appendChild(messageElement); | ||||
|             chatContainer.scrollTop = chatContainer.scrollHeight; | ||||
|  | ||||
|             if (currentChatId) { | ||||
|                 chats[currentChatId].messages.push({ content: message, isUser: isUser }); | ||||
|                 saveChats(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function startThinking() { | ||||
|             thinkingElement = document.createElement('div'); | ||||
|             thinkingElement.classList.add('thought-summary', 'collapsible'); | ||||
|         function addThought(type, content, details = '', thinkingSection) { | ||||
|             const stepElement = document.createElement('div'); | ||||
|             stepElement.classList.add('thought-summary', 'collapsible', type); | ||||
|             stepElement.textContent = type.charAt(0).toUpperCase() + type.slice(1).replace('_', ' ') + ':'; | ||||
|             stepElement.onclick = toggleStepDetails; | ||||
|  | ||||
|             const stepDetails = document.createElement('div'); | ||||
|             stepDetails.classList.add('thought-details'); | ||||
|              | ||||
|             const led = document.createElement('div'); | ||||
|             led.classList.add('led', 'blinking'); | ||||
|              | ||||
|             const textNode = document.createTextNode('Thinking...'); | ||||
|              | ||||
|             thinkingElement.appendChild(led); | ||||
|             thinkingElement.appendChild(textNode); | ||||
|             thinkingElement.onclick = toggleThinkingDetails; | ||||
|              | ||||
|             thinkingDetails = document.createElement('div'); | ||||
|             thinkingDetails.classList.add('thought-details'); | ||||
|              | ||||
|             chatContainer.appendChild(thinkingElement); | ||||
|             chatContainer.appendChild(thinkingDetails); | ||||
|              | ||||
|             thinkingStartTime = Date.now(); | ||||
|             if (type === 'error') { | ||||
|                 stepElement.classList.add('error-message'); | ||||
|                 if (content.includes('retrying')) { | ||||
|                     stepElement.classList.add('retrying'); | ||||
|                 } | ||||
|                 stepDetails.innerHTML = marked.parse(content + '\n\nDetails:\n```\n' + details + '\n```'); | ||||
|             } else { | ||||
|                 stepDetails.innerHTML = marked.parse(content); | ||||
|             } | ||||
|  | ||||
|             thinkingSection.appendChild(stepElement); | ||||
|             thinkingSection.appendChild(stepDetails); | ||||
|             chatContainer.scrollTop = chatContainer.scrollHeight; | ||||
|         } | ||||
|  | ||||
|         function addThought(step, content) { | ||||
|             if (thinkingDetails) { | ||||
|                 const stepElement = document.createElement('div'); | ||||
|                 stepElement.classList.add('thought-summary', 'collapsible'); | ||||
|                 stepElement.textContent = step; | ||||
|                 stepElement.onclick = toggleStepDetails; | ||||
|  | ||||
|                 const stepDetails = document.createElement('div'); | ||||
|                 stepDetails.classList.add('thought-details'); | ||||
|                 stepDetails.innerHTML = content; | ||||
|  | ||||
|                 thinkingDetails.appendChild(stepElement); | ||||
|                 thinkingDetails.appendChild(stepDetails); | ||||
|                 chatContainer.scrollTop = chatContainer.scrollHeight; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function endThinking(thinkingTime) { | ||||
|             if (thinkingElement) { | ||||
|                 const textNode = thinkingElement.childNodes[1]; | ||||
|                 textNode.nodeValue = `Thinking... (${thinkingTime}s)`; | ||||
|                 const led = thinkingElement.querySelector('.led'); | ||||
|                 led.classList.remove('blinking'); | ||||
|                 led.style.backgroundColor = '#0f0'; | ||||
|                 led.style.boxShadow = '0 0 10px #0f0'; | ||||
|                 thinkingStartTime = null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function toggleThinkingDetails() { | ||||
|             this.classList.toggle('open'); | ||||
|             const details = this.nextElementSibling; | ||||
|             if (details) { | ||||
|                 details.style.display = details.style.display === 'none' ? 'block' : 'none'; | ||||
|             if (currentChatId) { | ||||
|                 const currentThinkingSection = chats[currentChatId].thinkingSections[chats[currentChatId].thinkingSections.length - 1]; | ||||
|                 currentThinkingSection.thoughts.push({ type, content, details }); | ||||
|                 saveChats(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -362,34 +521,71 @@ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         socket.on('thinking', (data) => { | ||||
|             if (!thinkingElement) startThinking(); | ||||
|             addThought(data.step, 'Started'); | ||||
|         }); | ||||
|         function saveChats() { | ||||
|             localStorage.setItem('chats', JSON.stringify(chats)); | ||||
|         } | ||||
|  | ||||
|         socket.on('thought', (data) => { | ||||
|             addThought('Result', data.content); | ||||
|         }); | ||||
|  | ||||
|         socket.on('chat_response', (data) => { | ||||
|             endThinking(data.thinking_time); | ||||
|             addMessage(data.response, false); | ||||
|         }); | ||||
|  | ||||
|         socket.on('error', (data) => { | ||||
|             endThinking(data.thinking_time); | ||||
|             addMessage(`Error: ${data.message}`, false); | ||||
|         }); | ||||
|         function loadChats() { | ||||
|             const storedChats = localStorage.getItem('chats'); | ||||
|             if (storedChats) { | ||||
|                 chats = JSON.parse(storedChats); | ||||
|                 Object.keys(chats).forEach(chatId => addChatTab(chatId)); | ||||
|                 if (Object.keys(chats).length > 0) { | ||||
|                     switchToChat(Object.keys(chats)[0]); | ||||
|                 } else { | ||||
|                     createNewChat(); | ||||
|                 } | ||||
|             } else { | ||||
|                 createNewChat(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         function sendMessage() { | ||||
|             const message = userInput.value.trim(); | ||||
|             if (message) { | ||||
|             if (message && currentChatId) { | ||||
|                 addMessage(message, true); | ||||
|                 socket.emit('chat_request', { message: message }); | ||||
|                 chats[currentChatId].thinkingSections.push({ thoughts: [] }); | ||||
|                 socket.emit('chat_request', {  | ||||
|                     message: message, | ||||
|                     conversation_history: chats[currentChatId].messages.filter(m => !m.isUser).map(m => ({ role: 'assistant', content: m.content })) | ||||
|                         .concat(chats[currentChatId].messages.filter(m => m.isUser).map(m => ({ role: 'user', content: m.content }))) | ||||
|                 }); | ||||
|                 userInput.value = ''; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         socket.on('thinking', (data) => { | ||||
|             if (currentChatId) { | ||||
|                 const newThinkingSection = createThinkingSection(); | ||||
|                 chats[currentChatId].thinkingSections.push({ thoughts: [] }); | ||||
|                 addThought(data.step, 'Started', '', newThinkingSection); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         socket.on('thought', (data) => { | ||||
|             if (currentChatId) { | ||||
|                 const currentThinkingSection = chatContainer.querySelector('.thinking-section:last-child'); | ||||
|                 addThought(data.type, data.content, data.details, currentThinkingSection); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         socket.on('chat_response', (data) => { | ||||
|             if (currentChatId) { | ||||
|                 addMessage(data.response, false); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         socket.on('error', (data) => { | ||||
|             if (currentChatId) { | ||||
|                 const currentThinkingSection = chatContainer.querySelector('.thinking-section:last-child'); | ||||
|                 if (data.type === 'retrying') { | ||||
|                     addThought('error', data.content, '', currentThinkingSection); | ||||
|                 } else { | ||||
|                     addThought('error', data.message, '', currentThinkingSection); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         sendButton.addEventListener('click', sendMessage); | ||||
|         userInput.addEventListener('keypress', function(e) { | ||||
|             if (e.key === 'Enter' && !e.shiftKey) { | ||||
| @@ -398,6 +594,16 @@ | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // Add new chat button | ||||
|         const newChatButton = document.createElement('button'); | ||||
|         newChatButton.id = 'new-chat-button'; | ||||
|         newChatButton.textContent = '+ New Chat'; | ||||
|         newChatButton.onclick = createNewChat; | ||||
|         chatTabs.appendChild(newChatButton); | ||||
|  | ||||
|         // Load chats when the page loads | ||||
|         loadChats(); | ||||
|  | ||||
|         const chartOptions = { | ||||
|             type: 'line', | ||||
|             options: { | ||||
| @@ -570,6 +776,41 @@ | ||||
|  | ||||
|         window.addEventListener('resize', checkWindowSize); | ||||
|         checkWindowSize(); // Initial check | ||||
|  | ||||
|         // Add this new function to update the conversation history | ||||
|         function updateConversationHistory(history) { | ||||
|             const conversationHistoryElement = document.getElementById('conversation-history'); | ||||
|             conversationHistoryElement.innerHTML = ''; | ||||
|              | ||||
|             history.forEach(item => { | ||||
|                 const card = document.createElement('div'); | ||||
|                 card.classList.add('history-card'); | ||||
|                  | ||||
|                 const role = document.createElement('div'); | ||||
|                 role.classList.add('history-role'); | ||||
|                 role.textContent = item.role.charAt(0).toUpperCase() + item.role.slice(1); | ||||
|                  | ||||
|                 const content = document.createElement('pre'); | ||||
|                 content.classList.add('history-content'); | ||||
|                 content.innerHTML = hljs.highlightAuto(item.content).value; | ||||
|                  | ||||
|                 card.appendChild(role); | ||||
|                 card.appendChild(content); | ||||
|                 conversationHistoryElement.appendChild(card); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Add this new socket listener | ||||
|         socket.on('conversation_history', (data) => { | ||||
|             updateConversationHistory(data.history); | ||||
|         }); | ||||
|  | ||||
|         // Add event listener for the clear history button | ||||
|         clearHistoryButton.addEventListener('click', () => { | ||||
|             if (confirm('Are you sure you want to clear the conversation history?')) { | ||||
|                 clearConversationHistory(); | ||||
|             } | ||||
|         }); | ||||
|     </script> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										96
									
								
								models.py
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								models.py
									
									
									
									
									
								
							| @@ -3,30 +3,86 @@ import structlog | ||||
|  | ||||
| logger = structlog.get_logger() | ||||
|  | ||||
|  | ||||
| class ModelManager: | ||||
|     def __init__(self): | ||||
|         self.model_capabilities = { | ||||
|             "qwen2.5:7b": ["general_knowledge", "structured_output", "multilingual", "instruction_following", "structured_data"], | ||||
|             "llama3.1:8b": ["general_knowledge", "reasoning", "tool_calling", "conversation", "multilingual", "instruction_following"], | ||||
|             "qwen2.5-coder:7b": ["code_generation", "code_analysis", "instruction_following", "math_reasoning"], | ||||
|             "llama3.2:3b": ["summarization", "instruction_following", "tool_calling", "multilingual"], | ||||
|             "llava:7b": ["visual_reasoning", "visual_conversation", "visual_tool_calling", "vision", "ocr", "multimodal"], | ||||
|             "ajindal/llama3.1-storm:8b": [ | ||||
|                 "general_knowledge", | ||||
|                 "reasoning", | ||||
|                 "tool_calling", | ||||
|                 "conversation", | ||||
|                 "multilingual", | ||||
|                 "instruction_following", | ||||
|             ], | ||||
|             "llama3.1:8b": [ | ||||
|                 "general_knowledge", | ||||
|                 "reasoning", | ||||
|                 "tool_calling", | ||||
|                 "conversation", | ||||
|                 "multilingual", | ||||
|                 "instruction_following", | ||||
|             ], | ||||
|             "qwen2.5:7b": [ | ||||
|                 "general_knowledge", | ||||
|                 "reasoning", | ||||
|                 "tool_calling", | ||||
|                 "conversation", | ||||
|                 "multilingual", | ||||
|                 "instruction_following", | ||||
|             ], | ||||
|             "llama3.2:3b": [ | ||||
|                 "summarization", | ||||
|                 "instruction_following", | ||||
|                 "tool_calling", | ||||
|                 "multilingual", | ||||
|             ], | ||||
|             "llava:7b": [ | ||||
|                 "visual_reasoning", | ||||
|                 "visual_conversation", | ||||
|                 "visual_tool_calling", | ||||
|                 "vision", | ||||
|                 "ocr", | ||||
|                 "multimodal", | ||||
|             ], | ||||
|         } | ||||
|         logger.info("ModelManager initialized", model_capabilities=self.model_capabilities) | ||||
|         logger.info( | ||||
|             "ModelManager initialized", model_capabilities=self.model_capabilities | ||||
|         ) | ||||
|  | ||||
|     def get_model_capabilities(self, model_name): | ||||
|         capabilities = self.model_capabilities.get(model_name, []) | ||||
|         logger.debug("Retrieved model capabilities", model=model_name, capabilities=capabilities) | ||||
|         logger.debug( | ||||
|             "Retrieved model capabilities", model=model_name, capabilities=capabilities | ||||
|         ) | ||||
|         return capabilities | ||||
|  | ||||
|     def select_best_model(self, required_capability): | ||||
|         suitable_models = [model for model, capabilities in self.model_capabilities.items() if required_capability in capabilities] | ||||
|         selected_model = suitable_models[0] if suitable_models else list(self.model_capabilities.keys())[0] | ||||
|         logger.info("Selected best model", required_capability=required_capability, selected_model=selected_model) | ||||
|         suitable_models = [ | ||||
|             model | ||||
|             for model, capabilities in self.model_capabilities.items() | ||||
|             if required_capability in capabilities | ||||
|         ] | ||||
|         selected_model = ( | ||||
|             suitable_models[0] | ||||
|             if suitable_models | ||||
|             else list(self.model_capabilities.keys())[0] | ||||
|         ) | ||||
|         logger.info( | ||||
|             "Selected best model", | ||||
|             required_capability=required_capability, | ||||
|             selected_model=selected_model, | ||||
|         ) | ||||
|         return selected_model | ||||
|  | ||||
|     def generate_text(self, model_name, prompt, max_length=100, system="You are a helpful assistant."): | ||||
|         logger.debug("Generating text", model=model_name, prompt=prompt, max_length=max_length) | ||||
|     def generate_text( | ||||
|         self, | ||||
|         model_name, | ||||
|         prompt, | ||||
|         max_length=100, | ||||
|         system="You are a helpful assistant.", | ||||
|         tools=[], | ||||
|     ): | ||||
|         # Check if model exists | ||||
|         try: | ||||
|             ollama.pull(model_name) | ||||
| @@ -38,8 +94,16 @@ class ModelManager: | ||||
|             else: | ||||
|                 logger.exception("Error pulling model", model=model_name, error=str(e)) | ||||
|                 raise e | ||||
|         response = ollama.generate(model=model_name, prompt=prompt, system=system) | ||||
|         logger.debug("Text generated", model=model_name, response=response['response']) | ||||
|         return response['response'] | ||||
|  | ||||
| model_manager = ModelManager() | ||||
|         response = ollama.generate( | ||||
|             model=model_name, | ||||
|             prompt=prompt, | ||||
|             system=system, | ||||
|             tools=tools, | ||||
|             max_tokens=max_length, | ||||
|         ) | ||||
|         logger.debug("Text generated", model=model_name, response=response["response"]) | ||||
|         return response["response"] | ||||
|  | ||||
|  | ||||
| model_manager = ModelManager() | ||||
|   | ||||
| @@ -4,11 +4,19 @@ aiohttp==3.10.5 | ||||
| aiosignal==1.3.1 | ||||
| annotated-types==0.7.0 | ||||
| anyio==4.6.0 | ||||
| art==6.3 | ||||
| attrs==24.2.0 | ||||
| beautifulsoup4==4.12.3 | ||||
| bidict==0.23.1 | ||||
| black==24.8.0 | ||||
| blinker==1.8.2 | ||||
| bs4==0.0.2 | ||||
| certifi==2024.7.4 | ||||
| chardet==5.2.0 | ||||
| charset-normalizer==3.3.2 | ||||
| click==8.1.7 | ||||
| cloudpickle==3.0.0 | ||||
| cssselect==1.2.0 | ||||
| datasets==3.0.0 | ||||
| dill==0.3.8 | ||||
| diskcache==5.6.3 | ||||
| @@ -17,9 +25,13 @@ duckduckgo_search==6.2.6 | ||||
| einops==0.8.0 | ||||
| fastapi==0.115.0 | ||||
| filelock==3.15.4 | ||||
| Flask==3.0.3 | ||||
| flask-openapi3==3.1.3 | ||||
| Flask-SocketIO==5.3.7 | ||||
| frozenlist==1.4.1 | ||||
| fsspec==2024.6.1 | ||||
| gguf==0.9.1 | ||||
| GPUtil==1.4.0 | ||||
| h11==0.14.0 | ||||
| httpcore==1.0.5 | ||||
| httptools==0.6.1 | ||||
| @@ -29,6 +41,8 @@ idna==3.7 | ||||
| importlib_metadata==8.5.0 | ||||
| inquirerpy==0.3.4 | ||||
| interegular==0.3.3 | ||||
| isort==5.13.2 | ||||
| itsdangerous==2.2.0 | ||||
| Jinja2==3.1.4 | ||||
| jiter==0.5.0 | ||||
| jsonschema==4.23.0 | ||||
| @@ -36,6 +50,9 @@ jsonschema-specifications==2023.12.1 | ||||
| lark==1.2.2 | ||||
| llvmlite==0.43.0 | ||||
| lm-format-enforcer==0.10.6 | ||||
| lxml==5.3.0 | ||||
| lxml_html_clean==0.2.2 | ||||
| markdownify==0.13.1 | ||||
| MarkupSafe==2.1.5 | ||||
| mistral_common==1.4.3 | ||||
| mpmath==1.3.0 | ||||
| @@ -43,6 +60,7 @@ msgpack==1.1.0 | ||||
| msgspec==0.18.6 | ||||
| multidict==6.1.0 | ||||
| multiprocess==0.70.16 | ||||
| mypy-extensions==1.0.0 | ||||
| nest-asyncio==1.6.0 | ||||
| networkx==3.3 | ||||
| numba==0.60.0 | ||||
| @@ -60,13 +78,16 @@ nvidia-ml-py==12.560.30 | ||||
| nvidia-nccl-cu12==2.20.5 | ||||
| nvidia-nvjitlink-cu12==12.6.20 | ||||
| nvidia-nvtx-cu12==12.1.105 | ||||
| ollama==0.3.3 | ||||
| openai==1.47.1 | ||||
| outlines==0.0.46 | ||||
| packaging==24.1 | ||||
| pandas==2.2.3 | ||||
| partial-json-parser==0.2.1.1.post4 | ||||
| pathspec==0.12.1 | ||||
| pfzy==0.3.4 | ||||
| pillow==10.4.0 | ||||
| platformdirs==4.3.6 | ||||
| primp==0.5.5 | ||||
| prometheus-fastapi-instrumentator==7.0.0 | ||||
| prometheus_client==0.21.0 | ||||
| @@ -81,10 +102,13 @@ pydantic==2.9.2 | ||||
| pydantic_core==2.23.4 | ||||
| python-dateutil==2.9.0.post0 | ||||
| python-dotenv==1.0.1 | ||||
| python-engineio==4.9.1 | ||||
| python-socketio==5.11.4 | ||||
| pytz==2024.2 | ||||
| PyYAML==6.0.2 | ||||
| pyzmq==26.2.0 | ||||
| ray==2.36.1 | ||||
| readability-lxml==0.8.1 | ||||
| referencing==0.35.1 | ||||
| regex==2024.7.24 | ||||
| requests==2.32.3 | ||||
| @@ -92,9 +116,12 @@ rpds-py==0.20.0 | ||||
| safetensors==0.4.4 | ||||
| sentencepiece==0.2.0 | ||||
| setuptools==72.1.0 | ||||
| simple-websocket==1.0.0 | ||||
| six==1.16.0 | ||||
| sniffio==1.3.1 | ||||
| soupsieve==2.6 | ||||
| starlette==0.38.6 | ||||
| structlog==24.4.0 | ||||
| sympy==1.13.2 | ||||
| tiktoken==0.7.0 | ||||
| tokenizers==0.19.1 | ||||
| @@ -113,6 +140,8 @@ vllm-flash-attn==2.6.1 | ||||
| watchfiles==0.24.0 | ||||
| wcwidth==0.2.13 | ||||
| websockets==13.1 | ||||
| Werkzeug==3.0.4 | ||||
| wsproto==1.2.0 | ||||
| xformers==0.0.27.post2 | ||||
| xxhash==3.5.0 | ||||
| yarl==1.12.0 | ||||
|   | ||||
							
								
								
									
										16
									
								
								schema.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								schema.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| CREATE TABLE IF NOT EXISTS Keys ( | ||||
|     id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||
|     username TEXT NOT NULL UNIQUE, | ||||
|     api_key TEXT NOT NULL UNIQUE | ||||
| ); | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS Queries ( | ||||
|     id TEXT PRIMARY KEY, | ||||
|     ip TEXT NOT NULL, | ||||
|     timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, | ||||
|     query TEXT NOT NULL, | ||||
|     api_key_id INTEGER, | ||||
|     status TEXT NOT NULL, | ||||
|     conversation_history TEXT, | ||||
|     FOREIGN KEY (api_key_id) REFERENCES Keys (id) | ||||
| ); | ||||
							
								
								
									
										369
									
								
								tools.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								tools.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,369 @@ | ||||
| import subprocess | ||||
| import tempfile | ||||
| import time | ||||
| import json | ||||
| import requests | ||||
| from markdownify import markdownify as md | ||||
| from readability.readability import Document | ||||
| import duckduckgo_search | ||||
| import datetime | ||||
| import random | ||||
| import math | ||||
| import re | ||||
| import base64 | ||||
| from io import BytesIO | ||||
| from PIL import Image, ImageDraw, ImageFont | ||||
| import ollama | ||||
| import os | ||||
|  | ||||
| class Tool: | ||||
|     def __init__(self, name: str, description: str, arguments: dict, returns: str): | ||||
|         self.name = name | ||||
|         self.description = description | ||||
|         self.arguments = arguments | ||||
|         self.returns = returns | ||||
|  | ||||
|     def execute(self, arguments: dict) -> str: | ||||
|         pass | ||||
|  | ||||
|  | ||||
| class ToolManager: | ||||
|     def __init__(self): | ||||
|         self.tools = [] | ||||
|  | ||||
|     def add_tool(self, tool: Tool): | ||||
|         self.tools.append(tool) | ||||
|  | ||||
|     def get_tool(self, name: str) -> Tool: | ||||
|         for tool in self.tools: | ||||
|             if tool.name == name: | ||||
|                 return tool | ||||
|         return None | ||||
|  | ||||
|     def get_tools_and_descriptions_for_prompt(self): | ||||
|         return "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools]) | ||||
|  | ||||
|     def get_tools_for_ollama_dict(self): | ||||
|         return [ | ||||
|             { | ||||
|                 "type": "function", | ||||
|                 "function": { | ||||
|                     "name": tool.name, | ||||
|                     "description": tool.description, | ||||
|                     "parameters": tool.arguments, | ||||
|                 }, | ||||
|             } | ||||
|             for tool in self.tools | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class DefaultToolManager(ToolManager): | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
|         self.add_tool(SearchTool()) | ||||
|         self.add_tool(GetReadablePageContentsTool()) | ||||
|         self.add_tool(CalculatorTool()) | ||||
|         self.add_tool(PythonCodeTool()) | ||||
|         self.add_tool(DateTimeTool()) | ||||
|         self.add_tool(RandomNumberTool()) | ||||
|         self.add_tool(RegexTool()) | ||||
|         self.add_tool(Base64Tool()) | ||||
|         self.add_tool(SimpleChartTool()) | ||||
|         self.add_tool(LLAVAImageAnalysisTool()) | ||||
|  | ||||
|  | ||||
| class SearchTool(Tool): | ||||
|     def __init__(self): | ||||
|         super().__init__( | ||||
|             "search_web", | ||||
|             "Search the internet for information", | ||||
|             { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "query": {"type": "string", "description": "The search query"} | ||||
|                 }, | ||||
|             }, | ||||
|             "results:list[string]", | ||||
|         ) | ||||
|  | ||||
|     def execute(self, arg: dict) -> str: | ||||
|         try: | ||||
|             res = duckduckgo_search.DDGS().text(arg["query"], max_results=5) | ||||
|             return "\n\n".join([f"{r['title']}\n{r['body']}\n{r['href']}" for r in res]) | ||||
|         except Exception as e: | ||||
|             return f"Error searching the web: {str(e)}" | ||||
|  | ||||
|  | ||||
| def get_readable_page_contents(url: str) -> str: | ||||
|     try: | ||||
|         response = requests.get(url) | ||||
|         response.raise_for_status() | ||||
|         doc = Document(response.content) | ||||
|         content = doc.summary() | ||||
|         return md(content) | ||||
|     except Exception as e: | ||||
|         return f"Error fetching readable content: {str(e)}" | ||||
|  | ||||
|  | ||||
| class GetReadablePageContentsTool(Tool): | ||||
|     def __init__(self): | ||||
|         super().__init__( | ||||
|             "get_readable_page_contents", | ||||
|             "Get the contents of a web page in a readable format", | ||||
|             { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "url": {"type": "string", "description": "The url of the web page"} | ||||
|                 }, | ||||
|             }, | ||||
|             "contents:string", | ||||
|         ) | ||||
|  | ||||
|     def execute(self, arg: dict) -> str: | ||||
|         return get_readable_page_contents(arg["url"]) | ||||
|  | ||||
|  | ||||
| class CalculatorTool(Tool): | ||||
|     def __init__(self): | ||||
|         super().__init__( | ||||
|             "calculator", | ||||
|             "Perform a calculation using python's eval function", | ||||
|             { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "expression": { | ||||
|                         "type": "string", | ||||
|                         "description": "The mathematical expression to evaluate, should be a python mathematical expression", | ||||
|                     } | ||||
|                 }, | ||||
|             }, | ||||
|             "result:string", | ||||
|         ) | ||||
|  | ||||
|     def execute(self, arg: dict) -> str: | ||||
|         try: | ||||
|             return str(eval(arg["expression"])) | ||||
|         except Exception as e: | ||||
|             return f"Error executing code: {str(e)}" | ||||
|  | ||||
|  | ||||
| class PythonCodeTool(Tool): | ||||
|     def __init__(self): | ||||
|         super().__init__( | ||||
|             "python_code", | ||||
|             "Execute python code using a temporary file and a subprocess. You must print results to stdout.", | ||||
|             { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "code": { | ||||
|                         "type": "string", | ||||
|                         "description": "The python code to execute, can be multiline", | ||||
|                     } | ||||
|                 }, | ||||
|             }, | ||||
|             "result:string", | ||||
|         ) | ||||
|  | ||||
|     def execute(self, arg: dict) -> str: | ||||
|         try: | ||||
|             with tempfile.NamedTemporaryFile( | ||||
|                 suffix=".py", mode="w", delete=False | ||||
|             ) as temp_file: | ||||
|                 temp_file.write(arg["code"]) | ||||
|                 temp_file.flush() | ||||
|  | ||||
|                 start_time = time.time() | ||||
|                 process = subprocess.Popen( | ||||
|                     ["python", temp_file.name], | ||||
|                     stdout=subprocess.PIPE, | ||||
|                     stderr=subprocess.PIPE, | ||||
|                     text=True, | ||||
|                 ) | ||||
|                 stdout, stderr = process.communicate(timeout=10)  # 10 second timeout | ||||
|                 end_time = time.time() | ||||
|                 execution_time = end_time - start_time | ||||
|  | ||||
|                 result = { | ||||
|                     "stdout": stdout, | ||||
|                     "stderr": stderr, | ||||
|                     "return_value": process.returncode, | ||||
|                     "execution_time": execution_time, | ||||
|                 } | ||||
|  | ||||
|         except subprocess.TimeoutExpired: | ||||
|             process.kill() | ||||
|             return "Error: Code execution timed out after 10 seconds" | ||||
|         except Exception as e: | ||||
|             return f"Error executing code: {str(e)}" | ||||
|  | ||||
|         return "\n".join([f"{k}:\n{v}" for k, v in result.items()]) | ||||
|  | ||||
|  | ||||
| class DateTimeTool(Tool): | ||||
|     def __init__(self): | ||||
|         super().__init__( | ||||
|             "get_current_datetime", | ||||
|             "Get the current date and time", | ||||
|             {"type": "object", "properties": {}}, | ||||
|             "datetime:string" | ||||
|         ) | ||||
|  | ||||
|     def execute(self, arg: dict) -> str: | ||||
|         return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") | ||||
|  | ||||
|  | ||||
| class RandomNumberTool(Tool): | ||||
|     def __init__(self): | ||||
|         super().__init__( | ||||
|             "generate_random_number", | ||||
|             "Generate a random number within a given range", | ||||
|             { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "min": {"type": "number", "description": "The minimum value"}, | ||||
|                     "max": {"type": "number", "description": "The maximum value"} | ||||
|                 } | ||||
|             }, | ||||
|             "random_number:number" | ||||
|         ) | ||||
|  | ||||
|     def execute(self, arg: dict) -> str: | ||||
|         return str(random.uniform(arg["min"], arg["max"])) | ||||
|  | ||||
|  | ||||
| class RegexTool(Tool): | ||||
|     def __init__(self): | ||||
|         super().__init__( | ||||
|             "regex_match", | ||||
|             "Perform a regex match on a given text", | ||||
|             { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "text": {"type": "string", "description": "The text to search in"}, | ||||
|                     "pattern": {"type": "string", "description": "The regex pattern to match"} | ||||
|                 } | ||||
|             }, | ||||
|             "matches:list[string]" | ||||
|         ) | ||||
|  | ||||
|     def execute(self, arg: dict) -> str: | ||||
|         matches = re.findall(arg["pattern"], arg["text"]) | ||||
|         return json.dumps(matches) | ||||
|  | ||||
|  | ||||
| class Base64Tool(Tool): | ||||
|     def __init__(self): | ||||
|         super().__init__( | ||||
|             "base64_encode_decode", | ||||
|             "Encode or decode a string using Base64", | ||||
|             { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "action": {"type": "string", "enum": ["encode", "decode"], "description": "Whether to encode or decode"}, | ||||
|                     "text": {"type": "string", "description": "The text to encode or decode"} | ||||
|                 } | ||||
|             }, | ||||
|             "result:string" | ||||
|         ) | ||||
|  | ||||
|     def execute(self, arg: dict) -> str: | ||||
|         if arg["action"] == "encode": | ||||
|             return base64.b64encode(arg["text"].encode()).decode() | ||||
|         elif arg["action"] == "decode": | ||||
|             return base64.b64decode(arg["text"].encode()).decode() | ||||
|         else: | ||||
|             return "Invalid action. Use 'encode' or 'decode'." | ||||
|  | ||||
|  | ||||
| class SimpleChartTool(Tool): | ||||
|     def __init__(self): | ||||
|         super().__init__( | ||||
|             "generate_simple_chart", | ||||
|             "Generate a simple bar chart image", | ||||
|             { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "data": {"type": "array", "items": {"type": "number"}, "description": "List of numerical values for the chart"}, | ||||
|                     "labels": {"type": "array", "items": {"type": "string"}, "description": "Labels for each bar"} | ||||
|                 } | ||||
|             }, | ||||
|             "image_base64:string" | ||||
|         ) | ||||
|  | ||||
|     def execute(self, arg: dict) -> str: | ||||
|         data = arg["data"] | ||||
|         labels = arg["labels"] | ||||
|          | ||||
|         # Create a simple bar chart | ||||
|         width, height = 400, 300 | ||||
|         img = Image.new('RGB', (width, height), color='white') | ||||
|         draw = ImageDraw.Draw(img) | ||||
|          | ||||
|         # Draw bars | ||||
|         max_value = max(data) | ||||
|         bar_width = width // (len(data) + 1) | ||||
|         for i, value in enumerate(data): | ||||
|             bar_height = (value / max_value) * (height - 50) | ||||
|             left = (i + 1) * bar_width | ||||
|             draw.rectangle([left, height - bar_height, left + bar_width, height], fill='blue') | ||||
|          | ||||
|         # Add labels | ||||
|         font = ImageFont.load_default() | ||||
|         for i, label in enumerate(labels): | ||||
|             left = (i + 1) * bar_width + bar_width // 2 | ||||
|             draw.text((left, height - 20), label, fill='black', anchor='ms', font=font) | ||||
|          | ||||
|         # Convert to base64 | ||||
|         buffered = BytesIO() | ||||
|         img.save(buffered, format="PNG") | ||||
|         img_str = base64.b64encode(buffered.getvalue()).decode() | ||||
|         return img_str | ||||
|  | ||||
|  | ||||
| class LLAVAImageAnalysisTool(Tool): | ||||
|     def __init__(self): | ||||
|         super().__init__( | ||||
|             "analyze_image", | ||||
|             "Analyze an image using the LLAVA model", | ||||
|             { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                     "image_base64": {"type": "string", "description": "Base64 encoded image"}, | ||||
|                     "question": {"type": "string", "description": "Question about the image"} | ||||
|                 } | ||||
|             }, | ||||
|             "analysis:string" | ||||
|         ) | ||||
|  | ||||
|     def execute(self, arg: dict) -> str: | ||||
|         try: | ||||
|             # Decode base64 image | ||||
|             image_data = base64.b64decode(arg["image_base64"]) | ||||
|             image = Image.open(BytesIO(image_data)) | ||||
|  | ||||
|             # Save image to a temporary file | ||||
|             with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file: | ||||
|                 image.save(temp_file, format="PNG") | ||||
|                 temp_file_path = temp_file.name | ||||
|  | ||||
|             # Call LLAVA model | ||||
|             response = ollama.chat( | ||||
|                 model="llava:7b", | ||||
|                 messages=[ | ||||
|                     { | ||||
|                         "role": "user", | ||||
|                         "content": arg["question"], | ||||
|                         "images": [temp_file_path] | ||||
|                     } | ||||
|                 ] | ||||
|             ) | ||||
|  | ||||
|             # Clean up temporary file | ||||
|             os.remove(temp_file_path) | ||||
|  | ||||
|             # Unload LLAVA model | ||||
|             ollama.delete("llava:7b") | ||||
|  | ||||
|             return response['message']['content'] | ||||
|         except Exception as e: | ||||
|             return f"Error analyzing image: {str(e)}" | ||||
		Reference in New Issue
	
	Block a user