ziya 0.1.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.

Potentially problematic release.


This version of ziya might be problematic. Click here for more details.

app/__init__.py ADDED
File without changes
app/agents/__init__.py ADDED
File without changes
app/agents/agent.py ADDED
@@ -0,0 +1,114 @@
1
+ import os
2
+ from typing import Generator, List, Tuple, Set, Union
3
+
4
+ import tiktoken
5
+ from langchain.agents import AgentExecutor
6
+ from langchain.agents.format_scratchpad import format_xml
7
+ from langchain_aws import ChatBedrock
8
+ from langchain_community.document_loaders import TextLoader
9
+ from langchain_core.messages import AIMessage, HumanMessage
10
+ from langchain_core.pydantic_v1 import BaseModel, Field
11
+
12
+ from app.agents.prompts import conversational_prompt, parse_output
13
+ from app.utils.directory_util import get_ignored_patterns, get_complete_file_list
14
+ from app.utils.logging_utils import logger
15
+ from app.utils.print_tree_util import print_file_tree
16
+
17
+
18
+ def _format_chat_history(chat_history: List[Tuple[str, str]]) -> List[Union[HumanMessage, AIMessage]]:
19
+ buffer = []
20
+ for human, ai in chat_history:
21
+ buffer.append(HumanMessage(content=human))
22
+ buffer.append(AIMessage(content=ai))
23
+ return buffer
24
+
25
+ aws_profile = os.environ.get("ZIYA_AWS_PROFILE")
26
+ if aws_profile:
27
+ logger.info(f"Using AWS Profile: {aws_profile}")
28
+ else:
29
+ logger.info("No AWS profile specified via --aws-profile flag, using default credentials")
30
+ model_id = {
31
+ "sonnet": "anthropic.claude-3-sonnet-20240229-v1:0",
32
+ "haiku": "anthropic.claude-3-haiku-20240307-v1:0",
33
+ "opus": "anthropic.claude-3-opus-20240229-v1:0",
34
+ }[os.environ.get("ZIYA_AWS_MODEL", "haiku")]
35
+ logger.info(f"Using Claude Model: {model_id}")
36
+
37
+ model = ChatBedrock(
38
+ model_id=model_id,
39
+ model_kwargs={"max_tokens": 4096},
40
+ credentials_profile_name=aws_profile if aws_profile else None,
41
+ )
42
+
43
+
44
+ def get_combined_document_contents() -> str:
45
+ user_codebase_dir: str = os.environ["ZIYA_USER_CODEBASE_DIR"]
46
+ print(f"Reading user's current codebase: {user_codebase_dir}")
47
+
48
+ combined_contents: str = ""
49
+ ignored_patterns: List[str] = get_ignored_patterns(user_codebase_dir)
50
+ all_files: List[str] = get_complete_file_list(user_codebase_dir, ignored_patterns)
51
+
52
+ print_file_tree(all_files)
53
+
54
+ seen_dirs: Set[str] = set()
55
+ for file_path in all_files:
56
+ try:
57
+ docs = TextLoader(file_path).load()
58
+ for doc in docs:
59
+ combined_contents += f"File: {file_path}\n{doc.page_content}\n\n"
60
+ dir_path = os.path.dirname(file_path)
61
+ if dir_path not in seen_dirs:
62
+ # print_file_tree(dir_path, ignored_patterns)
63
+ seen_dirs.add(dir_path)
64
+ except Exception as e:
65
+ print(f"Skipping file {file_path} due to error: {e}")
66
+
67
+ print("--------------------------------------------------------")
68
+ print(f"Ignoring following paths based on gitIgnore and other defaults: {ignored_patterns}")
69
+ print("--------------------------------------------------------")
70
+ print(f"Codebase word count: {len(combined_contents.split()):,}")
71
+ token_count = len(tiktoken.get_encoding("cl100k_base").encode(combined_contents))
72
+ print(f"Codebase token count: {token_count:,}")
73
+ print(f"Max Claude Token limit: 200,000")
74
+ print("--------------------------------------------------------")
75
+ return combined_contents
76
+
77
+
78
+ def get_child_paths(directory: str) -> Generator[str, None, None]:
79
+ for entry in os.listdir(directory):
80
+ child_path = os.path.join(directory, entry)
81
+ yield child_path
82
+
83
+
84
+ prompt = conversational_prompt.partial(
85
+ codebase=get_combined_document_contents,
86
+ )
87
+ llm_with_stop = model.bind(stop=["</tool_input>"])
88
+
89
+ agent = (
90
+ {
91
+ "question": lambda x: x["question"],
92
+ "agent_scratchpad": lambda x: format_xml(x["intermediate_steps"]),
93
+ "chat_history": lambda x: _format_chat_history(x["chat_history"]),
94
+ }
95
+ | prompt
96
+ | llm_with_stop
97
+ | parse_output
98
+ )
99
+
100
+
101
+ class AgentInput(BaseModel):
102
+ question: str
103
+ chat_history: List[Tuple[str, str]] = Field(..., extra={"widget": {"type": "chat"}})
104
+
105
+
106
+ agent_executor = AgentExecutor(
107
+ agent=agent, tools=[], verbose=True, handle_parsing_errors=True
108
+ ).with_types(input_type=AgentInput)
109
+
110
+ agent_executor = agent_executor | (lambda x: x["output"])
111
+
112
+ if __name__ == "__main__":
113
+ question = "How are you ?"
114
+ print(agent_executor.invoke({"question": question, "chat_history": []}))
app/agents/prompts.py ADDED
@@ -0,0 +1,47 @@
1
+ from langchain_core.agents import AgentFinish
2
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
3
+ # import pydevd_pycharm
4
+
5
+ template = """You are an excellent coder. Help the user with their coding tasks. You are given the entire codebase
6
+ of the user in your context. It is in the format like below where first line has the File path and then the content follows.
7
+
8
+ File: <filepath>
9
+ <Content of the file>.
10
+
11
+ Now below is the current codebase of the user:
12
+
13
+ {codebase}
14
+ """
15
+
16
+ conversational_prompt = ChatPromptTemplate.from_messages(
17
+ [
18
+ ("system", template),
19
+ MessagesPlaceholder(variable_name="chat_history"),
20
+ ("user", "{question}"),
21
+ ("ai", "{agent_scratchpad}"),
22
+ ]
23
+ )
24
+
25
+
26
+ def parse_output(message):
27
+ text = message.content
28
+ # pydevd_pycharm.settrace('localhost', port=61565, stdoutToServer=True, stderrToServer=True)
29
+ # print('----TEXT START----')
30
+ # print(text)
31
+ # print('----TEXT END----')
32
+ # if "</tool>" in text:
33
+ # tool, tool_input = text.split("</tool>")
34
+ # _tool = tool.split("<tool>")[1]
35
+ # _tool_input = tool_input.split("<tool_input>")[1]
36
+ #
37
+ # if _tool == "write_file":
38
+ # _tool_input = json.loads(_tool_input)
39
+ # # newline_index = _tool_input.find('\n')
40
+ # # file_path = _tool_input[:newline_index]
41
+ # # text = _tool_input[newline_index + 1:]
42
+ # if "</tool_input>" in _tool_input:
43
+ # _tool_input = _tool_input.split("</tool_input>")[0]
44
+ #
45
+ # return AgentAction(tool=_tool, tool_input=_tool_input, log=text)
46
+ # else:
47
+ return AgentFinish(return_values={"output": text}, log=text)
app/main.py ADDED
@@ -0,0 +1,33 @@
1
+ import os
2
+ from langchain_cli.cli import serve
3
+ import argparse
4
+
5
+ def main():
6
+ os.environ["ZIYA_USER_CODEBASE_DIR"] = os.getcwd()
7
+
8
+ parser = argparse.ArgumentParser(description="Run with custom options")
9
+ parser.add_argument("--exclude", default=[], type=lambda x: x.split(','),
10
+ help="List of files or directories to exclude (e.g., --exclude 'tst,build,*.py')")
11
+ parser.add_argument("--profile", type=str, default=None,
12
+ help="AWS profile to use (e.g., --profile ziya)")
13
+ parser.add_argument("--model", type=str, choices=["sonnet", "haiku", "opus"], default="haiku",
14
+ help="AWS Bedrock Model to use (e.g., --model sonnet)")
15
+ parser.add_argument("--port", type=int, default=6969,
16
+ help="Port number to run Ziya frontend on (e.g., --port 8080)")
17
+ args = parser.parse_args()
18
+
19
+ additional_excluded_dirs = ','.join([item for item in args.exclude])
20
+ os.environ["ZIYA_ADDITIONAL_EXCLUDE_DIRS"] = additional_excluded_dirs
21
+
22
+ if args.profile:
23
+ os.environ["ZIYA_AWS_PROFILE"] = args.profile
24
+ if args.model:
25
+ os.environ["ZIYA_AWS_MODEL"] = args.model
26
+
27
+ langchain_serve_directory = os.path.dirname(os.path.abspath(__file__))
28
+ os.chdir(langchain_serve_directory)
29
+ serve(port=args.port)
30
+
31
+
32
+ if __name__ == "__main__":
33
+ main()
app/server.py ADDED
@@ -0,0 +1,22 @@
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.staticfiles import StaticFiles
3
+ from fastapi.templating import Jinja2Templates
4
+ from langserve import add_routes
5
+ from app.agents.agent import agent_executor
6
+
7
+ import uvicorn
8
+
9
+
10
+ app = FastAPI()
11
+ app.mount("/static", StaticFiles(directory="../static"), name="static")
12
+ templates = Jinja2Templates(directory="../templates")
13
+
14
+ # Add a route for the frontend
15
+ add_routes(app, agent_executor, disabled_endpoints=["playground"], path="/ziya")
16
+
17
+ @app.get("/")
18
+ async def root(request: Request):
19
+ return templates.TemplateResponse("index.html", {"request": request})
20
+
21
+ if __name__ == "__main__":
22
+ uvicorn.run(app, host="0.0.0.0", port=8000)
app/utils/__init__.py ADDED
File without changes
@@ -0,0 +1,57 @@
1
+ import glob
2
+ import os
3
+ from typing import List, Set, Tuple
4
+
5
+ from app.utils.gitignore_parser import parse_gitignore_patterns
6
+
7
+
8
+ def get_ignored_patterns(directory: str) -> List[Tuple[str, str]]:
9
+ ignored_patterns: List[Tuple[str, str]] = [
10
+ ("poetry.lock", os.environ["ZIYA_USER_CODEBASE_DIR"]),
11
+ ("package-lock.json", os.environ["ZIYA_USER_CODEBASE_DIR"]),
12
+ (".DS_Store", os.environ["ZIYA_USER_CODEBASE_DIR"]),
13
+ (".git", os.environ["ZIYA_USER_CODEBASE_DIR"]),
14
+ *[(pattern, os.environ["ZIYA_USER_CODEBASE_DIR"])
15
+ for pattern in os.environ["ZIYA_ADDITIONAL_EXCLUDE_DIRS"].split(',')
16
+ if pattern]
17
+ ]
18
+
19
+ def read_gitignore(path: str) -> List[Tuple[str, str]]:
20
+ gitignore_patterns: List[Tuple[str, str]] = []
21
+ with open(path, "r") as f:
22
+ for line in f:
23
+ line = line.strip()
24
+ if line and not line.startswith("#"):
25
+ gitignore_patterns.append((line, os.path.dirname(path)))
26
+ return gitignore_patterns
27
+
28
+ def get_patterns_recursive(path: str) -> List[Tuple[str, str]]:
29
+ patterns: List[Tuple[str, str]] = []
30
+ gitignore_path = os.path.join(path, ".gitignore")
31
+
32
+ if os.path.exists(gitignore_path):
33
+ patterns.extend(read_gitignore(gitignore_path))
34
+
35
+ for subdir in glob.glob(os.path.join(path, "*/")):
36
+ patterns.extend(get_patterns_recursive(subdir))
37
+
38
+ return patterns
39
+
40
+ ignored_patterns.extend(get_patterns_recursive(directory))
41
+ return ignored_patterns
42
+
43
+
44
+ def get_complete_file_list(user_codebase_dir: str, ignored_patterns: List[str]) -> List[str]:
45
+ should_ignore = parse_gitignore_patterns(ignored_patterns, base_dir=user_codebase_dir)
46
+ file_set: Set[str] = set()
47
+
48
+ for root, dirs, files in os.walk(user_codebase_dir):
49
+ # Filter out ignored directories
50
+ dirs[:] = [d for d in dirs if not should_ignore(os.path.join(root, d))]
51
+
52
+ for file in files:
53
+ file_path = os.path.join(root, file)
54
+ if not should_ignore(file_path):
55
+ file_set.add(file_path)
56
+
57
+ return list(file_set)
@@ -0,0 +1,217 @@
1
+ import collections
2
+ import os
3
+ import re
4
+
5
+ from os.path import abspath, dirname
6
+ from pathlib import Path
7
+ from typing import Reversible, Union, List, Tuple
8
+
9
+
10
+ def handle_negation(file_path, rules: Reversible["IgnoreRule"]):
11
+ for rule in reversed(rules):
12
+ if rule.match(file_path):
13
+ return not rule.negation
14
+ return False
15
+
16
+
17
+ def parse_gitignore_patterns(patterns: List[Tuple[str, str]], base_dir=None):
18
+ if base_dir is None:
19
+ base_dir = os.getcwd()
20
+ rules = []
21
+ for counter, (pattern, directory) in enumerate(patterns, start=1):
22
+ rule = rule_from_pattern(pattern, base_path=_normalize_path(directory),
23
+ source=('in-memory', counter))
24
+ if rule:
25
+ rules.append(rule)
26
+ if not any(r.negation for r in rules):
27
+ return lambda file_path: any(r.match(file_path) for r in rules)
28
+ else:
29
+ # We have negation rules. We can't use a simple "any" to evaluate them.
30
+ # Later rules override earlier rules.
31
+ return lambda file_path: handle_negation(file_path, rules)
32
+
33
+ def rule_from_pattern(pattern, base_path=None, source=None):
34
+ """
35
+ Take a .gitignore match pattern, such as "*.py[cod]" or "**/*.bak",
36
+ and return an IgnoreRule suitable for matching against files and
37
+ directories. Patterns which do not match files, such as comments
38
+ and blank lines, will return None.
39
+ Because git allows for nested .gitignore files, a base_path value
40
+ is required for correct behavior. The base path should be absolute.
41
+ """
42
+ # Store the exact pattern for our repr and string functions
43
+ orig_pattern = pattern
44
+ # Early returns follow
45
+ # Discard comments and separators
46
+ if pattern.strip() == '' or pattern[0] == '#':
47
+ return
48
+ # Strip leading bang before examining double asterisks
49
+ if pattern[0] == '!':
50
+ negation = True
51
+ pattern = pattern[1:]
52
+ else:
53
+ negation = False
54
+ # Multi-asterisks not surrounded by slashes (or at the start/end) should
55
+ # be treated like single-asterisks.
56
+ pattern = re.sub(r'([^/])\*{2,}', r'\1*', pattern)
57
+ pattern = re.sub(r'\*{2,}([^/])', r'*\1', pattern)
58
+
59
+ # Special-casing '/', which doesn't match any files or directories
60
+ if pattern.rstrip() == '/':
61
+ return
62
+
63
+ directory_only = pattern[-1] == '/'
64
+ # A slash is a sign that we're tied to the base_path of our rule
65
+ # set.
66
+ anchored = '/' in pattern[:-1]
67
+ if pattern[0] == '/':
68
+ pattern = pattern[1:]
69
+ if pattern[0] == '*' and len(pattern) >= 2 and pattern[1] == '*':
70
+ pattern = pattern[2:]
71
+ anchored = False
72
+ if pattern[0] == '/':
73
+ pattern = pattern[1:]
74
+ if pattern[-1] == '/':
75
+ pattern = pattern[:-1]
76
+ # patterns with leading hashes or exclamation marks are escaped with a
77
+ # backslash in front, unescape it
78
+ if pattern[0] == '\\' and pattern[1] in ('#', '!'):
79
+ pattern = pattern[1:]
80
+ # trailing spaces are ignored unless they are escaped with a backslash
81
+ i = len(pattern)-1
82
+ striptrailingspaces = True
83
+ while i > 1 and pattern[i] == ' ':
84
+ if pattern[i-1] == '\\':
85
+ pattern = pattern[:i-1] + pattern[i:]
86
+ i = i - 1
87
+ striptrailingspaces = False
88
+ else:
89
+ if striptrailingspaces:
90
+ pattern = pattern[:i]
91
+ i = i - 1
92
+ regex = fnmatch_pathname_to_regex(
93
+ pattern, directory_only, negation, anchored=bool(anchored)
94
+ )
95
+ return IgnoreRule(
96
+ pattern=orig_pattern,
97
+ regex=regex,
98
+ negation=negation,
99
+ directory_only=directory_only,
100
+ anchored=anchored,
101
+ base_path=_normalize_path(base_path) if base_path else None,
102
+ source=source
103
+ )
104
+
105
+
106
+ IGNORE_RULE_FIELDS = [
107
+ 'pattern', 'regex', # Basic values
108
+ 'negation', 'directory_only', 'anchored', # Behavior flags
109
+ 'base_path', # Meaningful for gitignore-style behavior
110
+ 'source' # (file, line) tuple for reporting
111
+ ]
112
+
113
+
114
+ class IgnoreRule(collections.namedtuple('IgnoreRule_', IGNORE_RULE_FIELDS)):
115
+ def __str__(self):
116
+ return self.pattern
117
+
118
+ def __repr__(self):
119
+ return ''.join(['IgnoreRule(\'', self.pattern, '\')'])
120
+
121
+ def match(self, abs_path: Union[str, Path]):
122
+ matched = False
123
+ if self.base_path:
124
+ if str(abs_path).startswith(str(self.base_path)):
125
+ rel_path = str(_normalize_path(abs_path).relative_to(self.base_path))
126
+ else:
127
+ rel_path = str(_normalize_path(abs_path))
128
+ else:
129
+ rel_path = str(_normalize_path(abs_path))
130
+ # Path() strips the trailing slash, so we need to preserve it
131
+ # in case of directory-only negation
132
+ if self.negation and type(abs_path) == str and abs_path[-1] == '/':
133
+ rel_path += '/'
134
+ if rel_path.startswith('./'):
135
+ rel_path = rel_path[2:]
136
+ if re.search(self.regex, rel_path):
137
+ matched = True
138
+ return matched
139
+
140
+ # Frustratingly, python's fnmatch doesn't provide the FNM_PATHNAME
141
+ # option that .gitignore's behavior depends on.
142
+ def fnmatch_pathname_to_regex(
143
+ pattern, directory_only: bool, negation: bool, anchored: bool = False
144
+ ):
145
+ """
146
+ Implements fnmatch style-behavior, as though with FNM_PATHNAME flagged;
147
+ the path separator will not match shell-style '*' and '.' wildcards.
148
+ """
149
+ i, n = 0, len(pattern)
150
+
151
+ seps = [re.escape(os.sep)]
152
+ if os.altsep is not None:
153
+ seps.append(re.escape(os.altsep))
154
+ seps_group = '[' + '|'.join(seps) + ']'
155
+ nonsep = r'[^{}]'.format('|'.join(seps))
156
+
157
+ res = []
158
+ while i < n:
159
+ c = pattern[i]
160
+ i += 1
161
+ if c == '*':
162
+ try:
163
+ if pattern[i] == '*':
164
+ i += 1
165
+ res.append('.*')
166
+ else:
167
+ res.append(''.join([nonsep, '*']))
168
+ except IndexError:
169
+ res.append(''.join([nonsep, '*']))
170
+ elif c == '?':
171
+ res.append(nonsep)
172
+ elif c == '/':
173
+ res.append(seps_group)
174
+ elif c == '[':
175
+ j = i
176
+ if j < n and pattern[j] == '!':
177
+ j += 1
178
+ if j < n and pattern[j] == ']':
179
+ j += 1
180
+ while j < n and pattern[j] != ']':
181
+ j += 1
182
+ if j >= n:
183
+ res.append('\\[')
184
+ else:
185
+ stuff = pattern[i:j].replace('\\', '\\\\').replace('/', '')
186
+ i = j + 1
187
+ if stuff[0] == '!':
188
+ stuff = ''.join(['^', stuff[1:]])
189
+ elif stuff[0] == '^':
190
+ stuff = ''.join('\\' + stuff)
191
+ res.append('[{}]'.format(stuff))
192
+ else:
193
+ res.append(re.escape(c))
194
+ if anchored:
195
+ res.insert(0, '^')
196
+ else:
197
+ # Handle leading slash patterns
198
+ if pattern.startswith('/'):
199
+ res.insert(0, '^')
200
+ else:
201
+ res.insert(0, f"(^|{seps_group})")
202
+ if not directory_only:
203
+ res.append('$')
204
+ elif directory_only and negation:
205
+ res.append('/$')
206
+ else:
207
+ res.append('($|\\/)')
208
+ return ''.join(res)
209
+
210
+ def _normalize_path(path: Union[str, Path]) -> Path:
211
+ """Normalize a path without resolving symlinks.
212
+
213
+ This is equivalent to `Path.resolve()` except that it does not resolve symlinks.
214
+ Note that this simplifies paths by removing double slashes, `..`, `.` etc. like
215
+ `Path.resolve()` does.
216
+ """
217
+ return Path(abspath(path))
@@ -0,0 +1,10 @@
1
+ import logging
2
+
3
+ # Create and configure the logger
4
+ formatter = logging.Formatter("\033[35mZIYA\033[0m: %(message)s")
5
+ handler = logging.StreamHandler()
6
+ handler.setFormatter(formatter)
7
+
8
+ logger = logging.getLogger(__name__)
9
+ logger.setLevel(logging.INFO)
10
+ logger.addHandler(handler)
@@ -0,0 +1,43 @@
1
+ import os
2
+
3
+ def print_file_tree(file_paths):
4
+ # Create a dictionary to store files and directories
5
+ file_tree = {}
6
+
7
+ # Populate the file tree dictionary
8
+ for path in file_paths:
9
+ dir_path, file_name = os.path.split(path)
10
+ if dir_path not in file_tree:
11
+ file_tree[dir_path] = []
12
+ file_tree[dir_path].append(file_name)
13
+
14
+ # Sort directories and files
15
+ sorted_dirs = sorted(file_tree.keys())
16
+ for dir_path in sorted_dirs:
17
+ file_tree[dir_path].sort()
18
+
19
+ # Print the file tree recursively
20
+ def print_dir(dir_path, indent):
21
+ nonlocal printed_dirs
22
+ if dir_path in printed_dirs:
23
+ return
24
+
25
+ printed_dirs.add(dir_path)
26
+ print(f"{indent}{os.path.basename(dir_path)}")
27
+ if file_tree[dir_path]:
28
+ for i, file_name in enumerate(file_tree[dir_path]):
29
+ if i == len(file_tree[dir_path]) - 1:
30
+ print(f"{indent} └── {file_name}")
31
+ else:
32
+ print(f"{indent} ├── {file_name}")
33
+ else:
34
+ print(f"{indent} (empty)")
35
+
36
+ # Recursively print subdirectories
37
+ subdirs = [subdir for subdir in sorted_dirs if subdir.startswith(dir_path + os.sep)]
38
+ for subdir in subdirs:
39
+ print_dir(subdir, indent + " ")
40
+
41
+ printed_dirs = set()
42
+ for dir_path in sorted_dirs:
43
+ print_dir(dir_path, "")
pyproject.toml ADDED
@@ -0,0 +1,33 @@
1
+ [tool.poetry]
2
+ name = "ziya"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = ["Vishnu Krishnaprasad <vishnukool@gmail.com>"]
6
+ readme = "README.md"
7
+ include = ["static/**/*", "templates/**/*", "pyproject.toml"]
8
+ packages = [
9
+ { include = "app" },
10
+ ]
11
+
12
+ [tool.poetry.dependencies]
13
+ python = ">=3.9,<4.0"
14
+ uvicorn = "^0.23.2"
15
+ pydantic = "<2"
16
+ jinja2 = "^3.1.3"
17
+ tiktoken = "^0.6.0"
18
+ boto3 = "^1.34.88"
19
+ langchain-aws = "^0.1.0"
20
+ langchain = "^0.1"
21
+ langchainhub = ">=0.1.15"
22
+ langchain-anthropic = "^0.1.4"
23
+ langchain-cli = ">=0.0.15"
24
+
25
+ [tool.poetry.group.dev.dependencies]
26
+ pydevd-pycharm = "^241.15989.57"
27
+
28
+ [build-system]
29
+ requires = ["poetry-core"]
30
+ build-backend = "poetry.core.masonry.api"
31
+
32
+ [tool.poetry.scripts]
33
+ ziya = 'app.main:main'
static/app.js ADDED
@@ -0,0 +1,104 @@
1
+ const {useState, useRef, useEffect} = React;
2
+
3
+ const App = () => {
4
+ const [messages, setMessages] = useState([]);
5
+ const [question, setQuestion] = useState('');
6
+ const [streamedContent, setStreamedContent] = useState('');
7
+ const [isStreaming, setIsStreaming] = useState(false);
8
+ const inputTextareaRef = useRef(null);
9
+
10
+ useEffect(() => {
11
+ if (inputTextareaRef.current) {
12
+ inputTextareaRef.current.focus();
13
+ }
14
+ }, []);
15
+
16
+ const handleSendPayload = async () => {
17
+ setQuestion('');
18
+ setIsStreaming(true);
19
+ setMessages((messages) => [...messages, {content: question, role: 'human'}]);
20
+ setStreamedContent('');
21
+ await window.sendPayload(messages, question, setStreamedContent, setIsStreaming);
22
+ setStreamedContent((cont) => {
23
+ setMessages((messages) => [...messages, {content: cont, role: 'assistant'}]);
24
+ return ""
25
+ });
26
+ inputTextareaRef.current.focus();
27
+ };
28
+
29
+ const handleChange = (event) => {
30
+ setQuestion(event.target.value);
31
+ };
32
+
33
+ return (
34
+ <div><h2 style={{textAlign: "center"}}>Ziya: Code Assist</h2>
35
+ <div className="container">
36
+ <InputContainer
37
+ inputTextareaRef={inputTextareaRef}
38
+ question={question}
39
+ handleChange={handleChange}
40
+ sendPayload={handleSendPayload}
41
+ isStreaming={isStreaming}
42
+ />
43
+ {streamedContent || messages.length > 0 ? <div className="chat-container">
44
+ {streamedContent && <StreamedContent streamedContent={streamedContent}/>}
45
+ {messages.length > 0 && <ChatContainer messages={messages}/>}
46
+ </div> : null}
47
+ </div>
48
+ </div>
49
+ );
50
+ };
51
+
52
+ const ChatContainer = ({messages}) => (
53
+ <div>
54
+ {messages.slice().reverse().map((msg, index) => (
55
+ <div key={index} className={`message ${msg.role}`}>
56
+ {msg.role === 'human' ? <div className="message-sender">You:</div> :
57
+ <div className="message-sender">AI:</div>}
58
+ <MarkdownRenderer markdown={msg.content}/>
59
+ </div>
60
+ ))}
61
+ </div>
62
+ );
63
+
64
+ const isQuestionEmpty = (input) => input.trim().length === 0;
65
+
66
+ const StreamedContent = ({streamedContent}) => (
67
+ <div className="message assistant">
68
+ <div className="message-sender">AI:</div>
69
+ <MarkdownRenderer markdown={streamedContent}/>
70
+ </div>
71
+ );
72
+ const InputContainer = ({question, handleChange, sendPayload, isStreaming, inputTextareaRef}) => (
73
+
74
+ <div className="input-container">
75
+ <textarea
76
+ ref={inputTextareaRef}
77
+ value={question}
78
+ onChange={handleChange}
79
+ placeholder="Enter your question.."
80
+ rows={3}
81
+ className="input-textarea"
82
+ onKeyDown={(event) => {
83
+ if (event.key === 'Enter' && !event.shiftKey && !isQuestionEmpty(question)) {
84
+ event.preventDefault(); // Prevent the default behavior of adding a new line
85
+ sendPayload();
86
+ }
87
+ }}
88
+ />
89
+ <button onClick={sendPayload} disabled={isStreaming || isQuestionEmpty(question)} className="send-button">
90
+ {isStreaming ? `Sending..` : `Send`}
91
+ </button>
92
+ </div>
93
+ );
94
+
95
+ const MarkdownRenderer = ({markdown}) => {
96
+ const renderMarkdown = () => {
97
+ const html = marked.parse(markdown);
98
+ return {__html: html};
99
+ };
100
+
101
+ return <div dangerouslySetInnerHTML={renderMarkdown()}/>;
102
+ };
103
+
104
+ ReactDOM.render(<App/>, document.getElementById('root'));
static/favicon.ico ADDED
Binary file
static/sendPayload.js ADDED
@@ -0,0 +1,62 @@
1
+ async function getApiResponse(messages, question) {
2
+ const messageTuples = [];
3
+ let tempArray = [];
4
+ for (let i = 0; i < messages.length; i++) {
5
+ tempArray.push(messages[i].content);
6
+ if (tempArray.length === 2) {
7
+ messageTuples.push(tempArray);
8
+ tempArray = [];
9
+ }
10
+ }
11
+ const response = await fetch('/ziya/stream_log', {
12
+ method: 'POST', headers: {'Content-Type': 'application/json'},
13
+ body: JSON.stringify({
14
+ input: {
15
+ chat_history: messageTuples,
16
+ question: question,
17
+ },
18
+ config: {},
19
+ }),
20
+ });
21
+ return response;
22
+ }
23
+
24
+ const processLine = (line, setStreamedContent) => {
25
+ const data = JSON.parse(line.slice(6));
26
+ const streamedOutputOp = data.ops.find(
27
+ (op) => op.op === 'add' && op.path === '/logs/ChatBedrock/streamed_output_str/-'
28
+ );
29
+
30
+ if (streamedOutputOp) {
31
+ setStreamedContent((prevContent) => prevContent + streamedOutputOp.value);
32
+ }
33
+ };
34
+
35
+ window.sendPayload = async (messages, question, setStreamedContent, setIsStreaming) => {
36
+ try {
37
+ const response = await getApiResponse(messages, question);
38
+ const reader = response.body.getReader();
39
+
40
+ const processData = async ({done, value}) => {
41
+ if (done) {
42
+ setIsStreaming(false);
43
+ return;
44
+ }
45
+ let buffer = '';
46
+ buffer += new TextDecoder('utf-8').decode(value);
47
+ const lines = buffer.split('\n');
48
+ buffer = lines.pop() || '';
49
+
50
+ for (const line of lines) {
51
+ if (line.startsWith('data: ')) {
52
+ processLine(line, setStreamedContent);
53
+ }
54
+ }
55
+ await reader.read().then(processData);
56
+ };
57
+ await reader.read().then(processData);
58
+ } catch (error) {
59
+ console.error(error);
60
+ setIsStreaming(false);
61
+ }
62
+ };
static/ziya.css ADDED
@@ -0,0 +1,88 @@
1
+ /* General Styles */
2
+ body {
3
+ font-family: Arial, sans-serif;
4
+ margin: 0;
5
+ padding: 0;
6
+ background-color: #f5f5f5;
7
+ }
8
+
9
+ .container {
10
+ max-width: 800px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ background-color: #fff;
14
+ border-radius: 8px;
15
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
16
+ }
17
+
18
+ /* Chat Container */
19
+ .chat-container {
20
+ height: fit-content;
21
+ overflow-y: scroll;
22
+ padding: 10px;
23
+ border: 1px solid #ccc;
24
+ border-radius: 4px;
25
+ margin: 20px 0;
26
+ }
27
+
28
+ .message {
29
+ padding: 10px;
30
+ border-radius: 8px;
31
+ }
32
+
33
+ .message.human {
34
+ background-color: #efefef;
35
+ align-self: flex-start;
36
+ margin-right: auto;
37
+ }
38
+
39
+ .message.ai {
40
+ background-color: #f0f0f0;
41
+ align-self: flex-end;
42
+ margin-left: auto;
43
+ }
44
+
45
+ .message-sender {
46
+ font-weight: bold;
47
+ margin-bottom: 5px;
48
+ }
49
+
50
+ /* Input Container */
51
+ .input-container {
52
+ display: flex;
53
+ align-items: center;
54
+ }
55
+
56
+ .input-textarea {
57
+ flex-grow: 1;
58
+ padding: 10px;
59
+ border: 1px solid #ccc;
60
+ border-radius: 4px;
61
+ resize: none;
62
+ font-size: 16px;
63
+ }
64
+
65
+ .send-button {
66
+ margin-left: 10px;
67
+ padding: 10px 20px;
68
+ background-color: #007bff;
69
+ color: #fff;
70
+ border: none;
71
+ border-radius: 4px;
72
+ cursor: pointer;
73
+ font-size: 16px;
74
+ }
75
+
76
+ .send-button:disabled {
77
+ background-color: #ccc;
78
+ cursor: not-allowed;
79
+ }
80
+
81
+ /* Streamed Content */
82
+ .streamed-content {
83
+ margin-top: 20px;
84
+ padding: 10px;
85
+ border: 1px solid #ccc;
86
+ border-radius: 4px;
87
+ background-color: #f0f0f0;
88
+ }
templates/index.html ADDED
@@ -0,0 +1,19 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Ziya - Code Assistant</title>
7
+ <link rel="stylesheet" href="/static/ziya.css">
8
+ <link rel="icon" href="/static/favicon.ico" type="image/x-icon">
9
+ </head>
10
+ <body>
11
+ <div id="root"/>
12
+ <script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
13
+ <script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
14
+ <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
16
+ <script type="text/babel" src="/static/sendPayload.js"></script>
17
+ <script type="text/babel" src="/static/app.js"></script>
18
+ </body>
19
+ </html>
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 LangChain, Inc.
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,86 @@
1
+ Metadata-Version: 2.1
2
+ Name: ziya
3
+ Version: 0.1.0
4
+ Summary:
5
+ Author: Vishnu Krishnaprasad
6
+ Author-email: vishnukool@gmail.com
7
+ Requires-Python: >=3.9,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: boto3 (>=1.34.88,<2.0.0)
14
+ Requires-Dist: jinja2 (>=3.1.3,<4.0.0)
15
+ Requires-Dist: langchain (>=0.1,<0.2)
16
+ Requires-Dist: langchain-anthropic (>=0.1.4,<0.2.0)
17
+ Requires-Dist: langchain-aws (>=0.1.0,<0.2.0)
18
+ Requires-Dist: langchain-cli (>=0.0.15)
19
+ Requires-Dist: langchainhub (>=0.1.15)
20
+ Requires-Dist: pydantic (<2)
21
+ Requires-Dist: tiktoken (>=0.6.0,<0.7.0)
22
+ Requires-Dist: uvicorn (>=0.23.2,<0.24.0)
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Ziya
26
+
27
+ Ziya is a code assist tool for AWS Bedrock models. It can read your entire codebase and answer questions.
28
+
29
+ The current version only performs read operations. However, future versions will be able to:
30
+
31
+ 1. Write and edit code.
32
+ 2. Search the web for resources.
33
+ 3. Run commands locally.
34
+ 4. Iteratively continue to do 1,2,3 for a given objective.
35
+
36
+ ## Pre-requisites
37
+ ### Setup AWS credentials:
38
+ The easiest way is to set the env variables with access to AWS Bedrock claude models.
39
+
40
+ ```bash
41
+ export AWS_ACCESS_KEY_ID=<YOUR-KEY>
42
+ export AWS_SECRET_ACCESS_KEY=<YOUR-SECRET>
43
+ ```
44
+ ### Installation
45
+
46
+ ```bash
47
+ pip install ziya
48
+ ```
49
+
50
+ ## Run Ziya
51
+
52
+ ```bash
53
+ ziya
54
+ ```
55
+ Then navigate to http://localhost:6969 in your browser and start chatting with your codebase.
56
+
57
+ When you ask a question Ziya sends your entire codebase as context to the LLM, along with your question and any chat history.
58
+ ```
59
+ > Entering new AgentExecutor chain...
60
+ Reading user's current codebase: /Users/vkrishnaprasad/personal_projects/ziya
61
+ ziya
62
+ ├── .gitignore
63
+ ├── DEVELOPMENT.md
64
+ ├── LICENSE
65
+ ├── README.md
66
+ └── pyproject.toml
67
+ app
68
+ ├── __init__.py
69
+ ├── main.py
70
+ └── server.py
71
+ ...
72
+ ```
73
+
74
+ ### Options
75
+
76
+ `--exclude`: Comma-separated list of files or directories to exclude from the codebase.
77
+
78
+ `--profile`: AWS profile to use for the Bedrock LLM.
79
+
80
+ `--model`: The AWS Bedrock Model to use, one of `haiku`(default), `sonnet` or `opus`.
81
+
82
+ `--port`: The port number for frontend app. Default is `6969`.
83
+
84
+ ```bash
85
+ ziya --exclude='tst,build,*.py' --profile=ziya --model=sonnet --port=8080
86
+ ```
@@ -0,0 +1,22 @@
1
+ app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ app/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ app/agents/agent.py,sha256=k0e0GMKFfU6-3e-GyAX95R9iV3sWwvLknL1gxd3TO88,4181
4
+ app/agents/prompts.py,sha256=O54rH2KupyY7ZSjPEvm3j9DSGjdL3jJOz8F9PTcePQQ,1674
5
+ app/main.py,sha256=blgAdLjoMa2HRqlr17lwbZ3hQYz2jUZJwu18KLI9FiQ,1359
6
+ app/server.py,sha256=YTEHYUsFQhgkGRHZ_y7K3Ju3SmUIbVxLSdgrgi1BuEU,676
7
+ app/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ app/utils/directory_util.py,sha256=XOImpzqVa2NrIAPl054VN2WC4kk1QUIE4COEL062ULw,2132
9
+ app/utils/gitignore_parser.py,sha256=ksAAvXr8_GFh94cvOdkLmES5k3xxNxW2_tQuQtialIA,7496
10
+ app/utils/logging_utils.py,sha256=8JEcc1t7L-a0G4HLmM8zFydNNSOd5l2-gkTxviLQUns,280
11
+ app/utils/print_tree_util.py,sha256=O4Rb-GS8UODxfH7Z6bZGsr-opG6QLmvdaU1im5kh4IA,1419
12
+ pyproject.toml,sha256=p35kypx9MscMQR1a_MWhgKOr764YZJOlNa9FW7s0Wrw,716
13
+ static/app.js,sha256=qz2F0e5LxuVFI3MXWvcON9l1cN0J_ODdQOwbgT3ZqWA,3622
14
+ static/favicon.ico,sha256=HgB8xAZdDHFK2lODUsp2H_Dds_i14pnpydx7NEJrNrU,15086
15
+ static/sendPayload.js,sha256=VnypSWF7t8bv83026LGU3GMyr9tMrp05hmWATcbCG4k,1956
16
+ static/ziya.css,sha256=OaieSXw_7LOk0WNNi-1mPsAaS3rOxhHycKFgFDoiZB0,1395
17
+ templates/index.html,sha256=8auy9lGLdcDzBkzpjYB1ygrAVbXpo0rqQ8p1FgMEeck,800
18
+ ziya-0.1.0.dist-info/LICENSE,sha256=DppmdYJVSc1jd0aio6ptnMUn5tIHrdAhQ12SclEBfBg,1072
19
+ ziya-0.1.0.dist-info/METADATA,sha256=V8cWv_zwPjFzBheptnTXc7wimA2x3Pdz8iu4ApLJNgk,2472
20
+ ziya-0.1.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
21
+ ziya-0.1.0.dist-info/entry_points.txt,sha256=1HspxMqCYRli3SqM6CqmT6gsW_G14G4ab3sHlrYUCAA,38
22
+ ziya-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ ziya=app.main:main
3
+