nlsql-coder 0.0.1__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.
- nlsql_coder/__init__.py +0 -0
- nlsql_coder/app/__init__.py +0 -0
- nlsql_coder/app/agent/__init__.py +0 -0
- nlsql_coder/app/agent/helpers/__init__.py +0 -0
- nlsql_coder/app/agent/helpers/get_agent.py +23 -0
- nlsql_coder/app/agent/helpers/memory_manager.py +6 -0
- nlsql_coder/app/agent/helpers/project_details.py +32 -0
- nlsql_coder/app/agent/model/__init__.py +0 -0
- nlsql_coder/app/agent/model/agent.py +99 -0
- nlsql_coder/app/agent/tools/__init__.py +0 -0
- nlsql_coder/app/agent/tools/file_tools.py +147 -0
- nlsql_coder/app/agent/tools/tool_specs.py +161 -0
- nlsql_coder/app/cli/__init__.py +0 -0
- nlsql_coder/app/cli/arg_parsing.py +7 -0
- nlsql_coder/app/cli/interface.py +96 -0
- nlsql_coder/app/cli/tool_calling.py +27 -0
- nlsql_coder/app/providers/__init__.py +0 -0
- nlsql_coder/app/providers/aws/__init__.py +0 -0
- nlsql_coder/app/providers/aws/provider.py +139 -0
- nlsql_coder/app/providers/base.py +81 -0
- nlsql_coder/app/providers/config.py +22 -0
- nlsql_coder/app/providers/gcp/__init__.py +0 -0
- nlsql_coder/data/__init__.py +0 -0
- nlsql_coder/data/create_agent_md.py +76 -0
- nlsql_coder-0.0.1.dist-info/METADATA +55 -0
- nlsql_coder-0.0.1.dist-info/RECORD +30 -0
- nlsql_coder-0.0.1.dist-info/WHEEL +5 -0
- nlsql_coder-0.0.1.dist-info/entry_points.txt +2 -0
- nlsql_coder-0.0.1.dist-info/licenses/LICENSE +21 -0
- nlsql_coder-0.0.1.dist-info/top_level.txt +1 -0
nlsql_coder/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from nlsql_coder.app.providers.config import ProviderConfig
|
|
3
|
+
from nlsql_coder.app.agent.model.agent import Agent
|
|
4
|
+
from nlsql_coder.app.agent.tools.tool_specs import read_file_spec, write_file_spec, get_project_tree_spec, edit_file_spec, insert_relative_spec, insert_to_file_spec
|
|
5
|
+
from nlsql_coder.app.cli.arg_parsing import args
|
|
6
|
+
|
|
7
|
+
provider_config = ProviderConfig()
|
|
8
|
+
agent = None
|
|
9
|
+
system_prompt = ""
|
|
10
|
+
with open(Path(__file__).resolve().parent / "system_prompt.md", "r") as f:
|
|
11
|
+
system_prompt = f.read()
|
|
12
|
+
agents_md = Path(args.workspace, "AGENTS.md")
|
|
13
|
+
if agents_md.exists():
|
|
14
|
+
with open(agents_md, "r") as f:
|
|
15
|
+
content = f.read()
|
|
16
|
+
system_prompt += content
|
|
17
|
+
tools = [read_file_spec, write_file_spec, get_project_tree_spec, edit_file_spec, insert_to_file_spec, insert_relative_spec]
|
|
18
|
+
|
|
19
|
+
if provider_config.model_provider == "aws":
|
|
20
|
+
from nlsql_coder.app.providers.aws.provider import AWSBedrockProvider
|
|
21
|
+
model_id = provider_config.model_name
|
|
22
|
+
llm = AWSBedrockProvider(model_id)
|
|
23
|
+
agent = Agent(llm, system_prompt, tools)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Project:
|
|
6
|
+
"""
|
|
7
|
+
Project class to gather context about the current project.
|
|
8
|
+
"""
|
|
9
|
+
def __init__(self, root):
|
|
10
|
+
self.root = root
|
|
11
|
+
self.ignore_paths = [".venv", "venv", "__pycache__", ".git"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def generate_tree(self, path: Path = None, indent: str = "", lines=None):
|
|
15
|
+
if lines is None:
|
|
16
|
+
lines = []
|
|
17
|
+
path = Path(self.root)
|
|
18
|
+
lines.append(f"{path.name}/")
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
items = sorted([i for i in os.listdir(path) if i not in self.ignore_paths])
|
|
22
|
+
for item in items:
|
|
23
|
+
full_path = path / item
|
|
24
|
+
if full_path.is_dir():
|
|
25
|
+
lines.append(f"{indent} {item}/")
|
|
26
|
+
self.generate_tree(full_path, indent + " ", lines)
|
|
27
|
+
else:
|
|
28
|
+
lines.append(f"{indent} {item}")
|
|
29
|
+
except PermissionError:
|
|
30
|
+
lines.append(f"{indent} [Access Denied]")
|
|
31
|
+
return "\n".join(lines)
|
|
32
|
+
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Planner agent class.
|
|
3
|
+
Implements the functionallity for the planner agent.
|
|
4
|
+
"""
|
|
5
|
+
from nlsql_coder.app.providers.base import BaseProvider, Message
|
|
6
|
+
from typing import Type, Iterator
|
|
7
|
+
|
|
8
|
+
from nlsql_coder.app.agent.tools.tool_specs import ToolSpecification
|
|
9
|
+
|
|
10
|
+
class Agent:
|
|
11
|
+
"""
|
|
12
|
+
Main agent class, highest level class - first point of call for user messages.
|
|
13
|
+
Responsible for planning the steps involved in completing the give task and invoking other agents if needed.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, provider_class: Type[BaseProvider], system_prompt: str, tools: list[ToolSpecification] = None):
|
|
16
|
+
self.llm = provider_class
|
|
17
|
+
self.messages = []
|
|
18
|
+
|
|
19
|
+
self.kwargs = {
|
|
20
|
+
"system_prompt": system_prompt,
|
|
21
|
+
"tools": tools
|
|
22
|
+
}
|
|
23
|
+
if not tools:
|
|
24
|
+
self.kwargs.pop("tools")
|
|
25
|
+
|
|
26
|
+
def chat(self, message: str | list) -> Iterator[str | dict]:
|
|
27
|
+
if not message:
|
|
28
|
+
raise ValueError("Message is empty.")
|
|
29
|
+
|
|
30
|
+
# results = []
|
|
31
|
+
if isinstance(message, str):
|
|
32
|
+
user_message = Message(role="user", content=message)
|
|
33
|
+
self.messages.append(self.llm.form_message(user_message))
|
|
34
|
+
elif isinstance(message, list):
|
|
35
|
+
tool_result_messages = self.llm.form_message(message)
|
|
36
|
+
# content = tool_result_messages["content"]
|
|
37
|
+
# for item in content:
|
|
38
|
+
# results.append((item["toolResult"]["toolUseId"], item["toolResult"]["content"]))
|
|
39
|
+
self.messages.append(tool_result_messages)
|
|
40
|
+
|
|
41
|
+
response = self.llm.stream_chat(self.messages, **self.kwargs)
|
|
42
|
+
|
|
43
|
+
accumulated = ""
|
|
44
|
+
tools_used = []
|
|
45
|
+
usage_data = None
|
|
46
|
+
for chunk in response:
|
|
47
|
+
if isinstance(chunk, str):
|
|
48
|
+
if not "**Using tool:**" in chunk:
|
|
49
|
+
accumulated += chunk
|
|
50
|
+
yield chunk
|
|
51
|
+
elif isinstance(chunk, dict):
|
|
52
|
+
if chunk.get("input_tokens"):
|
|
53
|
+
usage_data = chunk
|
|
54
|
+
else:
|
|
55
|
+
for v in chunk.values():
|
|
56
|
+
tools_used.append(v)
|
|
57
|
+
|
|
58
|
+
if accumulated:
|
|
59
|
+
assistant_message = Message(role="assistant", content=accumulated.strip())
|
|
60
|
+
self.messages.append(self.llm.form_message(assistant_message))
|
|
61
|
+
if tools_used:
|
|
62
|
+
tool_message = {
|
|
63
|
+
"role": "assistant",
|
|
64
|
+
"content": [{"toolUse": tool} for tool in tools_used]
|
|
65
|
+
}
|
|
66
|
+
self.messages.append(tool_message)
|
|
67
|
+
yield tools_used
|
|
68
|
+
# else:
|
|
69
|
+
# assistant_message = Message(role="assistant", content=accumulated.strip())
|
|
70
|
+
# self.messages.append(self.llm.form_message(assistant_message))
|
|
71
|
+
|
|
72
|
+
# if results:
|
|
73
|
+
# kwargs = {
|
|
74
|
+
# "system_prompt": "You specialize in writing the shortest possible summaries of tool call results, respond ONLY with the summary and no other text."
|
|
75
|
+
# }
|
|
76
|
+
|
|
77
|
+
# for tool_id, result in results:
|
|
78
|
+
# msg = Message(role="user", content=f"Summarize the following tool result: {result}")
|
|
79
|
+
# msg = self.llm.form_message(msg)
|
|
80
|
+
|
|
81
|
+
# summary_stream = self.llm.stream_chat([msg], **kwargs)
|
|
82
|
+
|
|
83
|
+
# summary_text = ""
|
|
84
|
+
# for chunk in summary_stream:
|
|
85
|
+
# if isinstance(chunk, str):
|
|
86
|
+
# summary_text += chunk
|
|
87
|
+
|
|
88
|
+
# summary_text = summary_text.strip()
|
|
89
|
+
|
|
90
|
+
# # Replace the original tool result content with the summary
|
|
91
|
+
# for m in self.messages:
|
|
92
|
+
# if m["role"] == "user":
|
|
93
|
+
# for item in m.get("content", []):
|
|
94
|
+
# tool_result = item.get("toolResult")
|
|
95
|
+
# if tool_result and tool_result["toolUseId"] == tool_id:
|
|
96
|
+
# tool_result["content"] = [{"text": summary_text}]
|
|
97
|
+
|
|
98
|
+
if usage_data:
|
|
99
|
+
yield usage_data
|
|
File without changes
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from nlsql_coder.app.agent.helpers.project_details import Project
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from nlsql_coder.app.cli.arg_parsing import args
|
|
4
|
+
|
|
5
|
+
FILE_TOOL_REGISTRY = {}
|
|
6
|
+
|
|
7
|
+
project = Project(args.workspace)
|
|
8
|
+
|
|
9
|
+
def get_project_tree():
|
|
10
|
+
"""
|
|
11
|
+
Get's the current project's file tree
|
|
12
|
+
"""
|
|
13
|
+
return project.generate_tree()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def read_file(file_path: str, from_line: int = 1, to_line: int = 100):
|
|
17
|
+
"""
|
|
18
|
+
Reads a given file starting at from_line ending at to_line and returns its contents.
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
root_path = Path(project.root).resolve()
|
|
22
|
+
target_path = (root_path / file_path).resolve()
|
|
23
|
+
|
|
24
|
+
if not str(target_path).startswith(str(root_path)):
|
|
25
|
+
return "error: access outside workspace is not allowed."
|
|
26
|
+
if from_line > to_line:
|
|
27
|
+
return "error: from_line must be less than or equal to to_line."
|
|
28
|
+
|
|
29
|
+
contents = []
|
|
30
|
+
with open(target_path, "r", encoding="utf-8") as f:
|
|
31
|
+
for i, line in enumerate(f, start=1):
|
|
32
|
+
if i > to_line:
|
|
33
|
+
break
|
|
34
|
+
if i >= from_line:
|
|
35
|
+
contents.append(line)
|
|
36
|
+
return "".join(contents)
|
|
37
|
+
except FileNotFoundError:
|
|
38
|
+
return "error: the file does not exist."
|
|
39
|
+
except IsADirectoryError:
|
|
40
|
+
return f"error: {file_path} is a directory."
|
|
41
|
+
except PermissionError:
|
|
42
|
+
return "error: you do not have permission to read this file."
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def write_file(file_path: str, content: str):
|
|
46
|
+
"""
|
|
47
|
+
Writes the given content to a new file at the specified file path.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
if not file_path:
|
|
51
|
+
return "error: file_path not given."
|
|
52
|
+
if not content:
|
|
53
|
+
return "error: no content given."
|
|
54
|
+
root_path = Path(project.root).resolve()
|
|
55
|
+
target_path = (root_path / file_path).resolve()
|
|
56
|
+
if not str(target_path).startswith(str(root_path)):
|
|
57
|
+
return "error: access outside workspace is not allowed."
|
|
58
|
+
with open(target_path, "w") as f:
|
|
59
|
+
f.write(content)
|
|
60
|
+
return "success"
|
|
61
|
+
except PermissionError:
|
|
62
|
+
return "error: you do not have permission to write to this file."
|
|
63
|
+
except Exception as e:
|
|
64
|
+
return f"error: there was an error writing the file: {e}"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def edit_file(file_path: str, edits: list[dict[str, str]]):
|
|
68
|
+
"""Apply the given edits to a file."""
|
|
69
|
+
# TODO: This tool needs work, using replace will replace all instances (it's dangerous)
|
|
70
|
+
try:
|
|
71
|
+
root_path = Path(project.root).resolve()
|
|
72
|
+
target_path = (root_path / file_path).resolve()
|
|
73
|
+
contents = ""
|
|
74
|
+
with open(target_path, "r") as f:
|
|
75
|
+
contents = f.read()
|
|
76
|
+
for edit in edits:
|
|
77
|
+
if not edit["search"].endswith("\n"):
|
|
78
|
+
edit["search"] += "\n"
|
|
79
|
+
contents = contents.replace(edit["search"], edit["replace"])
|
|
80
|
+
with open(target_path, "w") as f:
|
|
81
|
+
f.write(contents)
|
|
82
|
+
return "success"
|
|
83
|
+
except FileNotFoundError:
|
|
84
|
+
return "error: the file does not exist."
|
|
85
|
+
except IsADirectoryError:
|
|
86
|
+
return f"error: {file_path} is a directory."
|
|
87
|
+
except PermissionError:
|
|
88
|
+
return "error: you do not have permission to edit this file."
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def insert_to_file(file_path: str, code: str, mode: str = "append"):
|
|
92
|
+
"""
|
|
93
|
+
mode:
|
|
94
|
+
- append
|
|
95
|
+
- prepend
|
|
96
|
+
"""
|
|
97
|
+
root_path = Path(project.root).resolve()
|
|
98
|
+
target_path = (root_path / file_path).resolve()
|
|
99
|
+
|
|
100
|
+
with open(target_path, "r") as f:
|
|
101
|
+
contents = f.read()
|
|
102
|
+
|
|
103
|
+
if mode == "append":
|
|
104
|
+
contents = contents.rstrip() + "\n\n" + code + "\n"
|
|
105
|
+
elif mode == "prepend":
|
|
106
|
+
contents = code.rstrip() + "\n\n" + contents
|
|
107
|
+
else:
|
|
108
|
+
raise ValueError("Invalid mode")
|
|
109
|
+
|
|
110
|
+
with open(target_path, "w") as f:
|
|
111
|
+
f.write(contents)
|
|
112
|
+
|
|
113
|
+
return "success"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def insert_relative(file_path: str, anchor: str, code: str, position: str = "after"):
|
|
117
|
+
root_path = Path(project.root).resolve()
|
|
118
|
+
target_path = (root_path / file_path).resolve()
|
|
119
|
+
|
|
120
|
+
with open(target_path, "r") as f:
|
|
121
|
+
lines = f.readlines()
|
|
122
|
+
|
|
123
|
+
for i, line in enumerate(lines):
|
|
124
|
+
if anchor in line:
|
|
125
|
+
if position == "after":
|
|
126
|
+
lines.insert(i + 1, code + "\n")
|
|
127
|
+
elif position == "before":
|
|
128
|
+
lines.insert(i, code + "\n")
|
|
129
|
+
else:
|
|
130
|
+
raise ValueError("position must be before or after")
|
|
131
|
+
break
|
|
132
|
+
else:
|
|
133
|
+
raise ValueError("Anchor not found")
|
|
134
|
+
|
|
135
|
+
with open(target_path, "w") as f:
|
|
136
|
+
f.writelines(lines)
|
|
137
|
+
|
|
138
|
+
return "success"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
FILE_TOOL_REGISTRY["read_file"] = read_file
|
|
143
|
+
FILE_TOOL_REGISTRY["write_file"] = write_file
|
|
144
|
+
FILE_TOOL_REGISTRY["get_project_tree"] = get_project_tree
|
|
145
|
+
FILE_TOOL_REGISTRY["edit_file"] = edit_file
|
|
146
|
+
FILE_TOOL_REGISTRY["insert_to_file"] = insert_to_file
|
|
147
|
+
FILE_TOOL_REGISTRY["insert_relative"] = insert_relative
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool specifications & provider convertion functions
|
|
3
|
+
Current tools:
|
|
4
|
+
file_tools.py
|
|
5
|
+
read_file(file_path: str, from_line: int = 0, to_line: int = 100)
|
|
6
|
+
"""
|
|
7
|
+
from nlsql_coder.app.providers.base import ToolSpecification, ToolSchema, ToolProperty
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
############### Tools Specifications ###################
|
|
11
|
+
|
|
12
|
+
read_file_spec = ToolSpecification(
|
|
13
|
+
name="read_file",
|
|
14
|
+
description="Reads a given file starting at from_line ending at to_line and returns its contents.",
|
|
15
|
+
schema=ToolSchema(
|
|
16
|
+
type="object",
|
|
17
|
+
properties={
|
|
18
|
+
"file_path": ToolProperty(
|
|
19
|
+
type="string",
|
|
20
|
+
description="Path to the file to read (relative to the workspace root)."
|
|
21
|
+
),
|
|
22
|
+
"from_line": ToolProperty(
|
|
23
|
+
type="integer",
|
|
24
|
+
description="Line number to start reading from (1-indexed). Defaults to 1.",
|
|
25
|
+
default=1
|
|
26
|
+
),
|
|
27
|
+
"to_line": ToolProperty(
|
|
28
|
+
type="integer",
|
|
29
|
+
description="Line number to stop reading at (inclusive). Defaults to 100.",
|
|
30
|
+
default=100
|
|
31
|
+
)
|
|
32
|
+
},
|
|
33
|
+
required=["file_path"]
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
write_file_spec = ToolSpecification(
|
|
38
|
+
name="write_file",
|
|
39
|
+
description="Writes the contents to a new file at the given location",
|
|
40
|
+
schema=ToolSchema(
|
|
41
|
+
type="object",
|
|
42
|
+
properties={
|
|
43
|
+
"file_path": ToolProperty(
|
|
44
|
+
type="string",
|
|
45
|
+
description="Path to the file to write (relative to the workspace root (must include file name))."
|
|
46
|
+
),
|
|
47
|
+
"content": ToolProperty(
|
|
48
|
+
type="string",
|
|
49
|
+
description="The content to write to the file.",
|
|
50
|
+
),
|
|
51
|
+
},
|
|
52
|
+
required=["file_path", "content"]
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
edit_file_spec = ToolSpecification(
|
|
57
|
+
name="edit_file",
|
|
58
|
+
description="Apply one or more edits to a file.",
|
|
59
|
+
schema=ToolSchema(
|
|
60
|
+
properties={
|
|
61
|
+
"file_path": ToolProperty(
|
|
62
|
+
type="string",
|
|
63
|
+
description="File path"
|
|
64
|
+
),
|
|
65
|
+
"edits": ToolProperty(
|
|
66
|
+
type="array",
|
|
67
|
+
description="List of edits",
|
|
68
|
+
items=ToolProperty(
|
|
69
|
+
type="object",
|
|
70
|
+
description="Single edit",
|
|
71
|
+
properties={
|
|
72
|
+
"search": ToolProperty(
|
|
73
|
+
type="string",
|
|
74
|
+
description="Text to find"
|
|
75
|
+
),
|
|
76
|
+
"replace": ToolProperty(
|
|
77
|
+
type="string",
|
|
78
|
+
description="Replacement text"
|
|
79
|
+
),
|
|
80
|
+
},
|
|
81
|
+
required=["search", "replace"]
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
},
|
|
85
|
+
required=["file_path", "edits"]
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
insert_to_file_spec = ToolSpecification(
|
|
90
|
+
name="insert_to_file",
|
|
91
|
+
description="Insert code into a file by appending or prepending it.",
|
|
92
|
+
schema=ToolSchema(
|
|
93
|
+
properties={
|
|
94
|
+
"file_path": ToolProperty(
|
|
95
|
+
type="string",
|
|
96
|
+
description="Path to the file"
|
|
97
|
+
),
|
|
98
|
+
"code": ToolProperty(
|
|
99
|
+
type="string",
|
|
100
|
+
description="Code to insert into the file"
|
|
101
|
+
),
|
|
102
|
+
"mode": ToolProperty(
|
|
103
|
+
type="string",
|
|
104
|
+
description="Where to insert the code: append or prepend",
|
|
105
|
+
)
|
|
106
|
+
},
|
|
107
|
+
required=["file_path", "code", "mode"]
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
insert_relative_spec = ToolSpecification(
|
|
112
|
+
name="insert_relative",
|
|
113
|
+
description="Insert code before or after a matching anchor line in a file.",
|
|
114
|
+
schema=ToolSchema(
|
|
115
|
+
properties={
|
|
116
|
+
"file_path": ToolProperty(
|
|
117
|
+
type="string",
|
|
118
|
+
description="Path to the file"
|
|
119
|
+
),
|
|
120
|
+
"anchor": ToolProperty(
|
|
121
|
+
type="string",
|
|
122
|
+
description="Text to search for in a line (anchor point)"
|
|
123
|
+
),
|
|
124
|
+
"code": ToolProperty(
|
|
125
|
+
type="string",
|
|
126
|
+
description="Code to insert"
|
|
127
|
+
),
|
|
128
|
+
"position": ToolProperty(
|
|
129
|
+
type="string",
|
|
130
|
+
description="Where to insert relative to anchor: before or after",
|
|
131
|
+
)
|
|
132
|
+
},
|
|
133
|
+
required=["file_path", "anchor", "code", "position"]
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
get_project_tree_spec = ToolSpecification(
|
|
138
|
+
name="get_project_tree",
|
|
139
|
+
description="Gets the current project's file tree.",
|
|
140
|
+
schema=ToolSchema(
|
|
141
|
+
type="object",
|
|
142
|
+
properties={},
|
|
143
|
+
required=[]
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
#################################################
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
########### Conversion functions ##################
|
|
151
|
+
|
|
152
|
+
def tool_aws_bedrock(tool: ToolSpecification):
|
|
153
|
+
return {
|
|
154
|
+
"toolSpec": {
|
|
155
|
+
"name": tool.name,
|
|
156
|
+
"description": tool.description,
|
|
157
|
+
"inputSchema": {
|
|
158
|
+
"json": tool.schema.serialize()
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
arg_parser = argparse.ArgumentParser()
|
|
4
|
+
arg_parser.add_argument("-w", "--workspace", default=".", help="Path to the targe project's workspace.")
|
|
5
|
+
arg_parser.add_argument("-u", "--usage", action="store_true", help="Displayed usage data after each conversation turn.")
|
|
6
|
+
|
|
7
|
+
args = arg_parser.parse_args()
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from nlsql_coder.app.agent.helpers.get_agent import agent
|
|
2
|
+
from nlsql_coder.app.cli.tool_calling import call_tools
|
|
3
|
+
from nlsql_coder.app.cli.arg_parsing import args
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.markdown import Markdown
|
|
7
|
+
from rich.live import Live
|
|
8
|
+
from rich.align import Align
|
|
9
|
+
from nlsql_coder.data.create_agent_md import create_md
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
welcome_message = """[blue bold]\n
|
|
16
|
+
Welcome _ _ _ ____ ___ _
|
|
17
|
+
| \ | || | / ___| / _ \| |
|
|
18
|
+
to | \| || | \___ \| | | | |
|
|
19
|
+
| |\ || |___ ___) | |_| | |___
|
|
20
|
+
|_| \_||_____||____/ \__\_\_____| Code\n\n[/blue bold]
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main_loop():
|
|
26
|
+
try:
|
|
27
|
+
usage_data = {}
|
|
28
|
+
console.clear()
|
|
29
|
+
with console.status("[bold]Creating AGENTS.md file...\n[/bold]", spinner="dots"):
|
|
30
|
+
success = create_md()
|
|
31
|
+
if success == "created":
|
|
32
|
+
console.print("[bold]AGENTS.md file created at project root.\n[/bold]")
|
|
33
|
+
elif success == "exists":
|
|
34
|
+
console.print("[bold]AGENTS.md file already exists at project root.\n[/bold]")
|
|
35
|
+
elif success == "empty":
|
|
36
|
+
console.print("[bold]Starting from scratch!\n[/bold]")
|
|
37
|
+
else:
|
|
38
|
+
console.print("[bold]Failed to create AGENTS.md file.\n[/bold]")
|
|
39
|
+
time.sleep(2)
|
|
40
|
+
console.clear()
|
|
41
|
+
console.print(Align.center(welcome_message))
|
|
42
|
+
|
|
43
|
+
while True:
|
|
44
|
+
|
|
45
|
+
if args.usage and usage_data:
|
|
46
|
+
table = (
|
|
47
|
+
"| Input Tokens | Output Tokens | Total Tokens |\n"
|
|
48
|
+
"|--------------|---------------|---------------|\n"
|
|
49
|
+
f"| {usage_data.get('input_tokens')} | {usage_data.get('output_tokens')} | {usage_data.get('total_tokens')} |\n"
|
|
50
|
+
)
|
|
51
|
+
console.print("[bold]\n\nUsage Data[/bold]")
|
|
52
|
+
console.print(Markdown(table))
|
|
53
|
+
|
|
54
|
+
messages = console.input("\n[bold green][You]:[/bold green] ")
|
|
55
|
+
|
|
56
|
+
if messages.lower() in ["quit", "exit"]:
|
|
57
|
+
console.print("\n\n[bold purple]Goodbye[/bold purple]\n")
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
console.print("\n[bold cyan][Assistant]:[/bold cyan]")
|
|
61
|
+
|
|
62
|
+
while True:
|
|
63
|
+
usage_data = {}
|
|
64
|
+
|
|
65
|
+
found_tool_calls = False
|
|
66
|
+
buffer = ""
|
|
67
|
+
with Live(console=console, refresh_per_second=1) as live:
|
|
68
|
+
for chunk in agent.chat(messages):
|
|
69
|
+
if isinstance(chunk, str):
|
|
70
|
+
# Chat message chunk
|
|
71
|
+
buffer += chunk
|
|
72
|
+
live.update(Markdown(buffer))
|
|
73
|
+
elif isinstance(chunk, list):
|
|
74
|
+
# Tool execution
|
|
75
|
+
found_tool_calls = True
|
|
76
|
+
tool_results = call_tools(chunk)
|
|
77
|
+
messages = tool_results
|
|
78
|
+
elif isinstance(chunk, dict):
|
|
79
|
+
# Usage data
|
|
80
|
+
if args.usage:
|
|
81
|
+
if not usage_data:
|
|
82
|
+
usage_data["input_tokens"] = chunk["input_tokens"]
|
|
83
|
+
usage_data["output_tokens"] = chunk["output_tokens"]
|
|
84
|
+
usage_data["total_tokens"] = chunk["total_tokens"]
|
|
85
|
+
else:
|
|
86
|
+
usage_data["input_tokens"] += chunk["input_tokens"]
|
|
87
|
+
usage_data["output_tokens"] += chunk["output_tokens"]
|
|
88
|
+
usage_data["total_tokens"] += chunk["total_tokens"]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if not found_tool_calls:
|
|
92
|
+
break
|
|
93
|
+
except KeyboardInterrupt:
|
|
94
|
+
console.print("\n\n[bold purple]Goodbye[/bold purple]\n")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
console.print(f"\n\n[bold red]Oh no! Something went wrong. Here's the error:\n\n{e}[/bold red]\n")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from nlsql_coder.app.agent.tools.file_tools import FILE_TOOL_REGISTRY
|
|
2
|
+
|
|
3
|
+
def call_tools(tool_calls: list[dict]) -> list[dict]:
|
|
4
|
+
tool_results = []
|
|
5
|
+
for tool in tool_calls:
|
|
6
|
+
tool_id = tool["toolUseId"]
|
|
7
|
+
tool_name = tool["name"]
|
|
8
|
+
tool_input = tool["input"]
|
|
9
|
+
|
|
10
|
+
tool_function = FILE_TOOL_REGISTRY.get(tool_name)
|
|
11
|
+
|
|
12
|
+
if tool_function:
|
|
13
|
+
tool_results.append({
|
|
14
|
+
"toolUseId": tool_id,
|
|
15
|
+
"name": tool_name,
|
|
16
|
+
"result": tool_function(**tool_input),
|
|
17
|
+
"status": "success"
|
|
18
|
+
})
|
|
19
|
+
else:
|
|
20
|
+
tool_results.append({
|
|
21
|
+
"toolUseId": tool_id,
|
|
22
|
+
"name": tool_name,
|
|
23
|
+
"result": "error: Unknown tool.",
|
|
24
|
+
"status": "error"
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
return tool_results
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from nlsql_coder.app.providers.base import BaseProvider, Message
|
|
2
|
+
from typing import Iterator
|
|
3
|
+
from nlsql_coder.app.providers.config import AWSConfig
|
|
4
|
+
from nlsql_coder.app.agent.tools.tool_specs import ToolSpecification, tool_aws_bedrock
|
|
5
|
+
import boto3
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AWSBedrockProvider(BaseProvider):
|
|
10
|
+
|
|
11
|
+
def __init__(self, model_id: str):
|
|
12
|
+
config = AWSConfig()
|
|
13
|
+
|
|
14
|
+
self.model_id = model_id
|
|
15
|
+
self.client = boto3.client(
|
|
16
|
+
"bedrock-runtime",
|
|
17
|
+
region_name=config.region,
|
|
18
|
+
aws_access_key_id=config.access_key_id,
|
|
19
|
+
aws_secret_access_key=config.secret_access_key,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def form_message(self, message: Message | list[dict]) -> dict:
|
|
23
|
+
if isinstance(message, Message):
|
|
24
|
+
if isinstance(message.content, str): # Standard chat message
|
|
25
|
+
return {"role": message.role, "content": [{"text": message.content}]}
|
|
26
|
+
if isinstance(message.content, dict):
|
|
27
|
+
if message.content["type"] == "tool_use":
|
|
28
|
+
return {"role": message.role, "content": [{"toolUse": message.content}]} # Tool call message
|
|
29
|
+
else:
|
|
30
|
+
results = {
|
|
31
|
+
"role": "user",
|
|
32
|
+
"content": [
|
|
33
|
+
{
|
|
34
|
+
"toolResult": {
|
|
35
|
+
"toolUseId": m["toolUseId"],
|
|
36
|
+
"content": [{"text": m["result"]}],
|
|
37
|
+
"status": m["status"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for m in message
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
return results
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def stream_chat(self, messages: list[Message],
|
|
47
|
+
max_tokens: int = 8192,
|
|
48
|
+
temperature: float = 0.7,
|
|
49
|
+
system_prompt: str = "",
|
|
50
|
+
tools: list[ToolSpecification] | None = None) -> Iterator[str]:
|
|
51
|
+
|
|
52
|
+
system_message = [{"text": system_prompt}]
|
|
53
|
+
inference_config = {"maxTokens": max_tokens}
|
|
54
|
+
|
|
55
|
+
if not "opus-4-7" in self.model_id:
|
|
56
|
+
inference_config["temperature"] = temperature
|
|
57
|
+
|
|
58
|
+
kwargs = {
|
|
59
|
+
"modelId": self.model_id,
|
|
60
|
+
"messages": messages,
|
|
61
|
+
"system": system_message,
|
|
62
|
+
"inferenceConfig": inference_config,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if tools:
|
|
66
|
+
tools = [tool_aws_bedrock(tool) for tool in tools]
|
|
67
|
+
kwargs["toolConfig"] = {"tools": tools}
|
|
68
|
+
|
|
69
|
+
response = self.client.converse_stream(**kwargs)
|
|
70
|
+
|
|
71
|
+
tool_calls = {}
|
|
72
|
+
usage_data = {}
|
|
73
|
+
|
|
74
|
+
for event in response["stream"]:
|
|
75
|
+
if "contentBlockStart" in event:
|
|
76
|
+
idx = event["contentBlockStart"]["contentBlockIndex"]
|
|
77
|
+
start_event = event["contentBlockStart"]["start"]
|
|
78
|
+
if "toolUse" in start_event:
|
|
79
|
+
tool_use = start_event['toolUse']
|
|
80
|
+
tool_calls[idx] = {
|
|
81
|
+
"toolUseId": tool_use["toolUseId"] ,
|
|
82
|
+
"name": tool_use["name"],
|
|
83
|
+
"input": "",
|
|
84
|
+
"type": tool_use["type"]
|
|
85
|
+
}
|
|
86
|
+
yield f"\n\n**Using tool:** {tool_use['name']}\n" # User-facing tool call message
|
|
87
|
+
|
|
88
|
+
if "contentBlockDelta" in event:
|
|
89
|
+
delta_event = event["contentBlockDelta"]
|
|
90
|
+
idx = delta_event["contentBlockIndex"]
|
|
91
|
+
if "delta" in delta_event:
|
|
92
|
+
delta = delta_event["delta"]
|
|
93
|
+
if "text" in delta:
|
|
94
|
+
yield delta["text"] # Message chunk output
|
|
95
|
+
if "toolUse" in delta:
|
|
96
|
+
tool_input_chunk = delta["toolUse"]["input"] # Tool input chunks
|
|
97
|
+
if idx in tool_calls:
|
|
98
|
+
tool_calls[idx]["input"] += tool_input_chunk
|
|
99
|
+
|
|
100
|
+
if "contentBlockStop" in event:
|
|
101
|
+
idx = event["contentBlockStop"]["contentBlockIndex"]
|
|
102
|
+
if tool_calls:
|
|
103
|
+
# Turn tool input json string to dict
|
|
104
|
+
for v in tool_calls.values():
|
|
105
|
+
if isinstance(v.get("input"), str):
|
|
106
|
+
raw_val = v["input"].strip()
|
|
107
|
+
if raw_val:
|
|
108
|
+
try:
|
|
109
|
+
v["input"] = json.loads(raw_val)
|
|
110
|
+
if v.get("name") == "read_file":
|
|
111
|
+
yield f"**Reading {v['input'].get('file_path')}**\n\n"
|
|
112
|
+
if v.get("name") in ["edit_file", "insert_to_file", "insert_relative"]:
|
|
113
|
+
yield f"**Editing {v['input'].get('file_path')}**\n\n"
|
|
114
|
+
if v.get("name") == "write_file":
|
|
115
|
+
yield f"**Writing {v['input'].get('file_path')}**\n\n"
|
|
116
|
+
except json.JSONDecodeError:
|
|
117
|
+
v["input"] = {}
|
|
118
|
+
else:
|
|
119
|
+
v["input"] = {}
|
|
120
|
+
|
|
121
|
+
if "metadata" in event:
|
|
122
|
+
metadata = event["metadata"]
|
|
123
|
+
if "usage" in metadata:
|
|
124
|
+
usage = metadata["usage"]
|
|
125
|
+
usage_data = {
|
|
126
|
+
"input_tokens": usage["inputTokens"],
|
|
127
|
+
"output_tokens": usage["outputTokens"],
|
|
128
|
+
"total_tokens": usage["totalTokens"]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# If tools were called yield the calls and execute them in the agent class
|
|
132
|
+
if tool_calls:
|
|
133
|
+
yield tool_calls
|
|
134
|
+
if usage_data:
|
|
135
|
+
yield usage_data
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base provider class.
|
|
3
|
+
Normalizes LLM interaction and responses amoung the different LLM providers.
|
|
4
|
+
"""
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Iterator, Literal, Dict, Optional, Any, List
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Message:
|
|
11
|
+
role: Literal["user", "assistant", "system"]
|
|
12
|
+
content: str | dict
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ToolProperty:
|
|
17
|
+
type: str
|
|
18
|
+
description: str
|
|
19
|
+
default: Optional[Any] = None
|
|
20
|
+
items: Optional["ToolProperty"] = None
|
|
21
|
+
properties: Optional[Dict[str, "ToolProperty"]] = None
|
|
22
|
+
required: Optional[List[str]] = None
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ToolSchema:
|
|
26
|
+
type: str = "object"
|
|
27
|
+
properties: Dict[str, ToolProperty] = field(default_factory=dict)
|
|
28
|
+
required: List[str] = field(default_factory=list)
|
|
29
|
+
|
|
30
|
+
def serialize(self):
|
|
31
|
+
return serialize_schema(self)
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ToolSpecification:
|
|
35
|
+
name: str
|
|
36
|
+
description: str
|
|
37
|
+
schema: ToolSchema
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def serialize_property(prop: ToolProperty):
|
|
41
|
+
result = {
|
|
42
|
+
"type": prop.type,
|
|
43
|
+
"description": prop.description
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if prop.default is not None:
|
|
47
|
+
result["default"] = prop.default
|
|
48
|
+
|
|
49
|
+
if prop.type == "array" and prop.items:
|
|
50
|
+
result["items"] = serialize_property(prop.items)
|
|
51
|
+
|
|
52
|
+
if prop.type == "object" and prop.properties:
|
|
53
|
+
result["properties"] = {
|
|
54
|
+
k: serialize_property(v) for k, v in prop.properties.items()
|
|
55
|
+
}
|
|
56
|
+
result["required"] = prop.required or []
|
|
57
|
+
result["additionalProperties"] = False
|
|
58
|
+
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
def serialize_schema(schema: ToolSchema):
|
|
62
|
+
result = {
|
|
63
|
+
"type": schema.type,
|
|
64
|
+
"properties": {
|
|
65
|
+
k: serialize_property(v)
|
|
66
|
+
for k, v in schema.properties.items()
|
|
67
|
+
},
|
|
68
|
+
"required": schema.required or [],
|
|
69
|
+
"additionalProperties": False
|
|
70
|
+
}
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class BaseProvider(ABC):
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def stream_chat(self, messages: list[Message], **kwargs) -> Iterator[str]:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def form_message(self, message: Message):
|
|
81
|
+
pass
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider configuration.
|
|
3
|
+
"""
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
load_dotenv()
|
|
9
|
+
|
|
10
|
+
# Provider & Model config
|
|
11
|
+
@dataclass
|
|
12
|
+
class ProviderConfig:
|
|
13
|
+
model_provider: str = os.getenv("MODEL_PROVIDER", "")
|
|
14
|
+
model_name: str = os.getenv("MODEL_NAME", "")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# AWS Config
|
|
18
|
+
@dataclass
|
|
19
|
+
class AWSConfig:
|
|
20
|
+
access_key_id: str = os.getenv("AWS_ACCESS_KEY_ID", "")
|
|
21
|
+
secret_access_key: str = os.getenv("AWS_SECRET_ACCESS_KEY", "")
|
|
22
|
+
region: str = os.getenv("AWS_DEFAULT_REGION", "")
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from nlsql_coder.app.agent.helpers.project_details import Project
|
|
2
|
+
from nlsql_coder.app.cli.arg_parsing import args
|
|
3
|
+
from nlsql_coder.app.agent.helpers.get_agent import llm
|
|
4
|
+
from nlsql_coder.app.agent.model.agent import Agent
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Get relevant project files for context
|
|
8
|
+
def create_md():
|
|
9
|
+
try:
|
|
10
|
+
root = Path(args.workspace)
|
|
11
|
+
if Path(root, "AGENTS.md").exists():
|
|
12
|
+
return "exists"
|
|
13
|
+
patterns = ["README*", "readme*", "requirements*", "package.json", "*.md", "*.txt", "main.*", "run.*", "Dockerfile", "*compose*.yml", "*compose*.yaml", "index.html"]
|
|
14
|
+
exclude_dirs = {".venv", "node_modules", ".git", "__pycache__"}
|
|
15
|
+
|
|
16
|
+
files = []
|
|
17
|
+
for pattern in patterns:
|
|
18
|
+
for f in root.rglob(pattern):
|
|
19
|
+
if not any(part in exclude_dirs for part in f.parts):
|
|
20
|
+
files.append(f)
|
|
21
|
+
if not files:
|
|
22
|
+
return "empty"
|
|
23
|
+
|
|
24
|
+
project = Project(args.workspace)
|
|
25
|
+
|
|
26
|
+
project_tree = project.generate_tree()
|
|
27
|
+
|
|
28
|
+
overview_creator_prompt = """
|
|
29
|
+
# Role
|
|
30
|
+
You are a coding project summerizer
|
|
31
|
+
|
|
32
|
+
# Task
|
|
33
|
+
Summarize the project based on the given context and extract any important information for the development agent's context.
|
|
34
|
+
|
|
35
|
+
Your summary should include:
|
|
36
|
+
- Brief overview of the project (i.e. name, what it does, who it's for.)
|
|
37
|
+
- Commands/steps to buidl and run the application (if applicable)
|
|
38
|
+
- Project archetecture (i.e. main components, frontend, backend, languages used)
|
|
39
|
+
- Any other relevant observations or notes
|
|
40
|
+
|
|
41
|
+
# Response rules
|
|
42
|
+
- Respond ONLY with the project summary
|
|
43
|
+
- Respond in Markdown format
|
|
44
|
+
- Keep the summary consice and on-topic
|
|
45
|
+
"""
|
|
46
|
+
overview_creator = Agent(llm, overview_creator_prompt)
|
|
47
|
+
|
|
48
|
+
file_contents = ""
|
|
49
|
+
for file in files:
|
|
50
|
+
with open(Path(root, file), "r") as f:
|
|
51
|
+
file_contents += f"{str(file)}:\n{f.read()}"
|
|
52
|
+
|
|
53
|
+
context_message = f"""
|
|
54
|
+
CONTEXT:
|
|
55
|
+
{file_contents}
|
|
56
|
+
"""
|
|
57
|
+
accumulated = ""
|
|
58
|
+
response = overview_creator.chat(context_message)
|
|
59
|
+
for chunk in response:
|
|
60
|
+
if isinstance(chunk, str):
|
|
61
|
+
accumulated += chunk
|
|
62
|
+
|
|
63
|
+
md = f"""
|
|
64
|
+
# Project Tree
|
|
65
|
+
```
|
|
66
|
+
{project_tree}
|
|
67
|
+
```
|
|
68
|
+
{accumulated}
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
with open(Path(root, "AGENTS.md"), "w") as f:
|
|
72
|
+
f.write(md)
|
|
73
|
+
|
|
74
|
+
return "created"
|
|
75
|
+
except Exception as e:
|
|
76
|
+
return False
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nlsql-coder
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: NLSQL's internal AI coding agent CLI tool for interacting with codebases
|
|
5
|
+
Author: NLSQL
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: ai,agent,cli,coding,llm
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: boto3<2.0,>=1.34
|
|
16
|
+
Requires-Dist: python-dotenv>=1.0
|
|
17
|
+
Requires-Dist: rich>=13.0.0
|
|
18
|
+
Requires-Dist: faiss-cpu>=1.7.0
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# NLSQL Internal Coding Agent
|
|
22
|
+
|
|
23
|
+
NLSQL's internal coding agent is a private, tool-driven, workspace-aware AI system designed to help with both internal and client-based development tasks in the NLSQL ecosystem.
|
|
24
|
+
|
|
25
|
+
# Features
|
|
26
|
+
|
|
27
|
+
- User-friendly command line interface.
|
|
28
|
+
- Wide array of LLM/provider support acorss many cloud ecosystems.
|
|
29
|
+
- Primary privacy & security focus.
|
|
30
|
+
- Parallel tool calling ability for speed and efficency.
|
|
31
|
+
|
|
32
|
+
# How it Works
|
|
33
|
+
|
|
34
|
+
The agent operates using a ReAct-style loop. A user submits a prompt, and the agent processes it by reasoning about the task, performing actions using available tools, and evaluating the results.
|
|
35
|
+
|
|
36
|
+
If the outcome is insufficient or incomplete, the agent continues this cycle, refining its approach and taking further actions until it can produce a final response.
|
|
37
|
+
|
|
38
|
+
# Usage
|
|
39
|
+
|
|
40
|
+
To use the application, follow the steps below:
|
|
41
|
+
|
|
42
|
+
1. Clone this repo to your local system: `git clone <repo-link-here>`
|
|
43
|
+
2. Make sure you are at `cd internal-coding-agent`
|
|
44
|
+
3. Create a virtual environment: `python3 -m venv .venv`
|
|
45
|
+
4. Activate the venv: `source .venv/bin/activate`
|
|
46
|
+
6. Install requirements: (if venv is activated correctly you should see "(venv)" in your terminal) `pip install -r requirements.txt`
|
|
47
|
+
7. Add .env file into `cd internal-coding-agent` root folder
|
|
48
|
+
8. Run the application: `python3 run.py -u --workspace path/to/your/project`
|
|
49
|
+
|
|
50
|
+
## Arguments
|
|
51
|
+
|
|
52
|
+
| Short | Long | Description | Defaults to | Required |
|
|
53
|
+
|-|-|-|-|-|
|
|
54
|
+
| `-w` | `--workspace` | The root directory of the project you want to work on. | `.` | ✔ |
|
|
55
|
+
| `-u` | `--usage` | Displays usage data after each conversation turn (input & output tokens) | `False` | ✗ |
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
nlsql_coder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
nlsql_coder/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
nlsql_coder/app/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
nlsql_coder/app/agent/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
nlsql_coder/app/agent/helpers/get_agent.py,sha256=z10raBVCj_lyvqJdxQoR1GzCXn28eLzKYvqylGNJf3k,1052
|
|
6
|
+
nlsql_coder/app/agent/helpers/memory_manager.py,sha256=0glsSiXsmQeICKCMADUDG4oPjd5WuB5n2J0JKZEBOPE,96
|
|
7
|
+
nlsql_coder/app/agent/helpers/project_details.py,sha256=82lCMZSINTkGKcWn2XVeIDK61m7ulba5ZkdCzr1xABE,1004
|
|
8
|
+
nlsql_coder/app/agent/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
nlsql_coder/app/agent/model/agent.py,sha256=0FLqfSC_WIN798X31tyVryTYRdS_sAWGEzVFdeD8O1s,3989
|
|
10
|
+
nlsql_coder/app/agent/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
nlsql_coder/app/agent/tools/file_tools.py,sha256=1MFW1rjJaaKBReyvCQUFzsf3hMia6uFx8krL-enY2is,4832
|
|
12
|
+
nlsql_coder/app/agent/tools/tool_specs.py,sha256=Fqj-mn8D-gsopuXONO3Q8FsHqSnKX8lyMSMlIYZrkVQ,4977
|
|
13
|
+
nlsql_coder/app/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
nlsql_coder/app/cli/arg_parsing.py,sha256=NLI7zRjLeMi5gQ7nCwj98xYRbksnKk9pf2U3ItCAM6U,313
|
|
15
|
+
nlsql_coder/app/cli/interface.py,sha256=L9DW60tev4I2Ubz7op77-vigqMuEwUgK26QbCSAj0dU,3968
|
|
16
|
+
nlsql_coder/app/cli/tool_calling.py,sha256=DhnKnTiGHesh1Wr8nVHPduCpjeSTSM6ZyATbNbepoTk,835
|
|
17
|
+
nlsql_coder/app/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
nlsql_coder/app/providers/base.py,sha256=bYcoU5KLfZ0GYECwlmQfTFMXv6G8MinP1wV_71R82Wc,2056
|
|
19
|
+
nlsql_coder/app/providers/config.py,sha256=y8VavW7eVhmaGxg2dVFBYBi_HR7dEOm9BapDaJBU8nE,515
|
|
20
|
+
nlsql_coder/app/providers/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
nlsql_coder/app/providers/aws/provider.py,sha256=M0JigWjybTcVOjth44d_V2hk74bCm2xaafvOn4-gsBc,5712
|
|
22
|
+
nlsql_coder/app/providers/gcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
nlsql_coder/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
nlsql_coder/data/create_agent_md.py,sha256=f-bP5DGAILP7jlf5dmvZHhjewZFl3TZxKlwHoNVxBL8,2550
|
|
25
|
+
nlsql_coder-0.0.1.dist-info/licenses/LICENSE,sha256=GNO7NFi7navN9edNrPJWozm4pkhjOKsJqXuoZdMhSSg,1072
|
|
26
|
+
nlsql_coder-0.0.1.dist-info/METADATA,sha256=0y-QUYp_VtYB1Qb6QECChZOUISHLJUSrNuUPeoa9GT4,2263
|
|
27
|
+
nlsql_coder-0.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
28
|
+
nlsql_coder-0.0.1.dist-info/entry_points.txt,sha256=BK04QmRGok7YDWL-dBySr-gghDPK-iQcJ3S7PoifNwM,72
|
|
29
|
+
nlsql_coder-0.0.1.dist-info/top_level.txt,sha256=J16-Bz42sGpUXjB1uWqE66v4JPIiymCnGc7LhJkt_60,12
|
|
30
|
+
nlsql_coder-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Microsoft Corporation.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nlsql_coder
|