patchllm 0.2.1__py3-none-any.whl → 0.2.2__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.
- patchllm/context.py +9 -9
- patchllm/main.py +137 -97
- {patchllm-0.2.1.dist-info → patchllm-0.2.2.dist-info}/METADATA +31 -29
- patchllm-0.2.2.dist-info/RECORD +12 -0
- patchllm-0.2.1.dist-info/RECORD +0 -12
- {patchllm-0.2.1.dist-info → patchllm-0.2.2.dist-info}/WHEEL +0 -0
- {patchllm-0.2.1.dist-info → patchllm-0.2.2.dist-info}/entry_points.txt +0 -0
- {patchllm-0.2.1.dist-info → patchllm-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {patchllm-0.2.1.dist-info → patchllm-0.2.2.dist-info}/top_level.txt +0 -0
patchllm/context.py
CHANGED
@@ -175,23 +175,23 @@ def fetch_and_process_urls(urls: list[str]) -> str:
|
|
175
175
|
|
176
176
|
# --- Main Context Building Function ---
|
177
177
|
|
178
|
-
def build_context(
|
178
|
+
def build_context(scope: dict) -> dict | None:
|
179
179
|
"""
|
180
|
-
Builds the context string from files specified in the
|
180
|
+
Builds the context string from files specified in the scope.
|
181
181
|
|
182
182
|
Args:
|
183
|
-
|
183
|
+
scope (dict): The scope for file searching.
|
184
184
|
|
185
185
|
Returns:
|
186
186
|
dict: A dictionary with the source tree and formatted context, or None.
|
187
187
|
"""
|
188
|
-
base_path = Path(
|
188
|
+
base_path = Path(scope.get("path", ".")).resolve()
|
189
189
|
|
190
|
-
include_patterns =
|
191
|
-
exclude_patterns =
|
192
|
-
exclude_extensions =
|
193
|
-
search_words =
|
194
|
-
urls =
|
190
|
+
include_patterns = scope.get("include_patterns", [])
|
191
|
+
exclude_patterns = scope.get("exclude_patterns", [])
|
192
|
+
exclude_extensions = scope.get("exclude_extensions", DEFAULT_EXCLUDE_EXTENSIONS)
|
193
|
+
search_words = scope.get("search_words", [])
|
194
|
+
urls = scope.get("urls", [])
|
195
195
|
|
196
196
|
# Step 1: Find files
|
197
197
|
relevant_files = find_files(base_path, include_patterns, exclude_patterns)
|
patchllm/main.py
CHANGED
@@ -15,16 +15,16 @@ console = Console()
|
|
15
15
|
|
16
16
|
# --- Core Functions ---
|
17
17
|
|
18
|
-
def collect_context(
|
19
|
-
"""Builds the code context from a provided
|
18
|
+
def collect_context(scope_name, scopes):
|
19
|
+
"""Builds the code context from a provided scope dictionary."""
|
20
20
|
console.print("\n--- Building Code Context... ---", style="bold")
|
21
|
-
if not
|
22
|
-
raise FileNotFoundError("Could not find a '
|
23
|
-
|
24
|
-
if
|
25
|
-
raise KeyError(f"Context
|
21
|
+
if not scopes:
|
22
|
+
raise FileNotFoundError("Could not find a 'scopes.py' file.")
|
23
|
+
selected_scope = scopes.get(scope_name)
|
24
|
+
if selected_scope is None:
|
25
|
+
raise KeyError(f"Context scope '{scope_name}' not found in provided scopes file.")
|
26
26
|
|
27
|
-
context_object = build_context(
|
27
|
+
context_object = build_context(selected_scope)
|
28
28
|
if context_object:
|
29
29
|
tree, context = context_object.values()
|
30
30
|
console.print("--- Context Building Finished. The following files were extracted ---", style="bold")
|
@@ -34,9 +34,9 @@ def collect_context(config_name, configs):
|
|
34
34
|
console.print("--- Context Building Failed (No files found) ---", style="yellow")
|
35
35
|
return None
|
36
36
|
|
37
|
-
def
|
37
|
+
def run_llm_query(task_instructions, model_name, history, context=None):
|
38
38
|
"""
|
39
|
-
Assembles the final prompt, sends it to the LLM, and
|
39
|
+
Assembles the final prompt, sends it to the LLM, and returns the response.
|
40
40
|
"""
|
41
41
|
console.print("\n--- Sending Prompt to LLM... ---", style="bold")
|
42
42
|
final_prompt = task_instructions
|
@@ -53,23 +53,24 @@ def run_update(task_instructions, model_name, history, context=None):
|
|
53
53
|
history.append({"role": "assistant", "content": assistant_response_content})
|
54
54
|
|
55
55
|
if not assistant_response_content or not assistant_response_content.strip():
|
56
|
-
console.print("⚠️ Response is empty. Nothing to
|
57
|
-
return
|
56
|
+
console.print("⚠️ Response is empty. Nothing to process.", style="yellow")
|
57
|
+
return None
|
58
58
|
|
59
|
-
|
60
|
-
paste_response(assistant_response_content)
|
61
|
-
console.print("--- File Update Process Finished ---", style="bold")
|
59
|
+
return assistant_response_content
|
62
60
|
|
63
61
|
except Exception as e:
|
64
62
|
history.pop() # Keep history clean on error
|
65
63
|
raise RuntimeError(f"An error occurred while communicating with the LLM via litellm: {e}") from e
|
66
64
|
|
67
|
-
def
|
68
|
-
"""Utility function to write
|
69
|
-
console.print("
|
70
|
-
|
71
|
-
|
72
|
-
|
65
|
+
def write_to_file(file_path, content):
|
66
|
+
"""Utility function to write content to a file."""
|
67
|
+
console.print(f"Writing to {file_path}..", style="cyan")
|
68
|
+
try:
|
69
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
70
|
+
file.write(content)
|
71
|
+
console.print(f'✅ Content saved to {file_path}', style="green")
|
72
|
+
except Exception as e:
|
73
|
+
raise RuntimeError(f"Failed to write to file {file_path}: {e}") from e
|
73
74
|
|
74
75
|
def read_from_file(file_path):
|
75
76
|
"""Utility function to read and return the content of a file."""
|
@@ -82,18 +83,18 @@ def read_from_file(file_path):
|
|
82
83
|
except Exception as e:
|
83
84
|
raise RuntimeError(f"Failed to read from file {file_path}: {e}") from e
|
84
85
|
|
85
|
-
def
|
86
|
-
"""Interactively creates a new
|
87
|
-
console.print(f"\n--- Creating a new
|
86
|
+
def create_new_scope(scopes, scopes_file_str):
|
87
|
+
"""Interactively creates a new scope and saves it to the specified scopes file."""
|
88
|
+
console.print(f"\n--- Creating a new scope in '{scopes_file_str}' ---", style="bold")
|
88
89
|
|
89
90
|
try:
|
90
|
-
name = console.input("[bold]Enter a name for the new
|
91
|
+
name = console.input("[bold]Enter a name for the new scope: [/]").strip()
|
91
92
|
if not name:
|
92
|
-
console.print("❌
|
93
|
+
console.print("❌ Scope name cannot be empty.", style="red")
|
93
94
|
return
|
94
95
|
|
95
|
-
if name in
|
96
|
-
overwrite = console.input(f"
|
96
|
+
if name in scopes:
|
97
|
+
overwrite = console.input(f"Scope '[bold]{name}[/]' already exists. Overwrite? (y/n): ").lower()
|
97
98
|
if overwrite not in ['y', 'yes']:
|
98
99
|
console.print("Operation cancelled.", style="yellow")
|
99
100
|
return
|
@@ -108,29 +109,24 @@ def create_new_config(configs, configs_file_str):
|
|
108
109
|
exclude_raw = console.input('[cyan]> (e.g., "[bold]**/tests/*, venv/*[/]"): [/]').strip()
|
109
110
|
exclude_patterns = [p.strip() for p in exclude_raw.split(',') if p.strip()]
|
110
111
|
|
111
|
-
|
112
|
-
urls_raw = console.input('[cyan]> (e.g., "[bold]https://docs.example.com, ...[/]"): [/]').strip()
|
113
|
-
urls = [u.strip() for u in urls_raw.split(',') if u.strip()]
|
114
|
-
|
115
|
-
new_config_data = {
|
112
|
+
new_scope_data = {
|
116
113
|
"path": path,
|
117
114
|
"include_patterns": include_patterns,
|
118
|
-
"exclude_patterns": exclude_patterns
|
119
|
-
"urls": urls,
|
115
|
+
"exclude_patterns": exclude_patterns
|
120
116
|
}
|
121
117
|
|
122
|
-
|
118
|
+
scopes[name] = new_scope_data
|
123
119
|
|
124
|
-
with open(
|
125
|
-
f.write("#
|
126
|
-
f.write("
|
127
|
-
f.write(pprint.pformat(
|
120
|
+
with open(scopes_file_str, "w", encoding="utf-8") as f:
|
121
|
+
f.write("# scopes.py\n")
|
122
|
+
f.write("scopes = ")
|
123
|
+
f.write(pprint.pformat(scopes, indent=4))
|
128
124
|
f.write("\n")
|
129
125
|
|
130
|
-
console.print(f"\n✅ Successfully created and saved
|
126
|
+
console.print(f"\n✅ Successfully created and saved scope '[bold]{name}[/]' in '[bold]{scopes_file_str}[/]'.", style="green")
|
131
127
|
|
132
128
|
except KeyboardInterrupt:
|
133
|
-
console.print("\n\n⚠️
|
129
|
+
console.print("\n\n⚠️ Scope creation cancelled by user.", style="yellow")
|
134
130
|
return
|
135
131
|
|
136
132
|
def main():
|
@@ -139,73 +135,81 @@ def main():
|
|
139
135
|
"""
|
140
136
|
load_dotenv()
|
141
137
|
|
142
|
-
|
138
|
+
scopes_file_path = os.getenv("PATCHLLM_SCOPES_FILE", "./scopes.py")
|
143
139
|
|
144
140
|
parser = argparse.ArgumentParser(
|
145
141
|
description="A CLI tool to apply code changes using an LLM.",
|
146
142
|
formatter_class=argparse.RawTextHelpFormatter
|
147
143
|
)
|
148
144
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
145
|
+
# --- Group: Core Patching Flow ---
|
146
|
+
patch_group = parser.add_argument_group('Core Patching Flow')
|
147
|
+
patch_group.add_argument("-s", "--scope", type=str, default=None, help="Name of the scope to use from the scopes file.")
|
148
|
+
patch_group.add_argument("-t", "--task", type=str, default=None, help="The task instructions to guide the assistant.")
|
149
|
+
patch_group.add_argument("-p", "--patch", action="store_true", help="Query the LLM and directly apply the file updates from the response. Requires --task.")
|
150
|
+
|
151
|
+
# --- Group: Scope Management ---
|
152
|
+
scope_group = parser.add_argument_group('Scope Management')
|
153
|
+
scope_group.add_argument("-i", "--init", action="store_true", help="Create a new scope interactively.")
|
154
|
+
scope_group.add_argument("-sl", "--list-scopes", action="store_true", help="List all available scopes from the scopes file and exit.")
|
155
|
+
scope_group.add_argument("-ss", "--show-scope", type=str, help="Display the settings for a specific scope and exit.")
|
156
|
+
|
157
|
+
# --- Group: I/O Utils---
|
158
|
+
code_io = parser.add_argument_group('Code I/O')
|
159
|
+
code_io.add_argument("-co", "--context-out", nargs='?', const="context.md", default=None, help="Export the generated context to a file. Defaults to 'context.md'.")
|
160
|
+
code_io.add_argument("-ci", "--context-in", type=str, default=None, help="Import a previously saved context from a file.")
|
161
|
+
code_io.add_argument("-tf", "--to-file", nargs='?', const="response.md", default=None, help="Query the LLM and save the response to a file. Requires --task. Defaults to 'response.md'.")
|
162
|
+
code_io.add_argument("-tc", "--to-clipboard", action="store_true", help="Query the LLM and save the response to the clipboard. Requires --task.")
|
163
|
+
code_io.add_argument("-ff", "--from-file", type=str, default=None, help="Apply code updates directly from a file.")
|
164
|
+
code_io.add_argument("-fc", "--from-clipboard", action="store_true", help="Apply code updates directly from the clipboard.")
|
163
165
|
|
164
|
-
|
165
|
-
parser.
|
166
|
+
# --- Group: General Options ---
|
167
|
+
options_group = parser.add_argument_group('General Options')
|
168
|
+
options_group.add_argument("-m", "--model", type=str, default="gemini/gemini-2.5-flash", help="Model name to use (e.g., 'gpt-4o', 'claude-3-sonnet').")
|
169
|
+
options_group.add_argument("-v", "--voice", type=str, default="False", help="Enable voice interaction for providing task instructions. (True/False)")
|
166
170
|
|
167
171
|
args = parser.parse_args()
|
168
172
|
|
169
173
|
try:
|
170
|
-
|
174
|
+
scopes = load_from_py_file(scopes_file_path, "scopes")
|
171
175
|
except FileNotFoundError:
|
172
|
-
|
173
|
-
if not any([args.init, args.
|
174
|
-
console.print(f"⚠️
|
176
|
+
scopes = {}
|
177
|
+
if not any([args.init, args.list_scopes, args.show_scope]):
|
178
|
+
console.print(f"⚠️ Scope file '{scopes_file_path}' not found. You can create one with the --init flag.", style="yellow")
|
175
179
|
|
176
180
|
|
177
|
-
if args.
|
178
|
-
console.print(f"Available
|
179
|
-
if not
|
180
|
-
console.print(f" -> No
|
181
|
+
if args.list_scopes:
|
182
|
+
console.print(f"Available scopes in '[bold]{scopes_file_path}[/]':", style="bold")
|
183
|
+
if not scopes:
|
184
|
+
console.print(f" -> No scopes found or '{scopes_file_path}' is missing.")
|
181
185
|
else:
|
182
|
-
for
|
183
|
-
console.print(f" - {
|
186
|
+
for scope_name in scopes:
|
187
|
+
console.print(f" - {scope_name}")
|
184
188
|
return
|
185
189
|
|
186
|
-
if args.
|
187
|
-
|
188
|
-
if not
|
189
|
-
console.print(f"⚠️
|
190
|
+
if args.show_scope:
|
191
|
+
scope_name = args.show_scope
|
192
|
+
if not scopes:
|
193
|
+
console.print(f"⚠️ Scope file '{scopes_file_path}' not found or is empty.", style="yellow")
|
190
194
|
return
|
191
195
|
|
192
|
-
|
193
|
-
if
|
194
|
-
|
196
|
+
scope_data = scopes.get(scope_name)
|
197
|
+
if scope_data:
|
198
|
+
pretty_scope = pprint.pformat(scope_data, indent=2)
|
195
199
|
console.print(
|
196
200
|
Panel(
|
197
|
-
|
198
|
-
title=f"[bold cyan]
|
199
|
-
subtitle=f"[dim]from {
|
201
|
+
pretty_scope,
|
202
|
+
title=f"[bold cyan]Scope: '{scope_name}'[/]",
|
203
|
+
subtitle=f"[dim]from {scopes_file_path}[/dim]",
|
200
204
|
border_style="blue"
|
201
205
|
)
|
202
206
|
)
|
203
207
|
else:
|
204
|
-
console.print(f"❌
|
208
|
+
console.print(f"❌ Scope '[bold]{scope_name}[/]' not found in '{scopes_file_path}'.", style="red")
|
205
209
|
return
|
206
210
|
|
207
211
|
if args.init:
|
208
|
-
|
212
|
+
create_new_scope(scopes, scopes_file_path)
|
209
213
|
return
|
210
214
|
|
211
215
|
if args.from_clipboard:
|
@@ -260,27 +264,63 @@ def main():
|
|
260
264
|
speak(f"You said: {task}. Should I proceed?")
|
261
265
|
confirm = listen()
|
262
266
|
if confirm and "yes" in confirm.lower():
|
263
|
-
|
264
|
-
|
265
|
-
|
267
|
+
if not args.scope:
|
268
|
+
parser.error("A --scope name is required when using --voice.")
|
269
|
+
context = collect_context(args.scope, scopes)
|
270
|
+
llm_response = run_llm_query(task, args.model, history, context)
|
271
|
+
if llm_response:
|
272
|
+
paste_response(llm_response)
|
273
|
+
speak("Changes applied.")
|
266
274
|
else:
|
267
275
|
speak("Cancelled.")
|
268
276
|
return
|
269
277
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
if
|
274
|
-
parser.error("A
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
278
|
+
# --- Main LLM Task Logic ---
|
279
|
+
if args.task:
|
280
|
+
action_flags = [args.patch, args.to_file is not None, args.to_clipboard]
|
281
|
+
if sum(action_flags) == 0:
|
282
|
+
parser.error("A task was provided, but no action was specified. Use --patch, --to-file, or --to-clipboard.")
|
283
|
+
if sum(action_flags) > 1:
|
284
|
+
parser.error("Please specify only one action: --patch, --to-file, or --to-clipboard.")
|
285
|
+
|
286
|
+
if args.context_in:
|
287
|
+
context = read_from_file(args.context_in)
|
288
|
+
else:
|
289
|
+
if not args.scope:
|
290
|
+
parser.error("A --scope name is required to build context for a task.")
|
291
|
+
context = collect_context(args.scope, scopes)
|
292
|
+
if context and args.context_out:
|
293
|
+
write_to_file(args.context_out, context)
|
294
|
+
|
295
|
+
if not context:
|
296
|
+
console.print("Proceeding with task but without any file context.", style="yellow")
|
297
|
+
|
298
|
+
llm_response = run_llm_query(args.task, args.model, history, context)
|
299
|
+
|
300
|
+
if llm_response:
|
301
|
+
if args.patch:
|
302
|
+
console.print("\n--- Updating files ---", style="bold")
|
303
|
+
paste_response(llm_response)
|
304
|
+
console.print("--- File Update Process Finished ---", style="bold")
|
305
|
+
|
306
|
+
elif args.to_file is not None:
|
307
|
+
write_to_file(args.to_file, llm_response)
|
308
|
+
|
309
|
+
elif args.to_clipboard:
|
310
|
+
try:
|
311
|
+
import pyperclip
|
312
|
+
pyperclip.copy(llm_response)
|
313
|
+
console.print("✅ Copied LLM response to clipboard.", style="green")
|
314
|
+
except ImportError:
|
315
|
+
console.print("❌ The 'pyperclip' library is required for clipboard functionality.", style="red")
|
316
|
+
console.print("Please install it using: pip install pyperclip", style="cyan")
|
317
|
+
except Exception as e:
|
318
|
+
console.print(f"❌ An error occurred while copying to the clipboard: {e}", style="red")
|
319
|
+
|
320
|
+
elif args.scope and args.context_out:
|
321
|
+
context = collect_context(args.scope, scopes)
|
282
322
|
if context:
|
283
|
-
|
323
|
+
write_to_file(args.context_out, context)
|
284
324
|
|
285
325
|
if __name__ == "__main__":
|
286
326
|
main()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: patchllm
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.2
|
4
4
|
Summary: Lightweight tool to manage contexts and update code with LLMs
|
5
5
|
Author: nassimberrada
|
6
6
|
License: MIT License
|
@@ -49,21 +49,21 @@ Dynamic: license-file
|
|
49
49
|
PatchLLM is a command-line tool that lets you flexibly build LLM context from your codebase using glob patterns, URLs, and keyword searches. It then automatically applies file edits directly from the LLM's response.
|
50
50
|
|
51
51
|
## Usage
|
52
|
-
PatchLLM is designed to be used directly from your terminal.
|
52
|
+
PatchLLM is designed to be used directly from your terminal. The core workflow is to define a **scope** of files, provide a **task**, and choose an **action** (like patching files directly).
|
53
53
|
|
54
|
-
### 1. Initialize a
|
55
|
-
The easiest way to get started is to run the interactive initializer. This will create a `
|
54
|
+
### 1. Initialize a Scope
|
55
|
+
The easiest way to get started is to run the interactive initializer. This will create a `scopes.py` file for you, which holds your saved scopes.
|
56
56
|
|
57
57
|
```bash
|
58
58
|
patchllm --init
|
59
59
|
```
|
60
60
|
|
61
|
-
This will guide you through creating your first
|
61
|
+
This will guide you through creating your first scope, including setting a base path and file patterns. You can add multiple scopes to this file for different projects or tasks.
|
62
62
|
|
63
|
-
A generated `
|
63
|
+
A generated `scopes.py` might look like this:
|
64
64
|
```python
|
65
|
-
#
|
66
|
-
|
65
|
+
# scopes.py
|
66
|
+
scopes = {
|
67
67
|
"default": {
|
68
68
|
"path": ".",
|
69
69
|
"include_patterns": ["**/*.py"],
|
@@ -78,45 +78,47 @@ configs = {
|
|
78
78
|
```
|
79
79
|
|
80
80
|
### 2. Run a Task
|
81
|
-
Use the `patchllm` command with a
|
81
|
+
Use the `patchllm` command with a scope, a task, and an action flag like `--patch` (`-p`).
|
82
82
|
|
83
83
|
```bash
|
84
|
-
# Apply a change using the 'default'
|
85
|
-
patchllm
|
84
|
+
# Apply a change using the 'default' scope and the --patch action
|
85
|
+
patchllm -s default -t "Add type hints to the main function in main.py" -p
|
86
86
|
```
|
87
87
|
|
88
88
|
The tool will then:
|
89
|
-
1. Build a context from the files and URLs matching your
|
89
|
+
1. Build a context from the files and URLs matching your `default` scope.
|
90
90
|
2. Send the context and your task to the configured LLM.
|
91
91
|
3. Parse the response and automatically write the changes to the relevant files.
|
92
92
|
|
93
93
|
### All Commands & Options
|
94
94
|
|
95
|
-
####
|
96
|
-
*
|
97
|
-
*
|
98
|
-
*
|
95
|
+
#### Core Patching Flow
|
96
|
+
* `-s, --scope <name>`: Name of the scope to use from your `scopes.py` file.
|
97
|
+
* `-t, --task "<instruction>"`: The task instruction for the LLM.
|
98
|
+
* `-p, --patch`: Query the LLM and directly apply the file updates from the response. **This is the main action flag.**
|
99
99
|
|
100
|
-
####
|
101
|
-
*
|
102
|
-
*
|
103
|
-
*
|
100
|
+
#### Scope Management
|
101
|
+
* `-i, --init`: Create a new scope interactively.
|
102
|
+
* `-sl, --list-scopes`: List all available scopes from your `scopes.py` file.
|
103
|
+
* `-ss, --show-scope <name>`: Display the settings for a specific scope.
|
104
104
|
|
105
|
-
#### Context
|
106
|
-
*
|
107
|
-
*
|
108
|
-
*
|
105
|
+
#### I/O & Context Management
|
106
|
+
* `-co, --context-out [filename]`: Export the generated context to a file (defaults to `context.md`) instead of running a task.
|
107
|
+
* `-ci, --context-in <filename>`: Use a previously saved context file as input for a task.
|
108
|
+
* `-tf, --to-file [filename]`: Send the LLM response to a file (defaults to `response.md`) instead of patching directly.
|
109
|
+
* `-tc, --to-clipboard`: Copy the LLM response to the clipboard.
|
110
|
+
* `-ff, --from-file <filename>`: Apply patches from a local file instead of an LLM response.
|
111
|
+
* `-fc, --from-clipboard`: Apply patches directly from your clipboard content.
|
109
112
|
|
110
|
-
####
|
111
|
-
* `--
|
112
|
-
* `--
|
113
|
-
* `--voice True`: Use voice recognition to provide the task instruction. Requires extra dependencies.
|
113
|
+
#### General Options
|
114
|
+
* `--model <model_name>`: Specify a different model (e.g., `gpt-4o`). Defaults to `gemini/gemini-1.5-flash`.
|
115
|
+
* `--voice`: Enable voice recognition to provide the task instruction.
|
114
116
|
|
115
117
|
### Setup
|
116
118
|
|
117
119
|
PatchLLM uses [LiteLLM](https://github.com/BerriAI/litellm) under the hood. Please refer to their documentation for setting up API keys (e.g., `OPENAI_API_KEY`, `GEMINI_API_KEY`) in a `.env` file and for a full list of available models.
|
118
120
|
|
119
|
-
To use the voice feature (`--voice
|
121
|
+
To use the voice feature (`--voice`), you will need to install extra dependencies:
|
120
122
|
```bash
|
121
123
|
pip install "speechrecognition>=3.10" "pyttsx3>=2.90"
|
122
124
|
# Note: speechrecognition may require PyAudio, which might have system-level dependencies.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
patchllm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
patchllm/context.py,sha256=_05amx0WgmHGhJrjB72K-QaWEP6u7vIAanhNjqL7YtQ,8503
|
3
|
+
patchllm/listener.py,sha256=VjQ_CrSRT4-PolXAAradPKyt8NSUaUQwvgPNH7Oi9q0,968
|
4
|
+
patchllm/main.py,sha256=y5OGNXEvRWTlUpxj9N6j3Ryhko-XVy_NkQ5axhCzljI,14944
|
5
|
+
patchllm/parser.py,sha256=DNcf9iUH8umExfK78CSIwac1Bbu7K9iE3754y7CvYzs,3229
|
6
|
+
patchllm/utils.py,sha256=hz28hd017gRGT632VQAYLPdX0KAS1GLvZzeUDCKbLc0,647
|
7
|
+
patchllm-0.2.2.dist-info/licenses/LICENSE,sha256=vZxgIRNxffjkTV2NWLemgYjDRu0hSMTyFXCZ1zEWbUc,1077
|
8
|
+
patchllm-0.2.2.dist-info/METADATA,sha256=xr-iByWSfelUa5xwGgd7G_mROO17ZtxU9QVILbfZQEc,5689
|
9
|
+
patchllm-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
10
|
+
patchllm-0.2.2.dist-info/entry_points.txt,sha256=xm-W7FKOQd3o9RgK_4krVnO2sC8phpYxDCobf0htLiU,48
|
11
|
+
patchllm-0.2.2.dist-info/top_level.txt,sha256=SLIZj9EhBXbSnYrbnV8EjL-OfNz-hXRwABCPCjE5Fas,9
|
12
|
+
patchllm-0.2.2.dist-info/RECORD,,
|
patchllm-0.2.1.dist-info/RECORD
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
patchllm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
patchllm/context.py,sha256=BZxhUUnlwejcY3I5jfIuk9r2xAqgN1eNcOARmD5VLzU,8520
|
3
|
-
patchllm/listener.py,sha256=VjQ_CrSRT4-PolXAAradPKyt8NSUaUQwvgPNH7Oi9q0,968
|
4
|
-
patchllm/main.py,sha256=lwEE98yFULW1C0Lo9i1z5u9_r7zJYPSalWxh8juSRT8,12931
|
5
|
-
patchllm/parser.py,sha256=DNcf9iUH8umExfK78CSIwac1Bbu7K9iE3754y7CvYzs,3229
|
6
|
-
patchllm/utils.py,sha256=hz28hd017gRGT632VQAYLPdX0KAS1GLvZzeUDCKbLc0,647
|
7
|
-
patchllm-0.2.1.dist-info/licenses/LICENSE,sha256=vZxgIRNxffjkTV2NWLemgYjDRu0hSMTyFXCZ1zEWbUc,1077
|
8
|
-
patchllm-0.2.1.dist-info/METADATA,sha256=zbk1TqpmkEbpXV1jtjLP34CYZ_MyDboZCRQ9FTnNCNk,5429
|
9
|
-
patchllm-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
10
|
-
patchllm-0.2.1.dist-info/entry_points.txt,sha256=xm-W7FKOQd3o9RgK_4krVnO2sC8phpYxDCobf0htLiU,48
|
11
|
-
patchllm-0.2.1.dist-info/top_level.txt,sha256=SLIZj9EhBXbSnYrbnV8EjL-OfNz-hXRwABCPCjE5Fas,9
|
12
|
-
patchllm-0.2.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|