auto-coder 0.1.375__py3-none-any.whl → 0.1.376__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.375.dist-info → auto_coder-0.1.376.dist-info}/METADATA +1 -1
- {auto_coder-0.1.375.dist-info → auto_coder-0.1.376.dist-info}/RECORD +17 -51
- autocoder/agent/base_agentic/base_agent.py +9 -8
- autocoder/auto_coder_rag.py +12 -0
- autocoder/models.py +2 -2
- autocoder/rag/cache/local_duckdb_storage_cache.py +63 -33
- autocoder/rag/conversation_to_queries.py +37 -5
- autocoder/rag/long_context_rag.py +161 -41
- autocoder/rag/tools/recall_tool.py +2 -1
- autocoder/rag/tools/search_tool.py +2 -1
- autocoder/rag/types.py +36 -0
- autocoder/utils/_markitdown.py +59 -13
- autocoder/version.py +1 -1
- autocoder/agent/agentic_edit.py +0 -833
- autocoder/agent/agentic_edit_tools/__init__.py +0 -28
- autocoder/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +0 -32
- autocoder/agent/agentic_edit_tools/attempt_completion_tool_resolver.py +0 -29
- autocoder/agent/agentic_edit_tools/base_tool_resolver.py +0 -29
- autocoder/agent/agentic_edit_tools/execute_command_tool_resolver.py +0 -84
- autocoder/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py +0 -75
- autocoder/agent/agentic_edit_tools/list_files_tool_resolver.py +0 -62
- autocoder/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py +0 -30
- autocoder/agent/agentic_edit_tools/read_file_tool_resolver.py +0 -36
- autocoder/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +0 -95
- autocoder/agent/agentic_edit_tools/search_files_tool_resolver.py +0 -70
- autocoder/agent/agentic_edit_tools/use_mcp_tool_resolver.py +0 -55
- autocoder/agent/agentic_edit_tools/write_to_file_tool_resolver.py +0 -98
- autocoder/agent/agentic_edit_types.py +0 -124
- autocoder/auto_coder_lang.py +0 -60
- autocoder/auto_coder_rag_client_mcp.py +0 -170
- autocoder/auto_coder_rag_mcp.py +0 -193
- autocoder/common/llm_rerank.py +0 -84
- autocoder/common/model_speed_test.py +0 -392
- autocoder/common/v2/agent/agentic_edit_conversation.py +0 -188
- autocoder/common/v2/agent/ignore_utils.py +0 -50
- autocoder/dispacher/actions/plugins/action_translate.py +0 -214
- autocoder/ignorefiles/__init__.py +0 -4
- autocoder/ignorefiles/ignore_file_utils.py +0 -63
- autocoder/ignorefiles/test_ignore_file_utils.py +0 -91
- autocoder/linters/code_linter.py +0 -588
- autocoder/rag/loaders/test_image_loader.py +0 -209
- autocoder/rag/raw_rag.py +0 -96
- autocoder/rag/simple_directory_reader.py +0 -646
- autocoder/rag/simple_rag.py +0 -404
- autocoder/regex_project/__init__.py +0 -162
- autocoder/utils/coder.py +0 -125
- autocoder/utils/tests.py +0 -37
- {auto_coder-0.1.375.dist-info → auto_coder-0.1.376.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.375.dist-info → auto_coder-0.1.376.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.375.dist-info → auto_coder-0.1.376.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.375.dist-info → auto_coder-0.1.376.dist-info}/top_level.txt +0 -0
autocoder/common/llm_rerank.py
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import byzerllm
|
|
2
|
-
from typing import Union,List
|
|
3
|
-
from loguru import logger
|
|
4
|
-
from llama_index.core.schema import QueryBundle,NodeWithScore
|
|
5
|
-
|
|
6
|
-
class LLMRerank():
|
|
7
|
-
def __init__(self,llm:byzerllm.ByzerLLM):
|
|
8
|
-
self.llm = llm
|
|
9
|
-
|
|
10
|
-
@byzerllm.prompt(llm=lambda self: self.llm)
|
|
11
|
-
def rereank(self,context_str:str, query_str:str)->str:
|
|
12
|
-
'''
|
|
13
|
-
以下显示了一份文档列表。每个文档旁边都有一个编号以及该文档的摘要。还提供了一个问题。
|
|
14
|
-
回答问题时,请按照相关性顺序列出你应该参考的文档的编号,并给出相关性评分。相关性评分是一个1-10的数字,基于你认为该文档与问题的相关程度。
|
|
15
|
-
不要包括任何与问题无关的文档。
|
|
16
|
-
示例格式:
|
|
17
|
-
文档1:
|
|
18
|
-
<文档1的摘要>
|
|
19
|
-
|
|
20
|
-
文档2:
|
|
21
|
-
<文档2的摘要>
|
|
22
|
-
|
|
23
|
-
...
|
|
24
|
-
|
|
25
|
-
文档10:
|
|
26
|
-
<文档10的摘要>
|
|
27
|
-
|
|
28
|
-
问题:<问题>
|
|
29
|
-
回答:
|
|
30
|
-
文档:9,相关性:7
|
|
31
|
-
文档:3,相关性:4
|
|
32
|
-
文档:7,相关性:3
|
|
33
|
-
|
|
34
|
-
现在让我们试一试:
|
|
35
|
-
|
|
36
|
-
{{ context_str }}
|
|
37
|
-
|
|
38
|
-
问题:{{ query_str }}
|
|
39
|
-
回答:
|
|
40
|
-
'''
|
|
41
|
-
|
|
42
|
-
def postprocess_nodes(self,
|
|
43
|
-
nodes:List[NodeWithScore],
|
|
44
|
-
query_bundle:Union[str,QueryBundle],
|
|
45
|
-
choice_batch_size:int=5, top_n:int=1,verbose:bool=False)->List[NodeWithScore]:
|
|
46
|
-
if isinstance(query_bundle, str):
|
|
47
|
-
query_bundle = QueryBundle(query_str=query_bundle)
|
|
48
|
-
|
|
49
|
-
# 给每个节点添加全局索引
|
|
50
|
-
indexed_nodes = list(enumerate(nodes))
|
|
51
|
-
|
|
52
|
-
# 按 choice_batch_size 切分 nodes
|
|
53
|
-
node_batches = [indexed_nodes[i:i+choice_batch_size] for i in range(0, len(indexed_nodes), choice_batch_size)]
|
|
54
|
-
|
|
55
|
-
# 合并排序后的结果
|
|
56
|
-
sorted_nodes = []
|
|
57
|
-
for batch in node_batches:
|
|
58
|
-
context_str = "\n\n".join([f"文档{idx}:\n{node.node.get_text()}" for idx, node in batch])
|
|
59
|
-
rerank_output = self.rereank(context_str, query_bundle.query_str)
|
|
60
|
-
if verbose:
|
|
61
|
-
logger.info(self.rereank.prompt(context_str, query_bundle.query_str))
|
|
62
|
-
logger.info(rerank_output)
|
|
63
|
-
|
|
64
|
-
# 解析 rerank 的输出
|
|
65
|
-
rerank_result = []
|
|
66
|
-
for line in rerank_output.split("\n"):
|
|
67
|
-
if line.startswith("文档:"):
|
|
68
|
-
parts = line.split(",")
|
|
69
|
-
if len(parts) == 2:
|
|
70
|
-
try:
|
|
71
|
-
doc_idx = int(parts[0].split(":")[1])
|
|
72
|
-
relevance = float(parts[1].split(":")[1])
|
|
73
|
-
rerank_result.append((doc_idx, relevance))
|
|
74
|
-
except:
|
|
75
|
-
logger.warning(f"Failed to parse line: {line}")
|
|
76
|
-
pass
|
|
77
|
-
|
|
78
|
-
# 更新 batch 中节点的分数
|
|
79
|
-
for doc_idx, relevance in rerank_result:
|
|
80
|
-
indexed_nodes[doc_idx][1].score = relevance
|
|
81
|
-
|
|
82
|
-
sorted_nodes.extend([node for _, node in sorted(indexed_nodes, key=lambda x: x[1].score, reverse=True)])
|
|
83
|
-
|
|
84
|
-
return sorted_nodes[:top_n]
|
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import byzerllm
|
|
3
|
-
from typing import Dict, Any, List, Optional
|
|
4
|
-
from rich.console import Console
|
|
5
|
-
from rich.table import Table
|
|
6
|
-
from rich.panel import Panel
|
|
7
|
-
from autocoder.common.printer import Printer
|
|
8
|
-
from autocoder import models as models_module
|
|
9
|
-
from autocoder.utils.llms import get_single_llm
|
|
10
|
-
import byzerllm
|
|
11
|
-
import pkg_resources
|
|
12
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
13
|
-
from typing import Dict, List, Tuple
|
|
14
|
-
from pydantic import BaseModel
|
|
15
|
-
|
|
16
|
-
class ModelSpeedTestResult(BaseModel):
|
|
17
|
-
model_name: str
|
|
18
|
-
tokens_per_second: float
|
|
19
|
-
first_token_time: float
|
|
20
|
-
input_tokens_count: float
|
|
21
|
-
generated_tokens_count: float
|
|
22
|
-
input_tokens_cost: float
|
|
23
|
-
generated_tokens_cost: float
|
|
24
|
-
status: str
|
|
25
|
-
error: Optional[str] = None
|
|
26
|
-
|
|
27
|
-
class SpeedTestResults(BaseModel):
|
|
28
|
-
results: List[ModelSpeedTestResult]
|
|
29
|
-
|
|
30
|
-
byzerllm_content = ""
|
|
31
|
-
try:
|
|
32
|
-
byzerllm_conten_path = pkg_resources.resource_filename(
|
|
33
|
-
"autocoder", "data/byzerllm.md"
|
|
34
|
-
)
|
|
35
|
-
with open(byzerllm_conten_path, "r",encoding="utf-8") as f:
|
|
36
|
-
byzerllm_content = f.read()
|
|
37
|
-
except FileNotFoundError:
|
|
38
|
-
pass
|
|
39
|
-
|
|
40
|
-
@byzerllm.prompt()
|
|
41
|
-
def long_context_prompt() -> str:
|
|
42
|
-
'''
|
|
43
|
-
下面是我们提供的一份文档:
|
|
44
|
-
<document>
|
|
45
|
-
{{ content }}
|
|
46
|
-
</document>
|
|
47
|
-
|
|
48
|
-
请根据上述文档,实现用户的需求:
|
|
49
|
-
|
|
50
|
-
<query>
|
|
51
|
-
我想开发一个翻译程序,使用prompt 函数实现。
|
|
52
|
-
</query>
|
|
53
|
-
'''
|
|
54
|
-
return {
|
|
55
|
-
"content": byzerllm_content
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
@byzerllm.prompt()
|
|
59
|
-
def short_context_prompt() -> str:
|
|
60
|
-
'''
|
|
61
|
-
Hello, can you help me test the response speed?
|
|
62
|
-
'''
|
|
63
|
-
return {}
|
|
64
|
-
|
|
65
|
-
def test_model_speed(model_name: str,
|
|
66
|
-
product_mode: str,
|
|
67
|
-
test_rounds: int = 3,
|
|
68
|
-
enable_long_context: bool = False
|
|
69
|
-
) -> Dict[str, Any]:
|
|
70
|
-
from autocoder.models import get_model_by_name
|
|
71
|
-
"""
|
|
72
|
-
测试单个模型的速度
|
|
73
|
-
|
|
74
|
-
Args:
|
|
75
|
-
model_name: 模型名称
|
|
76
|
-
product_mode: 产品模式 (lite/pro)
|
|
77
|
-
test_rounds: 测试轮数
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
Dict包含测试结果:
|
|
81
|
-
- avg_time: 平均响应时间
|
|
82
|
-
- min_time: 最小响应时间
|
|
83
|
-
- max_time: 最大响应时间
|
|
84
|
-
- first_token_time: 首token时间
|
|
85
|
-
- success: 是否测试成功
|
|
86
|
-
- error: 错误信息(如果有)
|
|
87
|
-
"""
|
|
88
|
-
try:
|
|
89
|
-
llm = get_single_llm(model_name, product_mode)
|
|
90
|
-
model_info = get_model_by_name(model_name)
|
|
91
|
-
|
|
92
|
-
times = []
|
|
93
|
-
first_token_times = []
|
|
94
|
-
tokens_per_seconds = []
|
|
95
|
-
input_tokens_counts = []
|
|
96
|
-
generated_tokens_counts = []
|
|
97
|
-
|
|
98
|
-
input_tokens_costs = []
|
|
99
|
-
generated_tokens_costs = []
|
|
100
|
-
|
|
101
|
-
input_tokens_cost_per_m = model_info.get("input_price", 0.0) / 1000000
|
|
102
|
-
output_tokens_cost_per_m = model_info.get("output_price", 0.0) / 1000000
|
|
103
|
-
|
|
104
|
-
test_query = short_context_prompt.prompt()
|
|
105
|
-
if enable_long_context:
|
|
106
|
-
test_query = long_context_prompt.prompt()
|
|
107
|
-
|
|
108
|
-
content = ""
|
|
109
|
-
for _ in range(test_rounds):
|
|
110
|
-
start_time = time.time()
|
|
111
|
-
first_token_received = False
|
|
112
|
-
first_token_time = None
|
|
113
|
-
last_meta = None
|
|
114
|
-
input_tokens_count = 0
|
|
115
|
-
generated_tokens_count = 0
|
|
116
|
-
input_tokens_cost = 0
|
|
117
|
-
generated_tokens_cost = 0
|
|
118
|
-
for chunk,meta in llm.stream_chat_oai(conversations=[{
|
|
119
|
-
"role": "user",
|
|
120
|
-
"content": test_query
|
|
121
|
-
}],delta_mode=True):
|
|
122
|
-
content += chunk
|
|
123
|
-
last_meta = meta
|
|
124
|
-
current_time = time.time()
|
|
125
|
-
if not first_token_received:
|
|
126
|
-
first_token_time = current_time - start_time
|
|
127
|
-
first_token_received = True
|
|
128
|
-
first_token_times.append(first_token_time)
|
|
129
|
-
|
|
130
|
-
end_time = time.time()
|
|
131
|
-
generated_tokens_count = 0
|
|
132
|
-
if last_meta:
|
|
133
|
-
generated_tokens_count = last_meta.generated_tokens_count
|
|
134
|
-
input_tokens_count = last_meta.input_tokens_count
|
|
135
|
-
input_tokens_cost = input_tokens_count * input_tokens_cost_per_m
|
|
136
|
-
generated_tokens_cost = generated_tokens_count * output_tokens_cost_per_m
|
|
137
|
-
|
|
138
|
-
input_tokens_costs.append(input_tokens_cost)
|
|
139
|
-
generated_tokens_costs.append(generated_tokens_cost)
|
|
140
|
-
generated_tokens_counts.append(generated_tokens_count)
|
|
141
|
-
input_tokens_counts.append(input_tokens_count)
|
|
142
|
-
|
|
143
|
-
tokens_per_seconds.append(generated_tokens_count / (end_time - start_time))
|
|
144
|
-
times.append(end_time - start_time)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
avg_time = sum(times) / len(times)
|
|
148
|
-
return {
|
|
149
|
-
"tokens_per_second": sum(tokens_per_seconds) / len(tokens_per_seconds),
|
|
150
|
-
"avg_time": avg_time,
|
|
151
|
-
"min_time": min(times),
|
|
152
|
-
"max_time": max(times),
|
|
153
|
-
"first_token_time": sum(first_token_times) / len(first_token_times),
|
|
154
|
-
"input_tokens_count": sum(input_tokens_counts) / len(input_tokens_counts),
|
|
155
|
-
"generated_tokens_count": sum(generated_tokens_counts) / len(generated_tokens_counts),
|
|
156
|
-
"success": True,
|
|
157
|
-
"error": None,
|
|
158
|
-
"input_tokens_cost": sum(input_tokens_costs) / len(input_tokens_costs),
|
|
159
|
-
"generated_tokens_cost": sum(generated_tokens_costs) / len(generated_tokens_costs)
|
|
160
|
-
}
|
|
161
|
-
except Exception as e:
|
|
162
|
-
return {
|
|
163
|
-
"tokens_per_second": 0,
|
|
164
|
-
"avg_time": 0,
|
|
165
|
-
"min_time": 0,
|
|
166
|
-
"max_time": 0,
|
|
167
|
-
"first_token_time": 0,
|
|
168
|
-
"input_tokens_count": 0,
|
|
169
|
-
"generated_tokens_count": 0,
|
|
170
|
-
"success": False,
|
|
171
|
-
"error": str(e),
|
|
172
|
-
"input_tokens_cost": 0.0,
|
|
173
|
-
"generated_tokens_cost": 0.0
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
def test_model_speed_wrapper(args: Tuple[str, str, int, bool]) -> Tuple[str, Dict[str, Any]]:
|
|
177
|
-
"""
|
|
178
|
-
包装测试函数以适应线程池调用
|
|
179
|
-
|
|
180
|
-
Args:
|
|
181
|
-
args: (model_name, product_mode, test_rounds)的元组
|
|
182
|
-
|
|
183
|
-
Returns:
|
|
184
|
-
(model_name, test_results)的元组
|
|
185
|
-
"""
|
|
186
|
-
model_name, product_mode, test_rounds,enable_long_context = args
|
|
187
|
-
results = test_model_speed(model_name, product_mode, test_rounds,enable_long_context)
|
|
188
|
-
return (model_name, results)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def run_speed_test(product_mode: str, test_rounds: int = 3, max_workers: Optional[int] = None, enable_long_context: bool = False) -> SpeedTestResults:
|
|
192
|
-
"""
|
|
193
|
-
运行所有已激活模型的速度测试
|
|
194
|
-
|
|
195
|
-
Args:
|
|
196
|
-
product_mode: 产品模式 (lite/pro)
|
|
197
|
-
test_rounds: 每个模型测试的轮数
|
|
198
|
-
max_workers: 最大线程数,默认为None(ThreadPoolExecutor会自动设置)
|
|
199
|
-
enable_long_context: 是否启用长文本上下文测试
|
|
200
|
-
|
|
201
|
-
Returns:
|
|
202
|
-
SpeedTestResults: 包含所有模型测试结果的pydantic模型
|
|
203
|
-
"""
|
|
204
|
-
# 获取所有模型
|
|
205
|
-
models_data = models_module.load_models()
|
|
206
|
-
active_models = [m for m in models_data if "api_key" in m] if product_mode == "lite" else models_data
|
|
207
|
-
|
|
208
|
-
if not active_models:
|
|
209
|
-
return SpeedTestResults(results=[])
|
|
210
|
-
|
|
211
|
-
# 准备测试参数
|
|
212
|
-
test_args = [(model["name"], product_mode, test_rounds, enable_long_context) for model in active_models]
|
|
213
|
-
|
|
214
|
-
# 存储结果用于排序
|
|
215
|
-
results_list = []
|
|
216
|
-
|
|
217
|
-
# 使用线程池并发测试
|
|
218
|
-
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
219
|
-
# 提交所有测试任务并获取future对象
|
|
220
|
-
future_to_model = {executor.submit(test_model_speed_wrapper, args): args[0]
|
|
221
|
-
for args in test_args}
|
|
222
|
-
|
|
223
|
-
# 收集结果
|
|
224
|
-
for future in future_to_model:
|
|
225
|
-
model_name = future_to_model[future]
|
|
226
|
-
|
|
227
|
-
try:
|
|
228
|
-
_, results = future.result()
|
|
229
|
-
|
|
230
|
-
if results["success"]:
|
|
231
|
-
status = "✓"
|
|
232
|
-
results_list.append((
|
|
233
|
-
results['tokens_per_second'],
|
|
234
|
-
ModelSpeedTestResult(
|
|
235
|
-
model_name=model_name,
|
|
236
|
-
tokens_per_second=results['tokens_per_second'],
|
|
237
|
-
first_token_time=results['first_token_time'],
|
|
238
|
-
input_tokens_count=results['input_tokens_count'],
|
|
239
|
-
generated_tokens_count=results['generated_tokens_count'],
|
|
240
|
-
status=status,
|
|
241
|
-
input_tokens_cost=results['input_tokens_cost'],
|
|
242
|
-
generated_tokens_cost=results['generated_tokens_cost'],
|
|
243
|
-
)
|
|
244
|
-
))
|
|
245
|
-
try:
|
|
246
|
-
# 更新模型的平均速度
|
|
247
|
-
models_module.update_model_speed(model_name, results['tokens_per_second'])
|
|
248
|
-
except Exception:
|
|
249
|
-
pass
|
|
250
|
-
else:
|
|
251
|
-
results_list.append((
|
|
252
|
-
0,
|
|
253
|
-
ModelSpeedTestResult(
|
|
254
|
-
model_name=model_name,
|
|
255
|
-
tokens_per_second=0,
|
|
256
|
-
first_token_time=0,
|
|
257
|
-
input_tokens_count=0,
|
|
258
|
-
generated_tokens_count=0,
|
|
259
|
-
status=f"✗ {results['error']}",
|
|
260
|
-
error=results['error'],
|
|
261
|
-
input_tokens_cost=0.0,
|
|
262
|
-
generated_tokens_cost=0.0
|
|
263
|
-
)
|
|
264
|
-
))
|
|
265
|
-
except Exception as e:
|
|
266
|
-
results_list.append((
|
|
267
|
-
0,
|
|
268
|
-
ModelSpeedTestResult(
|
|
269
|
-
model_name=model_name,
|
|
270
|
-
tokens_per_second=0,
|
|
271
|
-
first_token_time=0,
|
|
272
|
-
input_tokens_count=0,
|
|
273
|
-
generated_tokens_count=0,
|
|
274
|
-
status=f"✗ {str(e)}",
|
|
275
|
-
error=str(e),
|
|
276
|
-
input_tokens_cost=0.0,
|
|
277
|
-
generated_tokens_cost=0.0
|
|
278
|
-
)
|
|
279
|
-
))
|
|
280
|
-
|
|
281
|
-
# 按速度排序
|
|
282
|
-
results_list.sort(key=lambda x: x[0], reverse=True)
|
|
283
|
-
|
|
284
|
-
return SpeedTestResults(results=[result[1] for result in results_list])
|
|
285
|
-
|
|
286
|
-
def render_speed_test_in_terminal(product_mode: str, test_rounds: int = 3, max_workers: Optional[int] = None,enable_long_context: bool = False) -> None:
|
|
287
|
-
"""
|
|
288
|
-
运行所有已激活模型的速度测试
|
|
289
|
-
|
|
290
|
-
Args:
|
|
291
|
-
product_mode: 产品模式 (lite/pro)
|
|
292
|
-
test_rounds: 每个模型测试的轮数
|
|
293
|
-
max_workers: 最大线程数,默认为None(ThreadPoolExecutor会自动设置)
|
|
294
|
-
"""
|
|
295
|
-
printer = Printer()
|
|
296
|
-
console = Console()
|
|
297
|
-
|
|
298
|
-
# 获取所有模型
|
|
299
|
-
models_data = models_module.load_models()
|
|
300
|
-
active_models = [m for m in models_data if "api_key" in m] if product_mode == "lite" else models_data
|
|
301
|
-
|
|
302
|
-
if not active_models:
|
|
303
|
-
printer.print_in_terminal("models_no_active", style="yellow")
|
|
304
|
-
return
|
|
305
|
-
|
|
306
|
-
# 创建结果表格
|
|
307
|
-
table = Table(
|
|
308
|
-
title=printer.get_message_from_key("models_speed_test_results"),
|
|
309
|
-
show_header=True,
|
|
310
|
-
header_style="bold magenta",
|
|
311
|
-
show_lines=True
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
table.add_column("Model", style="cyan", width=30)
|
|
315
|
-
table.add_column("Tokens/s", style="green", width=15)
|
|
316
|
-
table.add_column("First Token(s)", style="magenta", width=15)
|
|
317
|
-
table.add_column("Input Tokens", style="magenta", width=15)
|
|
318
|
-
table.add_column("Generated Tokens", style="magenta", width=15)
|
|
319
|
-
table.add_column("Input Tokens Cost", style="yellow", width=15)
|
|
320
|
-
table.add_column("Generated Tokens Cost", style="yellow", width=15)
|
|
321
|
-
table.add_column("Status", style="red", width=20)
|
|
322
|
-
|
|
323
|
-
# 准备测试参数
|
|
324
|
-
test_args = [(model["name"], product_mode, test_rounds, enable_long_context) for model in active_models]
|
|
325
|
-
|
|
326
|
-
# 存储结果用于排序
|
|
327
|
-
results_list = []
|
|
328
|
-
|
|
329
|
-
# 使用线程池并发测试
|
|
330
|
-
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
331
|
-
printer.print_in_terminal("models_testing_start", style="yellow")
|
|
332
|
-
|
|
333
|
-
# 提交所有测试任务并获取future对象
|
|
334
|
-
future_to_model = {executor.submit(test_model_speed_wrapper, args): args[0]
|
|
335
|
-
for args in test_args}
|
|
336
|
-
|
|
337
|
-
# 收集结果
|
|
338
|
-
completed = 0
|
|
339
|
-
total = len(future_to_model)
|
|
340
|
-
for future in future_to_model:
|
|
341
|
-
completed += 1
|
|
342
|
-
printer.print_in_terminal("models_testing_progress", style="yellow", completed=completed, total=total)
|
|
343
|
-
model_name = future_to_model[future]
|
|
344
|
-
printer.print_in_terminal("models_testing", style="yellow", name=model_name)
|
|
345
|
-
|
|
346
|
-
try:
|
|
347
|
-
_, results = future.result()
|
|
348
|
-
|
|
349
|
-
if results["success"]:
|
|
350
|
-
status = "✓"
|
|
351
|
-
results['status'] = status
|
|
352
|
-
results_list.append((
|
|
353
|
-
results['tokens_per_second'],
|
|
354
|
-
model_name,
|
|
355
|
-
results
|
|
356
|
-
))
|
|
357
|
-
try:
|
|
358
|
-
# 更新模型的平均速度
|
|
359
|
-
models_module.update_model_speed(model_name, results['tokens_per_second'])
|
|
360
|
-
except Exception as e:
|
|
361
|
-
pass
|
|
362
|
-
else:
|
|
363
|
-
status = f"✗ ({results['error']})"
|
|
364
|
-
results_list.append((
|
|
365
|
-
0,
|
|
366
|
-
model_name,
|
|
367
|
-
{"tokens_per_second":0,"avg_time": 0, "input_tokens_count":0, "generated_tokens_count":0, "min_time": 0, "max_time": 0, "first_token_time": 0, "input_tokens_cost": 0.0, "generated_tokens_cost": 0.0, "status": status}
|
|
368
|
-
))
|
|
369
|
-
except Exception as e:
|
|
370
|
-
results_list.append((
|
|
371
|
-
0,
|
|
372
|
-
model_name,
|
|
373
|
-
{"tokens_per_second":0,"avg_time": 0, "input_tokens_count":0, "generated_tokens_count":0, "min_time": 0, "max_time": 0, "first_token_time": 0, "input_tokens_cost": 0.0, "generated_tokens_cost": 0.0, "status": f"✗ ({str(e)})"}
|
|
374
|
-
))
|
|
375
|
-
|
|
376
|
-
# 按速度排序
|
|
377
|
-
results_list.sort(key=lambda x: x[0], reverse=True)
|
|
378
|
-
|
|
379
|
-
# 添加排序后的结果到表格
|
|
380
|
-
for tokens_per_second, model_name, results in results_list:
|
|
381
|
-
table.add_row(
|
|
382
|
-
model_name,
|
|
383
|
-
f"{tokens_per_second:.2f}",
|
|
384
|
-
f"{results['first_token_time']:.2f}",
|
|
385
|
-
f"{results['input_tokens_count']}",
|
|
386
|
-
f"{results['generated_tokens_count']}",
|
|
387
|
-
f"{results['input_tokens_cost']:.4f}",
|
|
388
|
-
f"{results['generated_tokens_cost']:.4f}",
|
|
389
|
-
results['status']
|
|
390
|
-
)
|
|
391
|
-
|
|
392
|
-
console.print(Panel(table, border_style="blue"))
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
# src/autocoder/common/v2/agent/agentic_edit_conversation.py
|
|
2
|
-
import os
|
|
3
|
-
import json
|
|
4
|
-
import uuid
|
|
5
|
-
from typing import List, Dict, Any, Optional
|
|
6
|
-
from autocoder.common import AutoCoderArgs
|
|
7
|
-
|
|
8
|
-
# Define a type alias for a message dictionary
|
|
9
|
-
MessageType = Dict[str, Any]
|
|
10
|
-
|
|
11
|
-
class AgenticConversation:
|
|
12
|
-
"""
|
|
13
|
-
Manages the conversation history for an agentic editing process.
|
|
14
|
-
|
|
15
|
-
Handles adding messages (user, assistant, tool calls, tool results)
|
|
16
|
-
and retrieving the history.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def __init__(self, args: AutoCoderArgs, initial_history: Optional[List[MessageType]] = None, conversation_name: Optional[str] = None):
|
|
20
|
-
"""
|
|
21
|
-
Initializes the conversation history.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
initial_history: An optional list of messages to start with.
|
|
25
|
-
conversation_name: Optional conversation identifier. If provided, history is saved/loaded from a file named after it.
|
|
26
|
-
"""
|
|
27
|
-
self.project_path = args.source_dir
|
|
28
|
-
self._history: List[MessageType] = initial_history if initial_history is not None else []
|
|
29
|
-
|
|
30
|
-
# Determine the memory directory
|
|
31
|
-
memory_dir = os.path.join(self.project_path, ".auto-coder", "memory", "agentic_edit_memory")
|
|
32
|
-
os.makedirs(memory_dir, exist_ok=True)
|
|
33
|
-
|
|
34
|
-
# Determine conversation file path
|
|
35
|
-
if conversation_name:
|
|
36
|
-
filename = f"{conversation_name}.json"
|
|
37
|
-
else:
|
|
38
|
-
conversation_name = str(uuid.uuid4())
|
|
39
|
-
filename = f"{conversation_name}.json"
|
|
40
|
-
|
|
41
|
-
self.conversation_name = conversation_name
|
|
42
|
-
self.memory_file_path = os.path.join(memory_dir, filename)
|
|
43
|
-
|
|
44
|
-
# Load existing history if file exists
|
|
45
|
-
self._load_memory()
|
|
46
|
-
|
|
47
|
-
def add_message(self, role: str, content: Any, **kwargs):
|
|
48
|
-
"""
|
|
49
|
-
Adds a message to the conversation history.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
role: The role of the message sender (e.g., "user", "assistant", "tool").
|
|
53
|
-
content: The content of the message. Can be None for messages like tool calls.
|
|
54
|
-
**kwargs: Additional key-value pairs to include in the message dictionary (e.g., tool_calls, tool_call_id).
|
|
55
|
-
"""
|
|
56
|
-
message: MessageType = {"role": role}
|
|
57
|
-
if content is not None:
|
|
58
|
-
message["content"] = content
|
|
59
|
-
message.update(kwargs)
|
|
60
|
-
self._history.append(message)
|
|
61
|
-
self._save_memory()
|
|
62
|
-
|
|
63
|
-
def add_user_message(self, content: str):
|
|
64
|
-
"""Adds a user message."""
|
|
65
|
-
self.add_message(role="user", content=content)
|
|
66
|
-
|
|
67
|
-
def add_assistant_message(self, content: str):
|
|
68
|
-
"""Adds an assistant message (potentially containing text response)."""
|
|
69
|
-
self.add_message(role="assistant", content=content)
|
|
70
|
-
|
|
71
|
-
def append_to_last_message(self, content: str, role: str = "assistant"):
|
|
72
|
-
"""Appends content to the last message."""
|
|
73
|
-
if self._history:
|
|
74
|
-
last_message = self._history[-1]
|
|
75
|
-
if role and last_message["role"] == role:
|
|
76
|
-
last_message["content"] += content
|
|
77
|
-
elif not role:
|
|
78
|
-
last_message["content"] += content
|
|
79
|
-
|
|
80
|
-
def add_assistant_tool_call_message(self, tool_calls: List[Dict[str, Any]], content: Optional[str] = None):
|
|
81
|
-
"""
|
|
82
|
-
Adds a message representing one or more tool calls from the assistant.
|
|
83
|
-
Optionally includes assistant's textual reasoning/content alongside the calls.
|
|
84
|
-
"""
|
|
85
|
-
self.add_message(role="assistant", content=content, tool_calls=tool_calls)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def add_tool_result_message(self, tool_call_id: str, content: Any):
|
|
89
|
-
"""Adds a message representing the result of a specific tool call."""
|
|
90
|
-
# The content here is typically the output/result from the tool execution.
|
|
91
|
-
self.add_message(role="tool", content=content, tool_call_id=tool_call_id)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def get_history(self) -> List[MessageType]:
|
|
95
|
-
"""
|
|
96
|
-
Returns the latest 20 pairs of (user, assistant) conversation history.
|
|
97
|
-
Merges adjacent same-role messages into one, concatenated by newline.
|
|
98
|
-
Ensures that each user message is paired with the subsequent assistant response,
|
|
99
|
-
skips other roles, and that the last message is always assistant (drops trailing user if unpaired).
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
A list of message dictionaries, ordered chronologically.
|
|
103
|
-
"""
|
|
104
|
-
paired_history = []
|
|
105
|
-
pair_count = 0
|
|
106
|
-
pending_assistant = None
|
|
107
|
-
pending_user = None
|
|
108
|
-
|
|
109
|
-
# Traverse history in reverse to collect latest pairs with merging
|
|
110
|
-
for msg in reversed(self._history):
|
|
111
|
-
role = msg.get("role")
|
|
112
|
-
if role == "assistant":
|
|
113
|
-
if pending_assistant is None:
|
|
114
|
-
pending_assistant = dict(msg)
|
|
115
|
-
else:
|
|
116
|
-
# Merge with previous assistant
|
|
117
|
-
prev_content = pending_assistant.get("content", "")
|
|
118
|
-
curr_content = msg.get("content", "")
|
|
119
|
-
merged_content = (curr_content.strip() + "\n" + prev_content.strip()).strip()
|
|
120
|
-
pending_assistant["content"] = merged_content
|
|
121
|
-
elif role == "user":
|
|
122
|
-
if pending_user is None:
|
|
123
|
-
pending_user = dict(msg)
|
|
124
|
-
else:
|
|
125
|
-
# Merge with previous user
|
|
126
|
-
prev_content = pending_user.get("content", "")
|
|
127
|
-
curr_content = msg.get("content", "")
|
|
128
|
-
merged_content = (curr_content.strip() + "\n" + prev_content.strip()).strip()
|
|
129
|
-
pending_user["content"] = merged_content
|
|
130
|
-
|
|
131
|
-
if pending_assistant is not None:
|
|
132
|
-
# Have a full pair, insert in order
|
|
133
|
-
paired_history.insert(0, pending_user)
|
|
134
|
-
paired_history.insert(1, pending_assistant)
|
|
135
|
-
pair_count += 1
|
|
136
|
-
pending_assistant = None
|
|
137
|
-
pending_user = None
|
|
138
|
-
if pair_count >= 20:
|
|
139
|
-
break
|
|
140
|
-
else:
|
|
141
|
-
# User without assistant yet, continue accumulating
|
|
142
|
-
continue
|
|
143
|
-
else:
|
|
144
|
-
# Ignore other roles
|
|
145
|
-
continue
|
|
146
|
-
|
|
147
|
-
# Ensure last message is assistant, drop trailing user if unpaired
|
|
148
|
-
if paired_history and paired_history[-1].get("role") == "user":
|
|
149
|
-
paired_history.pop()
|
|
150
|
-
|
|
151
|
-
return paired_history
|
|
152
|
-
|
|
153
|
-
def clear_history(self):
|
|
154
|
-
"""Clears the conversation history."""
|
|
155
|
-
self._history = []
|
|
156
|
-
self._save_memory()
|
|
157
|
-
|
|
158
|
-
def __len__(self) -> int:
|
|
159
|
-
"""Returns the number of messages in the history."""
|
|
160
|
-
return len(self._history)
|
|
161
|
-
|
|
162
|
-
def __str__(self) -> str:
|
|
163
|
-
"""Returns a string representation of the conversation history."""
|
|
164
|
-
# Consider a more readable format if needed for debugging
|
|
165
|
-
return str(self._history)
|
|
166
|
-
|
|
167
|
-
# Potential future enhancements:
|
|
168
|
-
# - Method to limit history size (by tokens or message count)
|
|
169
|
-
# - Method to format history specifically for different LLM APIs
|
|
170
|
-
# - Serialization/deserialization methods
|
|
171
|
-
|
|
172
|
-
def _save_memory(self):
|
|
173
|
-
try:
|
|
174
|
-
os.makedirs(os.path.dirname(self.memory_file_path), exist_ok=True)
|
|
175
|
-
with open(self.memory_file_path, "w", encoding="utf-8") as f:
|
|
176
|
-
json.dump(self._history, f, ensure_ascii=False, indent=2)
|
|
177
|
-
except Exception as e:
|
|
178
|
-
# Optionally log or ignore
|
|
179
|
-
pass
|
|
180
|
-
|
|
181
|
-
def _load_memory(self):
|
|
182
|
-
try:
|
|
183
|
-
if os.path.exists(self.memory_file_path):
|
|
184
|
-
with open(self.memory_file_path, "r", encoding="utf-8") as f:
|
|
185
|
-
self._history = json.load(f)
|
|
186
|
-
except Exception as e:
|
|
187
|
-
# Ignore loading errors, start fresh
|
|
188
|
-
pass
|