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.
- quantalogic/agent.py +268 -24
- quantalogic/create_custom_agent.py +26 -78
- quantalogic/prompts/chat_system_prompt.j2 +10 -7
- quantalogic/prompts/code_2_system_prompt.j2 +190 -0
- quantalogic/prompts/code_system_prompt.j2 +142 -0
- quantalogic/prompts/doc_system_prompt.j2 +178 -0
- quantalogic/prompts/legal_2_system_prompt.j2 +218 -0
- quantalogic/prompts/legal_system_prompt.j2 +140 -0
- quantalogic/prompts/system_prompt.j2 +6 -2
- quantalogic/prompts/task_prompt.j2 +1 -1
- quantalogic/prompts/tools_prompt.j2 +2 -4
- quantalogic/prompts.py +23 -4
- quantalogic/server/agent_server.py +1 -1
- quantalogic/tools/__init__.py +2 -0
- quantalogic/tools/duckduckgo_search_tool.py +1 -0
- quantalogic/tools/execute_bash_command_tool.py +114 -57
- quantalogic/tools/file_tracker_tool.py +49 -0
- quantalogic/tools/google_packages/google_news_tool.py +3 -0
- quantalogic/tools/image_generation/dalle_e.py +89 -137
- quantalogic/tools/rag_tool/__init__.py +2 -9
- quantalogic/tools/rag_tool/document_rag_sources_.py +728 -0
- quantalogic/tools/rag_tool/ocr_pdf_markdown.py +144 -0
- quantalogic/tools/replace_in_file_tool.py +1 -1
- quantalogic/tools/terminal_capture_tool.py +293 -0
- quantalogic/tools/tool.py +4 -0
- quantalogic/tools/utilities/__init__.py +2 -0
- quantalogic/tools/utilities/download_file_tool.py +3 -5
- quantalogic/tools/utilities/llm_tool.py +283 -0
- quantalogic/tools/utilities/selenium_tool.py +296 -0
- quantalogic/tools/utilities/vscode_tool.py +1 -1
- quantalogic/tools/web_navigation/__init__.py +5 -0
- quantalogic/tools/web_navigation/web_tool.py +145 -0
- quantalogic/tools/write_file_tool.py +72 -36
- {quantalogic-0.59.2.dist-info → quantalogic-0.60.0.dist-info}/METADATA +2 -2
- {quantalogic-0.59.2.dist-info → quantalogic-0.60.0.dist-info}/RECORD +38 -29
- quantalogic/tools/rag_tool/document_metadata.py +0 -15
- quantalogic/tools/rag_tool/query_response.py +0 -20
- quantalogic/tools/rag_tool/rag_tool.py +0 -566
- quantalogic/tools/rag_tool/rag_tool_beta.py +0 -264
- {quantalogic-0.59.2.dist-info → quantalogic-0.60.0.dist-info}/LICENSE +0 -0
- {quantalogic-0.59.2.dist-info → quantalogic-0.60.0.dist-info}/WHEEL +0 -0
- {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 =
|
18
|
+
need_validation: bool = False
|
19
19
|
arguments: list = [
|
20
20
|
ToolArgument(
|
21
21
|
name="workspace_path",
|