quantalogic 0.2.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/__init__.py +20 -0
- quantalogic/agent.py +638 -0
- quantalogic/agent_config.py +138 -0
- quantalogic/coding_agent.py +83 -0
- quantalogic/event_emitter.py +223 -0
- quantalogic/generative_model.py +226 -0
- quantalogic/interactive_text_editor.py +190 -0
- quantalogic/main.py +185 -0
- quantalogic/memory.py +217 -0
- quantalogic/model_names.py +19 -0
- quantalogic/print_event.py +66 -0
- quantalogic/prompts.py +99 -0
- quantalogic/server/__init__.py +3 -0
- quantalogic/server/agent_server.py +633 -0
- quantalogic/server/models.py +60 -0
- quantalogic/server/routes.py +117 -0
- quantalogic/server/state.py +199 -0
- quantalogic/server/static/js/event_visualizer.js +430 -0
- quantalogic/server/static/js/quantalogic.js +571 -0
- quantalogic/server/templates/index.html +134 -0
- quantalogic/tool_manager.py +68 -0
- quantalogic/tools/__init__.py +46 -0
- quantalogic/tools/agent_tool.py +88 -0
- quantalogic/tools/download_http_file_tool.py +64 -0
- quantalogic/tools/edit_whole_content_tool.py +70 -0
- quantalogic/tools/elixir_tool.py +240 -0
- quantalogic/tools/execute_bash_command_tool.py +116 -0
- quantalogic/tools/input_question_tool.py +57 -0
- quantalogic/tools/language_handlers/__init__.py +21 -0
- quantalogic/tools/language_handlers/c_handler.py +33 -0
- quantalogic/tools/language_handlers/cpp_handler.py +33 -0
- quantalogic/tools/language_handlers/go_handler.py +33 -0
- quantalogic/tools/language_handlers/java_handler.py +37 -0
- quantalogic/tools/language_handlers/javascript_handler.py +42 -0
- quantalogic/tools/language_handlers/python_handler.py +29 -0
- quantalogic/tools/language_handlers/rust_handler.py +33 -0
- quantalogic/tools/language_handlers/scala_handler.py +33 -0
- quantalogic/tools/language_handlers/typescript_handler.py +42 -0
- quantalogic/tools/list_directory_tool.py +123 -0
- quantalogic/tools/llm_tool.py +119 -0
- quantalogic/tools/markitdown_tool.py +105 -0
- quantalogic/tools/nodejs_tool.py +515 -0
- quantalogic/tools/python_tool.py +469 -0
- quantalogic/tools/read_file_block_tool.py +140 -0
- quantalogic/tools/read_file_tool.py +79 -0
- quantalogic/tools/replace_in_file_tool.py +300 -0
- quantalogic/tools/ripgrep_tool.py +353 -0
- quantalogic/tools/search_definition_names.py +419 -0
- quantalogic/tools/task_complete_tool.py +35 -0
- quantalogic/tools/tool.py +146 -0
- quantalogic/tools/unified_diff_tool.py +387 -0
- quantalogic/tools/write_file_tool.py +97 -0
- quantalogic/utils/__init__.py +17 -0
- quantalogic/utils/ask_user_validation.py +12 -0
- quantalogic/utils/download_http_file.py +77 -0
- quantalogic/utils/get_coding_environment.py +15 -0
- quantalogic/utils/get_environment.py +26 -0
- quantalogic/utils/get_quantalogic_rules_content.py +19 -0
- quantalogic/utils/git_ls.py +121 -0
- quantalogic/utils/read_file.py +54 -0
- quantalogic/utils/read_http_text_content.py +101 -0
- quantalogic/xml_parser.py +242 -0
- quantalogic/xml_tool_parser.py +99 -0
- quantalogic-0.2.0.dist-info/LICENSE +201 -0
- quantalogic-0.2.0.dist-info/METADATA +1034 -0
- quantalogic-0.2.0.dist-info/RECORD +68 -0
- quantalogic-0.2.0.dist-info/WHEEL +4 -0
- quantalogic-0.2.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
import tree_sitter_scala as tsscala
|
2
|
+
from tree_sitter import Language
|
3
|
+
|
4
|
+
|
5
|
+
class ScalaLanguageHandler:
|
6
|
+
"""Handler for Scala-specific language processing."""
|
7
|
+
|
8
|
+
def get_language(self):
|
9
|
+
"""Returns the Tree-sitter Language object for Scala."""
|
10
|
+
return Language(tsscala.language())
|
11
|
+
|
12
|
+
def validate_root_node(self, root_node):
|
13
|
+
"""Validates the root node for Scala syntax trees."""
|
14
|
+
return root_node.type == "compilation_unit"
|
15
|
+
|
16
|
+
def process_node(
|
17
|
+
self, node, current_class, definitions, process_method, process_function, process_class, process_class_variable
|
18
|
+
):
|
19
|
+
"""Processes a node in a Scala syntax tree."""
|
20
|
+
if node.type in ("function_definition", "method_definition"):
|
21
|
+
if current_class:
|
22
|
+
process_method(node, definitions["classes"][current_class]["methods"])
|
23
|
+
else:
|
24
|
+
process_function(node, definitions["functions"])
|
25
|
+
return "function"
|
26
|
+
elif node.type == "class_definition":
|
27
|
+
class_name = process_class(node)
|
28
|
+
definitions["classes"][class_name] = {
|
29
|
+
"line": (node.start_point[0] + 1, node.end_point[0] + 1),
|
30
|
+
"methods": [],
|
31
|
+
"variables": [],
|
32
|
+
}
|
33
|
+
return "class"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import tree_sitter_typescript as tstypescript
|
2
|
+
from tree_sitter import Language
|
3
|
+
|
4
|
+
|
5
|
+
class TypeScriptLanguageHandler:
|
6
|
+
"""Handler for TypeScript-specific language processing."""
|
7
|
+
|
8
|
+
def get_language(self):
|
9
|
+
"""Returns the Tree-sitter Language object for TypeScript."""
|
10
|
+
return Language(tstypescript.language())
|
11
|
+
|
12
|
+
def validate_root_node(self, root_node):
|
13
|
+
"""Validates the root node for TypeScript syntax trees."""
|
14
|
+
return root_node.type == "program"
|
15
|
+
|
16
|
+
def process_node(
|
17
|
+
self, node, current_class, definitions, process_method, process_function, process_class, process_class_variable
|
18
|
+
):
|
19
|
+
"""Processes a node in a TypeScript syntax tree."""
|
20
|
+
if node.type in ("function_declaration", "method_definition"):
|
21
|
+
if current_class:
|
22
|
+
process_method(node, definitions["classes"][current_class]["methods"])
|
23
|
+
else:
|
24
|
+
process_function(node, definitions["functions"])
|
25
|
+
return "function"
|
26
|
+
elif node.type == "class_declaration":
|
27
|
+
class_name = process_class(node)
|
28
|
+
definitions["classes"][class_name] = {
|
29
|
+
"line": (node.start_point[0] + 1, node.end_point[0] + 1),
|
30
|
+
"methods": [],
|
31
|
+
"variables": [],
|
32
|
+
}
|
33
|
+
return "class"
|
34
|
+
elif node.type == "method_definition":
|
35
|
+
if current_class:
|
36
|
+
process_method(node, definitions["classes"][current_class]["methods"])
|
37
|
+
return "method"
|
38
|
+
elif node.type == "class_variable_definition":
|
39
|
+
if current_class:
|
40
|
+
process_class_variable(node, definitions["classes"][current_class]["variables"])
|
41
|
+
return "variable"
|
42
|
+
return None
|
@@ -0,0 +1,123 @@
|
|
1
|
+
"""Tool for listing the contents of a directory."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
|
5
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
6
|
+
from quantalogic.utils.git_ls import git_ls
|
7
|
+
|
8
|
+
|
9
|
+
class ListDirectoryTool(Tool):
|
10
|
+
"""Lists directory contents with pagination and .gitignore support."""
|
11
|
+
|
12
|
+
name: str = "list_directory_tool"
|
13
|
+
description: str = "Lists directory contents with pagination and .gitignore filtering"
|
14
|
+
arguments: list[ToolArgument] = [
|
15
|
+
ToolArgument(
|
16
|
+
name="directory_path",
|
17
|
+
arg_type="string",
|
18
|
+
description="Absolute or relative path to target directory",
|
19
|
+
required=True,
|
20
|
+
example="~/documents/projects",
|
21
|
+
),
|
22
|
+
ToolArgument(
|
23
|
+
name="recursive",
|
24
|
+
arg_type="string",
|
25
|
+
description="Enable recursive traversal (true/false)",
|
26
|
+
required=False,
|
27
|
+
default="false",
|
28
|
+
example="true",
|
29
|
+
),
|
30
|
+
ToolArgument(
|
31
|
+
name="max_depth",
|
32
|
+
arg_type="int",
|
33
|
+
description="Maximum directory traversal depth",
|
34
|
+
required=False,
|
35
|
+
default="1",
|
36
|
+
example="1",
|
37
|
+
),
|
38
|
+
ToolArgument(
|
39
|
+
name="start_line",
|
40
|
+
arg_type="int",
|
41
|
+
description="First line to return in paginated results",
|
42
|
+
required=False,
|
43
|
+
default="1",
|
44
|
+
example="1",
|
45
|
+
),
|
46
|
+
ToolArgument(
|
47
|
+
name="end_line",
|
48
|
+
arg_type="int",
|
49
|
+
description="Last line to return in paginated results",
|
50
|
+
required=False,
|
51
|
+
default="200",
|
52
|
+
example="200",
|
53
|
+
),
|
54
|
+
]
|
55
|
+
|
56
|
+
def execute(
|
57
|
+
self,
|
58
|
+
directory_path: str,
|
59
|
+
recursive: str = "false",
|
60
|
+
max_depth: str = "1",
|
61
|
+
start_line: str = "1",
|
62
|
+
end_line: str = "200",
|
63
|
+
) -> str:
|
64
|
+
"""
|
65
|
+
List directory contents with pagination and .gitignore support.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
directory_path: Absolute or relative path to target directory
|
69
|
+
recursive: Enable recursive traversal (true/false)
|
70
|
+
max_depth: Maximum directory traversal depth
|
71
|
+
start_line: First line to return in paginated results
|
72
|
+
end_line: Last line to return in paginated results
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
str: Paginated directory listing with metadata
|
76
|
+
|
77
|
+
Raises:
|
78
|
+
ValueError: For invalid directory paths or pagination parameters
|
79
|
+
"""
|
80
|
+
# Expand user home directory to full path
|
81
|
+
# This ensures compatibility with '~' shorthand for home directory
|
82
|
+
if directory_path.startswith("~"):
|
83
|
+
directory_path = os.path.expanduser(directory_path)
|
84
|
+
|
85
|
+
# Validate directory existence and type
|
86
|
+
# Fail early with clear error messages if path is invalid
|
87
|
+
if not os.path.exists(directory_path):
|
88
|
+
raise ValueError(f"The directory '{directory_path}' does not exist.")
|
89
|
+
if not os.path.isdir(directory_path):
|
90
|
+
raise ValueError(f"The path '{directory_path}' is not a directory.")
|
91
|
+
|
92
|
+
# Safely convert inputs with default values
|
93
|
+
start = int(start_line or "1")
|
94
|
+
end = int(end_line or "200")
|
95
|
+
max_depth_int = int(max_depth or "1")
|
96
|
+
is_recursive = (recursive or "false").lower() == "true"
|
97
|
+
|
98
|
+
# Validate pagination parameters
|
99
|
+
if start > end:
|
100
|
+
raise ValueError("start_line must be less than or equal to end_line.")
|
101
|
+
|
102
|
+
try:
|
103
|
+
# Use git_ls for directory listing with .gitignore support
|
104
|
+
all_lines = git_ls(
|
105
|
+
directory_path=directory_path,
|
106
|
+
recursive=is_recursive,
|
107
|
+
max_depth=max_depth_int,
|
108
|
+
start_line=start,
|
109
|
+
end_line=end,
|
110
|
+
)
|
111
|
+
return all_lines
|
112
|
+
except Exception as e:
|
113
|
+
import traceback
|
114
|
+
|
115
|
+
traceback.print_exc()
|
116
|
+
return f"Error: {str(e)} occurred during directory listing. See logs for details."
|
117
|
+
|
118
|
+
|
119
|
+
if __name__ == "__main__":
|
120
|
+
tool = ListDirectoryTool()
|
121
|
+
current_directory = os.getcwd()
|
122
|
+
tool.execute(directory_path=current_directory, recursive="true")
|
123
|
+
print(tool.to_markdown())
|
@@ -0,0 +1,119 @@
|
|
1
|
+
"""LLM Tool for generating answers to questions using a language model."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
|
5
|
+
from pydantic import ConfigDict, Field
|
6
|
+
|
7
|
+
from quantalogic.generative_model import GenerativeModel, Message
|
8
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
9
|
+
|
10
|
+
|
11
|
+
class LLMTool(Tool):
|
12
|
+
"""Tool to generate answers using a specified language model."""
|
13
|
+
|
14
|
+
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
|
15
|
+
|
16
|
+
name: str = Field(default="llm_tool")
|
17
|
+
description: str = Field(
|
18
|
+
default=(
|
19
|
+
"Generates answers to questions using a specified language model. "
|
20
|
+
"Note: This tool operates in isolation and does not have access to: "
|
21
|
+
" - Memory: All context must be explicitly provided in the prompt. "
|
22
|
+
" - File system."
|
23
|
+
" - Variables: Any required variables should be interpolated into the prompt (e.g., $var1$). "
|
24
|
+
" - Other tools: It cannot invoke or interact with other tools. "
|
25
|
+
"Ensure all necessary information is included directly in your prompt."
|
26
|
+
)
|
27
|
+
)
|
28
|
+
arguments: list = Field(
|
29
|
+
default=[
|
30
|
+
ToolArgument(
|
31
|
+
name="system_prompt",
|
32
|
+
arg_type="string",
|
33
|
+
description=("The persona or system prompt to guide the language model's behavior. "),
|
34
|
+
required=True,
|
35
|
+
example=("You are an expert in natural language processing and machine learning. "),
|
36
|
+
),
|
37
|
+
ToolArgument(
|
38
|
+
name="prompt",
|
39
|
+
arg_type="string",
|
40
|
+
description=("The question to ask the language model. Use interpolation if possible example $var1$."),
|
41
|
+
required=True,
|
42
|
+
example="What is the meaning of $var1$ ?",
|
43
|
+
),
|
44
|
+
ToolArgument(
|
45
|
+
name="temperature",
|
46
|
+
arg_type="string",
|
47
|
+
description='Sampling temperature between "0.0" and "1.0": "0.0" no creativity, "1.0" full creativity. (float)',
|
48
|
+
required=True,
|
49
|
+
default="0.5",
|
50
|
+
example="0.5",
|
51
|
+
),
|
52
|
+
]
|
53
|
+
)
|
54
|
+
|
55
|
+
model_name: str = Field(..., description="The name of the language model to use")
|
56
|
+
generative_model: GenerativeModel | None = Field(default=None)
|
57
|
+
|
58
|
+
def model_post_init(self, __context):
|
59
|
+
"""Initialize the generative model after model initialization."""
|
60
|
+
if self.generative_model is None:
|
61
|
+
self.generative_model = GenerativeModel(model=self.model_name)
|
62
|
+
logging.debug(f"Initialized LLMTool with model: {self.model_name}")
|
63
|
+
|
64
|
+
def execute(self, system_prompt: str, prompt: str, temperature: str = "0.7") -> str:
|
65
|
+
"""Execute the tool to generate an answer based on the provided question.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
system_prompt (str): The system prompt to guide the model.
|
69
|
+
prompt (str): The question to be answered.
|
70
|
+
temperature (str, optional): Sampling temperature. Defaults to "0.7".
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
str: The generated answer.
|
74
|
+
|
75
|
+
Raises:
|
76
|
+
ValueError: If temperature is not a valid float between 0 and 1.
|
77
|
+
Exception: If there's an error during response generation.
|
78
|
+
"""
|
79
|
+
try:
|
80
|
+
temp = float(temperature)
|
81
|
+
if not (0.0 <= temp <= 1.0):
|
82
|
+
raise ValueError("Temperature must be between 0 and 1.")
|
83
|
+
except ValueError as ve:
|
84
|
+
logging.error(f"Invalid temperature value: {temperature}")
|
85
|
+
raise ValueError(f"Invalid temperature value: {temperature}") from ve
|
86
|
+
|
87
|
+
# Prepare the messages history
|
88
|
+
messages_history = [
|
89
|
+
Message(role="system", content=system_prompt),
|
90
|
+
Message(role="user", content=prompt),
|
91
|
+
]
|
92
|
+
|
93
|
+
# Set the model's temperature
|
94
|
+
if self.generative_model:
|
95
|
+
self.generative_model.temperature = temp
|
96
|
+
|
97
|
+
# Generate the response using the generative model
|
98
|
+
try:
|
99
|
+
response_stats = self.generative_model.generate_with_history(
|
100
|
+
messages_history=messages_history, prompt=""
|
101
|
+
)
|
102
|
+
response = response_stats.response.strip()
|
103
|
+
logging.info(f"Generated response: {response}")
|
104
|
+
return response
|
105
|
+
except Exception as e:
|
106
|
+
logging.error(f"Error generating response: {e}")
|
107
|
+
raise Exception(f"Error generating response: {e}") from e
|
108
|
+
else:
|
109
|
+
raise ValueError("Generative model not initialized")
|
110
|
+
|
111
|
+
|
112
|
+
if __name__ == "__main__":
|
113
|
+
# Example usage of LLMTool
|
114
|
+
tool = LLMTool(model_name="gpt-4o-mini")
|
115
|
+
system_prompt = 'Answer the question as truthfully as possible using the provided context, and if the answer is not contained within the context, say "I don\'t know".'
|
116
|
+
question = "What is the meaning of life?"
|
117
|
+
temperature = "0.7"
|
118
|
+
answer = tool.execute(system_prompt=system_prompt, prompt=question, temperature=temperature)
|
119
|
+
print(answer)
|
@@ -0,0 +1,105 @@
|
|
1
|
+
"""Tool for converting various file formats to Markdown using the MarkItDown library."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
import tempfile
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
8
|
+
from quantalogic.utils.download_http_file import download_http_file
|
9
|
+
|
10
|
+
MAX_LINES = 2000 # Maximum number of lines to return when no output file is specified
|
11
|
+
|
12
|
+
|
13
|
+
class MarkitdownTool(Tool):
|
14
|
+
"""Tool for converting various file formats to Markdown using the MarkItDown library."""
|
15
|
+
|
16
|
+
name: str = "markitdown_tool"
|
17
|
+
description: str = (
|
18
|
+
"Converts various file formats to Markdown using the MarkItDown library. "
|
19
|
+
"Supports both local file paths and URLs (http://, https://). "
|
20
|
+
"Supported formats include: PDF, PowerPoint, Word, Excel, HTML"
|
21
|
+
"Don't use the output_file_path argument if you want to return the result in markdown directly"
|
22
|
+
)
|
23
|
+
arguments: list = [
|
24
|
+
ToolArgument(
|
25
|
+
name="file_path",
|
26
|
+
arg_type="string",
|
27
|
+
description="The path to the file to convert. Can be a local path or URL (http://, https://).",
|
28
|
+
required=True,
|
29
|
+
example="/path/to/file.txt or https://example.com/file.pdf",
|
30
|
+
),
|
31
|
+
ToolArgument(
|
32
|
+
name="output_file_path",
|
33
|
+
arg_type="string",
|
34
|
+
description="Path to write the Markdown output to. You can use a temp file.",
|
35
|
+
required=False,
|
36
|
+
example="/path/to/output.md",
|
37
|
+
),
|
38
|
+
]
|
39
|
+
|
40
|
+
def execute(self, file_path: str, output_file_path: Optional[str] = None) -> str:
|
41
|
+
"""Converts a file to Markdown and returns or writes the content.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
file_path (str): The path to the file to convert. Can be a local path or URL.
|
45
|
+
output_file_path (str, optional): Optional path to write the Markdown output to.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
str: The Markdown content or a success message.
|
49
|
+
"""
|
50
|
+
# Handle tilde expansion for local paths
|
51
|
+
if file_path.startswith("~"):
|
52
|
+
file_path = os.path.expanduser(file_path)
|
53
|
+
|
54
|
+
# Handle URL paths
|
55
|
+
if file_path.startswith(("http://", "https://")):
|
56
|
+
try:
|
57
|
+
# Create a temporary file
|
58
|
+
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
59
|
+
temp_path = temp_file.name
|
60
|
+
# Download the file from URL
|
61
|
+
download_http_file(file_path, temp_path)
|
62
|
+
# Use the temporary file path for conversion
|
63
|
+
file_path = temp_path
|
64
|
+
is_temp_file = True
|
65
|
+
except Exception as e:
|
66
|
+
return f"Error downloading file from URL: {str(e)}"
|
67
|
+
else:
|
68
|
+
is_temp_file = False
|
69
|
+
|
70
|
+
try:
|
71
|
+
from markitdown import MarkItDown
|
72
|
+
|
73
|
+
md = MarkItDown()
|
74
|
+
result = md.convert(file_path)
|
75
|
+
|
76
|
+
if output_file_path:
|
77
|
+
with open(output_file_path, "w", encoding="utf-8") as f:
|
78
|
+
f.write(result.text_content)
|
79
|
+
output_message = f"Markdown content successfully written to {output_file_path}"
|
80
|
+
else:
|
81
|
+
# Truncate content if it exceeds MAX_LINES
|
82
|
+
lines = result.text_content.splitlines()
|
83
|
+
if len(lines) > MAX_LINES:
|
84
|
+
truncated_content = "\n".join(lines[:MAX_LINES])
|
85
|
+
output_message = f"Markdown content truncated to {MAX_LINES} lines:\n{truncated_content}"
|
86
|
+
else:
|
87
|
+
output_message = result.text_content
|
88
|
+
|
89
|
+
return output_message
|
90
|
+
except Exception as e:
|
91
|
+
return f"Error converting file to Markdown: {str(e)}"
|
92
|
+
finally:
|
93
|
+
# Clean up temporary file if it was created
|
94
|
+
if is_temp_file and os.path.exists(file_path):
|
95
|
+
os.remove(file_path)
|
96
|
+
|
97
|
+
|
98
|
+
if __name__ == "__main__":
|
99
|
+
tool = MarkitdownTool()
|
100
|
+
print(tool.to_markdown())
|
101
|
+
|
102
|
+
# Example usage:
|
103
|
+
print(tool.execute(file_path="./examples/2412.18601v1.pdf"))
|
104
|
+
|
105
|
+
print(tool.execute(file_path="https://arxiv.org/pdf/2412.18601"))
|