auto-coder 0.1.214__py3-none-any.whl → 0.1.217__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.

autocoder/command_args.py CHANGED
@@ -171,7 +171,7 @@ def parse_args(input_args: Optional[List[str]] = None) -> AutoCoderArgs:
171
171
  parser.add_argument("--rag_url", default="", help="")
172
172
  parser.add_argument("--rag_params_max_tokens", default=4096, help="")
173
173
  parser.add_argument(
174
- "--rag_type", default="storage", help="RAG type, default is storage"
174
+ "--rag_type", default="simple", help="RAG type, default is simple"
175
175
  )
176
176
 
177
177
  parser.add_argument(
@@ -502,8 +502,8 @@ def parse_args(input_args: Optional[List[str]] = None) -> AutoCoderArgs:
502
502
  chat_parser.add_argument("--rag_params_max_tokens", default=4096, help="")
503
503
  chat_parser.add_argument(
504
504
  "--rag_type",
505
- default="storage",
506
- help="RAG type (simple/storage), default is storage",
505
+ default="simple",
506
+ help="RAG type (simple/storage), default is simple",
507
507
  )
508
508
  chat_parser.add_argument("--target_file", default="./output.txt", help="")
509
509
  chat_parser.add_argument(
@@ -661,7 +661,7 @@ def parse_args(input_args: Optional[List[str]] = None) -> AutoCoderArgs:
661
661
  auto_tool_parser.add_argument(
662
662
  "--rag_params_max_tokens", default=4096, help="")
663
663
  auto_tool_parser.add_argument(
664
- "--rag_type", default="storage", help="RAG type, default is storage"
664
+ "--rag_type", default="simple", help="RAG type, default is simple"
665
665
  )
666
666
  auto_tool_parser.add_argument(
667
667
  "--target_file", default="./output.txt", help="")
@@ -728,7 +728,7 @@ def parse_args(input_args: Optional[List[str]] = None) -> AutoCoderArgs:
728
728
  planner_parser.add_argument(
729
729
  "--rag_params_max_tokens", default=4096, help="")
730
730
  planner_parser.add_argument(
731
- "--rag_type", default="storage", help="RAG type, default is storage"
731
+ "--rag_type", default="simple", help="RAG type, default is simple"
732
732
  )
733
733
  planner_parser.add_argument(
734
734
  "--target_file", default="./output.txt", help="")
@@ -0,0 +1,196 @@
1
+ import os
2
+ from typing import List, Optional, Dict, Any, Tuple
3
+ from PIL import Image
4
+ import fitz # PyMuPDF
5
+ import byzerllm
6
+ from autocoder.common import AutoCoderArgs
7
+ from loguru import logger
8
+ import pydantic
9
+ from docx import Document
10
+ from spire.doc import Document
11
+ from spire.doc import ImageType
12
+ from PIL import Image
13
+ from concurrent.futures import ThreadPoolExecutor, as_completed
14
+ class ImageInfo(pydantic.BaseModel):
15
+ """
16
+ 图片信息
17
+ """
18
+ coordinates: List[float] = pydantic.Field(..., description="图片坐标 [x1,y1,x2,y2]")
19
+ text: Optional[str] = pydantic.Field(None, description="图片描述")
20
+
21
+ class Page(pydantic.BaseModel):
22
+ """
23
+ 页面信息,包含文本和图片
24
+ """
25
+ text: str = pydantic.Field(..., description="页面文本内容")
26
+ images: List[ImageInfo] = pydantic.Field(default_factory=list, description="页面中的图片信息")
27
+ width: int = pydantic.Field(..., description="页面宽度")
28
+ height: int = pydantic.Field(..., description="页面高度")
29
+
30
+ class Anything2Img:
31
+ def __init__(
32
+ self,
33
+ llm: byzerllm.ByzerLLM,
34
+ args: AutoCoderArgs,
35
+ keep_conversion: bool = False,
36
+ ):
37
+ self.llm = llm
38
+ self.vl_model = llm.get_sub_client("vl_model")
39
+ self.args = args
40
+ self.output_dir = args.output
41
+ os.makedirs(self.output_dir, exist_ok=True)
42
+ self.keep_conversion = keep_conversion
43
+
44
+ @byzerllm.prompt()
45
+ def analyze_image(self, image_path: str) -> str:
46
+ """
47
+ {{ image }}
48
+ 图片中一般包含文字,图片,图表。分析图片,返回该图片包含的文本内容以及图片位置信息。
49
+ 请遵循以下格式返回:
50
+
51
+ ```json
52
+ {
53
+ "text": "页面的文本内容",
54
+ "images": [
55
+ {
56
+ "coordinates": [x1, y1, x2, y2],
57
+ "text": "对图片的描述"
58
+ }
59
+ ],
60
+ "width": 页面宽度,
61
+ "height": 页面高度
62
+ }
63
+ ```
64
+
65
+ 注意:
66
+ 1. 其中x1,y1是左上角坐标,x2,y2是右下角坐标,使用绝对坐标,也就是图片的像素坐标。
67
+ 2. 文本内容应保持原有的段落格式
68
+ 3. width和height是页面宽度,高度,要求整数类型
69
+ 4. 格局图片中文本和图片的位置关系,在文本中使用 <image_placeholder> 来表示图片。
70
+ """
71
+ image = byzerllm.Image.load_image_from_path(image_path)
72
+ return {"image": image}
73
+
74
+ def convert_pdf(self, file_path: str) -> List[str]:
75
+ """转换PDF文件为图片列表"""
76
+ pdf_document = fitz.open(file_path)
77
+ image_paths = []
78
+ try:
79
+ # 分别保存每一页
80
+ for page_num in range(len(pdf_document)):
81
+ page = pdf_document[page_num]
82
+ pix = page.get_pixmap()
83
+ basename = os.path.basename(file_path).replace(" ", "_")
84
+ image_path = os.path.join(self.output_dir, f"{basename}_page{page_num + 1}.png")
85
+ pix.save(image_path)
86
+ image_paths.append(image_path)
87
+ finally:
88
+ # 确保PDF文档关闭
89
+ pdf_document.close()
90
+ return image_paths
91
+
92
+ def convert_docx(self, file_path: str) -> List[str]:
93
+ """使用 Spire.Doc 将 Word 文档直接转换为图片"""
94
+ # 创建 Spire.Doc 文档对象
95
+ doc = Document()
96
+ doc.LoadFromFile(file_path)
97
+
98
+ # 设置图片保存选项
99
+ image_paths = []
100
+ try:
101
+ # 将每一页保存为图片
102
+ for i in range(doc.GetPageCount()):
103
+ imageStream = doc.SaveImageToStreams(i, ImageType.Bitmap)
104
+ basename = os.path.basename(file_path).replace(" ", "_")
105
+ image_path = os.path.join(self.output_dir, f"{basename}_page{i + 1}.png")
106
+ with open(image_path, 'wb') as imageFile:
107
+ imageFile.write(imageStream.ToArray())
108
+ image_paths.append(image_path)
109
+ finally:
110
+ # 确保文档关闭
111
+ doc.Close()
112
+
113
+ return image_paths
114
+
115
+ def convert(self, file_path: str) -> List[str]:
116
+ """根据文件类型选择合适的转换方法"""
117
+ file_path = os.path.abspath(file_path)
118
+ if file_path.lower().endswith('.pdf'):
119
+ return self.convert_pdf(file_path)
120
+ elif file_path.lower().endswith('.docx'):
121
+ return self.convert_docx(file_path)
122
+ else:
123
+ raise ValueError(f"Unsupported file format: {file_path}")
124
+
125
+ def to_markdown(self, file_path: str, size: int = -1, max_workers: int = 10) -> str:
126
+ """
127
+ 将文档转换为Markdown格式
128
+
129
+ Args:
130
+ file_path: 文件路径
131
+ size: 转换的页数,-1表示全部
132
+ max_workers: 并行度,控制同时分析图片的线程数
133
+ """
134
+ # 创建 _images 目录
135
+ images_dir = os.path.join(self.output_dir, "_images")
136
+ os.makedirs(images_dir, exist_ok=True)
137
+
138
+ # 转换文档为图片
139
+ if size == -1:
140
+ image_paths = self.convert(file_path)
141
+ else:
142
+ image_paths = self.convert(file_path)[0:size]
143
+
144
+ pages: List[Page] = []
145
+ # 使用线程池并行分析图片
146
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
147
+ futures = {
148
+ executor.submit(
149
+ self.analyze_image.with_llm(self.vl_model).with_return_type(Page).run,
150
+ image_path
151
+ ): image_path for image_path in image_paths
152
+ }
153
+
154
+ for future in as_completed(futures):
155
+ image_path = futures[future]
156
+ try:
157
+ result = future.result()
158
+ pages.append(result)
159
+ logger.info(f"Analyzed {image_path}")
160
+ except Exception as e:
161
+ logger.error(f"Failed to analyze {image_path}: {str(e)}")
162
+
163
+ # 生成Markdown内容
164
+ markdown_content = []
165
+
166
+ # 遍历每个页面和对应的图片路径
167
+ for page, image_path in zip(pages, image_paths):
168
+ # 处理页面中的每个图片
169
+ for img in page.images:
170
+ # 打开原始图片
171
+ original_image = Image.open(image_path)
172
+
173
+ # 获得坐标
174
+ x1 = img.coordinates[0]
175
+ y1 = img.coordinates[1]
176
+ x2 = img.coordinates[2]
177
+ y2 = img.coordinates[3]
178
+
179
+ # 截取图片
180
+ cropped_image = original_image.crop((x1, y1, x2, y2))
181
+
182
+ # 保存截取后的图片
183
+ cropped_image_path = os.path.join(images_dir, f"cropped_{os.path.basename(image_path)}")
184
+ cropped_image.save(cropped_image_path)
185
+
186
+ # 将图片路径转换为Markdown格式
187
+ image_markdown = f"![{img.text}]({cropped_image_path})"
188
+
189
+ # 替换文本中的<image_placeholder>为实际的图片Markdown
190
+ page.text = page.text.replace("<image_placeholder>", image_markdown, 1)
191
+
192
+ # 将处理后的页面文本添加到Markdown内容中
193
+ markdown_content.append(page.text)
194
+
195
+ # 将所有页面内容合并为一个Markdown文档
196
+ return '\n\n'.join(markdown_content)
@@ -52,12 +52,15 @@ class CodeAutoGenerate:
52
52
 
53
53
  {%- if content %}
54
54
  下面是一些文件路径以及每个文件对应的源码:
55
-
55
+ <files>
56
56
  {{ content }}
57
+ </files>
57
58
  {%- endif %}
58
59
 
59
60
  {%- if context %}
61
+ <extra_context>
60
62
  {{ context }}
63
+ </extra_context>
61
64
  {%- endif %}
62
65
 
63
66
  下面是用户的需求:
@@ -131,12 +131,15 @@ class CodeAutoGenerateDiff:
131
131
 
132
132
  {%- if content %}
133
133
  下面是一些文件路径以及每个文件对应的源码:
134
-
134
+ <files>
135
135
  {{ content }}
136
+ </files>
136
137
  {%- endif %}
137
138
 
138
139
  {%- if context %}
140
+ <extra_context>
139
141
  {{ context }}
142
+ </extra_context>
140
143
  {%- endif %}
141
144
 
142
145
  下面是用户的需求:
@@ -265,12 +268,15 @@ class CodeAutoGenerateDiff:
265
268
 
266
269
  {%- if content %}
267
270
  下面是一些文件路径以及每个文件对应的源码:
268
-
271
+ <files>
269
272
  {{ content }}
273
+ </files>
270
274
  {%- endif %}
271
275
 
272
276
  {%- if context %}
277
+ <extra_context>
273
278
  {{ context }}
279
+ </extra_context>
274
280
  {%- endif %}
275
281
 
276
282
  下面是用户的需求:
@@ -182,12 +182,15 @@ class CodeAutoGenerateEditBlock:
182
182
 
183
183
  {%- if content %}
184
184
  下面是一些文件路径以及每个文件对应的源码:
185
-
185
+ <files>
186
186
  {{ content }}
187
+ </files>
187
188
  {%- endif %}
188
189
 
189
190
  {%- if context %}
191
+ <extra_context>
190
192
  {{ context }}
193
+ </extra_context>
191
194
  {%- endif %}
192
195
 
193
196
  下面是用户的需求:
@@ -343,12 +346,15 @@ class CodeAutoGenerateEditBlock:
343
346
 
344
347
  {%- if content %}
345
348
  下面是一些文件路径以及每个文件对应的源码:
346
-
349
+ <files>
347
350
  {{ content }}
351
+ </files>
348
352
  {%- endif %}
349
353
 
350
354
  {%- if context %}
355
+ <extra_context>
351
356
  {{ context }}
357
+ </extra_context>
352
358
  {%- endif %}
353
359
 
354
360
  下面是用户的需求:
@@ -114,12 +114,15 @@ class CodeAutoGenerateStrictDiff:
114
114
 
115
115
  {%- if content %}
116
116
  下面是一些文件路径以及每个文件对应的源码:
117
-
117
+ <files>
118
118
  {{ content }}
119
+ </files>
119
120
  {%- endif %}
120
121
 
121
122
  {%- if context %}
123
+ <extra_context>
122
124
  {{ context }}
125
+ </extra_context>
123
126
  {%- endif %}
124
127
 
125
128
  下面是用户的需求:
@@ -235,12 +238,15 @@ class CodeAutoGenerateStrictDiff:
235
238
 
236
239
  {%- if content %}
237
240
  下面是一些文件路径以及每个文件对应的源码:
238
-
241
+ <files>
239
242
  {{ content }}
243
+ </files>
240
244
  {%- endif %}
241
245
 
242
246
  {%- if context %}
247
+ <extra_context>
243
248
  {{ context }}
249
+ </extra_context>
244
250
  {%- endif %}
245
251
 
246
252
  下面是用户的需求:
@@ -14,6 +14,12 @@ COMMANDS = {
14
14
  },
15
15
  "/coding": {"/apply": {}, "/next": {}},
16
16
  "/chat": {"/new": {}, "/review": {}, "/no_context": {}},
17
+ "/mcp": {
18
+ "/add": "",
19
+ "/remove": "",
20
+ "/list": "",
21
+ "/list_running": ""
22
+ },
17
23
  "/lib": {
18
24
  "/add": "",
19
25
  "/remove": "",
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import json
3
3
  import asyncio
4
+ import aiohttp
4
5
  from datetime import datetime
5
6
  from typing import Dict, List, Optional, Any, Set, Optional
6
7
  from pathlib import Path
@@ -10,6 +11,8 @@ from mcp import ClientSession
10
11
  from mcp.client.stdio import stdio_client, StdioServerParameters
11
12
  import mcp.types as mcp_types
12
13
  from loguru import logger
14
+ import time
15
+
13
16
 
14
17
  class McpTool(BaseModel):
15
18
  """Represents an MCP tool configuration"""
@@ -60,6 +63,24 @@ class McpConnection:
60
63
  )
61
64
 
62
65
 
66
+ MCP_PERPLEXITY_SERVER = '''
67
+ {
68
+ "perplexity": {
69
+ "command": "python",
70
+ "args": [
71
+ "-m", "autocoder.common.mcp_servers.mcp_server_perplexity"
72
+ ],
73
+ "env": {
74
+ "PERPLEXITY_API_KEY": "{{PERPLEXITY_API_KEY}}"
75
+ }
76
+ }
77
+ }
78
+ '''
79
+
80
+ MCP_BUILD_IN_SERVERS = {
81
+ "perplexity": json.loads(MCP_PERPLEXITY_SERVER)["perplexity"]
82
+ }
83
+
63
84
  class McpHub:
64
85
  """
65
86
  Manages MCP server connections and interactions.
@@ -98,6 +119,45 @@ class McpHub:
98
119
  with open(self.settings_path, "w") as f:
99
120
  json.dump(default_settings, f, indent=2)
100
121
 
122
+ async def add_server_config(self, name: str, config:Dict[str,Any]) -> None:
123
+ """
124
+ Add or update MCP server configuration in settings file.
125
+
126
+ Args:
127
+ server_name_or_config: Name of the server or server configuration dictionary
128
+ """
129
+ try:
130
+ settings = self._read_settings()
131
+ settings["mcpServers"][name] = config
132
+ with open(self.settings_path, "w") as f:
133
+ json.dump(settings, f, indent=2, ensure_ascii=False)
134
+ await self.initialize()
135
+ logger.info(f"Added/updated MCP server config: {name}")
136
+ except Exception as e:
137
+ logger.error(f"Failed to add MCP server config: {e}")
138
+ raise
139
+
140
+ async def remove_server_config(self, name: str) -> None:
141
+ """
142
+ Remove MCP server configuration from settings file.
143
+
144
+ Args:
145
+ name: Name of the server to remove
146
+ """
147
+ try:
148
+ settings = self._read_settings()
149
+ if name in settings["mcpServers"]:
150
+ del settings["mcpServers"][name]
151
+ with open(self.settings_path, "w") as f:
152
+ json.dump(settings, f, indent=2, ensure_ascii=False)
153
+ logger.info(f"Removed MCP server config: {name}")
154
+ await self.initialize()
155
+ else:
156
+ logger.warning(f"MCP server {name} not found in settings")
157
+ except Exception as e:
158
+ logger.error(f"Failed to remove MCP server config: {e}")
159
+ raise
160
+
101
161
  async def initialize(self):
102
162
  """Initialize MCP server connections from settings"""
103
163
  try:
@@ -128,14 +188,15 @@ class McpHub:
128
188
  server_params = StdioServerParameters(
129
189
  command=config["command"],
130
190
  args=config.get("args", []),
131
- env={**config.get("env", {}), "PATH": os.environ.get("PATH", "")},
191
+ env={**config.get("env", {}),
192
+ "PATH": os.environ.get("PATH", "")},
132
193
  )
133
194
 
134
195
  # Create transport using context manager
135
196
  transport_manager = stdio_client(server_params)
136
197
  transport = await transport_manager.__aenter__()
137
198
  try:
138
- session = await ClientSession(transport[0], transport[1]).__aenter__()
199
+ session = await ClientSession(transport[0], transport[1]).__aenter__()
139
200
  await session.initialize()
140
201
 
141
202
  # Store connection with transport manager
@@ -148,9 +209,9 @@ class McpHub:
148
209
  server.resources = await self._fetch_resources(name)
149
210
  server.resource_templates = await self._fetch_resource_templates(name)
150
211
 
151
- except Exception as e:
212
+ except Exception as e:
152
213
  # Clean up transport if session initialization fails
153
-
214
+
154
215
  await transport_manager.__aexit__(None, None, None)
155
216
  raise
156
217
 
@@ -204,7 +265,8 @@ class McpHub:
204
265
  elif current_conn.server.config != json.dumps(config):
205
266
  # Updated configuration
206
267
  await self.connect_to_server(name, config)
207
- logger.info(f"Reconnected MCP server with updated config: {name}")
268
+ logger.info(
269
+ f"Reconnected MCP server with updated config: {name}")
208
270
 
209
271
  finally:
210
272
  self.is_connecting = False
@@ -284,7 +346,8 @@ class McpHub:
284
346
  for template in response.resourceTemplates
285
347
  ]
286
348
  except Exception as e:
287
- logger.error(f"Failed to fetch resource templates for {server_name}: {e}")
349
+ logger.error(
350
+ f"Failed to fetch resource templates for {server_name}: {e}")
288
351
  return []
289
352
 
290
353
  def _read_settings(self) -> dict: