auto-coder 0.1.226__py3-none-any.whl → 0.1.228__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 auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.226.dist-info → auto_coder-0.1.228.dist-info}/METADATA +2 -2
- {auto_coder-0.1.226.dist-info → auto_coder-0.1.228.dist-info}/RECORD +30 -27
- autocoder/auto_coder.py +424 -131
- autocoder/chat_auto_coder.py +267 -143
- autocoder/chat_auto_coder_lang.py +30 -1
- autocoder/common/__init__.py +2 -1
- autocoder/common/code_auto_generate.py +23 -9
- autocoder/common/code_auto_generate_diff.py +23 -9
- autocoder/common/code_auto_generate_editblock.py +23 -9
- autocoder/common/code_auto_generate_strict_diff.py +23 -9
- autocoder/common/code_auto_merge.py +3 -3
- autocoder/common/code_auto_merge_diff.py +3 -4
- autocoder/common/code_auto_merge_editblock.py +3 -7
- autocoder/common/command_completer.py +6 -0
- autocoder/common/files.py +26 -0
- autocoder/common/types.py +1 -0
- autocoder/common/utils_code_auto_generate.py +38 -0
- autocoder/dispacher/actions/action.py +4 -4
- autocoder/dispacher/actions/plugins/action_regex_project.py +6 -2
- autocoder/models.py +158 -0
- autocoder/pyproject/__init__.py +5 -8
- autocoder/regexproject/__init__.py +2 -3
- autocoder/suffixproject/__init__.py +2 -3
- autocoder/tsproject/__init__.py +2 -2
- autocoder/utils/rest.py +3 -2
- autocoder/version.py +1 -1
- {auto_coder-0.1.226.dist-info → auto_coder-0.1.228.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.226.dist-info → auto_coder-0.1.228.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.226.dist-info → auto_coder-0.1.228.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.226.dist-info → auto_coder-0.1.228.dist-info}/top_level.txt +0 -0
|
@@ -6,6 +6,7 @@ from autocoder.utils.queue_communicate import queue_communicate, CommunicateEven
|
|
|
6
6
|
from autocoder.common import sys_prompt
|
|
7
7
|
from concurrent.futures import ThreadPoolExecutor
|
|
8
8
|
import json
|
|
9
|
+
from autocoder.common.utils_code_auto_generate import chat_with_continue
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class CodeAutoGenerateDiff:
|
|
@@ -336,35 +337,48 @@ class CodeAutoGenerateDiff:
|
|
|
336
337
|
|
|
337
338
|
conversations_list = []
|
|
338
339
|
results = []
|
|
340
|
+
input_tokens_count = 0
|
|
341
|
+
generated_tokens_count = 0
|
|
339
342
|
if not self.args.human_as_model:
|
|
340
343
|
with ThreadPoolExecutor(max_workers=len(self.llms) * self.generate_times_same_model) as executor:
|
|
341
344
|
futures = []
|
|
342
345
|
for llm in self.llms:
|
|
343
346
|
for _ in range(self.generate_times_same_model):
|
|
344
347
|
futures.append(executor.submit(
|
|
345
|
-
llm
|
|
346
|
-
|
|
348
|
+
chat_with_continue, llm=llm, conversations=conversations, llm_config=llm_config))
|
|
349
|
+
temp_results = [future.result() for future in futures]
|
|
350
|
+
for result in temp_results:
|
|
351
|
+
results.append(result.content)
|
|
352
|
+
input_tokens_count += result.input_tokens_count
|
|
353
|
+
generated_tokens_count += result.generated_tokens_count
|
|
354
|
+
|
|
347
355
|
for result in results:
|
|
348
356
|
conversations_list.append(
|
|
349
357
|
conversations + [{"role": "assistant", "content": result}])
|
|
350
358
|
else:
|
|
351
359
|
for _ in range(self.args.human_model_num):
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
360
|
+
single_result = chat_with_continue(llm=self.llms[0], conversations=conversations, llm_config=llm_config)
|
|
361
|
+
results.append(single_result.content)
|
|
362
|
+
input_tokens_count += single_result.input_tokens_count
|
|
363
|
+
generated_tokens_count += single_result.generated_tokens_count
|
|
355
364
|
conversations_list.append(
|
|
356
|
-
conversations + [{"role": "assistant", "content":
|
|
357
|
-
|
|
365
|
+
conversations + [{"role": "assistant", "content": single_result.content}])
|
|
366
|
+
|
|
367
|
+
statistics = {
|
|
368
|
+
"input_tokens_count": input_tokens_count,
|
|
369
|
+
"generated_tokens_count": generated_tokens_count
|
|
370
|
+
}
|
|
371
|
+
|
|
358
372
|
if self.args.request_id and not self.args.skip_events:
|
|
359
373
|
_ = queue_communicate.send_event(
|
|
360
374
|
request_id=self.args.request_id,
|
|
361
375
|
event=CommunicateEvent(
|
|
362
376
|
event_type=CommunicateEventType.CODE_GENERATE_END.value,
|
|
363
|
-
data=json.dumps(
|
|
377
|
+
data=json.dumps(statistics, ensure_ascii=False),
|
|
364
378
|
),
|
|
365
379
|
)
|
|
366
380
|
|
|
367
|
-
return CodeGenerateResult(contents=results, conversations=conversations_list)
|
|
381
|
+
return CodeGenerateResult(contents=results, conversations=conversations_list, metadata=statistics)
|
|
368
382
|
|
|
369
383
|
def multi_round_run(
|
|
370
384
|
self, query: str, source_content: str, max_steps: int = 10
|
|
@@ -10,6 +10,7 @@ from autocoder.utils.queue_communicate import (
|
|
|
10
10
|
)
|
|
11
11
|
import json
|
|
12
12
|
from concurrent.futures import ThreadPoolExecutor
|
|
13
|
+
from autocoder.common.utils_code_auto_generate import chat_with_continue
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class CodeAutoGenerateEditBlock:
|
|
@@ -418,34 +419,47 @@ class CodeAutoGenerateEditBlock:
|
|
|
418
419
|
|
|
419
420
|
conversations_list = []
|
|
420
421
|
results = []
|
|
422
|
+
input_tokens_count = 0
|
|
423
|
+
generated_tokens_count = 0
|
|
421
424
|
if not self.args.human_as_model:
|
|
422
425
|
with ThreadPoolExecutor(max_workers=len(self.llms) * self.generate_times_same_model) as executor:
|
|
423
426
|
futures = []
|
|
424
427
|
for llm in self.llms:
|
|
425
428
|
for _ in range(self.generate_times_same_model):
|
|
426
429
|
futures.append(executor.submit(
|
|
427
|
-
llm
|
|
428
|
-
|
|
430
|
+
chat_with_continue,llm=llm, conversations=conversations, llm_config=llm_config))
|
|
431
|
+
temp_results = [future.result() for future in futures]
|
|
432
|
+
for result in temp_results:
|
|
433
|
+
results.append(result.content)
|
|
434
|
+
input_tokens_count += result.input_tokens_count
|
|
435
|
+
generated_tokens_count += result.generated_tokens_count
|
|
436
|
+
|
|
429
437
|
for result in results:
|
|
430
438
|
conversations_list.append(
|
|
431
439
|
conversations + [{"role": "assistant", "content": result}])
|
|
432
440
|
else:
|
|
433
441
|
for _ in range(self.args.human_model_num):
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
442
|
+
single_result = chat_with_continue(llm=self.llms[0], conversations=conversations, llm_config=llm_config)
|
|
443
|
+
results.append(single_result.content)
|
|
444
|
+
input_tokens_count += single_result.input_tokens_count
|
|
445
|
+
generated_tokens_count += single_result.generated_tokens_count
|
|
446
|
+
conversations_list.append(conversations + [{"role": "assistant", "content": single_result.content}])
|
|
447
|
+
|
|
448
|
+
statistics = {
|
|
449
|
+
"input_tokens_count": input_tokens_count,
|
|
450
|
+
"generated_tokens_count": generated_tokens_count
|
|
451
|
+
}
|
|
452
|
+
|
|
439
453
|
if self.args.request_id and not self.args.skip_events:
|
|
440
454
|
_ = queue_communicate.send_event(
|
|
441
455
|
request_id=self.args.request_id,
|
|
442
456
|
event=CommunicateEvent(
|
|
443
457
|
event_type=CommunicateEventType.CODE_GENERATE_END.value,
|
|
444
|
-
data=json.dumps(
|
|
458
|
+
data=json.dumps(statistics, ensure_ascii=False),
|
|
445
459
|
),
|
|
446
460
|
)
|
|
447
461
|
|
|
448
|
-
return CodeGenerateResult(contents=results, conversations=conversations_list)
|
|
462
|
+
return CodeGenerateResult(contents=results, conversations=conversations_list, metadata=statistics)
|
|
449
463
|
|
|
450
464
|
def multi_round_run(
|
|
451
465
|
self, query: str, source_content: str, max_steps: int = 10
|
|
@@ -6,6 +6,7 @@ from autocoder.utils.queue_communicate import queue_communicate, CommunicateEven
|
|
|
6
6
|
from autocoder.common import sys_prompt
|
|
7
7
|
from concurrent.futures import ThreadPoolExecutor
|
|
8
8
|
import json
|
|
9
|
+
from autocoder.common.utils_code_auto_generate import chat_with_continue
|
|
9
10
|
|
|
10
11
|
class CodeAutoGenerateStrictDiff:
|
|
11
12
|
def __init__(
|
|
@@ -306,34 +307,47 @@ class CodeAutoGenerateStrictDiff:
|
|
|
306
307
|
|
|
307
308
|
conversations_list = []
|
|
308
309
|
results = []
|
|
310
|
+
input_tokens_count = 0
|
|
311
|
+
generated_tokens_count = 0
|
|
309
312
|
if not self.args.human_as_model:
|
|
310
313
|
with ThreadPoolExecutor(max_workers=len(self.llms) * self.generate_times_same_model) as executor:
|
|
311
314
|
futures = []
|
|
312
315
|
for llm in self.llms:
|
|
313
316
|
for _ in range(self.generate_times_same_model):
|
|
314
317
|
futures.append(executor.submit(
|
|
315
|
-
llm
|
|
316
|
-
|
|
318
|
+
chat_with_continue, llm=llm, conversations=conversations, llm_config=llm_config))
|
|
319
|
+
temp_results = [future.result() for future in futures]
|
|
320
|
+
for result in temp_results:
|
|
321
|
+
results.append(result.content)
|
|
322
|
+
input_tokens_count += result.input_tokens_count
|
|
323
|
+
generated_tokens_count += result.generated_tokens_count
|
|
324
|
+
|
|
317
325
|
for result in results:
|
|
318
326
|
conversations_list.append(
|
|
319
327
|
conversations + [{"role": "assistant", "content": result}])
|
|
320
328
|
else:
|
|
321
329
|
for _ in range(self.args.human_model_num):
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
330
|
+
single_result = chat_with_continue(llm=self.llms[0], conversations=conversations, llm_config=llm_config)
|
|
331
|
+
results.append(single_result.content)
|
|
332
|
+
input_tokens_count += single_result.input_tokens_count
|
|
333
|
+
generated_tokens_count += single_result.generated_tokens_count
|
|
334
|
+
conversations_list.append(conversations + [{"role": "assistant", "content": single_result.content}])
|
|
335
|
+
|
|
336
|
+
statistics = {
|
|
337
|
+
"input_tokens_count": input_tokens_count,
|
|
338
|
+
"generated_tokens_count": generated_tokens_count
|
|
339
|
+
}
|
|
340
|
+
|
|
327
341
|
if self.args.request_id and not self.args.skip_events:
|
|
328
342
|
_ = queue_communicate.send_event(
|
|
329
343
|
request_id=self.args.request_id,
|
|
330
344
|
event=CommunicateEvent(
|
|
331
345
|
event_type=CommunicateEventType.CODE_GENERATE_END.value,
|
|
332
|
-
data=json.dumps(
|
|
346
|
+
data=json.dumps(statistics, ensure_ascii=False),
|
|
333
347
|
),
|
|
334
348
|
)
|
|
335
349
|
|
|
336
|
-
return CodeGenerateResult(contents=results, conversations=conversations_list)
|
|
350
|
+
return CodeGenerateResult(contents=results, conversations=conversations_list, metadata=statistics)
|
|
337
351
|
|
|
338
352
|
def multi_round_run(
|
|
339
353
|
self, query: str, source_content: str, max_steps: int = 10
|
|
@@ -9,6 +9,7 @@ from loguru import logger
|
|
|
9
9
|
from autocoder.common.types import CodeGenerateResult, MergeCodeWithoutEffect
|
|
10
10
|
from autocoder.common.code_modification_ranker import CodeModificationRanker
|
|
11
11
|
import hashlib
|
|
12
|
+
from autocoder.common import files as FileUtils
|
|
12
13
|
|
|
13
14
|
class PathAndCode(pydantic.BaseModel):
|
|
14
15
|
path: str
|
|
@@ -135,8 +136,7 @@ class CodeAutoMerge:
|
|
|
135
136
|
file_content_mapping[file_path] = block.content
|
|
136
137
|
else:
|
|
137
138
|
if file_path not in file_content_mapping:
|
|
138
|
-
|
|
139
|
-
file_content_mapping[file_path] = f.read()
|
|
139
|
+
file_content_mapping[file_path] = FileUtils.read_file(file_path)
|
|
140
140
|
if file_content_mapping[file_path] != block.content:
|
|
141
141
|
file_content_mapping[file_path] = block.content
|
|
142
142
|
else:
|
|
@@ -150,7 +150,7 @@ class CodeAutoMerge:
|
|
|
150
150
|
def _merge_code(self, content: str,force_skip_git:bool=False):
|
|
151
151
|
total = 0
|
|
152
152
|
|
|
153
|
-
file_content =
|
|
153
|
+
file_content = FileUtils.read_file(self.args.file)
|
|
154
154
|
md5 = hashlib.md5(file_content.encode('utf-8')).hexdigest()
|
|
155
155
|
# get the file name
|
|
156
156
|
file_name = os.path.basename(self.args.file)
|
|
@@ -17,6 +17,7 @@ from autocoder.common.search_replace import (
|
|
|
17
17
|
)
|
|
18
18
|
from autocoder.common.types import CodeGenerateResult, MergeCodeWithoutEffect
|
|
19
19
|
from autocoder.common.code_modification_ranker import CodeModificationRanker
|
|
20
|
+
from autocoder.common import files as FileUtils
|
|
20
21
|
|
|
21
22
|
class PathAndCode(pydantic.BaseModel):
|
|
22
23
|
path: str
|
|
@@ -438,8 +439,7 @@ class CodeAutoMergeDiff:
|
|
|
438
439
|
errors = []
|
|
439
440
|
for path, hunk in uniq:
|
|
440
441
|
full_path = self.abs_root_path(path)
|
|
441
|
-
|
|
442
|
-
content = f.read()
|
|
442
|
+
content = FileUtils.read_file(full_path)
|
|
443
443
|
|
|
444
444
|
original, _ = hunk_to_before_after(hunk)
|
|
445
445
|
|
|
@@ -488,8 +488,7 @@ class CodeAutoMergeDiff:
|
|
|
488
488
|
continue
|
|
489
489
|
|
|
490
490
|
if full_path not in file_content_mapping:
|
|
491
|
-
|
|
492
|
-
file_content_mapping[full_path] = f.read()
|
|
491
|
+
file_content_mapping[full_path] = FileUtils.read_file(full_path)
|
|
493
492
|
|
|
494
493
|
content = file_content_mapping[full_path]
|
|
495
494
|
new_content = do_replace(full_path, content, hunk)
|
|
@@ -20,7 +20,7 @@ import json
|
|
|
20
20
|
from typing import Union, List, Tuple
|
|
21
21
|
from autocoder.common.types import CodeGenerateResult, MergeCodeWithoutEffect
|
|
22
22
|
from autocoder.common.code_modification_ranker import CodeModificationRanker
|
|
23
|
-
|
|
23
|
+
from autocoder.common import files as FileUtils
|
|
24
24
|
|
|
25
25
|
class PathAndCode(pydantic.BaseModel):
|
|
26
26
|
path: str
|
|
@@ -235,9 +235,7 @@ class CodeAutoMergeEditBlock:
|
|
|
235
235
|
file_content_mapping[file_path] = update
|
|
236
236
|
else:
|
|
237
237
|
if file_path not in file_content_mapping:
|
|
238
|
-
|
|
239
|
-
temp = f.read()
|
|
240
|
-
file_content_mapping[file_path] = temp
|
|
238
|
+
file_content_mapping[file_path] = FileUtils.read_file(file_path)
|
|
241
239
|
existing_content = file_content_mapping[file_path]
|
|
242
240
|
|
|
243
241
|
# First try exact match
|
|
@@ -290,9 +288,7 @@ class CodeAutoMergeEditBlock:
|
|
|
290
288
|
changes_made = True
|
|
291
289
|
else:
|
|
292
290
|
if file_path not in file_content_mapping:
|
|
293
|
-
|
|
294
|
-
temp = f.read()
|
|
295
|
-
file_content_mapping[file_path] = temp
|
|
291
|
+
file_content_mapping[file_path] = FileUtils.read_file(file_path)
|
|
296
292
|
existing_content = file_content_mapping[file_path]
|
|
297
293
|
new_content = (
|
|
298
294
|
existing_content.replace(head, update, 1)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
def read_file(file_path):
|
|
2
|
+
"""Read a file with automatic encoding detection.
|
|
3
|
+
|
|
4
|
+
Tries common encodings in sequence (UTF-8 > GBK > UTF-16 > Latin-1) to handle
|
|
5
|
+
cross-platform encoding issues between Windows and Linux systems.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
file_path (str): Path to the file to read
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
str: The file contents as a string
|
|
12
|
+
|
|
13
|
+
Raises:
|
|
14
|
+
ValueError: If the file cannot be decoded with any of the tried encodings
|
|
15
|
+
"""
|
|
16
|
+
encodings = ['utf-8', 'gbk', 'utf-16', 'latin-1']
|
|
17
|
+
|
|
18
|
+
for encoding in encodings:
|
|
19
|
+
try:
|
|
20
|
+
with open(file_path, 'r', encoding=encoding) as f:
|
|
21
|
+
content = f.read()
|
|
22
|
+
return content
|
|
23
|
+
except UnicodeDecodeError:
|
|
24
|
+
continue
|
|
25
|
+
|
|
26
|
+
raise ValueError(f"无法解码文件: {file_path}。尝试的编码: {', '.join(encodings)}")
|
autocoder/common/types.py
CHANGED
|
@@ -12,6 +12,7 @@ class StepNum(pydantic.BaseModel):
|
|
|
12
12
|
class CodeGenerateResult(pydantic.BaseModel):
|
|
13
13
|
contents:List[str]
|
|
14
14
|
conversations:List[List[Dict[str, Any]]]
|
|
15
|
+
metadata:Dict[str, Any] = {}
|
|
15
16
|
|
|
16
17
|
class MergeCodeWithoutEffect(pydantic.BaseModel):
|
|
17
18
|
success_blocks: List[Tuple[str, str]]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from byzerllm import ByzerLLM
|
|
2
|
+
from typing import List,Any,Union
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from loguru import logger
|
|
5
|
+
class ChatWithContinueResult(BaseModel):
|
|
6
|
+
content: str
|
|
7
|
+
input_tokens_count: int
|
|
8
|
+
generated_tokens_count: int
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def chat_with_continue(llm: ByzerLLM, conversations: List[dict], llm_config: dict) -> ChatWithContinueResult:
|
|
12
|
+
final_result = ChatWithContinueResult(content="", input_tokens_count=0, generated_tokens_count=0)
|
|
13
|
+
v = llm.chat_oai(
|
|
14
|
+
conversations=conversations, llm_config=llm_config)
|
|
15
|
+
|
|
16
|
+
single_result = v[0].output
|
|
17
|
+
metadata = v[0].metadata
|
|
18
|
+
|
|
19
|
+
final_result.input_tokens_count += metadata.get("input_tokens_count", 0)
|
|
20
|
+
final_result.generated_tokens_count += metadata.get("generated_tokens_count", 0)
|
|
21
|
+
|
|
22
|
+
temp_conversations = conversations + \
|
|
23
|
+
[{"role": "assistant", "content": single_result}]
|
|
24
|
+
|
|
25
|
+
count = 1
|
|
26
|
+
while (metadata.get("finish_reason", "stop") == "length" and count < 6):
|
|
27
|
+
v = llm.chat_oai(
|
|
28
|
+
conversations=temp_conversations, llm_config={**llm_config, "gen.response_prefix": True})
|
|
29
|
+
metadata = v[0].metadata
|
|
30
|
+
single_result += v[0].output
|
|
31
|
+
final_result.input_tokens_count += metadata.get("input_tokens_count", 0)
|
|
32
|
+
final_result.generated_tokens_count += metadata.get("generated_tokens_count", 0)
|
|
33
|
+
count += 1
|
|
34
|
+
|
|
35
|
+
# if count >= 2:
|
|
36
|
+
# logger.info(f"The code generation is exceed the max length, continue to generate the code {count -1 } times")
|
|
37
|
+
final_result.content = single_result
|
|
38
|
+
return final_result
|
|
@@ -116,7 +116,7 @@ class ActionTSProject(BaseAction):
|
|
|
116
116
|
generate_result = generate.single_round_run(
|
|
117
117
|
query=args.query, source_content=content
|
|
118
118
|
)
|
|
119
|
-
logger.info(f"Code generation completed in {time.time() - start_time:.2f} seconds")
|
|
119
|
+
logger.info(f"Code generation completed in {time.time() - start_time:.2f} seconds, input_tokens_count: {generate_result.metadata.get('input_tokens_count', 0)}, generated_tokens_count: {generate_result.metadata.get('generated_tokens_count', 0)}")
|
|
120
120
|
merge_result = None
|
|
121
121
|
if args.execute and args.auto_merge:
|
|
122
122
|
logger.info("Auto merge the code...")
|
|
@@ -200,7 +200,7 @@ class ActionPyScriptProject(BaseAction):
|
|
|
200
200
|
query=args.query, source_content=content
|
|
201
201
|
)
|
|
202
202
|
|
|
203
|
-
logger.info(f"Code generation completed in {time.time() - start_time:.2f} seconds")
|
|
203
|
+
logger.info(f"Code generation completed in {time.time() - start_time:.2f} seconds, input_tokens_count: {generate_result.metadata.get('input_tokens_count', 0)}, generated_tokens_count: {generate_result.metadata.get('generated_tokens_count', 0)}")
|
|
204
204
|
merge_result = None
|
|
205
205
|
if args.execute and args.auto_merge:
|
|
206
206
|
logger.info("Auto merge the code...")
|
|
@@ -302,7 +302,7 @@ class ActionPyProject(BaseAction):
|
|
|
302
302
|
generate_result = generate.single_round_run(
|
|
303
303
|
query=args.query, source_content=content
|
|
304
304
|
)
|
|
305
|
-
logger.info(f"Code generation completed in {time.time() - start_time:.2f} seconds")
|
|
305
|
+
logger.info(f"Code generation completed in {time.time() - start_time:.2f} seconds, input_tokens_count: {generate_result.metadata.get('input_tokens_count', 0)}, generated_tokens_count: {generate_result.metadata.get('generated_tokens_count', 0)}")
|
|
306
306
|
merge_result = None
|
|
307
307
|
if args.execute and args.auto_merge:
|
|
308
308
|
logger.info("Auto merge the code...")
|
|
@@ -396,7 +396,7 @@ class ActionSuffixProject(BaseAction):
|
|
|
396
396
|
query=args.query, source_content=content
|
|
397
397
|
)
|
|
398
398
|
|
|
399
|
-
logger.info(f"Code generation completed in {time.time() - start_time:.2f} seconds")
|
|
399
|
+
logger.info(f"Code generation completed in {time.time() - start_time:.2f} seconds, input_tokens_count: {generate_result.metadata.get('input_tokens_count', 0)}, generated_tokens_count: {generate_result.metadata.get('generated_tokens_count', 0)}")
|
|
400
400
|
merge_result = None
|
|
401
401
|
if args.execute and args.auto_merge:
|
|
402
402
|
logger.info("Auto merge the code...")
|
|
@@ -13,7 +13,7 @@ from autocoder.index.index import build_index_and_filter_files
|
|
|
13
13
|
from autocoder.regexproject import RegexProject
|
|
14
14
|
from autocoder.utils.conversation_store import store_code_model_conversation
|
|
15
15
|
from loguru import logger
|
|
16
|
-
|
|
16
|
+
import time
|
|
17
17
|
|
|
18
18
|
class ActionRegexProject:
|
|
19
19
|
def __init__(
|
|
@@ -51,8 +51,10 @@ class ActionRegexProject:
|
|
|
51
51
|
)
|
|
52
52
|
content = content[: self.args.model_max_input_length]
|
|
53
53
|
|
|
54
|
+
start_time = time.time()
|
|
54
55
|
if args.execute:
|
|
55
56
|
logger.info("Auto generate the code...")
|
|
57
|
+
|
|
56
58
|
if args.auto_merge == "diff":
|
|
57
59
|
generate = CodeAutoGenerateDiff(
|
|
58
60
|
llm=self.llm, args=self.args, action=self
|
|
@@ -75,6 +77,8 @@ class ActionRegexProject:
|
|
|
75
77
|
generate_result = generate.single_round_run(
|
|
76
78
|
query=args.query, source_content=content
|
|
77
79
|
)
|
|
80
|
+
|
|
81
|
+
logger.info(f"Code generation completed in {time.time() - start_time:.2f} seconds, input_tokens_count: {generate_result.metadata.get('input_tokens_count', 0)}, generated_tokens_count: {generate_result.metadata.get('generated_tokens_count', 0)}")
|
|
78
82
|
merge_result = None
|
|
79
83
|
if args.execute and args.auto_merge:
|
|
80
84
|
logger.info("Auto merge the code...")
|
|
@@ -90,7 +94,7 @@ class ActionRegexProject:
|
|
|
90
94
|
else:
|
|
91
95
|
code_merge = CodeAutoMerge(llm=self.llm, args=self.args)
|
|
92
96
|
merge_result = code_merge.merge_code(generate_result=generate_result)
|
|
93
|
-
|
|
97
|
+
|
|
94
98
|
if merge_result is not None:
|
|
95
99
|
content = merge_result.contents[0]
|
|
96
100
|
store_code_model_conversation(
|
autocoder/models.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
MODELS_JSON = os.path.expanduser("~/.auto-coder/keys/models.json")
|
|
7
|
+
|
|
8
|
+
# Default built-in models
|
|
9
|
+
default_models_list = [
|
|
10
|
+
{
|
|
11
|
+
"name": "deepseek_r1_chat",
|
|
12
|
+
"description": "DeepSeek Reasoner is for design/review",
|
|
13
|
+
"model_name": "deepseek-reasoner",
|
|
14
|
+
"model_type": "saas/openai",
|
|
15
|
+
"base_url": "https://api.deepseek.com/v1",
|
|
16
|
+
"api_key_path": "api.deepseek.com"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "deepseek_chat",
|
|
20
|
+
"description": "DeepSeek Chat is for coding",
|
|
21
|
+
"model_name": "deepseek-chat",
|
|
22
|
+
"model_type": "saas/openai",
|
|
23
|
+
"base_url": "https://api.deepseek.com/v1",
|
|
24
|
+
"api_key_path": "api.deepseek.com"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name":"o1",
|
|
28
|
+
"description": "o1 is for design/review",
|
|
29
|
+
"model_name": "o1-2024-12-17",
|
|
30
|
+
"model_type": "saas/openai",
|
|
31
|
+
"base_url": "https://api.openai.com/v1",
|
|
32
|
+
"api_key_path": ""
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
def load_models() -> List[Dict]:
|
|
37
|
+
"""
|
|
38
|
+
Load models from ~/.auto-coder/keys/models.json and merge with default_models_list.
|
|
39
|
+
Models are merged and deduplicated based on their name field.
|
|
40
|
+
If file doesn't exist or is invalid, use default_models_list.
|
|
41
|
+
"""
|
|
42
|
+
os.makedirs(os.path.dirname(MODELS_JSON), exist_ok=True)
|
|
43
|
+
|
|
44
|
+
# Start with default models
|
|
45
|
+
models_dict = {model["name"]: model for model in default_models_list}
|
|
46
|
+
|
|
47
|
+
# If JSON file exists, read and merge with defaults
|
|
48
|
+
if os.path.exists(MODELS_JSON):
|
|
49
|
+
try:
|
|
50
|
+
with open(MODELS_JSON, 'r', encoding='utf-8') as f:
|
|
51
|
+
custom_models = json.load(f)
|
|
52
|
+
# Custom models will override defaults with same name
|
|
53
|
+
for model in custom_models:
|
|
54
|
+
models_dict[model["name"]] = model
|
|
55
|
+
except json.JSONDecodeError:
|
|
56
|
+
# If JSON is invalid, just use defaults
|
|
57
|
+
save_models(default_models_list)
|
|
58
|
+
else:
|
|
59
|
+
# If file doesn't exist, create it with defaults
|
|
60
|
+
save_models(default_models_list)
|
|
61
|
+
|
|
62
|
+
# Convert merged dictionary back to list
|
|
63
|
+
target_models = list(models_dict.values())
|
|
64
|
+
api_key_dir = os.path.expanduser("~/.auto-coder/keys")
|
|
65
|
+
for model in target_models:
|
|
66
|
+
if model.get("api_key_path",""):
|
|
67
|
+
api_key_file = os.path.join(api_key_dir, model["api_key_path"])
|
|
68
|
+
if os.path.exists(api_key_file):
|
|
69
|
+
with open(api_key_file, "r") as f:
|
|
70
|
+
model["api_key"] = f.read()
|
|
71
|
+
return target_models
|
|
72
|
+
|
|
73
|
+
def save_models(models: List[Dict]) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Save models to ~/.auto-coder/keys/models.json
|
|
76
|
+
"""
|
|
77
|
+
os.makedirs(os.path.dirname(MODELS_JSON), exist_ok=True)
|
|
78
|
+
with open(MODELS_JSON, 'w', encoding='utf-8') as f:
|
|
79
|
+
json.dump(models, f, indent=2, ensure_ascii=False)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def process_api_key_path(base_url: str) -> str:
|
|
83
|
+
"""
|
|
84
|
+
从 base_url 中提取 host 部分并处理特殊字符
|
|
85
|
+
例如: https://api.example.com:8080/v1 -> api.example.com_8080
|
|
86
|
+
"""
|
|
87
|
+
if not base_url:
|
|
88
|
+
return ""
|
|
89
|
+
|
|
90
|
+
parsed = urlparse(base_url)
|
|
91
|
+
host = parsed.netloc
|
|
92
|
+
|
|
93
|
+
# 将冒号替换为下划线
|
|
94
|
+
host = host.replace(":", "_")
|
|
95
|
+
|
|
96
|
+
return host
|
|
97
|
+
|
|
98
|
+
def get_model_by_name(name: str) -> Dict:
|
|
99
|
+
"""
|
|
100
|
+
根据模型名称查找模型
|
|
101
|
+
"""
|
|
102
|
+
models = load_models()
|
|
103
|
+
v = [m for m in models if m["name"] == name.strip()]
|
|
104
|
+
|
|
105
|
+
if len(v) == 0:
|
|
106
|
+
raise Exception(f"Model {name} not found")
|
|
107
|
+
return v[0]
|
|
108
|
+
|
|
109
|
+
def update_model_with_api_key(name: str, api_key: str) -> Dict:
|
|
110
|
+
"""
|
|
111
|
+
根据模型名称查找并更新模型的 api_key_path。
|
|
112
|
+
如果找到模型,会根据其 base_url 处理 api_key_path。
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
name: 模型名称
|
|
116
|
+
api_key: API密钥
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Dict: 更新后的模型信息,如果未找到则返回None
|
|
120
|
+
"""
|
|
121
|
+
models = load_models()
|
|
122
|
+
|
|
123
|
+
# 在现有模型中查找
|
|
124
|
+
found_model = None
|
|
125
|
+
for model in models:
|
|
126
|
+
if model["name"] == name:
|
|
127
|
+
found_model = model
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
if not found_model:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
# 从 base_url 中提取并处理 host
|
|
134
|
+
api_key_path = process_api_key_path(found_model["base_url"])
|
|
135
|
+
if api_key_path:
|
|
136
|
+
found_model["api_key_path"] = api_key_path
|
|
137
|
+
|
|
138
|
+
# 保存 API 密钥
|
|
139
|
+
api_key_dir = os.path.expanduser("~/.auto-coder/keys")
|
|
140
|
+
os.makedirs(api_key_dir, exist_ok=True)
|
|
141
|
+
api_key_file = os.path.join(api_key_dir, api_key_path)
|
|
142
|
+
with open(api_key_file, "w") as f:
|
|
143
|
+
f.write(api_key)
|
|
144
|
+
|
|
145
|
+
# 如果是新模型,添加到模型列表中
|
|
146
|
+
if all(model["name"] != name for model in models):
|
|
147
|
+
models.append(found_model)
|
|
148
|
+
else:
|
|
149
|
+
# 更新现有模型
|
|
150
|
+
for i, model in enumerate(models):
|
|
151
|
+
if model["name"] == name:
|
|
152
|
+
models[i] = found_model
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
save_models(models)
|
|
156
|
+
|
|
157
|
+
return found_model
|
|
158
|
+
|
autocoder/pyproject/__init__.py
CHANGED
|
@@ -17,7 +17,7 @@ from pydantic import BaseModel, Field
|
|
|
17
17
|
from rich.console import Console
|
|
18
18
|
import json
|
|
19
19
|
from autocoder.utils.queue_communicate import queue_communicate, CommunicateEvent, CommunicateEventType
|
|
20
|
-
|
|
20
|
+
from autocoder.common import files as FileUtils
|
|
21
21
|
|
|
22
22
|
class RegPattern(BaseModel):
|
|
23
23
|
pattern: str = Field(
|
|
@@ -35,9 +35,8 @@ class Level1PyProject:
|
|
|
35
35
|
|
|
36
36
|
def get_imports_from_script(self, file_path):
|
|
37
37
|
script = ""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
tree = ast.parse(script, filename=file_path)
|
|
38
|
+
script = FileUtils.read_file(file_path)
|
|
39
|
+
tree = ast.parse(script, filename=file_path)
|
|
41
40
|
|
|
42
41
|
imports = [
|
|
43
42
|
node
|
|
@@ -61,8 +60,7 @@ class Level1PyProject:
|
|
|
61
60
|
def fetch_source_code(self, import_name):
|
|
62
61
|
spec = importlib.util.find_spec(import_name)
|
|
63
62
|
if spec and spec.origin:
|
|
64
|
-
|
|
65
|
-
return file.read()
|
|
63
|
+
return FileUtils.read_file(spec.origin)
|
|
66
64
|
return None
|
|
67
65
|
|
|
68
66
|
@byzerllm.prompt(render="jinja")
|
|
@@ -188,8 +186,7 @@ class PyProject:
|
|
|
188
186
|
result.append(f"{line_number}:{line}")
|
|
189
187
|
return "\n".join(result)
|
|
190
188
|
|
|
191
|
-
|
|
192
|
-
return file.read()
|
|
189
|
+
return FileUtils.read_file(file_path)
|
|
193
190
|
|
|
194
191
|
def convert_to_source_code(self, file_path):
|
|
195
192
|
module_name = file_path
|
|
@@ -12,7 +12,7 @@ from pydantic import BaseModel, Field
|
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
import json
|
|
14
14
|
from autocoder.utils.queue_communicate import queue_communicate, CommunicateEvent, CommunicateEventType
|
|
15
|
-
|
|
15
|
+
from autocoder.common import files as FileUtils
|
|
16
16
|
|
|
17
17
|
class RegPattern(BaseModel):
|
|
18
18
|
pattern: str = Field(
|
|
@@ -84,8 +84,7 @@ class RegexProject:
|
|
|
84
84
|
return re.search(self.regex_pattern, file_path) is not None
|
|
85
85
|
|
|
86
86
|
def read_file_content(self, file_path):
|
|
87
|
-
|
|
88
|
-
return file.read()
|
|
87
|
+
return FileUtils.read_file(file_path)
|
|
89
88
|
|
|
90
89
|
def convert_to_source_code(self, file_path):
|
|
91
90
|
module_name = file_path
|