PraisonAI 0.0.52__tar.gz → 0.0.54__tar.gz
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 PraisonAI might be problematic. Click here for more details.
- {praisonai-0.0.52 → praisonai-0.0.54}/PKG-INFO +21 -3
- {praisonai-0.0.52 → praisonai-0.0.54}/README.md +18 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/deploy.py +1 -1
- praisonai-0.0.54/praisonai/ui/context.py +201 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/pyproject.toml +2 -2
- praisonai-0.0.52/praisonai/ui/context.py +0 -216
- {praisonai-0.0.52 → praisonai-0.0.54}/LICENSE +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/__init__.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/__main__.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/agents_generator.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/auto.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/chainlit_ui.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/cli.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/inbuilt_tools/__init__.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/inbuilt_tools/autogen_tools.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/inc/__init__.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/inc/models.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/android-chrome-192x192.png +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/android-chrome-512x512.png +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/apple-touch-icon.png +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/fantasy.svg +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/favicon-16x16.png +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/favicon-32x32.png +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/favicon.ico +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/game.svg +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/logo_dark.png +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/logo_light.png +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/movie.svg +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/public/thriller.svg +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/test.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/ui/chat.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/ui/code.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/ui/public/fantasy.svg +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/ui/public/game.svg +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/ui/public/logo_dark.png +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/ui/public/logo_light.png +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/ui/public/movie.svg +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/ui/public/thriller.svg +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/ui/sql_alchemy.py +0 -0
- {praisonai-0.0.52 → praisonai-0.0.54}/praisonai/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PraisonAI
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.54
|
|
4
4
|
Summary: PraisonAI application combines AutoGen and CrewAI or similar frameworks into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customization, and efficient human-agent collaboration.
|
|
5
5
|
Author: Mervin Praison
|
|
6
6
|
Requires-Python: >=3.10,<3.13
|
|
@@ -19,12 +19,12 @@ Provides-Extra: gradio
|
|
|
19
19
|
Provides-Extra: openai
|
|
20
20
|
Provides-Extra: ui
|
|
21
21
|
Requires-Dist: agentops (>=0.2.6) ; extra == "agentops"
|
|
22
|
-
Requires-Dist: aiosqlite (>=0.20.0) ; extra == "code"
|
|
22
|
+
Requires-Dist: aiosqlite (>=0.20.0) ; extra == "chat" or extra == "code"
|
|
23
23
|
Requires-Dist: chainlit (>=1.1.301,<2.0.0) ; extra == "ui" or extra == "chat" or extra == "code"
|
|
24
24
|
Requires-Dist: crewai (>=0.32.0)
|
|
25
25
|
Requires-Dist: flask (>=3.0.0) ; extra == "api"
|
|
26
26
|
Requires-Dist: gradio (>=4.26.0) ; extra == "gradio"
|
|
27
|
-
Requires-Dist: greenlet (>=3.0.3) ; extra == "code"
|
|
27
|
+
Requires-Dist: greenlet (>=3.0.3) ; extra == "chat" or extra == "code"
|
|
28
28
|
Requires-Dist: langchain-anthropic (>=0.1.13) ; extra == "anthropic"
|
|
29
29
|
Requires-Dist: langchain-cohere (>=0.1.4) ; extra == "cohere"
|
|
30
30
|
Requires-Dist: langchain-google-genai (>=1.0.4) ; extra == "google"
|
|
@@ -61,6 +61,16 @@ Description-Content-Type: text/markdown
|
|
|
61
61
|
|
|
62
62
|
Praison AI, leveraging both AutoGen and CrewAI or any other agent framework, represents a low-code, centralised framework designed to simplify the creation and orchestration of multi-agent systems for various LLM applications, emphasizing ease of use, customization, and human-agent interaction.
|
|
63
63
|
|
|
64
|
+
<div align="center">
|
|
65
|
+
<picture>
|
|
66
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/images/architecture-dark.png">
|
|
67
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/images/architecture-light.png">
|
|
68
|
+
<img alt="PraisonAI Architecture" src="docs/images/architecture-light.png">
|
|
69
|
+
</picture>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
## Google Colab
|
|
73
|
+
|
|
64
74
|
| | Cookbook | Open in Colab |
|
|
65
75
|
| ------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
66
76
|
| Basic | PraisonAI | <a target="_blank" href="https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/praisonai-googlecolab.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a> |
|
|
@@ -75,6 +85,14 @@ praisonai --init create a movie script about dog in moon
|
|
|
75
85
|
praisonai
|
|
76
86
|
```
|
|
77
87
|
|
|
88
|
+
## Different User Interfaces:
|
|
89
|
+
|
|
90
|
+
| Interface | Description | URL |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| **UI** | Multi Agents such as CrewAI or AutoGen | [https://docs.praison.ai/ui/ui](https://docs.praison.ai/ui/ui) |
|
|
93
|
+
| **Chat** | Chat with 100+ LLMs, single AI Agent | [https://docs.praison.ai/ui/chat](https://docs.praison.ai/ui/chat) |
|
|
94
|
+
| **Code** | Chat with entire Codebase, single AI Agent | [https://docs.praison.ai/ui/code](https://docs.praison.ai/ui/code) |
|
|
95
|
+
|
|
78
96
|
## Table of Contents
|
|
79
97
|
|
|
80
98
|
- [Installation](#installation)
|
|
@@ -20,6 +20,16 @@
|
|
|
20
20
|
|
|
21
21
|
Praison AI, leveraging both AutoGen and CrewAI or any other agent framework, represents a low-code, centralised framework designed to simplify the creation and orchestration of multi-agent systems for various LLM applications, emphasizing ease of use, customization, and human-agent interaction.
|
|
22
22
|
|
|
23
|
+
<div align="center">
|
|
24
|
+
<picture>
|
|
25
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/images/architecture-dark.png">
|
|
26
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/images/architecture-light.png">
|
|
27
|
+
<img alt="PraisonAI Architecture" src="docs/images/architecture-light.png">
|
|
28
|
+
</picture>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
## Google Colab
|
|
32
|
+
|
|
23
33
|
| | Cookbook | Open in Colab |
|
|
24
34
|
| ------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
25
35
|
| Basic | PraisonAI | <a target="_blank" href="https://colab.research.google.com/github/MervinPraison/PraisonAI/blob/main/cookbooks/praisonai-googlecolab.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a> |
|
|
@@ -34,6 +44,14 @@ praisonai --init create a movie script about dog in moon
|
|
|
34
44
|
praisonai
|
|
35
45
|
```
|
|
36
46
|
|
|
47
|
+
## Different User Interfaces:
|
|
48
|
+
|
|
49
|
+
| Interface | Description | URL |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| **UI** | Multi Agents such as CrewAI or AutoGen | [https://docs.praison.ai/ui/ui](https://docs.praison.ai/ui/ui) |
|
|
52
|
+
| **Chat** | Chat with 100+ LLMs, single AI Agent | [https://docs.praison.ai/ui/chat](https://docs.praison.ai/ui/chat) |
|
|
53
|
+
| **Code** | Chat with entire Codebase, single AI Agent | [https://docs.praison.ai/ui/code](https://docs.praison.ai/ui/code) |
|
|
54
|
+
|
|
37
55
|
## Table of Contents
|
|
38
56
|
|
|
39
57
|
- [Installation](#installation)
|
|
@@ -56,7 +56,7 @@ class CloudDeployer:
|
|
|
56
56
|
file.write("FROM python:3.11-slim\n")
|
|
57
57
|
file.write("WORKDIR /app\n")
|
|
58
58
|
file.write("COPY . .\n")
|
|
59
|
-
file.write("RUN pip install flask praisonai==0.0.
|
|
59
|
+
file.write("RUN pip install flask praisonai==0.0.54 gunicorn markdown\n")
|
|
60
60
|
file.write("EXPOSE 8080\n")
|
|
61
61
|
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
|
|
62
62
|
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import fnmatch
|
|
3
|
+
import re
|
|
4
|
+
import yaml
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
# Set up logging
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
log_level = os.getenv("LOGLEVEL", "INFO").upper()
|
|
11
|
+
logger.handlers = []
|
|
12
|
+
|
|
13
|
+
# Set up logging to console
|
|
14
|
+
console_handler = logging.StreamHandler()
|
|
15
|
+
console_handler.setLevel(log_level)
|
|
16
|
+
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
17
|
+
console_handler.setFormatter(console_formatter)
|
|
18
|
+
logger.addHandler(console_handler)
|
|
19
|
+
|
|
20
|
+
# Set the logging level for the logger
|
|
21
|
+
logger.setLevel(log_level)
|
|
22
|
+
|
|
23
|
+
class ContextGatherer:
|
|
24
|
+
def __init__(self, directory='.', output_file='context.txt',
|
|
25
|
+
relevant_extensions=None, max_file_size=1_000_000, max_tokens=900000):
|
|
26
|
+
self.directory = directory
|
|
27
|
+
self.output_file = output_file
|
|
28
|
+
self.relevant_extensions = relevant_extensions or [
|
|
29
|
+
'.py', '.js', '.ts', '.java', '.rb', '.php', '.pl', '.pm', '.c', '.h',
|
|
30
|
+
'.cpp', '.hpp', '.cs', '.vb', '.swift', '.kt', '.m', '.mm', '.go', '.rs',
|
|
31
|
+
'.hs', '.r', '.lua', '.sh', '.bat', '.clj', '.scala', '.erl', '.ex',
|
|
32
|
+
'.ml', '.fs', '.groovy', '.jsm', '.jsx', '.tsx', '.yaml'
|
|
33
|
+
]
|
|
34
|
+
self.max_file_size = max_file_size
|
|
35
|
+
self.max_tokens = int(os.getenv("PRAISONAI_MAX_TOKENS", max_tokens))
|
|
36
|
+
self.ignore_patterns = self.get_ignore_patterns()
|
|
37
|
+
|
|
38
|
+
def get_ignore_patterns(self):
|
|
39
|
+
"""
|
|
40
|
+
Loads ignore patterns from various sources, prioritizing them in
|
|
41
|
+
the following order:
|
|
42
|
+
1. .praisonignore
|
|
43
|
+
2. settings.yaml (under code.ignore_files)
|
|
44
|
+
3. PRAISONAI_IGNORE_FILES environment variable
|
|
45
|
+
4. .gitignore
|
|
46
|
+
5. Default patterns
|
|
47
|
+
"""
|
|
48
|
+
ignore_patterns = []
|
|
49
|
+
|
|
50
|
+
def load_from_file(filepath):
|
|
51
|
+
if os.path.exists(filepath):
|
|
52
|
+
with open(filepath, 'r') as f:
|
|
53
|
+
ignore_patterns.extend(
|
|
54
|
+
line.strip() for line in f
|
|
55
|
+
if line.strip() and not line.startswith('#')
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# 1. Load from .praisonignore
|
|
59
|
+
load_from_file(os.path.join(self.directory, '.praisonignore'))
|
|
60
|
+
|
|
61
|
+
# 2. Load from settings.yaml
|
|
62
|
+
settings_path = os.path.join(self.directory, 'settings.yaml')
|
|
63
|
+
if os.path.exists(settings_path):
|
|
64
|
+
with open(settings_path, 'r') as f:
|
|
65
|
+
settings = yaml.safe_load(f)
|
|
66
|
+
if 'code' in settings and 'ignore_files' in settings['code']:
|
|
67
|
+
ignore_patterns.extend(settings['code']['ignore_files'])
|
|
68
|
+
|
|
69
|
+
# 3. Load from environment variable
|
|
70
|
+
ignore_files_env = os.getenv("PRAISONAI_IGNORE_FILES")
|
|
71
|
+
if ignore_files_env:
|
|
72
|
+
ignore_patterns.extend(ignore_files_env.split(","))
|
|
73
|
+
|
|
74
|
+
# 4. Load from .gitignore
|
|
75
|
+
load_from_file(os.path.join(self.directory, '.gitignore'))
|
|
76
|
+
|
|
77
|
+
# 5. Default patterns (only if no patterns loaded from above sources)
|
|
78
|
+
if not ignore_patterns:
|
|
79
|
+
ignore_patterns = [
|
|
80
|
+
".*", "*.pyc", "__pycache__", ".git", ".gitignore", ".vscode",
|
|
81
|
+
".idea", ".DS_Store", "*.lock", "*.pyc", ".env", "docs", "tests",
|
|
82
|
+
"test", "tmp", "temp", "*.txt", "*.md", "*.json", "*.csv", "*.tsv",
|
|
83
|
+
"public", "*.sql", "*.sqlite", "*.db", "*.db3", "*.sqlite3",
|
|
84
|
+
"*.log", "*.zip", "*.gz", "*.tar", "*.rar", "*.7z", "*.pdf",
|
|
85
|
+
"*.jpg", "*.jpeg", "*.png", "*.gif", "*.svg", "cookbooks",
|
|
86
|
+
"assets", "__pycache__", "dist", "build", "node_modules", "venv"
|
|
87
|
+
]
|
|
88
|
+
logger.debug(f"Using default ignore patterns: {ignore_patterns}")
|
|
89
|
+
|
|
90
|
+
# Modify patterns to match directories and add leading '*' if necessary
|
|
91
|
+
modified_ignore_patterns = [
|
|
92
|
+
'*' + pattern if not pattern.startswith('.') and not pattern.startswith('*') else pattern
|
|
93
|
+
for pattern in ignore_patterns
|
|
94
|
+
]
|
|
95
|
+
logger.debug(f"Final ignore patterns: {modified_ignore_patterns}")
|
|
96
|
+
return modified_ignore_patterns
|
|
97
|
+
|
|
98
|
+
def should_ignore(self, file_path):
|
|
99
|
+
"""
|
|
100
|
+
Check if a file or directory should be ignored based on patterns.
|
|
101
|
+
Handles both file names and directory names for more comprehensive filtering.
|
|
102
|
+
"""
|
|
103
|
+
relative_path = os.path.relpath(file_path, self.directory)
|
|
104
|
+
if relative_path.startswith('.'):
|
|
105
|
+
return True
|
|
106
|
+
for pattern in self.ignore_patterns:
|
|
107
|
+
if fnmatch.fnmatch(relative_path, pattern) or \
|
|
108
|
+
fnmatch.fnmatch(os.path.basename(file_path), pattern):
|
|
109
|
+
return True
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def is_relevant_file(self, file_path):
|
|
113
|
+
"""Determine if a file is relevant for the context."""
|
|
114
|
+
return os.path.isfile(file_path) and \
|
|
115
|
+
os.path.getsize(file_path) <= self.max_file_size and \
|
|
116
|
+
any(file_path.endswith(ext) for ext in self.relevant_extensions)
|
|
117
|
+
|
|
118
|
+
def gather_context(self):
|
|
119
|
+
"""Gather context from relevant files, respecting ignore patterns."""
|
|
120
|
+
context = []
|
|
121
|
+
total_files = 0
|
|
122
|
+
processed_files = 0
|
|
123
|
+
|
|
124
|
+
for root, dirs, files in os.walk(self.directory):
|
|
125
|
+
total_files += len(files)
|
|
126
|
+
dirs[:] = [d for d in dirs if not self.should_ignore(os.path.join(root, d))]
|
|
127
|
+
for file in files:
|
|
128
|
+
file_path = os.path.join(root, file)
|
|
129
|
+
if not self.should_ignore(file_path) and self.is_relevant_file(file_path):
|
|
130
|
+
try:
|
|
131
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
132
|
+
content = f.read()
|
|
133
|
+
context.append(f"File: {file_path}\n\n{content}\n\n{'='*50}\n")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(f"Error reading {file_path}: {e}")
|
|
136
|
+
processed_files += 1
|
|
137
|
+
print(f"\rProcessed {processed_files}/{total_files} files", end="", flush=True)
|
|
138
|
+
print() # New line after progress indicator
|
|
139
|
+
return '\n'.join(context)
|
|
140
|
+
|
|
141
|
+
def count_tokens(self, text):
|
|
142
|
+
"""Count tokens using a simple whitespace-based tokenizer."""
|
|
143
|
+
return len(text.split())
|
|
144
|
+
|
|
145
|
+
def truncate_context(self, context):
|
|
146
|
+
"""Truncate context to stay within the token limit."""
|
|
147
|
+
tokens = context.split()
|
|
148
|
+
if len(tokens) > self.max_tokens:
|
|
149
|
+
truncated_context = ' '.join(tokens[:self.max_tokens])
|
|
150
|
+
logger.warning("Context truncated due to token limit.")
|
|
151
|
+
return truncated_context
|
|
152
|
+
return context
|
|
153
|
+
|
|
154
|
+
def save_context(self, context):
|
|
155
|
+
"""Save the gathered context to a file."""
|
|
156
|
+
with open(self.output_file, 'w', encoding='utf-8') as f:
|
|
157
|
+
f.write(context)
|
|
158
|
+
|
|
159
|
+
def get_context_tree(self):
|
|
160
|
+
"""Generate a formatted tree structure of included files and folders."""
|
|
161
|
+
tree = []
|
|
162
|
+
start_dir = Path(self.directory)
|
|
163
|
+
|
|
164
|
+
def add_to_tree(path, prefix=''):
|
|
165
|
+
contents = sorted(path.iterdir())
|
|
166
|
+
pointers = [('└── ' if i == len(contents) - 1 else '├── ') for i in range(len(contents))]
|
|
167
|
+
for pointer, item in zip(pointers, contents):
|
|
168
|
+
# Use should_ignore for consistency
|
|
169
|
+
if self.should_ignore(item):
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
rel_path = item.relative_to(start_dir)
|
|
173
|
+
tree.append(f"{prefix}{pointer}{rel_path}")
|
|
174
|
+
|
|
175
|
+
if item.is_dir():
|
|
176
|
+
add_to_tree(item, prefix + (' ' if pointer == '└── ' else '│ '))
|
|
177
|
+
|
|
178
|
+
add_to_tree(start_dir)
|
|
179
|
+
return '\n'.join(tree)
|
|
180
|
+
|
|
181
|
+
def run(self):
|
|
182
|
+
"""Execute the context gathering, truncation, and reporting."""
|
|
183
|
+
context = self.gather_context()
|
|
184
|
+
context = self.truncate_context(context)
|
|
185
|
+
token_count = self.count_tokens(context)
|
|
186
|
+
print(f"Context gathered successfully.")
|
|
187
|
+
print(f"Total number of tokens (estimated): {token_count}")
|
|
188
|
+
# self.save_context(context)
|
|
189
|
+
context_tree = self.get_context_tree()
|
|
190
|
+
logger.debug(f"Context tree:\n{context_tree}")
|
|
191
|
+
return context, token_count, context_tree
|
|
192
|
+
|
|
193
|
+
def main():
|
|
194
|
+
gatherer = ContextGatherer()
|
|
195
|
+
context, token_count, context_tree = gatherer.run()
|
|
196
|
+
print(f"\nThe context contains approximately {token_count} tokens.")
|
|
197
|
+
print("First 500 characters of context:")
|
|
198
|
+
print(context[:500] + "...")
|
|
199
|
+
|
|
200
|
+
if __name__ == "__main__":
|
|
201
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "PraisonAI"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.54"
|
|
4
4
|
description = "PraisonAI application combines AutoGen and CrewAI or similar frameworks into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customization, and efficient human-agent collaboration."
|
|
5
5
|
authors = ["Mervin Praison"]
|
|
6
6
|
license = ""
|
|
@@ -99,5 +99,5 @@ google = ["langchain-google-genai"]
|
|
|
99
99
|
openai = ["langchain-openai"]
|
|
100
100
|
anthropic = ["langchain-anthropic"]
|
|
101
101
|
cohere = ["langchain-cohere"]
|
|
102
|
-
chat = ["chainlit", "litellm"]
|
|
102
|
+
chat = ["chainlit", "litellm", "aiosqlite", "greenlet"]
|
|
103
103
|
code = ["chainlit", "litellm", "aiosqlite", "greenlet"]
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import fnmatch
|
|
3
|
-
import re
|
|
4
|
-
import yaml
|
|
5
|
-
import logging
|
|
6
|
-
# Set up logging
|
|
7
|
-
logger = logging.getLogger(__name__)
|
|
8
|
-
log_level = os.getenv("LOGLEVEL", "INFO").upper()
|
|
9
|
-
logger.handlers = []
|
|
10
|
-
|
|
11
|
-
# Set up logging to console
|
|
12
|
-
console_handler = logging.StreamHandler()
|
|
13
|
-
console_handler.setLevel(log_level)
|
|
14
|
-
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
15
|
-
console_handler.setFormatter(console_formatter)
|
|
16
|
-
logger.addHandler(console_handler)
|
|
17
|
-
|
|
18
|
-
# Set the logging level for the logger
|
|
19
|
-
logger.setLevel(log_level)
|
|
20
|
-
|
|
21
|
-
class ContextGatherer:
|
|
22
|
-
def __init__(self, directory='.', output_file='context.txt',
|
|
23
|
-
relevant_extensions=None, max_file_size=1_000_000, max_tokens=900000):
|
|
24
|
-
self.directory = directory
|
|
25
|
-
self.output_file = output_file
|
|
26
|
-
self.relevant_extensions = relevant_extensions or [
|
|
27
|
-
'.py', # Python
|
|
28
|
-
'.js', # JavaScript
|
|
29
|
-
'.ts', # TypeScript
|
|
30
|
-
'.java', # Java
|
|
31
|
-
'.rb', # Ruby
|
|
32
|
-
'.php', # PHP
|
|
33
|
-
'.pl', # Perl
|
|
34
|
-
'.pm', # Perl Module
|
|
35
|
-
'.c', # C
|
|
36
|
-
'.h', # C Header
|
|
37
|
-
'.cpp', # C++
|
|
38
|
-
'.hpp', # C++ Header
|
|
39
|
-
'.cs', # C#
|
|
40
|
-
'.vb', # Visual Basic
|
|
41
|
-
'.swift', # Swift
|
|
42
|
-
'.kt', # Kotlin
|
|
43
|
-
'.m', # Objective-C
|
|
44
|
-
'.mm', # Objective-C++
|
|
45
|
-
'.go', # Go
|
|
46
|
-
'.rs', # Rust
|
|
47
|
-
'.hs', # Haskell
|
|
48
|
-
'.r', # R
|
|
49
|
-
'.lua', # Lua
|
|
50
|
-
'.sh', # Shell Script
|
|
51
|
-
'.bat', # Batch File
|
|
52
|
-
'.clj', # Clojure
|
|
53
|
-
'.scala', # Scala
|
|
54
|
-
'.erl', # Erlang
|
|
55
|
-
'.ex', # Elixir
|
|
56
|
-
'.ml', # OCaml
|
|
57
|
-
'.fs', # F#
|
|
58
|
-
'.groovy', # Groovy
|
|
59
|
-
'.jsm', # JavaScript Module
|
|
60
|
-
'.jsx', # JavaScript XML
|
|
61
|
-
'.tsx', # TypeScript XML
|
|
62
|
-
'.yaml', # YAML
|
|
63
|
-
]
|
|
64
|
-
self.max_file_size = max_file_size
|
|
65
|
-
self.max_tokens = int(os.getenv("PRAISONAI_MAX_TOKENS", max_tokens))
|
|
66
|
-
self.ignore_patterns = self.get_ignore_patterns()
|
|
67
|
-
|
|
68
|
-
def get_ignore_patterns(self):
|
|
69
|
-
"""Read .gitignore file and return ignore patterns."""
|
|
70
|
-
|
|
71
|
-
# 1. Check for .praisonignore
|
|
72
|
-
praisonignore_path = os.path.join(self.directory, '.praisonignore')
|
|
73
|
-
if os.path.exists(praisonignore_path):
|
|
74
|
-
with open(praisonignore_path, 'r') as f:
|
|
75
|
-
praisonignore_patterns = [line.strip() for line in f if line.strip() and not line.startswith('#')]
|
|
76
|
-
logger.debug(f"Using ignore patterns from .praisonignore: {praisonignore_patterns}")
|
|
77
|
-
return praisonignore_patterns
|
|
78
|
-
|
|
79
|
-
# 2. Fallback to settings.yaml
|
|
80
|
-
settings_path = os.path.join(self.directory, 'settings.yaml')
|
|
81
|
-
if os.path.exists(settings_path):
|
|
82
|
-
with open(settings_path, 'r') as f:
|
|
83
|
-
settings = yaml.safe_load(f)
|
|
84
|
-
if 'code' in settings and 'ignore_files' in settings['code']:
|
|
85
|
-
logger.debug(f"Using ignore patterns from settings.yaml: {settings['code']['ignore_files']}")
|
|
86
|
-
return settings['code']['ignore_files']
|
|
87
|
-
|
|
88
|
-
# 3. Fallback to PRAISONAI_IGNORE_FILES env variable
|
|
89
|
-
ignore_files_env = os.getenv("PRAISONAI_IGNORE_FILES")
|
|
90
|
-
if ignore_files_env:
|
|
91
|
-
logger.debug(f"Using ignore patterns from PRAISONAI_IGNORE_FILES: {ignore_files_env}")
|
|
92
|
-
return ignore_files_env.split(",")
|
|
93
|
-
|
|
94
|
-
default_patterns = [".*", "*.pyc", "__pycache__", ".git", ".gitignore", ".vscode",
|
|
95
|
-
".idea", ".DS_Store", "*.lock", "*.pyc", ".env",
|
|
96
|
-
"docs", "tests", "test", "tmp", "temp",
|
|
97
|
-
"*.txt", "*.md", "*.json", "*.csv", "*.tsv","public",
|
|
98
|
-
"*.sql", "*.sqlite", "*.db", "*.db3", "*.sqlite3", "*.log", "*.zip", "*.gz",
|
|
99
|
-
"*.tar", "*.rar", "*.7z", "*.pdf", "*.jpg", "*.jpeg", "*.png", "*.gif", "*.svg",
|
|
100
|
-
"cookbooks", "assets", "__pycache__", "dist", "build", "node_modules", "venv",]
|
|
101
|
-
gitignore_path = os.path.join(self.directory, '.gitignore')
|
|
102
|
-
if os.path.exists(gitignore_path):
|
|
103
|
-
with open(gitignore_path, 'r') as f:
|
|
104
|
-
gitignore_patterns = [line.strip() for line in f if line.strip() and not line.startswith('#')]
|
|
105
|
-
logger.debug(f"Ignored gitignore and default files: {ignore_files_env}")
|
|
106
|
-
return list(set(default_patterns + gitignore_patterns))
|
|
107
|
-
return default_patterns
|
|
108
|
-
|
|
109
|
-
def should_ignore(self, file_path):
|
|
110
|
-
"""Check if a file should be ignored based on patterns."""
|
|
111
|
-
relative_path = os.path.relpath(file_path, self.directory)
|
|
112
|
-
if relative_path.startswith('.'):
|
|
113
|
-
return True
|
|
114
|
-
for pattern in self.ignore_patterns:
|
|
115
|
-
if fnmatch.fnmatch(relative_path, pattern):
|
|
116
|
-
return True
|
|
117
|
-
return False
|
|
118
|
-
|
|
119
|
-
def is_relevant_file(self, file_path):
|
|
120
|
-
"""Determine if a file is relevant for the context."""
|
|
121
|
-
if os.path.getsize(file_path) > self.max_file_size:
|
|
122
|
-
return False
|
|
123
|
-
return any(file_path.endswith(ext) for ext in self.relevant_extensions)
|
|
124
|
-
|
|
125
|
-
def gather_context(self):
|
|
126
|
-
"""Gather context from relevant files in the directory."""
|
|
127
|
-
context = []
|
|
128
|
-
total_files = sum(len(files) for _, _, files in os.walk(self.directory))
|
|
129
|
-
processed_files = 0
|
|
130
|
-
|
|
131
|
-
for root, dirs, files in os.walk(self.directory):
|
|
132
|
-
dirs[:] = [d for d in dirs if not self.should_ignore(os.path.join(root, d))]
|
|
133
|
-
for file in files:
|
|
134
|
-
file_path = os.path.join(root, file)
|
|
135
|
-
if not self.should_ignore(file_path) and self.is_relevant_file(file_path):
|
|
136
|
-
try:
|
|
137
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
|
138
|
-
content = f.read()
|
|
139
|
-
context.append(f"File: {file_path}\n\n{content}\n\n{'='*50}\n")
|
|
140
|
-
except Exception as e:
|
|
141
|
-
print(f"Error reading {file_path}: {e}")
|
|
142
|
-
processed_files += 1
|
|
143
|
-
print(f"\rProcessed {processed_files}/{total_files} files", end="", flush=True)
|
|
144
|
-
print() # New line after progress indicator
|
|
145
|
-
return '\n'.join(context)
|
|
146
|
-
|
|
147
|
-
def count_tokens(self, text):
|
|
148
|
-
"""Count the number of tokens in the given text using a simple tokenizer."""
|
|
149
|
-
# Split on whitespace and punctuation
|
|
150
|
-
tokens = re.findall(r'\b\w+\b|[^\w\s]', text)
|
|
151
|
-
return len(tokens)
|
|
152
|
-
|
|
153
|
-
def truncate_context(self, context):
|
|
154
|
-
"""Truncate context to fit within the specified token limit."""
|
|
155
|
-
tokens = re.findall(r'\b\w+\b|[^\w\s]', context)
|
|
156
|
-
if len(tokens) > self.max_tokens:
|
|
157
|
-
truncated_tokens = tokens[:self.max_tokens]
|
|
158
|
-
return ' '.join(truncated_tokens)
|
|
159
|
-
return context
|
|
160
|
-
|
|
161
|
-
def save_context(self, context):
|
|
162
|
-
"""Save the gathered context to a file."""
|
|
163
|
-
with open(self.output_file, 'w', encoding='utf-8') as f:
|
|
164
|
-
f.write(context)
|
|
165
|
-
|
|
166
|
-
def get_context_tree(self):
|
|
167
|
-
"""Generate a formatted tree structure of the folder, including only relevant files."""
|
|
168
|
-
tree = []
|
|
169
|
-
start_dir = os.path.abspath(self.directory)
|
|
170
|
-
|
|
171
|
-
def add_to_tree(path, prefix=''):
|
|
172
|
-
contents = sorted(os.listdir(path))
|
|
173
|
-
pointers = [('└── ' if i == len(contents) - 1 else '├── ') for i in range(len(contents))]
|
|
174
|
-
for pointer, name in zip(pointers, contents):
|
|
175
|
-
full_path = os.path.join(path, name)
|
|
176
|
-
if self.should_ignore(full_path):
|
|
177
|
-
continue
|
|
178
|
-
|
|
179
|
-
rel_path = os.path.relpath(full_path, start_dir)
|
|
180
|
-
tree.append(f"{prefix}{pointer}{name}")
|
|
181
|
-
|
|
182
|
-
if os.path.isdir(full_path):
|
|
183
|
-
add_to_tree(full_path, prefix + (' ' if pointer == '└── ' else '│ '))
|
|
184
|
-
elif self.is_relevant_file(full_path):
|
|
185
|
-
continue # We've already added the file to the tree
|
|
186
|
-
|
|
187
|
-
add_to_tree(start_dir)
|
|
188
|
-
return '\n'.join(tree)
|
|
189
|
-
|
|
190
|
-
def run(self):
|
|
191
|
-
"""Run the context gathering process and return the context and token count."""
|
|
192
|
-
context = self.gather_context()
|
|
193
|
-
context = self.truncate_context(context)
|
|
194
|
-
token_count = self.count_tokens(context)
|
|
195
|
-
print(f"Context gathered successfully.")
|
|
196
|
-
print(f"Total number of tokens (estimated): {token_count}")
|
|
197
|
-
# self.save_context(context)
|
|
198
|
-
context_tree = self.get_context_tree()
|
|
199
|
-
logger.debug(f"Context tree:\n{context_tree}")
|
|
200
|
-
return context, token_count, context_tree
|
|
201
|
-
|
|
202
|
-
def main():
|
|
203
|
-
gatherer = ContextGatherer(
|
|
204
|
-
directory='.',
|
|
205
|
-
output_file='context.txt',
|
|
206
|
-
relevant_extensions=['.py'],
|
|
207
|
-
max_file_size=500_000, # 500KB
|
|
208
|
-
max_tokens=60000
|
|
209
|
-
)
|
|
210
|
-
context, token_count, context_tree = gatherer.run()
|
|
211
|
-
print(f"\nThe context contains approximately {token_count} tokens.")
|
|
212
|
-
print("First 500 characters of context:")
|
|
213
|
-
print(context[:500] + "...")
|
|
214
|
-
|
|
215
|
-
if __name__ == "__main__":
|
|
216
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|