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.

@@ -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
- "generate_rerank_model") or [self.llm]
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]