auto-coder 0.1.289__py3-none-any.whl → 0.1.290__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,494 @@
1
+ from typing import Dict, Any, Optional, Union, List, Tuple, Dict
2
+ import logging
3
+ from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ import glob
5
+ import json
6
+ from datetime import datetime
7
+ import time
8
+ import os
9
+
10
+ import byzerllm
11
+
12
+ from autocoder.common import SourceCode
13
+ from autocoder.rag.token_counter import count_tokens
14
+ from pydantic import BaseModel
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class FileUsage(BaseModel):
19
+ description: str
20
+
21
+
22
+ class FileMetaItem(BaseModel):
23
+ file_path:str
24
+ usage:str
25
+ md5:str
26
+ timestamp:float
27
+
28
+ class RAGFileMeta:
29
+ """
30
+ A class that generates short descriptions for files.
31
+ """
32
+
33
+ def __init__(self, llm: Union[byzerllm.ByzerLLM, byzerllm.SimpleByzerLLM]):
34
+ """
35
+ Initialize the FileMeta with a ByzerLLM instance.
36
+
37
+ Args:
38
+ llm: The ByzerLLM instance to use for generating descriptions.
39
+ """
40
+ self.llm = llm
41
+
42
+ @byzerllm.prompt()
43
+ def generate_file_description(self, file_path: str, content: str) -> str:
44
+ """
45
+ 分析文件内容,生成一个简洁的描述,概括文件的核心功能和用途。
46
+
47
+ 参数:
48
+ file_path: 文件路径
49
+ content: 文件内容
50
+
51
+ 返回:
52
+ 文件用途的简短描述(10个汉字左右)
53
+
54
+ 任务说明:
55
+ 你的目标是根据文档生成一个简短而精准的描述,帮助用户快速理解该文件的主要用途。
56
+
57
+ 要求:
58
+ 1. 生成的描述必须简洁,控制在10个汉字左右
59
+ 2. 描述应直接反映文件的核心功能,而非技术细节
60
+ 3. 使用专业且准确的术语
61
+ 4. 如果文件包含类,重点描述类的主要功能
62
+ 5. 如果文件包含多个函数,提炼出它们的共同目的
63
+ 6. 避免过于宽泛的描述,如"工具类"、"辅助函数"等
64
+
65
+ 优秀示例:
66
+ - 数据库连接池管理 (对于实现数据库连接管理的文件)
67
+ - 用户认证中间件 (对于处理用户身份验证的文件)
68
+ - 日志记录格式化器 (对于定义日志格式的文件)
69
+ - 图像缩放处理器 (对于处理图像大小的文件)
70
+ - 配置文件解析器 (对于解析配置文件的代码)
71
+ - 缓存失效策略 (对于处理缓存过期的文件)
72
+
73
+ ---
74
+
75
+ 文件路径: <path>{{ file_path }}</path>
76
+
77
+ 文件内容:
78
+ <document>
79
+ {{ content }}
80
+ </document>
81
+
82
+ 请分析上述文档,提供一个简洁的描述(10个汉字左右),准确概括该文件的核心功能。输出格式为
83
+
84
+ ```json
85
+ {
86
+ "description": "文件描述"
87
+ }
88
+ ```
89
+ """
90
+
91
+ @byzerllm.prompt()
92
+ def answer_with_file_meta(self, file_meta_list: List[FileMetaItem], conversations: List[Dict[str, Any]]) -> str:
93
+ """
94
+ 根据文件元数据上下文回答用户问题。
95
+
96
+ 参数:
97
+ file_meta_list: 文件元数据列表
98
+ conversations: 历史对话
99
+
100
+ 返回:
101
+ 基于文件元数据的回答
102
+
103
+ 任务说明:
104
+ 你是一个了解知识库结构的助手。你的任务是基于提供的文件元数据信息来回答用户的问题。
105
+ 元数据包含了每个文件的路径、用途描述和其他相关信息。
106
+
107
+ 要求:
108
+ 1. 根据文件元数据的用途描述,找出与用户问题最相关的文件
109
+ 2. 提供准确、有帮助的回答,引用相关文件的信息
110
+ 3. 如果用户询问特定功能,尝试找到实现该功能的文件并解释其作用
111
+ 4. 如果用户询问项目结构,根据文件路径和用途提供概述
112
+ 5. 回答应该直接明了,提供有价值的信息
113
+ 6. 如果元数据中没有足够信息,坦诚说明并提供尽可能有用的回应
114
+
115
+ ---
116
+
117
+ 项目文件元数据:
118
+ <file_meta_list>
119
+ {% for item in file_meta_list %}
120
+ - 文件: <path>{{ item.file_path }}</path>
121
+ 用途: {{ item.usage }}
122
+ {% endfor %}
123
+ </file_meta_list>
124
+
125
+ 历史对话:
126
+ <conversations>
127
+ {% for msg in conversations %}
128
+ <{{ msg.role }}>: {{ msg.content }}
129
+ {% endfor %}
130
+ </conversations>
131
+
132
+ 请根据上述文件元数据,根据历史对话,回答用户最后一个问题。
133
+ """
134
+
135
+ def describe_file(self, file_path: str, content: str) -> FileUsage:
136
+ """
137
+ Generate a description for a file.
138
+
139
+ Args:
140
+ file_path: The path to the file.
141
+ content: The content of the file.
142
+
143
+ Returns:
144
+ A short description of the file.
145
+ """
146
+ try:
147
+ response = self.generate_file_description.with_llm(self.llm).with_return_type(FileUsage).run(file_path=file_path, content=content)
148
+ return response
149
+ except Exception as e:
150
+ logger.error(f"Error generating description for {file_path}: {str(e)}")
151
+ return FileUsage(description="未知用途文件")
152
+
153
+ def answer_query(self, file_meta_items: List[FileMetaItem], query: str) -> str:
154
+ """
155
+ 根据文件元数据回答用户查询。
156
+
157
+ 参数:
158
+ file_meta_items: FileMetaItem 对象列表,包含文件元数据
159
+ query: 用户的问题或查询
160
+
161
+ 返回:
162
+ 基于文件元数据的回答
163
+ """
164
+ try:
165
+ # 将 FileMetaItem 对象转换为字典列表,以便在提示中使用
166
+ file_meta_dicts = [item.model_dump() for item in file_meta_items]
167
+
168
+ # 使用 prompt 函数生成回答
169
+ response = self.answer_with_file_meta.with_llm(self.llm).run(
170
+ file_meta_list=file_meta_dicts,
171
+ query=query
172
+ )
173
+
174
+ return response
175
+ except Exception as e:
176
+ logger.error(f"Error answering query with file metadata: {str(e)}")
177
+ return f"抱歉,无法处理您的查询: {str(e)}"
178
+
179
+ def describe_files(self, files: List[Tuple[str, str]], max_workers: Optional[int] = None) -> Dict[str, FileUsage]:
180
+ """
181
+ 使用多线程并发处理多个文件,为每个文件生成描述。
182
+
183
+ Args:
184
+ files: 包含文件路径和内容的元组列表,格式为 [(file_path1, content1), (file_path2, content2), ...]
185
+ max_workers: 最大线程数,默认为 None(使用 ThreadPoolExecutor 的默认值)
186
+
187
+ Returns:
188
+ 一个字典,键为文件路径,值为对应的 FileUsage 对象
189
+ """
190
+ results = {}
191
+
192
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
193
+ # 创建 Future 对象的字典,键为文件路径
194
+ future_to_path = {
195
+ executor.submit(self.describe_file, file_path, content): file_path
196
+ for file_path, content in files
197
+ }
198
+
199
+ # 处理完成的任务
200
+ for future in as_completed(future_to_path):
201
+ file_path = future_to_path[future]
202
+ try:
203
+ file_usage = future.result()
204
+ results[file_path] = file_usage
205
+ except Exception as e:
206
+ logger.error(f"处理文件 {file_path} 时发生错误: {str(e)}")
207
+ results[file_path] = FileUsage(description="处理失败")
208
+
209
+ return results
210
+
211
+ def describe_files_batch(self, files: List[Tuple[str, str]], batch_size: int = 10, max_workers: Optional[int] = None) -> Dict[str, FileUsage]:
212
+ """
213
+ 分批次使用多线程处理文件,适用于大量文件场景,避免创建过多线程。
214
+
215
+ Args:
216
+ files: 包含文件路径和内容的元组列表,格式为 [(file_path1, content1), (file_path2, content2), ...]
217
+ batch_size: 每批处理的文件数量
218
+ max_workers: 每批次的最大线程数
219
+
220
+ Returns:
221
+ 一个字典,键为文件路径,值为对应的 FileUsage 对象
222
+ """
223
+ all_results = {}
224
+
225
+ # 将文件列表分成批次
226
+ for i in range(0, len(files), batch_size):
227
+ batch = files[i:i + batch_size]
228
+ # 处理当前批次
229
+ batch_results = self.describe_files(batch, max_workers=max_workers)
230
+ # 合并结果
231
+ all_results.update(batch_results)
232
+
233
+ return all_results
234
+
235
+ def build_meta(doc_path: str, llm: Union[byzerllm.ByzerLLM, byzerllm.SimpleByzerLLM], batch_size: int = 10, max_workers: Optional[int] = None) -> str:
236
+ """
237
+ 构建文件元数据信息,支持增量更新。
238
+
239
+ 此函数会:
240
+ 1. 查找 doc 目录下 .cache 目录中文件名包含 cache 的 jsonl 文件
241
+ 2. 找出最新的文件并读取内容
242
+ 3. 如果 meta.jsonl 已存在,会读取其中已有的元数据
243
+ 4. 只对 MD5 发生变化或新文件生成新的用途描述
244
+ 5. 将结果保存到 meta.jsonl 文件
245
+
246
+ 参数:
247
+ doc_path: 文档根目录路径
248
+ llm: ByzerLLM 实例,用于生成文件描述
249
+ batch_size: 批处理大小,默认为 10
250
+ max_workers: 最大线程数,默认为 None(使用 ThreadPoolExecutor 的默认值)
251
+
252
+ 返回:
253
+ 生成的元数据文件路径
254
+ """
255
+ # 确保 .cache 目录存在
256
+ cache_dir = os.path.join(doc_path, ".cache")
257
+ if not os.path.exists(cache_dir):
258
+ logger.warning(f"Cache directory not found: {cache_dir}")
259
+ return ""
260
+
261
+ # 查找文件名包含 cache 的 jsonl 文件
262
+ cache_files = glob.glob(os.path.join(cache_dir, "*cache*.jsonl"))
263
+ if not cache_files:
264
+ logger.warning(f"No cache files found in {cache_dir}")
265
+ return ""
266
+
267
+ # 根据修改时间排序,找出最新的文件
268
+ latest_cache_file = max(cache_files, key=os.path.getmtime)
269
+ logger.info(f"Using latest cache file: {latest_cache_file}")
270
+
271
+ # 读取缓存文件内容
272
+ file_contents = []
273
+ file_md5_map = {} # 用于存储文件路径到MD5的映射
274
+ file_mtime_map = {} # 用于存储文件路径到修改时间的映射
275
+ current_files = set() # 用于跟踪当前存在的文件
276
+
277
+ try:
278
+ with open(latest_cache_file, 'r', encoding='utf-8') as f:
279
+ for line in f:
280
+ try:
281
+ data = json.loads(line.strip())
282
+ file_path = data.get("file_path", "")
283
+ if not file_path:
284
+ continue
285
+
286
+ # 标准化文件路径
287
+ file_path = os.path.normpath(file_path)
288
+ current_files.add(file_path)
289
+
290
+ # 提取MD5值
291
+ file_md5 = data.get("md5", "")
292
+ if file_path and file_md5:
293
+ file_md5_map[file_path] = file_md5
294
+
295
+ # 提取修改时间
296
+ modify_time = data.get("modify_time", 0.0)
297
+ if modify_time:
298
+ file_mtime_map[file_path] = float(modify_time)
299
+
300
+ if "content" in data:
301
+ # 先将内容解析为 SourceCode 对象
302
+ source_codes = []
303
+ try:
304
+ source_codes = [SourceCode.model_validate(item) for item in data["content"]]
305
+
306
+ # 从 SourceCode 对象中提取文本内容
307
+ file_content = ""
308
+ for source_code in source_codes:
309
+ if hasattr(source_code, "source_code") and source_code.source_code:
310
+ file_content += source_code.source_code + "\n"
311
+
312
+ if file_content:
313
+ file_contents.append((file_path, file_content))
314
+ except Exception as e:
315
+ logger.warning(f"Error parsing SourceCode for {file_path}: {str(e)}")
316
+ # 兼容旧格式:直接从 content_item 中提取 source_code
317
+ file_content = ""
318
+ for content_item in data["content"]:
319
+ if "source_code" in content_item:
320
+ file_content += content_item["source_code"] + "\n"
321
+
322
+ if file_content:
323
+ file_contents.append((file_path, file_content))
324
+ except json.JSONDecodeError:
325
+ logger.warning(f"Invalid JSON line in {latest_cache_file}")
326
+ continue
327
+ except Exception as e:
328
+ logger.warning(f"Error processing line in cache file: {str(e)}")
329
+ continue
330
+ except Exception as e:
331
+ logger.error(f"Error reading cache file {latest_cache_file}: {str(e)}")
332
+ return ""
333
+
334
+ if not file_contents:
335
+ logger.warning(f"No valid file contents found in {latest_cache_file}")
336
+ return ""
337
+
338
+ # 检查是否存在 meta.jsonl 文件,如果存在则读取以支持增量更新
339
+ meta_file_path = os.path.join(cache_dir, "meta.jsonl")
340
+ existing_meta = {}
341
+ if os.path.exists(meta_file_path):
342
+ try:
343
+ with open(meta_file_path, 'r', encoding='utf-8') as f:
344
+ for line in f:
345
+ try:
346
+ # 使用 FileMetaItem 解析元数据
347
+ meta_item = FileMetaItem.model_validate_json(line.strip())
348
+ file_path = meta_item.file_path
349
+
350
+ if not file_path:
351
+ continue
352
+
353
+ # 标准化文件路径
354
+ file_path = os.path.normpath(file_path)
355
+ existing_meta[file_path] = meta_item
356
+ except json.JSONDecodeError:
357
+ logger.warning(f"Invalid JSON line in {meta_file_path}")
358
+ continue
359
+ except Exception as e:
360
+ logger.warning(f"Error processing line in meta file: {str(e)}")
361
+ continue
362
+ logger.info(f"Loaded {len(existing_meta)} entries from existing meta file")
363
+ except Exception as e:
364
+ logger.warning(f"Error reading existing meta file {meta_file_path}: {str(e)}")
365
+
366
+ # 分类文件:哪些需要重新生成描述,哪些可以复用
367
+ files_to_process = []
368
+ reused_meta = {}
369
+
370
+ for file_path, content in file_contents:
371
+ current_md5 = file_md5_map.get(file_path, "")
372
+
373
+ # 如果文件在现有元数据中存在,且MD5未变化,则复用
374
+ # 注意:只有当缓存中和元数据中都有MD5,且相等时才复用
375
+ if (file_path in existing_meta and
376
+ existing_meta[file_path].md5 and
377
+ current_md5 and
378
+ existing_meta[file_path].md5 == current_md5):
379
+ reused_meta[file_path] = existing_meta[file_path]
380
+ logger.debug(f"Reusing metadata for unchanged file: {file_path}")
381
+ else:
382
+ # 否则需要重新处理
383
+ files_to_process.append((file_path, content))
384
+ logger.debug(f"Need to process file: {file_path}, md5 changed or new file")
385
+
386
+ # 使用 RAGFileMeta 生成新的文件描述
387
+ new_file_usages = {}
388
+ if files_to_process:
389
+ logger.info(f"Processing {len(files_to_process)} files with changed content or new files")
390
+ rag_file_meta = RAGFileMeta(llm)
391
+ try:
392
+ new_file_usages = rag_file_meta.describe_files_batch(
393
+ files=files_to_process,
394
+ batch_size=batch_size,
395
+ max_workers=max_workers
396
+ )
397
+ except Exception as e:
398
+ logger.error(f"Error during batch processing of files: {str(e)}")
399
+ # 继续执行,使用已处理的文件
400
+ else:
401
+ logger.info("No files need to be processed, all files unchanged")
402
+
403
+ # 合并新旧元数据,只包含当前存在的文件
404
+ all_meta_items = []
405
+
406
+ # 添加复用的元数据
407
+ for file_path, meta_item in reused_meta.items():
408
+ if file_path in current_files: # 只包含当前存在的文件
409
+ all_meta_items.append(meta_item)
410
+
411
+ # 添加新生成的元数据
412
+ current_time = time.time() # 获取当前时间戳(浮点数秒)
413
+ for file_path, file_usage in new_file_usages.items():
414
+ if file_path in current_files: # 只包含当前存在的文件
415
+ # 优先使用文件的实际修改时间,若不可用则使用当前时间
416
+ file_timestamp = file_mtime_map.get(file_path, current_time)
417
+
418
+ # 使用 FileMetaItem 模型创建新的元数据条目
419
+ new_meta_item = FileMetaItem(
420
+ file_path=file_path,
421
+ usage=file_usage.description,
422
+ md5=file_md5_map.get(file_path, ""),
423
+ timestamp=file_timestamp # 使用 float 类型的时间戳
424
+ )
425
+ all_meta_items.append(new_meta_item)
426
+
427
+ # 保存元数据到 meta.jsonl 文件
428
+ try:
429
+ with open(meta_file_path, 'w', encoding='utf-8') as f:
430
+ for meta_item in all_meta_items:
431
+ f.write(json.dumps(meta_item.model_dump(), ensure_ascii=False) + "\n")
432
+
433
+ removed_count = len(existing_meta) - len(reused_meta)
434
+ logger.info(
435
+ f"Metadata saved to {meta_file_path}, "
436
+ f"total {len(all_meta_items)} entries "
437
+ f"(reused: {len(reused_meta)}, new: {len(new_file_usages)}, removed: {removed_count})"
438
+ )
439
+ except Exception as e:
440
+ logger.error(f"Error writing metadata file {meta_file_path}: {str(e)}")
441
+ return ""
442
+
443
+ return meta_file_path
444
+
445
+ def get_meta(doc_path: str, llm: Optional[Union[byzerllm.ByzerLLM, byzerllm.SimpleByzerLLM]] = None) -> List[FileMetaItem]:
446
+ """
447
+ 获取文件元数据信息。
448
+
449
+ 此函数会读取 meta.jsonl 文件并返回解析后的 FileMetaItem 列表。
450
+ 如果文件不存在且提供了 llm 参数,将尝试通过调用 build_meta 生成元数据文件。
451
+
452
+ 参数:
453
+ doc_path: 文档根目录路径
454
+ llm: 可选的 ByzerLLM 实例,用于在元数据不存在时生成
455
+
456
+ 返回:
457
+ FileMetaItem 对象列表
458
+ """
459
+ # 确保 .cache 目录存在
460
+ cache_dir = os.path.join(doc_path, ".cache")
461
+ meta_file_path = os.path.join(cache_dir, "meta.jsonl")
462
+ meta_items = []
463
+
464
+ # 如果元数据文件不存在,尝试构建
465
+ if not os.path.exists(meta_file_path):
466
+ if llm is not None:
467
+ logger.info(f"Meta file not found, trying to build: {meta_file_path}")
468
+ build_meta(doc_path, llm)
469
+ else:
470
+ logger.warning(f"Meta file not found and no LLM provided: {meta_file_path}")
471
+ return []
472
+
473
+ # 读取元数据文件
474
+ if os.path.exists(meta_file_path):
475
+ try:
476
+ with open(meta_file_path, 'r', encoding='utf-8') as f:
477
+ for line in f:
478
+ try:
479
+ # 解析 FileMetaItem
480
+ meta_item = FileMetaItem.model_validate_json(line.strip())
481
+ # 标准化文件路径
482
+ meta_item.file_path = os.path.normpath(meta_item.file_path)
483
+ meta_items.append(meta_item)
484
+ except json.JSONDecodeError:
485
+ logger.warning(f"Invalid JSON line in {meta_file_path}")
486
+ continue
487
+ except Exception as e:
488
+ logger.warning(f"Error processing line in meta file: {str(e)}")
489
+ continue
490
+ logger.info(f"Loaded {len(meta_items)} metadata entries from {meta_file_path}")
491
+ except Exception as e:
492
+ logger.error(f"Error reading meta file {meta_file_path}: {str(e)}")
493
+
494
+ return meta_items
@@ -1,7 +1,10 @@
1
-
2
1
  from multiprocessing import Pool
3
2
  from autocoder.common import SourceCode
4
- from autocoder.rag.cache.base_cache import BaseCacheManager, DeleteEvent, AddOrUpdateEvent
3
+ from autocoder.rag.cache.base_cache import (
4
+ BaseCacheManager, DeleteEvent, AddOrUpdateEvent,
5
+ FileInfo,
6
+ CacheItem
7
+ )
5
8
  from typing import Dict, List, Tuple, Any, Optional, Union
6
9
  import os
7
10
  import threading
@@ -17,6 +20,7 @@ from autocoder.rag.utils import process_file_in_multi_process, process_file_loca
17
20
  from autocoder.rag.variable_holder import VariableHolder
18
21
  import hashlib
19
22
 
23
+
20
24
  default_ignore_dirs = [
21
25
  "__pycache__",
22
26
  "node_modules",
@@ -42,11 +46,52 @@ def generate_content_md5(content: Union[str, bytes]) -> str:
42
46
 
43
47
  class AutoCoderRAGAsyncUpdateQueue(BaseCacheManager):
44
48
  def __init__(self, path: str, ignore_spec, required_exts: list):
49
+ """
50
+ 初始化异步更新队列,用于管理代码文件的缓存。
51
+
52
+ 参数:
53
+ path: 需要索引的代码库根目录
54
+ ignore_spec: 指定哪些文件/目录应被忽略的规则
55
+ required_exts: 需要处理的文件扩展名列表
56
+
57
+ 缓存结构 (self.cache):
58
+ self.cache 是一个字典,其结构如下:
59
+ {
60
+ "file_path1": { # 键为文件的绝对路径
61
+ "file_path": str, # 文件的绝对路径
62
+ "relative_path": str, # 相对于项目根目录的路径
63
+ "content": List[Dict], # 文件内容的结构化表示,每个元素是 SourceCode 对象的序列化
64
+ "modify_time": float, # 文件最后修改时间的时间戳
65
+ "md5": str # 文件内容的 MD5 哈希值,用于检测变更
66
+ },
67
+ "file_path2": { ... },
68
+ ...
69
+ }
70
+
71
+ 这个缓存保存在项目根目录的 .cache/cache.jsonl 文件中,采用 JSONL 格式存储。
72
+ 每次启动时从磁盘加载,并在文件变更时异步更新。
73
+
74
+ 源代码处理函数:
75
+ 在缓存更新过程中使用了两个关键函数:
76
+
77
+ 1. process_file_in_multi_process: 在多进程环境中处理文件
78
+ - 参数: file_info (文件信息元组)
79
+ - 返回值: List[SourceCode] 或 None
80
+ - 用途: 在初始加载时并行处理多个文件
81
+
82
+ 2. process_file_local: 在当前进程中处理单个文件
83
+ - 参数: file_path (文件路径)
84
+ - 返回值: List[SourceCode] 或 None
85
+ - 用途: 在检测到文件更新时处理单个文件
86
+
87
+ 这两个函数返回的 SourceCode 对象列表会通过 model_dump() 方法序列化为字典,
88
+ 然后存储在缓存的 "content" 字段中。如果返回为空,则跳过缓存更新。
89
+ """
45
90
  self.path = path
46
91
  self.ignore_spec = ignore_spec
47
92
  self.required_exts = required_exts
48
93
  self.queue = []
49
- self.cache = {}
94
+ self.cache = {} # 初始化为空字典,稍后通过 read_cache() 填充
50
95
  self.lock = threading.Lock()
51
96
  self.stop_event = threading.Event()
52
97
  self.thread = threading.Thread(target=self._process_queue)
@@ -202,6 +247,25 @@ class AutoCoderRAGAsyncUpdateQueue(BaseCacheManager):
202
247
  def update_cache(
203
248
  self, file_info: Tuple[str, str, float, str], content: List[SourceCode]
204
249
  ):
250
+ """
251
+ 更新缓存中的文件信息。
252
+
253
+ 参数:
254
+ file_info: 包含文件信息的元组 (file_path, relative_path, modify_time, file_md5)
255
+ content: 解析后的文件内容,SourceCode 对象列表
256
+
257
+ 说明:
258
+ 此方法将文件的最新内容更新到缓存中。缓存项的结构为:
259
+ {
260
+ "file_path": str, # 文件的绝对路径
261
+ "relative_path": str, # 相对于项目根目录的路径
262
+ "content": List[Dict], # 文件内容的结构化表示,每个元素是 SourceCode 对象的序列化结果
263
+ "modify_time": float, # 文件最后修改时间的时间戳
264
+ "md5": str # 文件内容的 MD5 哈希值,用于检测变更
265
+ }
266
+
267
+ 该方法不会立即写入磁盘,需调用 write_cache() 方法将更新后的缓存持久化。
268
+ """
205
269
  file_path, relative_path, modify_time, file_md5 = file_info
206
270
  self.cache[file_path] = {
207
271
  "file_path": file_path,