quantalogic 0.59.2__py3-none-any.whl → 0.60.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. quantalogic/agent.py +268 -24
  2. quantalogic/create_custom_agent.py +26 -78
  3. quantalogic/prompts/chat_system_prompt.j2 +10 -7
  4. quantalogic/prompts/code_2_system_prompt.j2 +190 -0
  5. quantalogic/prompts/code_system_prompt.j2 +142 -0
  6. quantalogic/prompts/doc_system_prompt.j2 +178 -0
  7. quantalogic/prompts/legal_2_system_prompt.j2 +218 -0
  8. quantalogic/prompts/legal_system_prompt.j2 +140 -0
  9. quantalogic/prompts/system_prompt.j2 +6 -2
  10. quantalogic/prompts/task_prompt.j2 +1 -1
  11. quantalogic/prompts/tools_prompt.j2 +2 -4
  12. quantalogic/prompts.py +23 -4
  13. quantalogic/server/agent_server.py +1 -1
  14. quantalogic/tools/__init__.py +2 -0
  15. quantalogic/tools/duckduckgo_search_tool.py +1 -0
  16. quantalogic/tools/execute_bash_command_tool.py +114 -57
  17. quantalogic/tools/file_tracker_tool.py +49 -0
  18. quantalogic/tools/google_packages/google_news_tool.py +3 -0
  19. quantalogic/tools/image_generation/dalle_e.py +89 -137
  20. quantalogic/tools/rag_tool/__init__.py +2 -9
  21. quantalogic/tools/rag_tool/document_rag_sources_.py +728 -0
  22. quantalogic/tools/rag_tool/ocr_pdf_markdown.py +144 -0
  23. quantalogic/tools/replace_in_file_tool.py +1 -1
  24. quantalogic/tools/terminal_capture_tool.py +293 -0
  25. quantalogic/tools/tool.py +4 -0
  26. quantalogic/tools/utilities/__init__.py +2 -0
  27. quantalogic/tools/utilities/download_file_tool.py +3 -5
  28. quantalogic/tools/utilities/llm_tool.py +283 -0
  29. quantalogic/tools/utilities/selenium_tool.py +296 -0
  30. quantalogic/tools/utilities/vscode_tool.py +1 -1
  31. quantalogic/tools/web_navigation/__init__.py +5 -0
  32. quantalogic/tools/web_navigation/web_tool.py +145 -0
  33. quantalogic/tools/write_file_tool.py +72 -36
  34. {quantalogic-0.59.2.dist-info → quantalogic-0.60.0.dist-info}/METADATA +2 -2
  35. {quantalogic-0.59.2.dist-info → quantalogic-0.60.0.dist-info}/RECORD +38 -29
  36. quantalogic/tools/rag_tool/document_metadata.py +0 -15
  37. quantalogic/tools/rag_tool/query_response.py +0 -20
  38. quantalogic/tools/rag_tool/rag_tool.py +0 -566
  39. quantalogic/tools/rag_tool/rag_tool_beta.py +0 -264
  40. {quantalogic-0.59.2.dist-info → quantalogic-0.60.0.dist-info}/LICENSE +0 -0
  41. {quantalogic-0.59.2.dist-info → quantalogic-0.60.0.dist-info}/WHEEL +0 -0
  42. {quantalogic-0.59.2.dist-info → quantalogic-0.60.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,283 @@
1
+ """Legal-oriented LLM Tool for generating answers using retrieved legal context."""
2
+
3
+ import asyncio
4
+ from typing import Callable, Dict, List, Optional, Union
5
+
6
+ from loguru import logger
7
+ from pydantic import ConfigDict, Field
8
+
9
+ from quantalogic.console_print_token import console_print_token
10
+ from quantalogic.event_emitter import EventEmitter
11
+ from quantalogic.generative_model import GenerativeModel, Message
12
+ from quantalogic.tools.tool import Tool, ToolArgument
13
+
14
+
15
+ class OrientedLLMTool(Tool):
16
+ """Advanced LLM tool specialized for legal analysis and response generation."""
17
+
18
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
19
+
20
+ name: str = Field(default="legal_llm_tool")
21
+ description: str = Field(
22
+ default=(
23
+ "A specialized legal language model tool that generates expert legal analysis and responses. "
24
+ "Features:\n"
25
+ "- Legal expertise: Specialized in legal analysis and interpretation\n"
26
+ "- Context integration: Utilizes retrieved legal documents and precedents\n"
27
+ "- Multi-jurisdictional: Handles multiple legal systems and languages\n"
28
+ "- Citation support: Properly cites legal sources and precedents\n"
29
+ "\nCapabilities:\n"
30
+ "- Legal document analysis\n"
31
+ "- Statutory interpretation\n"
32
+ "- Case law analysis\n"
33
+ "- Regulatory compliance guidance"
34
+ )
35
+ )
36
+ arguments: list = Field(
37
+ default=[
38
+ ToolArgument(
39
+ name="role",
40
+ arg_type="string",
41
+ description="Legal specialization (e.g., 'commercial_law', 'environmental_law', 'constitutional_law')",
42
+ required=True,
43
+ default="general_legal_expert",
44
+ ),
45
+ ToolArgument(
46
+ name="legal_context",
47
+ arg_type="string",
48
+ description="Retrieved legal documents, precedents, and relevant context from RAG tool",
49
+ required=True,
50
+ ),
51
+ ToolArgument(
52
+ name="jurisdiction",
53
+ arg_type="string",
54
+ description="Relevant legal jurisdiction(s) for analysis",
55
+ required=True,
56
+ default="general",
57
+ ),
58
+ ToolArgument(
59
+ name="query_type",
60
+ arg_type="string",
61
+ description="Type of legal analysis needed (e.g., 'interpretation', 'compliance', 'comparison')",
62
+ required=True,
63
+ default="interpretation",
64
+ ),
65
+ ToolArgument(
66
+ name="prompt",
67
+ arg_type="string",
68
+ description="Specific legal question or analysis request",
69
+ required=True,
70
+ ),
71
+ ToolArgument(
72
+ name="temperature",
73
+ arg_type="float",
74
+ description="Response precision (0.0 for strict legal interpretation, 1.0 for creative analysis)",
75
+ required=False,
76
+ default="0.3",
77
+ ),
78
+ ]
79
+ )
80
+
81
+ model_name: str = Field(..., description="The name of the language model to use")
82
+ role: str = Field(default="general_legal_expert", description="The specific role or persona for the LLM")
83
+ jurisdiction: str = Field(default="general", description="The relevant jurisdiction for the LLM")
84
+ on_token: Callable | None = Field(default=None, exclude=True)
85
+ generative_model: GenerativeModel | None = Field(default=None, exclude=True)
86
+ event_emitter: EventEmitter | None = Field(default=None, exclude=True)
87
+
88
+ def __init__(
89
+ self,
90
+ model_name: str,
91
+ name: str = "legal_llm_tool",
92
+ role: str = "general_legal_expert",
93
+ jurisdiction: str = "general",
94
+ on_token: Optional[Callable] = None,
95
+ event_emitter: Optional[EventEmitter] = None,
96
+ ):
97
+ """Initialize the Legal LLM Tool.
98
+
99
+ Args:
100
+ model_name: Name of the language model
101
+ role: Legal specialization role
102
+ jurisdiction: Default jurisdiction
103
+ on_token: Optional callback for token streaming
104
+ event_emitter: Optional event emitter for streaming
105
+ """
106
+ super().__init__(
107
+ model_name=model_name,
108
+ name=name,
109
+ role=role,
110
+ jurisdiction=jurisdiction,
111
+ on_token=on_token,
112
+ event_emitter=event_emitter,
113
+ )
114
+
115
+ self.model_name = model_name
116
+ self.role = role
117
+ self.jurisdiction = jurisdiction
118
+ self.on_token = on_token
119
+ self.event_emitter = event_emitter
120
+ self.generative_model = None
121
+
122
+ # Initialize the model
123
+ self.model_post_init(None)
124
+
125
+ def model_post_init(self, __context):
126
+ """Initialize the generative model with legal-specific configuration."""
127
+ if self.generative_model is None:
128
+ self.generative_model = GenerativeModel(
129
+ model=self.model_name,
130
+ event_emitter=self.event_emitter
131
+ )
132
+ logger.debug(f"Initialized Legal LLM Tool with model: {self.model_name}")
133
+
134
+ if self.on_token is not None:
135
+ self.generative_model.event_emitter.on("stream_chunk", self.on_token)
136
+
137
+ def _build_legal_system_prompt(
138
+ self,
139
+ role: str,
140
+ jurisdiction: str,
141
+ query_type: str,
142
+ legal_context: Union[str, Dict, List]
143
+ ) -> str:
144
+ """Build a specialized legal system prompt.
145
+
146
+ Args:
147
+ role: Legal specialization role
148
+ jurisdiction: Relevant jurisdiction
149
+ query_type: Type of legal analysis
150
+ legal_context: Retrieved legal context from RAG
151
+
152
+ Returns:
153
+ Structured system prompt for legal analysis
154
+ """
155
+ # Format legal context if it's not a string
156
+ if not isinstance(legal_context, str):
157
+ legal_context = str(legal_context)
158
+
159
+ system_prompt = f"""You are an expert legal advisor specialized in {role}.
160
+ Jurisdiction: {jurisdiction}
161
+ Analysis Type: {query_type}
162
+
163
+ Your task is to provide a well-reasoned legal analysis based on the following context:
164
+
165
+ {legal_context}
166
+
167
+ Guidelines:
168
+ 1. Base your analysis strictly on provided legal sources
169
+ 2. Cite specific articles, sections, and precedents
170
+ 3. Consider jurisdictional context and limitations
171
+ 4. Highlight any legal uncertainties or ambiguities
172
+ 5. Provide clear, actionable conclusions
173
+
174
+ Format your response as follows:
175
+ 1. Legal Analysis
176
+ 2. Relevant Citations
177
+ 3. Key Considerations
178
+ 4. Conclusion
179
+
180
+ Remember: If a legal point is not supported by the provided context, acknowledge the limitation."""
181
+
182
+ return system_prompt
183
+
184
+ async def async_execute(
185
+ self,
186
+ prompt: str,
187
+ legal_context: Union[str, Dict, List],
188
+ role: Optional[str] = None,
189
+ jurisdiction: Optional[str] = None,
190
+ query_type: str = "interpretation",
191
+ temperature: float = 0.3
192
+ ) -> str:
193
+ """Execute legal analysis asynchronously.
194
+
195
+ Args:
196
+ prompt: Legal question or analysis request
197
+ legal_context: Retrieved legal documents and context
198
+ role: Optional override for legal specialization
199
+ jurisdiction: Optional override for jurisdiction
200
+ query_type: Type of legal analysis needed
201
+ temperature: Response precision (0.0-1.0)
202
+
203
+ Returns:
204
+ Detailed legal analysis and response
205
+ """
206
+ try:
207
+ if not (0.0 <= temperature <= 1.0):
208
+ raise ValueError("Temperature must be between 0 and 1")
209
+
210
+ used_role = role or self.role
211
+ used_jurisdiction = jurisdiction or self.jurisdiction
212
+
213
+ system_prompt = self._build_legal_system_prompt(
214
+ used_role,
215
+ used_jurisdiction,
216
+ query_type,
217
+ legal_context
218
+ )
219
+
220
+ messages = [
221
+ Message(role="system", content=system_prompt),
222
+ Message(role="user", content=prompt)
223
+ ]
224
+
225
+ if self.generative_model:
226
+ self.generative_model.temperature = temperature
227
+
228
+ is_streaming = self.on_token is not None
229
+ result = await self.generative_model.async_generate_with_history(
230
+ messages_history=messages,
231
+ prompt=prompt,
232
+ streaming=is_streaming
233
+ )
234
+
235
+ if is_streaming:
236
+ response = ""
237
+ async for chunk in result:
238
+ response += chunk
239
+ else:
240
+ response = result.response
241
+
242
+ logger.info(f"Generated legal analysis for {query_type} query in {used_jurisdiction} jurisdiction")
243
+ return response
244
+ else:
245
+ raise ValueError("Generative model not initialized")
246
+
247
+ except Exception as e:
248
+ logger.error(f"Error in legal analysis: {str(e)}")
249
+ raise
250
+
251
+ def execute(self, *args, **kwargs) -> str:
252
+ """Synchronous wrapper for async_execute."""
253
+ return asyncio.run(self.async_execute(*args, **kwargs))
254
+
255
+
256
+ if __name__ == "__main__":
257
+ # Example usage of OrientedLLMTool
258
+ tool = OrientedLLMTool(model_name="openrouter/openai/gpt-4o-mini")
259
+ legal_context = "Retrieved legal documents and context from RAG tool"
260
+ question = "What is the meaning of life?"
261
+ temperature = 0.7
262
+
263
+ # Synchronous execution
264
+ answer = tool.execute(prompt=question, legal_context=legal_context, temperature=temperature)
265
+ print("Synchronous Answer:")
266
+ print(answer)
267
+
268
+ # Asynchronous execution with streaming
269
+ pirate = OrientedLLMTool(
270
+ model_name="openrouter/openai/gpt-4o-mini", on_token=console_print_token
271
+ )
272
+ pirate_answer = asyncio.run(
273
+ pirate.async_execute(prompt=question, legal_context=legal_context, temperature=temperature)
274
+ )
275
+ print("\nAsynchronous Pirate Answer:")
276
+ print(f"Answer: {pirate_answer}")
277
+
278
+ # Display tool configuration in Markdown
279
+ custom_tool = OrientedLLMTool(
280
+ model_name="openrouter/openai/gpt-4o-mini", on_token=console_print_token
281
+ )
282
+ print("\nTool Configuration:")
283
+ print(custom_tool.to_markdown())
@@ -0,0 +1,296 @@
1
+ """Selenium Tool for web automation and testing.
2
+
3
+ This tool provides a high-level interface for web automation tasks using Selenium WebDriver.
4
+ It supports common web interactions like navigation, form filling, and element manipulation.
5
+ """
6
+
7
+ import asyncio
8
+ from typing import Optional, List, Dict, Any
9
+ from selenium import webdriver
10
+ from selenium.webdriver.common.by import By
11
+ from selenium.webdriver.support.ui import WebDriverWait
12
+ from selenium.webdriver.support import expected_conditions as EC
13
+ from selenium.webdriver.chrome.service import Service
14
+ from selenium.webdriver.chrome.options import Options
15
+ from webdriver_manager.chrome import ChromeDriverManager
16
+ from pydantic import Field, ConfigDict
17
+ from loguru import logger
18
+
19
+ from quantalogic.tools.tool import Tool, ToolArgument
20
+
21
+ class SeleniumTool(Tool):
22
+ """Tool for web automation using Selenium WebDriver."""
23
+
24
+ model_config = ConfigDict(arbitrary_types_allowed=True)
25
+
26
+ name: str = Field(default="selenium_tool")
27
+ description: str = Field(
28
+ default=(
29
+ "Automates web browser interactions using Selenium WebDriver. "
30
+ "Supports navigation, form filling, clicking, and extracting content. "
31
+ "Uses Chrome browser in headless mode by default."
32
+ )
33
+ )
34
+ arguments: list = Field(
35
+ default=[
36
+ ToolArgument(
37
+ name="action",
38
+ arg_type="string",
39
+ description=(
40
+ "The automation action to perform. Available actions: "
41
+ "navigate, click, type, extract_text, extract_attribute, wait_for_element"
42
+ ),
43
+ required=True,
44
+ example="navigate",
45
+ ),
46
+ ToolArgument(
47
+ name="url",
48
+ arg_type="string",
49
+ description="URL to navigate to (required for 'navigate' action)",
50
+ required=False,
51
+ example="https://example.com",
52
+ ),
53
+ ToolArgument(
54
+ name="selector",
55
+ arg_type="string",
56
+ description="CSS or XPath selector for target element",
57
+ required=False,
58
+ example="#login-button",
59
+ ),
60
+ ToolArgument(
61
+ name="selector_type",
62
+ arg_type="string",
63
+ description="Type of selector (css, xpath, id, name, class_name)",
64
+ required=False,
65
+ default="css",
66
+ example="css",
67
+ ),
68
+ ToolArgument(
69
+ name="value",
70
+ arg_type="string",
71
+ description="Value to type or attribute to extract",
72
+ required=False,
73
+ example="username123",
74
+ ),
75
+ ToolArgument(
76
+ name="timeout",
77
+ arg_type="int",
78
+ description="Maximum time to wait for element (seconds)",
79
+ required=False,
80
+ default="10",
81
+ example="10",
82
+ ),
83
+ ]
84
+ )
85
+
86
+ driver: Optional[webdriver.Chrome] = Field(default=None, exclude=True)
87
+ headless: bool = Field(default=True, description="Run browser in headless mode")
88
+ custom_options: List[str] = Field(default_factory=list, description="Custom Chrome options")
89
+
90
+ def __init__(
91
+ self,
92
+ headless: bool = True,
93
+ custom_options: Optional[List[str]] = None,
94
+ name: str = "selenium_tool"
95
+ ):
96
+ """Initialize SeleniumTool with browser configuration.
97
+
98
+ Args:
99
+ headless (bool): Run browser in headless mode. Defaults to True.
100
+ custom_options (List[str], optional): Custom Chrome options.
101
+ name (str): Name of the tool instance.
102
+ """
103
+ super().__init__(
104
+ **{
105
+ "headless": headless,
106
+ "custom_options": custom_options or [],
107
+ "name": name,
108
+ }
109
+ )
110
+ self._initialize_driver()
111
+
112
+ def _initialize_driver(self):
113
+ """Initialize Chrome WebDriver with configured options."""
114
+ try:
115
+ chrome_options = Options()
116
+ if self.headless:
117
+ chrome_options.add_argument("--headless")
118
+
119
+ # Add common options for stability
120
+ chrome_options.add_argument("--no-sandbox")
121
+ chrome_options.add_argument("--disable-dev-shm-usage")
122
+
123
+ # Add custom options
124
+ for option in self.custom_options:
125
+ chrome_options.add_argument(option)
126
+
127
+ # Initialize the driver
128
+ service = Service(ChromeDriverManager().install())
129
+ self.driver = webdriver.Chrome(service=service, options=chrome_options)
130
+ self.driver.implicitly_wait(5)
131
+ logger.info("Successfully initialized Chrome WebDriver")
132
+
133
+ except Exception as e:
134
+ logger.error(f"Failed to initialize WebDriver: {str(e)}")
135
+ raise
136
+
137
+ def _get_by_method(self, selector_type: str) -> By:
138
+ """Get the appropriate By method based on selector type.
139
+
140
+ Args:
141
+ selector_type: Type of selector (css, xpath, id, name, class_name)
142
+
143
+ Returns:
144
+ selenium.webdriver.common.by.By method
145
+ """
146
+ selector_types = {
147
+ "css": By.CSS_SELECTOR,
148
+ "xpath": By.XPATH,
149
+ "id": By.ID,
150
+ "name": By.NAME,
151
+ "class_name": By.CLASS_NAME,
152
+ }
153
+ return selector_types.get(selector_type.lower(), By.CSS_SELECTOR)
154
+
155
+ async def async_execute(
156
+ self,
157
+ action: str,
158
+ url: Optional[str] = None,
159
+ selector: Optional[str] = None,
160
+ selector_type: str = "css",
161
+ value: Optional[str] = None,
162
+ timeout: int = 10,
163
+ ) -> Dict[str, Any]:
164
+ """Execute a Selenium automation action asynchronously.
165
+
166
+ Args:
167
+ action: The automation action to perform
168
+ url: URL to navigate to (for navigate action)
169
+ selector: Element selector
170
+ selector_type: Type of selector (css, xpath, id, name, class_name)
171
+ value: Value to type or attribute to extract
172
+ timeout: Maximum time to wait for element (seconds)
173
+
174
+ Returns:
175
+ Dict containing action result and any extracted data
176
+ """
177
+ try:
178
+ if not self.driver:
179
+ self._initialize_driver()
180
+
181
+ result = {"success": False, "message": "", "data": None}
182
+
183
+ # Handle different actions
184
+ if action == "navigate":
185
+ if not url:
186
+ raise ValueError("URL is required for navigate action")
187
+ self.driver.get(url)
188
+ result["success"] = True
189
+ result["message"] = f"Successfully navigated to {url}"
190
+
191
+ elif action in ["click", "type", "extract_text", "extract_attribute", "wait_for_element"]:
192
+ if not selector:
193
+ raise ValueError("Selector is required for element actions")
194
+
195
+ # Wait for element
196
+ by_method = self._get_by_method(selector_type)
197
+ element = WebDriverWait(self.driver, timeout).until(
198
+ EC.presence_of_element_located((by_method, selector))
199
+ )
200
+
201
+ if action == "click":
202
+ element.click()
203
+ result["success"] = True
204
+ result["message"] = f"Successfully clicked element: {selector}"
205
+
206
+ elif action == "type":
207
+ if not value:
208
+ raise ValueError("Value is required for type action")
209
+ element.clear()
210
+ element.send_keys(value)
211
+ result["success"] = True
212
+ result["message"] = f"Successfully typed '{value}' into element: {selector}"
213
+
214
+ elif action == "extract_text":
215
+ text = element.text
216
+ result["success"] = True
217
+ result["message"] = "Successfully extracted text"
218
+ result["data"] = text
219
+
220
+ elif action == "extract_attribute":
221
+ if not value:
222
+ raise ValueError("Attribute name is required for extract_attribute action")
223
+ attr_value = element.get_attribute(value)
224
+ result["success"] = True
225
+ result["message"] = f"Successfully extracted attribute: {value}"
226
+ result["data"] = attr_value
227
+
228
+ elif action == "wait_for_element":
229
+ result["success"] = True
230
+ result["message"] = f"Element found: {selector}"
231
+
232
+ else:
233
+ raise ValueError(f"Unknown action: {action}")
234
+
235
+ return result
236
+
237
+ except Exception as e:
238
+ logger.error(f"Error in SeleniumTool.async_execute: {str(e)}")
239
+ return {
240
+ "success": False,
241
+ "message": f"Error: {str(e)}",
242
+ "data": None
243
+ }
244
+
245
+ def execute(
246
+ self,
247
+ action: str,
248
+ url: Optional[str] = None,
249
+ selector: Optional[str] = None,
250
+ selector_type: str = "css",
251
+ value: Optional[str] = None,
252
+ timeout: int = 10,
253
+ ) -> Dict[str, Any]:
254
+ """Synchronous wrapper for async_execute."""
255
+ return asyncio.run(
256
+ self.async_execute(
257
+ action=action,
258
+ url=url,
259
+ selector=selector,
260
+ selector_type=selector_type,
261
+ value=value,
262
+ timeout=timeout,
263
+ )
264
+ )
265
+
266
+ def __del__(self):
267
+ """Clean up WebDriver when the tool is destroyed."""
268
+ if self.driver:
269
+ try:
270
+ self.driver.quit()
271
+ logger.info("Successfully closed WebDriver")
272
+ except Exception as e:
273
+ logger.error(f"Error closing WebDriver: {str(e)}")
274
+
275
+
276
+ if __name__ == "__main__":
277
+ # Example usage
278
+ tool = SeleniumTool(headless=True)
279
+
280
+ # Navigate to a website
281
+ result = tool.execute(
282
+ action="navigate",
283
+ url="https://example.com"
284
+ )
285
+ print("Navigation result:", result)
286
+
287
+ # Extract text from an element
288
+ result = tool.execute(
289
+ action="extract_text",
290
+ selector="h1",
291
+ selector_type="css"
292
+ )
293
+ print("Extracted text:", result)
294
+
295
+ # Clean up
296
+ del tool
@@ -15,7 +15,7 @@ class VSCodeServerTool(Tool):
15
15
 
16
16
  name: str = "vscode_tool"
17
17
  description: str = "Launches a VS Code Server instance for remote development."
18
- need_validation: bool = True
18
+ need_validation: bool = False
19
19
  arguments: list = [
20
20
  ToolArgument(
21
21
  name="workspace_path",
@@ -0,0 +1,5 @@
1
+ """Web navigation tools package."""
2
+
3
+ from quantalogic.tools.web_navigation.web_tool import WebNavigationTool
4
+
5
+ __all__ = ["WebNavigationTool"]