auto-coder 0.1.259__py3-none-any.whl → 0.1.260__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.259.dist-info → auto_coder-0.1.260.dist-info}/METADATA +1 -1
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.260.dist-info}/RECORD +25 -20
- autocoder/auto_coder.py +24 -1
- autocoder/chat_auto_coder.py +327 -399
- autocoder/chat_auto_coder_lang.py +2 -0
- autocoder/commands/__init__.py +0 -0
- autocoder/commands/auto_command.py +1145 -0
- autocoder/commands/tools.py +533 -0
- autocoder/common/auto_coder_lang.py +34 -6
- autocoder/common/auto_configure.py +304 -0
- autocoder/common/code_modification_ranker.py +8 -7
- autocoder/common/command_completer.py +566 -0
- autocoder/common/git_utils.py +82 -1
- autocoder/common/result_manager.py +115 -0
- autocoder/common/utils_code_auto_generate.py +2 -2
- autocoder/dispacher/actions/action.py +8 -4
- autocoder/dispacher/actions/plugins/action_regex_project.py +2 -1
- autocoder/index/filter/quick_filter.py +14 -2
- autocoder/utils/auto_coder_utils/chat_stream_out.py +13 -6
- autocoder/utils/thread_utils.py +4 -0
- autocoder/version.py +1 -1
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.260.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.260.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.260.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.260.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
import traceback
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import Dict, Any, Optional, Union, Callable, List
|
|
8
|
+
from pydantic import BaseModel, Field, SkipValidation
|
|
9
|
+
import byzerllm
|
|
10
|
+
from byzerllm import ByzerLLM
|
|
11
|
+
from byzerllm.utils.client import code_utils
|
|
12
|
+
from autocoder.common.printer import Printer
|
|
13
|
+
from byzerllm.utils.str2model import to_model
|
|
14
|
+
from autocoder.utils.auto_coder_utils.chat_stream_out import stream_out
|
|
15
|
+
from autocoder.common.result_manager import ResultManager
|
|
16
|
+
from autocoder.utils import llms as llms_utils
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
class ConfigMessage(BaseModel):
|
|
20
|
+
role: str
|
|
21
|
+
content: str
|
|
22
|
+
|
|
23
|
+
class ExtenedConfigMessage(BaseModel):
|
|
24
|
+
message: ConfigMessage
|
|
25
|
+
timestamp: str
|
|
26
|
+
|
|
27
|
+
class ConfigConversation(BaseModel):
|
|
28
|
+
history: Dict[str, ExtenedConfigMessage]
|
|
29
|
+
current_conversation: List[ConfigMessage]
|
|
30
|
+
|
|
31
|
+
def save_to_memory_file(query: str, response: str):
|
|
32
|
+
"""Save conversation to memory file using ConfigConversation structure"""
|
|
33
|
+
memory_dir = os.path.join(".auto-coder", "memory")
|
|
34
|
+
os.makedirs(memory_dir, exist_ok=True)
|
|
35
|
+
file_path = os.path.join(memory_dir, "config_chat_history.json")
|
|
36
|
+
|
|
37
|
+
# Create new message objects
|
|
38
|
+
user_msg = ConfigMessage(role="user", content=query)
|
|
39
|
+
assistant_msg = ConfigMessage(role="assistant", content=response)
|
|
40
|
+
|
|
41
|
+
extended_user_msg = ExtenedConfigMessage(
|
|
42
|
+
message=user_msg,
|
|
43
|
+
timestamp=str(int(time.time()))
|
|
44
|
+
)
|
|
45
|
+
extended_assistant_msg = ExtenedConfigMessage(
|
|
46
|
+
message=assistant_msg,
|
|
47
|
+
timestamp=str(int(time.time()))
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Load existing conversation or create new
|
|
51
|
+
if os.path.exists(file_path):
|
|
52
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
53
|
+
try:
|
|
54
|
+
existing_conv = ConfigConversation.model_validate_json(f.read())
|
|
55
|
+
except Exception:
|
|
56
|
+
existing_conv = ConfigConversation(
|
|
57
|
+
history={},
|
|
58
|
+
current_conversation=[]
|
|
59
|
+
)
|
|
60
|
+
else:
|
|
61
|
+
existing_conv = ConfigConversation(
|
|
62
|
+
history={},
|
|
63
|
+
current_conversation=[]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
existing_conv.current_conversation.append(extended_user_msg)
|
|
67
|
+
existing_conv.current_conversation.append(extended_assistant_msg)
|
|
68
|
+
# Save updated conversation
|
|
69
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
70
|
+
f.write(existing_conv.model_dump_json(indent=2))
|
|
71
|
+
|
|
72
|
+
class MemoryConfig(BaseModel):
|
|
73
|
+
"""
|
|
74
|
+
A model to encapsulate memory configuration and operations.
|
|
75
|
+
"""
|
|
76
|
+
memory: Dict[str, Any]
|
|
77
|
+
save_memory_func: SkipValidation[Callable]
|
|
78
|
+
|
|
79
|
+
class Config:
|
|
80
|
+
arbitrary_types_allowed = True
|
|
81
|
+
|
|
82
|
+
def configure(self, conf: str, skip_print: bool = False) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Configure memory with the given key-value pair.
|
|
85
|
+
"""
|
|
86
|
+
printer = Printer()
|
|
87
|
+
parts = conf.split(None, 1)
|
|
88
|
+
if len(parts) == 2 and parts[0] in ["/drop", "/unset", "/remove"]:
|
|
89
|
+
key = parts[1].strip()
|
|
90
|
+
if key in self.memory["conf"]:
|
|
91
|
+
del self.memory["conf"][key]
|
|
92
|
+
self.save_memory_func()
|
|
93
|
+
printer.print_in_terminal("config_delete_success", style="green", key=key)
|
|
94
|
+
else:
|
|
95
|
+
printer.print_in_terminal("config_not_found", style="yellow", key=key)
|
|
96
|
+
else:
|
|
97
|
+
parts = conf.split(":", 1)
|
|
98
|
+
if len(parts) != 2:
|
|
99
|
+
printer.print_in_terminal("config_invalid_format", style="red")
|
|
100
|
+
return
|
|
101
|
+
key, value = parts
|
|
102
|
+
key = key.strip()
|
|
103
|
+
value = value.strip()
|
|
104
|
+
if not value:
|
|
105
|
+
printer.print_in_terminal("config_value_empty", style="red")
|
|
106
|
+
return
|
|
107
|
+
self.memory["conf"][key] = value
|
|
108
|
+
self.save_memory_func()
|
|
109
|
+
if not skip_print:
|
|
110
|
+
printer.print_in_terminal("config_set_success", style="green", key=key, value=value)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AutoConfigRequest(BaseModel):
|
|
115
|
+
query: str = Field(..., description="用户原始请求内容")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class AutoConfigResponse(BaseModel):
|
|
119
|
+
configs: List[Dict[str, Any]] = Field(default_factory=list)
|
|
120
|
+
reasoning: str = ""
|
|
121
|
+
|
|
122
|
+
class ConfigAutoTuner:
|
|
123
|
+
def __init__(self, llm: Union[byzerllm.ByzerLLM, byzerllm.SimpleByzerLLM], memory_config: MemoryConfig):
|
|
124
|
+
self.llm = llm
|
|
125
|
+
self.memory_config = memory_config
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def configure(self, conf: str, skip_print: bool = False) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Delegate configuration to MemoryConfig instance.
|
|
131
|
+
"""
|
|
132
|
+
self.memory_config.configure(conf, skip_print)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@byzerllm.prompt()
|
|
136
|
+
def config_readme(self) -> str:
|
|
137
|
+
"""
|
|
138
|
+
# 配置项说明
|
|
139
|
+
## auto_merge: 代码合并方式,可选值为editblock、diff、wholefile.
|
|
140
|
+
- editblock: 生成 SEARCH/REPLACE 块,然后根据 SEARCH块到对应的源码查找,如果相似度阈值大于 editblock_similarity, 那么则将
|
|
141
|
+
找到的代码块替换为 REPLACE 块。大部分情况都推荐使用 editblock。
|
|
142
|
+
- wholefile: 重新生成整个文件,然后替换原来的文件。对于重构场景,推荐使用 wholefile。
|
|
143
|
+
- diff: 生成标准 git diff 格式,适用于简单的代码修改。
|
|
144
|
+
|
|
145
|
+
## editblock_similarity: editblock相似度阈值
|
|
146
|
+
- editblock相似度阈值,取值范围为0-1,默认值为0.9。如果设置的太低,虽然能合并进去,但是会引入错误。推荐不要修改该值。
|
|
147
|
+
|
|
148
|
+
## generate_times_same_model: 相同模型生成次数
|
|
149
|
+
当进行生成代码时,大模型会对同一个需求生成多份代码,然后会使用 generate_rerank_model 模型对多份代码进行重排序,
|
|
150
|
+
然后选择得分最高的代码。一般次数越多,最终得到正确的代码概率越高。默认值为1,推荐设置为3。但是设置值越多,可能速度就越慢,消耗的token也越多。
|
|
151
|
+
|
|
152
|
+
## skip_filter_index: 是否跳过索引过滤
|
|
153
|
+
是否跳过根据用户的query 自动查找上下文。推荐设置为 false
|
|
154
|
+
|
|
155
|
+
## skip_build_index: 是否跳过索引构建
|
|
156
|
+
是否自动构建索引。推荐设置为 false。注意,如果该值设置为 true, 那么 skip_filter_index 设置不会生效。
|
|
157
|
+
|
|
158
|
+
## rank_times_same_model: 相同模型重排序次数
|
|
159
|
+
默认值为1. 如果 generate_times_same_model 参数设置大于1,那么 coding 函数会自动对多份代码进行重排序。
|
|
160
|
+
rank_times_same_model 表示重拍的次数,次数越多,选择到最好的代码的可能性越高,但是也会显著增加消耗的token和时间。
|
|
161
|
+
建议保持默认,要修改也建议不要超过3。
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def command_readme(self) -> str:
|
|
165
|
+
"""
|
|
166
|
+
# 命令说明
|
|
167
|
+
## /chat: 进入配置对话模式
|
|
168
|
+
## /coding: 进入代码生成模式
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
@byzerllm.prompt()
|
|
172
|
+
def _generate_config_str(self, request: AutoConfigRequest) -> str:
|
|
173
|
+
"""
|
|
174
|
+
配置项说明:
|
|
175
|
+
<config_readme>
|
|
176
|
+
{{ config_readme }}
|
|
177
|
+
</config_readme>
|
|
178
|
+
|
|
179
|
+
用户请求:
|
|
180
|
+
<query>
|
|
181
|
+
{{ query }}
|
|
182
|
+
</query>
|
|
183
|
+
|
|
184
|
+
当前配置:
|
|
185
|
+
<current_conf>
|
|
186
|
+
{{ current_conf }}
|
|
187
|
+
</current_conf>
|
|
188
|
+
|
|
189
|
+
上次执行情况:
|
|
190
|
+
<last_execution_stat>
|
|
191
|
+
{{ last_execution_stat }}
|
|
192
|
+
</last_execution_stat>
|
|
193
|
+
|
|
194
|
+
阅读配置说明,根据用户请求和当前配置以及上次执行情况,生成优化参数,严格使用以下JSON格式:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"configs": [{
|
|
199
|
+
"config": {
|
|
200
|
+
"auto_merge": "editblock",
|
|
201
|
+
},
|
|
202
|
+
"reasoning": "配置变更原因",
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
"""
|
|
208
|
+
return {
|
|
209
|
+
"query": request.query,
|
|
210
|
+
"current_conf": json.dumps(self.memory_config.memory["conf"], indent=2),
|
|
211
|
+
"last_execution_stat": "",
|
|
212
|
+
"config_readme": self.config_readme.prompt()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
def tune(self, request: AutoConfigRequest) -> 'AutoConfigResponse':
|
|
216
|
+
result_manager = ResultManager()
|
|
217
|
+
try:
|
|
218
|
+
# 获取 prompt 内容
|
|
219
|
+
prompt = self._generate_config_str.prompt(request)
|
|
220
|
+
|
|
221
|
+
# 构造对话上下文
|
|
222
|
+
conversations = [{"role": "user", "content": prompt}]
|
|
223
|
+
|
|
224
|
+
def extract_command_response(content):
|
|
225
|
+
# 提取 JSON 并转换为 AutoConfigResponse
|
|
226
|
+
try:
|
|
227
|
+
response = to_model(content, AutoConfigResponse)
|
|
228
|
+
return response.reasoning
|
|
229
|
+
except Exception as e:
|
|
230
|
+
return content
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# 使用 stream_out 进行输出
|
|
234
|
+
model_name = ",".join(llms_utils.get_llm_names(self.llm))
|
|
235
|
+
printer = Printer()
|
|
236
|
+
title = printer.get_message_from_key("auto_config_analyzing")
|
|
237
|
+
start_time = time.monotonic()
|
|
238
|
+
result, last_meta = stream_out(
|
|
239
|
+
self.llm.stream_chat_oai(conversations=conversations, delta_mode=True),
|
|
240
|
+
model_name=self.llm.default_model_name,
|
|
241
|
+
title=title,
|
|
242
|
+
display_func=extract_command_response
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
if last_meta:
|
|
246
|
+
elapsed_time = time.monotonic() - start_time
|
|
247
|
+
printer = Printer()
|
|
248
|
+
speed = last_meta.generated_tokens_count / elapsed_time
|
|
249
|
+
|
|
250
|
+
# Get model info for pricing
|
|
251
|
+
from autocoder.utils import llms as llm_utils
|
|
252
|
+
model_info = llm_utils.get_model_info(model_name, self.args.product_mode) or {}
|
|
253
|
+
input_price = model_info.get("input_price", 0.0) if model_info else 0.0
|
|
254
|
+
output_price = model_info.get("output_price", 0.0) if model_info else 0.0
|
|
255
|
+
|
|
256
|
+
# Calculate costs
|
|
257
|
+
input_cost = (last_meta.input_tokens_count * input_price) / 1000000 # Convert to millions
|
|
258
|
+
output_cost = (last_meta.generated_tokens_count * output_price) / 1000000 # Convert to millions
|
|
259
|
+
|
|
260
|
+
printer.print_in_terminal("stream_out_stats",
|
|
261
|
+
model_name=",".join(llms_utils.get_llm_names(self.llm)),
|
|
262
|
+
elapsed_time=elapsed_time,
|
|
263
|
+
first_token_time=last_meta.first_token_time,
|
|
264
|
+
input_tokens=last_meta.input_tokens_count,
|
|
265
|
+
output_tokens=last_meta.generated_tokens_count,
|
|
266
|
+
input_cost=round(input_cost, 4),
|
|
267
|
+
output_cost=round(output_cost, 4),
|
|
268
|
+
speed=round(speed, 2))
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# 提取 JSON 并转换为 AutoConfigResponse
|
|
272
|
+
response = to_model(result, AutoConfigResponse)
|
|
273
|
+
|
|
274
|
+
# 保存对话记录
|
|
275
|
+
save_to_memory_file(
|
|
276
|
+
query=request.query,
|
|
277
|
+
response=response.model_dump_json(indent=2)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
content = response.reasoning or "success"
|
|
281
|
+
for config in response.configs:
|
|
282
|
+
for k, v in config["config"].items():
|
|
283
|
+
self.configure(f"{k}:{v}")
|
|
284
|
+
content += f"\nconf({k}:{v})"
|
|
285
|
+
|
|
286
|
+
result_manager = ResultManager()
|
|
287
|
+
|
|
288
|
+
result_manager.add_result(content=content, meta={
|
|
289
|
+
"action": "help",
|
|
290
|
+
"input": {
|
|
291
|
+
"query": request.query
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
return response
|
|
295
|
+
except Exception as e:
|
|
296
|
+
v = f"help error: {str(e)} {traceback.format_exc()}"
|
|
297
|
+
logger.error(v)
|
|
298
|
+
result_manager.add_result(content=v, meta={
|
|
299
|
+
"action": "help",
|
|
300
|
+
"input": {
|
|
301
|
+
"query": request.query
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
return AutoConfigResponse()
|
|
@@ -18,10 +18,11 @@ class CodeModificationRanker:
|
|
|
18
18
|
def __init__(self, llm: byzerllm.ByzerLLM, args: AutoCoderArgs):
|
|
19
19
|
self.llm = llm
|
|
20
20
|
self.args = args
|
|
21
|
-
self.llms = self.llm.get_sub_client(
|
|
22
|
-
|
|
21
|
+
self.llms = self.llm.get_sub_client("generate_rerank_model") or [self.llm]
|
|
22
|
+
|
|
23
23
|
if not isinstance(self.llms, list):
|
|
24
24
|
self.llms = [self.llms]
|
|
25
|
+
|
|
25
26
|
self.printer = Printer()
|
|
26
27
|
|
|
27
28
|
@byzerllm.prompt()
|
|
@@ -65,9 +66,7 @@ class CodeModificationRanker:
|
|
|
65
66
|
if len(generate_result.contents) == 1:
|
|
66
67
|
self.printer.print_in_terminal("ranking_skip", style="blue")
|
|
67
68
|
return generate_result
|
|
68
|
-
|
|
69
|
-
self.printer.print_in_terminal(
|
|
70
|
-
"ranking_start", style="blue", count=len(generate_result.contents))
|
|
69
|
+
|
|
71
70
|
rank_times = self.args.rank_times_same_model
|
|
72
71
|
total_tasks = len(self.llms) * rank_times
|
|
73
72
|
|
|
@@ -79,7 +78,7 @@ class CodeModificationRanker:
|
|
|
79
78
|
with ThreadPoolExecutor(max_workers=total_tasks) as executor:
|
|
80
79
|
# Submit tasks for each model and generate_times
|
|
81
80
|
futures = []
|
|
82
|
-
for llm in self.llms:
|
|
81
|
+
for llm in self.llms:
|
|
83
82
|
model_name = ",".join(get_llm_names(llm))
|
|
84
83
|
self.printer.print_in_terminal(
|
|
85
84
|
"ranking_start", style="blue", count=len(generate_result.contents), model_name=model_name)
|
|
@@ -159,6 +158,7 @@ class CodeModificationRanker:
|
|
|
159
158
|
reverse=True)
|
|
160
159
|
|
|
161
160
|
elapsed = time.time() - start_time
|
|
161
|
+
speed = generated_tokens_count / elapsed
|
|
162
162
|
# Format scores for logging
|
|
163
163
|
score_details = ", ".join(
|
|
164
164
|
[f"candidate {i}: {candidate_scores[i]:.2f}" for i in sorted_candidates])
|
|
@@ -173,7 +173,8 @@ class CodeModificationRanker:
|
|
|
173
173
|
output_tokens=generated_tokens_count,
|
|
174
174
|
input_cost=total_input_cost,
|
|
175
175
|
output_cost=total_output_cost,
|
|
176
|
-
model_names=", ".join(model_names)
|
|
176
|
+
model_names=", ".join(model_names),
|
|
177
|
+
speed=f"{speed:.2f}"
|
|
177
178
|
)
|
|
178
179
|
|
|
179
180
|
rerank_contents = [generate_result.contents[i]
|