jarvis-ai-assistant 0.7.16__py3-none-any.whl → 1.0.2__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.
Files changed (279) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +567 -222
  3. jarvis/jarvis_agent/agent_manager.py +19 -12
  4. jarvis/jarvis_agent/builtin_input_handler.py +79 -11
  5. jarvis/jarvis_agent/config_editor.py +7 -2
  6. jarvis/jarvis_agent/event_bus.py +24 -13
  7. jarvis/jarvis_agent/events.py +19 -1
  8. jarvis/jarvis_agent/file_context_handler.py +67 -64
  9. jarvis/jarvis_agent/file_methodology_manager.py +38 -24
  10. jarvis/jarvis_agent/jarvis.py +186 -114
  11. jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
  12. jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
  13. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
  14. jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
  15. jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
  16. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
  17. jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
  18. jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
  19. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
  20. jarvis/jarvis_agent/language_support_info.py +250 -219
  21. jarvis/jarvis_agent/main.py +19 -23
  22. jarvis/jarvis_agent/memory_manager.py +9 -6
  23. jarvis/jarvis_agent/methodology_share_manager.py +21 -15
  24. jarvis/jarvis_agent/output_handler.py +4 -2
  25. jarvis/jarvis_agent/prompt_builder.py +7 -6
  26. jarvis/jarvis_agent/prompt_manager.py +113 -8
  27. jarvis/jarvis_agent/prompts.py +317 -85
  28. jarvis/jarvis_agent/protocols.py +5 -2
  29. jarvis/jarvis_agent/run_loop.py +192 -32
  30. jarvis/jarvis_agent/session_manager.py +7 -3
  31. jarvis/jarvis_agent/share_manager.py +23 -13
  32. jarvis/jarvis_agent/shell_input_handler.py +12 -8
  33. jarvis/jarvis_agent/stdio_redirect.py +25 -26
  34. jarvis/jarvis_agent/task_analyzer.py +29 -23
  35. jarvis/jarvis_agent/task_list.py +869 -0
  36. jarvis/jarvis_agent/task_manager.py +26 -23
  37. jarvis/jarvis_agent/tool_executor.py +6 -5
  38. jarvis/jarvis_agent/tool_share_manager.py +24 -14
  39. jarvis/jarvis_agent/user_interaction.py +3 -3
  40. jarvis/jarvis_agent/utils.py +9 -1
  41. jarvis/jarvis_agent/web_bridge.py +37 -17
  42. jarvis/jarvis_agent/web_output_sink.py +5 -2
  43. jarvis/jarvis_agent/web_server.py +165 -36
  44. jarvis/jarvis_c2rust/__init__.py +1 -1
  45. jarvis/jarvis_c2rust/cli.py +260 -141
  46. jarvis/jarvis_c2rust/collector.py +37 -18
  47. jarvis/jarvis_c2rust/constants.py +60 -0
  48. jarvis/jarvis_c2rust/library_replacer.py +242 -1010
  49. jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
  50. jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
  51. jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
  52. jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
  53. jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
  54. jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
  55. jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
  56. jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
  57. jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
  58. jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
  59. jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
  60. jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
  61. jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
  62. jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
  63. jarvis/jarvis_c2rust/loaders.py +28 -10
  64. jarvis/jarvis_c2rust/models.py +5 -2
  65. jarvis/jarvis_c2rust/optimizer.py +192 -1974
  66. jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
  67. jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
  68. jarvis/jarvis_c2rust/optimizer_config.py +49 -0
  69. jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
  70. jarvis/jarvis_c2rust/optimizer_options.py +48 -0
  71. jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
  72. jarvis/jarvis_c2rust/optimizer_report.py +52 -0
  73. jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
  74. jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
  75. jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
  76. jarvis/jarvis_c2rust/scanner.py +229 -166
  77. jarvis/jarvis_c2rust/transpiler.py +531 -2732
  78. jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
  79. jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
  80. jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
  81. jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
  82. jarvis/jarvis_c2rust/transpiler_config.py +178 -0
  83. jarvis/jarvis_c2rust/transpiler_context.py +122 -0
  84. jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
  85. jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
  86. jarvis/jarvis_c2rust/transpiler_git.py +163 -0
  87. jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
  88. jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
  89. jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
  90. jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
  91. jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
  92. jarvis/jarvis_c2rust/utils.py +269 -79
  93. jarvis/jarvis_code_agent/after_change.py +233 -0
  94. jarvis/jarvis_code_agent/build_validation_config.py +37 -30
  95. jarvis/jarvis_code_agent/builtin_rules.py +68 -0
  96. jarvis/jarvis_code_agent/code_agent.py +976 -1517
  97. jarvis/jarvis_code_agent/code_agent_build.py +227 -0
  98. jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
  99. jarvis/jarvis_code_agent/code_agent_git.py +525 -0
  100. jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
  101. jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
  102. jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
  103. jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
  104. jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
  105. jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
  106. jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
  107. jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
  108. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
  109. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
  110. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
  111. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
  112. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
  113. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
  114. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
  115. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
  116. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
  117. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
  118. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
  119. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
  120. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
  121. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
  122. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
  123. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
  124. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
  125. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
  126. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
  127. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
  128. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
  129. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
  130. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
  131. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
  132. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
  133. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
  134. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
  135. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
  136. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
  137. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
  138. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
  139. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
  140. jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
  141. jarvis/jarvis_code_agent/lint.py +223 -524
  142. jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
  143. jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
  144. jarvis/jarvis_code_agent/rules/code_review.md +115 -0
  145. jarvis/jarvis_code_agent/rules/documentation.md +165 -0
  146. jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
  147. jarvis/jarvis_code_agent/rules/performance.md +158 -0
  148. jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
  149. jarvis/jarvis_code_agent/rules/security.md +160 -0
  150. jarvis/jarvis_code_agent/rules/tdd.md +78 -0
  151. jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
  152. jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
  153. jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
  154. jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
  155. jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
  156. jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
  157. jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
  158. jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
  159. jarvis/jarvis_code_agent/utils.py +36 -26
  160. jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
  161. jarvis/jarvis_code_analysis/code_review.py +64 -33
  162. jarvis/jarvis_data/config_schema.json +285 -192
  163. jarvis/jarvis_git_squash/main.py +8 -6
  164. jarvis/jarvis_git_utils/git_commiter.py +53 -76
  165. jarvis/jarvis_mcp/__init__.py +5 -2
  166. jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
  167. jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
  168. jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
  169. jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
  170. jarvis/jarvis_methodology/main.py +48 -39
  171. jarvis/jarvis_multi_agent/__init__.py +56 -23
  172. jarvis/jarvis_multi_agent/main.py +15 -18
  173. jarvis/jarvis_platform/base.py +179 -111
  174. jarvis/jarvis_platform/human.py +27 -16
  175. jarvis/jarvis_platform/kimi.py +52 -45
  176. jarvis/jarvis_platform/openai.py +101 -40
  177. jarvis/jarvis_platform/registry.py +51 -33
  178. jarvis/jarvis_platform/tongyi.py +68 -38
  179. jarvis/jarvis_platform/yuanbao.py +59 -43
  180. jarvis/jarvis_platform_manager/main.py +68 -76
  181. jarvis/jarvis_platform_manager/service.py +24 -14
  182. jarvis/jarvis_rag/README_CONFIG.md +314 -0
  183. jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
  184. jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
  185. jarvis/jarvis_rag/__init__.py +57 -4
  186. jarvis/jarvis_rag/cache.py +3 -1
  187. jarvis/jarvis_rag/cli.py +48 -68
  188. jarvis/jarvis_rag/embedding_interface.py +39 -0
  189. jarvis/jarvis_rag/embedding_manager.py +7 -230
  190. jarvis/jarvis_rag/embeddings/__init__.py +41 -0
  191. jarvis/jarvis_rag/embeddings/base.py +114 -0
  192. jarvis/jarvis_rag/embeddings/cohere.py +66 -0
  193. jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
  194. jarvis/jarvis_rag/embeddings/local.py +260 -0
  195. jarvis/jarvis_rag/embeddings/openai.py +62 -0
  196. jarvis/jarvis_rag/embeddings/registry.py +293 -0
  197. jarvis/jarvis_rag/llm_interface.py +8 -6
  198. jarvis/jarvis_rag/query_rewriter.py +8 -9
  199. jarvis/jarvis_rag/rag_pipeline.py +61 -52
  200. jarvis/jarvis_rag/reranker.py +7 -75
  201. jarvis/jarvis_rag/reranker_interface.py +32 -0
  202. jarvis/jarvis_rag/rerankers/__init__.py +41 -0
  203. jarvis/jarvis_rag/rerankers/base.py +109 -0
  204. jarvis/jarvis_rag/rerankers/cohere.py +67 -0
  205. jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
  206. jarvis/jarvis_rag/rerankers/jina.py +79 -0
  207. jarvis/jarvis_rag/rerankers/local.py +89 -0
  208. jarvis/jarvis_rag/rerankers/registry.py +293 -0
  209. jarvis/jarvis_rag/retriever.py +58 -43
  210. jarvis/jarvis_sec/__init__.py +66 -141
  211. jarvis/jarvis_sec/agents.py +21 -17
  212. jarvis/jarvis_sec/analysis.py +80 -33
  213. jarvis/jarvis_sec/checkers/__init__.py +7 -13
  214. jarvis/jarvis_sec/checkers/c_checker.py +356 -164
  215. jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
  216. jarvis/jarvis_sec/cli.py +43 -21
  217. jarvis/jarvis_sec/clustering.py +430 -272
  218. jarvis/jarvis_sec/file_manager.py +99 -55
  219. jarvis/jarvis_sec/parsers.py +9 -6
  220. jarvis/jarvis_sec/prompts.py +4 -3
  221. jarvis/jarvis_sec/report.py +44 -22
  222. jarvis/jarvis_sec/review.py +180 -107
  223. jarvis/jarvis_sec/status.py +50 -41
  224. jarvis/jarvis_sec/types.py +3 -0
  225. jarvis/jarvis_sec/utils.py +160 -83
  226. jarvis/jarvis_sec/verification.py +411 -181
  227. jarvis/jarvis_sec/workflow.py +132 -21
  228. jarvis/jarvis_smart_shell/main.py +28 -41
  229. jarvis/jarvis_stats/cli.py +14 -12
  230. jarvis/jarvis_stats/stats.py +28 -19
  231. jarvis/jarvis_stats/storage.py +14 -8
  232. jarvis/jarvis_stats/visualizer.py +12 -7
  233. jarvis/jarvis_tools/base.py +5 -2
  234. jarvis/jarvis_tools/clear_memory.py +13 -9
  235. jarvis/jarvis_tools/cli/main.py +23 -18
  236. jarvis/jarvis_tools/edit_file.py +572 -873
  237. jarvis/jarvis_tools/execute_script.py +10 -7
  238. jarvis/jarvis_tools/file_analyzer.py +7 -8
  239. jarvis/jarvis_tools/meta_agent.py +287 -0
  240. jarvis/jarvis_tools/methodology.py +5 -3
  241. jarvis/jarvis_tools/read_code.py +305 -1438
  242. jarvis/jarvis_tools/read_symbols.py +50 -17
  243. jarvis/jarvis_tools/read_webpage.py +19 -18
  244. jarvis/jarvis_tools/registry.py +435 -156
  245. jarvis/jarvis_tools/retrieve_memory.py +16 -11
  246. jarvis/jarvis_tools/save_memory.py +8 -6
  247. jarvis/jarvis_tools/search_web.py +31 -31
  248. jarvis/jarvis_tools/sub_agent.py +32 -28
  249. jarvis/jarvis_tools/sub_code_agent.py +44 -60
  250. jarvis/jarvis_tools/task_list_manager.py +1811 -0
  251. jarvis/jarvis_tools/virtual_tty.py +29 -19
  252. jarvis/jarvis_utils/__init__.py +4 -0
  253. jarvis/jarvis_utils/builtin_replace_map.py +2 -1
  254. jarvis/jarvis_utils/clipboard.py +9 -8
  255. jarvis/jarvis_utils/collections.py +331 -0
  256. jarvis/jarvis_utils/config.py +699 -194
  257. jarvis/jarvis_utils/dialogue_recorder.py +294 -0
  258. jarvis/jarvis_utils/embedding.py +6 -3
  259. jarvis/jarvis_utils/file_processors.py +7 -1
  260. jarvis/jarvis_utils/fzf.py +9 -3
  261. jarvis/jarvis_utils/git_utils.py +71 -42
  262. jarvis/jarvis_utils/globals.py +116 -32
  263. jarvis/jarvis_utils/http.py +6 -2
  264. jarvis/jarvis_utils/input.py +318 -83
  265. jarvis/jarvis_utils/jsonnet_compat.py +119 -104
  266. jarvis/jarvis_utils/methodology.py +37 -28
  267. jarvis/jarvis_utils/output.py +201 -44
  268. jarvis/jarvis_utils/utils.py +986 -628
  269. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
  270. jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
  271. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
  272. jarvis/jarvis_tools/generate_new_tool.py +0 -205
  273. jarvis/jarvis_tools/lsp_client.py +0 -1552
  274. jarvis/jarvis_tools/rewrite_file.py +0 -105
  275. jarvis_ai_assistant-0.7.16.dist-info/RECORD +0 -218
  276. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
  277. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
  278. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
  279. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
@@ -4,183 +4,128 @@
4
4
  完全基于LLM实现,不依赖硬编码规则。
5
5
  """
6
6
 
7
-
8
- from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
9
7
  import json
10
8
  import os
11
9
  import re
12
- from typing import List, Optional, Any
10
+ from typing import Any
11
+ from typing import List
12
+ from typing import Optional
13
13
 
14
14
  from rich.console import Console
15
- from jarvis.jarvis_platform.registry import PlatformRegistry
16
- from jarvis.jarvis_utils.config import get_normal_platform_name, get_normal_model_name
15
+
17
16
  from jarvis.jarvis_code_agent.utils import get_project_overview
17
+ from jarvis.jarvis_platform.registry import PlatformRegistry
18
+ from jarvis.jarvis_utils.config import get_cheap_model_name
19
+ from jarvis.jarvis_utils.config import get_cheap_platform_name
20
+ from jarvis.jarvis_utils.globals import get_global_model_group
21
+ from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
18
22
 
19
- from .context_recommender import ContextRecommendation
20
23
  from .context_manager import ContextManager
24
+ from .context_recommender import ContextRecommendation
21
25
  from .symbol_extractor import Symbol
22
26
 
23
27
 
24
28
  class ContextRecommender:
25
29
  """智能上下文推荐器。
26
-
30
+
27
31
  使用LLM进行语义理解,根据编辑意图推荐相关的上下文信息。
28
32
  完全基于LLM实现,提供语义级别的推荐,而非简单的关键词匹配。
29
33
  """
30
34
 
31
- def __init__(self, context_manager: ContextManager, parent_model: Optional[Any] = None):
35
+ def __init__(
36
+ self, context_manager: ContextManager, parent_model: Optional[Any] = None
37
+ ):
32
38
  """初始化上下文推荐器
33
-
39
+
34
40
  Args:
35
41
  context_manager: 上下文管理器
36
- parent_model: 父Agent的模型实例,用于获取模型配置(平台名称、模型名称、模型组等)
37
-
38
- Raises:
39
- ValueError: 如果无法创建LLM模型
42
+ parent_model: 父Agent的模型实例(已废弃,保留参数兼容性)
43
+
44
+ Note:
45
+ LLM 模型实例不会在初始化时创建,而是在每次调用时重新创建,
46
+ 以避免上下文窗口累积导致的问题。
47
+ 模型配置从全局模型组获取,不再从parent_model继承。
40
48
  """
41
49
  self.context_manager = context_manager
42
-
43
- # 自己创建LLM模型实例,使用父Agent的配置
44
- try:
45
- registry = PlatformRegistry.get_global_platform_registry()
46
-
47
- # 从父Agent的model获取配置
48
- platform_name = None
49
- model_name = None
50
- model_group = None
51
-
52
- if parent_model:
53
- try:
54
- # 优先获取 model_group,因为它包含了完整的配置信息
55
- model_group = getattr(parent_model, 'model_group', None)
56
- platform_name = parent_model.platform_name()
57
- model_name = parent_model.name()
58
- except Exception:
59
- # 如果获取失败,使用默认配置
60
- pass
61
-
62
- # 优先根据 model_group 获取配置(确保配置一致性)
63
- # 如果 model_group 存在,强制使用它来解析,避免使用 parent_model 中可能不一致的值
64
- # 使用cheap平台,上下文推荐可以降低成本
65
- if model_group:
66
- try:
67
- platform_name = get_normal_platform_name(model_group)
68
- model_name = get_normal_model_name(model_group)
69
- except Exception:
70
- # 如果从 model_group 解析失败,回退到从 parent_model 获取的值
71
- pass
72
-
73
- # 创建平台实例
74
- if platform_name:
75
- self.llm_model = registry.create_platform(platform_name)
76
- if self.llm_model is None:
77
- # 如果创建失败,使用cheap平台
78
- self.llm_model = registry.get_cheap_platform()
79
- else:
80
- self.llm_model = registry.get_cheap_platform()
81
-
82
- # 先设置模型组(如果从父Agent获取到),因为 model_group 可能会影响模型名称的解析
83
- if model_group and self.llm_model:
84
- try:
85
- self.llm_model.set_model_group(model_group)
86
- except Exception:
87
- pass
88
-
89
- # 然后设置模型名称(如果从父Agent或model_group获取到)
90
- if model_name and self.llm_model:
91
- try:
92
- self.llm_model.set_model_name(model_name)
93
- except Exception:
94
- pass
95
-
96
- # 设置抑制输出,因为这是后台任务
97
- if self.llm_model:
98
- self.llm_model.set_suppress_output(True)
99
- else:
100
- raise ValueError("无法创建LLM模型实例")
101
- except Exception as e:
102
- raise ValueError(f"无法创建LLM模型: {e}")
50
+
51
+ # 保存配置信息,用于后续创建 LLM 实例
52
+ self._platform_name = None
53
+ self._model_name = None
54
+ # 使用全局模型组(不再从 parent_model 继承)
55
+ self._model_group = get_global_model_group()
56
+
57
+ # 根据 model_group 获取配置
58
+ # 使用cheap平台,筛选操作可以降低成本
59
+ if self._model_group:
60
+ try:
61
+ self._platform_name = get_cheap_platform_name(self._model_group)
62
+ self._model_name = get_cheap_model_name(self._model_group)
63
+ except Exception:
64
+ # 如果从 model_group 解析失败,使用默认配置
65
+ pass
103
66
 
104
67
  def recommend_context(
105
68
  self,
106
69
  user_input: str,
107
70
  ) -> ContextRecommendation:
108
71
  """根据编辑意图推荐上下文
109
-
72
+
110
73
  Args:
111
74
  user_input: 用户输入/任务描述
112
-
75
+
113
76
  Returns:
114
77
  ContextRecommendation: 推荐的上下文信息
115
78
  """
116
- print("🔍 开始智能上下文推荐分析...")
117
-
79
+
118
80
  # 0. 检查并填充符号表(如果为空)
119
81
  self._ensure_symbol_table_loaded()
120
-
121
- # 1. 使用LLM生成相关符号名
122
- model_name = self.llm_model.name() if self.llm_model else "LLM"
123
- print(f"📝 正在使用{model_name}生成相关符号名...")
124
- symbol_names = self._extract_symbol_names_with_llm(user_input)
125
- if symbol_names:
126
- print(f"✅ 生成 {len(symbol_names)} 个符号名: {', '.join(symbol_names[:5])}{'...' if len(symbol_names) > 5 else ''}")
127
- else:
128
- print("⚠️ 未能生成符号名,将使用基础搜索策略")
129
-
82
+
83
+ # 检查符号表是否为空(构建完成后仍然为空)
84
+ symbol_count = sum(
85
+ len(symbols)
86
+ for symbols in self.context_manager.symbol_table.symbols_by_name.values()
87
+ )
88
+ if symbol_count == 0:
89
+ return ContextRecommendation(recommended_symbols=[])
90
+
91
+ # 1. 使用LLM生成相关关键词
92
+ keywords = self._extract_keywords_with_llm(user_input)
93
+
130
94
  # 2. 初始化推荐结果
131
95
  recommended_symbols: List[Symbol] = []
132
96
 
133
- # 3. 基于符号名进行符号查找,然后使用LLM挑选关联度高的条目(主要推荐方式)
134
- if symbol_names:
135
- # 3.1 使用符号名进行精确查找,找到所有候选符号及其位置
136
- print("🔎 正在基于符号名搜索相关符号...")
137
- candidate_symbols = self._search_symbols_by_names(symbol_names)
138
-
139
- print(f"📊 符号名匹配: {len(candidate_symbols)} 个候选")
140
-
97
+ # 3. 基于关键词进行符号查找,然后使用LLM挑选关联度高的条目(主要推荐方式)
98
+ if keywords:
99
+ # 3.1 使用关键词进行模糊查找,找到所有候选符号及其位置
100
+ candidate_symbols = self._search_symbols_by_keywords(keywords)
101
+
141
102
  candidate_symbols_list = candidate_symbols
142
- print(f"📦 共 {len(candidate_symbols_list)} 个候选符号")
143
-
103
+
144
104
  # 3.2 使用LLM从候选符号中挑选关联度高的条目
145
105
  if candidate_symbols_list:
146
- model_name = self.llm_model.name() if self.llm_model else "LLM"
147
- print(f"🤖 正在使用{model_name}从 {len(candidate_symbols_list)} 个候选符号中筛选最相关的条目...")
148
106
  selected_symbols = self._select_relevant_symbols_with_llm(
149
- user_input, symbol_names, candidate_symbols_list
107
+ user_input, keywords, candidate_symbols_list
150
108
  )
151
109
  recommended_symbols.extend(selected_symbols)
152
- print(f"✅ {model_name}筛选完成,选中 {len(selected_symbols)} 个相关符号")
153
- else:
154
- print("⚠️ 没有找到候选符号")
155
- else:
156
- print("⚠️ 无符号名可用,跳过符号推荐")
157
110
 
158
- # 4. 对推荐符号去重(基于 name + file_path + line_start)
111
+ # 4. 对推荐符号去重(基于符号名称)
159
112
  seen = set()
160
113
  unique_symbols = []
161
114
  for symbol in recommended_symbols:
162
- key = (symbol.name, symbol.file_path, symbol.line_start)
115
+ key = (symbol.name,)
163
116
  if key not in seen:
164
117
  seen.add(key)
165
118
  unique_symbols.append(symbol)
166
-
167
- if len(unique_symbols) < len(recommended_symbols):
168
- print(f"🔄 去重: {len(recommended_symbols)} -> {len(unique_symbols)} 个符号")
169
-
119
+
170
120
  # 5. 限制符号数量
171
121
  final_symbols = unique_symbols[:10]
172
- if len(unique_symbols) > 10:
173
- print(f"📌 推荐结果已限制为前 10 个符号(共 {len(unique_symbols)} 个)")
174
-
175
- print(f"✨ 上下文推荐完成,共推荐 {len(final_symbols)} 个符号")
176
-
177
122
  return ContextRecommendation(
178
123
  recommended_symbols=final_symbols,
179
124
  )
180
125
 
181
126
  def _get_project_overview(self) -> str:
182
127
  """获取项目概况信息
183
-
128
+
184
129
  Returns:
185
130
  项目概况字符串
186
131
  """
@@ -188,37 +133,35 @@ class ContextRecommender:
188
133
 
189
134
  def _ensure_symbol_table_loaded(self) -> None:
190
135
  """确保符号表已加载(如果为空则扫描项目文件)
191
-
136
+
192
137
  在推荐上下文之前,需要确保符号表已经被填充。
193
138
  如果符号表为空,则扫描项目文件并填充符号表。
194
139
  """
195
140
  # 检查符号表是否为空
196
141
  if not self.context_manager.symbol_table.symbols_by_name:
197
- print("📚 符号表为空,开始扫描项目文件构建符号表...")
198
142
  self._build_symbol_table()
199
- else:
200
- symbol_count = sum(len(symbols) for symbols in self.context_manager.symbol_table.symbols_by_name.values())
201
- print(f"📚 符号表已就绪,包含 {symbol_count} 个符号")
202
143
 
203
144
  def _build_symbol_table(self) -> None:
204
145
  """扫描项目文件并构建符号表
205
-
146
+
206
147
  遍历项目目录,提取所有支持语言的符号。
207
148
  """
208
149
  import os
209
- from .language_support import detect_language, get_symbol_extractor
150
+
210
151
  from .file_ignore import filter_walk_dirs
211
-
152
+ from .language_support import detect_language
153
+ from .language_support import get_symbol_extractor
154
+
212
155
  console = Console()
213
156
  project_root = self.context_manager.project_root
214
157
  files_scanned = 0
215
158
  symbols_added = 0
216
159
  files_with_symbols = 0
217
160
  files_skipped = 0
218
-
161
+
219
162
  # 用于清除行的最大宽度(终端通常80-120字符,使用100作为安全值)
220
163
  max_line_width = 100
221
-
164
+
222
165
  # 快速统计总文件数(用于进度显示)
223
166
  console.print("📊 正在统计项目文件...", end="")
224
167
  total_files = 0
@@ -230,18 +173,20 @@ class ContextRecommender:
230
173
  if language and get_symbol_extractor(language):
231
174
  total_files += 1
232
175
  console.print(" 完成") # 统计完成,换行
233
-
176
+
234
177
  # 进度反馈间隔(每处理这么多文件输出一次,最多每10个文件输出一次)
235
178
  # progress_interval = max(1, min(total_files // 20, 10)) if total_files > 0 else 10
236
-
179
+
237
180
  if total_files > 0:
238
181
  console.print(f"📁 发现 {total_files} 个代码文件,开始扫描...")
239
182
  else:
240
183
  console.print("⚠️ 未发现可扫描的代码文件", style="yellow")
241
184
  return
242
-
185
+
243
186
  # 辅助函数:生成固定宽度的进度字符串(避免残留字符)
244
- def format_progress_msg(current_file: str, scanned: int, total: int, symbols: int, skipped: int) -> str:
187
+ def format_progress_msg(
188
+ current_file: str, scanned: int, total: int, symbols: int, skipped: int
189
+ ) -> str:
245
190
  progress_pct = (scanned * 100) // total if total > 0 else 0
246
191
  base_msg = f"⏳ 扫描进度: {scanned}/{total} ({progress_pct}%)"
247
192
  if symbols > 0:
@@ -253,25 +198,25 @@ class ContextRecommender:
253
198
  if len(base_msg) < max_line_width:
254
199
  base_msg += " " * (max_line_width - len(base_msg))
255
200
  return base_msg
256
-
201
+
257
202
  # 遍历项目目录
258
203
  for root, dirs, files in os.walk(project_root):
259
204
  # 过滤需要忽略的目录
260
205
  dirs[:] = filter_walk_dirs(dirs)
261
-
206
+
262
207
  for file in files:
263
208
  file_path = os.path.join(root, file)
264
-
209
+
265
210
  # 检测语言
266
211
  language = detect_language(file_path)
267
212
  if not language:
268
213
  continue
269
-
214
+
270
215
  # 获取符号提取器
271
216
  extractor = get_symbol_extractor(language)
272
217
  if not extractor:
273
218
  continue
274
-
219
+
275
220
  # 获取相对路径用于显示(限制长度)
276
221
  try:
277
222
  rel_path = os.path.relpath(file_path, project_root)
@@ -280,7 +225,7 @@ class ContextRecommender:
280
225
  rel_path = "..." + rel_path[-37:]
281
226
  except Exception:
282
227
  rel_path = file
283
-
228
+
284
229
  # 读取文件内容(跳过超大文件,避免内存问题)
285
230
  try:
286
231
  # 检查文件大小(超过 1MB 的文件跳过)
@@ -288,53 +233,81 @@ class ContextRecommender:
288
233
  if file_size > 1024 * 1024: # 1MB
289
234
  files_skipped += 1
290
235
  # 实时更新进度(不换行,文件名在最后)
291
- msg = format_progress_msg(rel_path, files_scanned, total_files, symbols_added, files_skipped)
236
+ msg = format_progress_msg(
237
+ rel_path,
238
+ files_scanned,
239
+ total_files,
240
+ symbols_added,
241
+ files_skipped,
242
+ )
292
243
  console.print(msg, end="\r")
293
244
  continue
294
-
245
+
295
246
  # 显示当前正在扫描的文件
296
- msg = format_progress_msg(rel_path, files_scanned, total_files, symbols_added, files_skipped)
247
+ msg = format_progress_msg(
248
+ rel_path,
249
+ files_scanned,
250
+ total_files,
251
+ symbols_added,
252
+ files_skipped,
253
+ )
297
254
  console.print(msg, end="\r")
298
-
299
- with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
255
+
256
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
300
257
  content = f.read()
301
258
  if not content:
302
259
  continue
303
-
260
+
304
261
  # 提取符号
305
262
  symbols = extractor.extract_symbols(file_path, content)
306
263
  if symbols:
307
264
  files_with_symbols += 1
308
265
  for symbol in symbols:
309
266
  # 不立即保存缓存,批量添加以提高性能
310
- self.context_manager.symbol_table.add_symbol(symbol, save_to_cache=False)
267
+ self.context_manager.symbol_table.add_symbol(
268
+ symbol, save_to_cache=False
269
+ )
311
270
  symbols_added += 1
312
-
271
+
313
272
  # 更新文件修改时间
314
273
  try:
315
- self.context_manager.symbol_table._file_mtimes[file_path] = os.path.getmtime(file_path)
274
+ self.context_manager.symbol_table._file_mtimes[file_path] = (
275
+ os.path.getmtime(file_path)
276
+ )
316
277
  except Exception:
317
278
  pass
318
-
279
+
319
280
  files_scanned += 1
320
-
281
+
321
282
  # 实时更新进度(不换行,文件名在最后)
322
- msg = format_progress_msg(rel_path, files_scanned, total_files, symbols_added, files_skipped)
283
+ msg = format_progress_msg(
284
+ rel_path,
285
+ files_scanned,
286
+ total_files,
287
+ symbols_added,
288
+ files_skipped,
289
+ )
323
290
  console.print(msg, end="\r")
324
291
  except Exception:
325
292
  # 跳过无法读取的文件
326
293
  files_skipped += 1
327
294
  # 实时更新进度(不换行,文件名在最后)
328
- msg = format_progress_msg(rel_path, files_scanned, total_files, symbols_added, files_skipped)
295
+ msg = format_progress_msg(
296
+ rel_path,
297
+ files_scanned,
298
+ total_files,
299
+ symbols_added,
300
+ files_skipped,
301
+ )
329
302
  console.print(msg, end="\r")
330
303
  continue
331
-
304
+
332
305
  # 完成时显示100%进度,然后换行并显示最终结果
333
306
  if total_files > 0:
334
307
  # 清除进度行
335
308
  console.print(" " * max_line_width, end="\r")
336
309
  console.print() # 换行
337
-
310
+
338
311
  # 批量保存缓存(扫描完成后一次性保存,提高性能)
339
312
  try:
340
313
  console.print("💾 正在保存符号表缓存...", end="\r")
@@ -342,50 +315,58 @@ class ContextRecommender:
342
315
  console.print("💾 符号表缓存已保存")
343
316
  except Exception as e:
344
317
  console.print(f"⚠️ 保存符号表缓存失败: {e}", style="yellow")
345
-
318
+
346
319
  skip_msg = f",跳过 {files_skipped} 个文件" if files_skipped > 0 else ""
347
320
  console.print(
348
321
  f"✅ 符号表构建完成: 扫描 {files_scanned} 个文件{skip_msg},提取 {symbols_added} 个符号(来自 {files_with_symbols} 个文件)",
349
- style="green"
322
+ style="green",
350
323
  )
351
324
 
352
- def _extract_symbol_names_with_llm(self, user_input: str) -> List[str]:
353
- """使用LLM生成相关符号名
354
-
325
+ def _extract_keywords_with_llm(self, user_input: str) -> List[str]:
326
+ """使用LLM生成相关关键词
327
+
355
328
  Args:
356
329
  user_input: 用户输入
357
-
330
+
358
331
  Returns:
359
- 符号名列表
332
+ 关键词列表(用于模糊匹配符号名)
360
333
  """
361
334
  # 获取项目概况和符号表信息
362
335
  project_overview = self._get_project_overview()
363
-
336
+
364
337
  # 获取所有可用的符号名(用于参考)
365
- all_symbol_names = list(self.context_manager.symbol_table.symbols_by_name.keys())
338
+ all_symbol_names = list(
339
+ self.context_manager.symbol_table.symbols_by_name.keys()
340
+ )
366
341
  symbol_names_sample = sorted(all_symbol_names)[:50] # 取前50个作为示例
367
-
368
- prompt = f"""分析代码编辑任务,生成5-15个可能相关的符号名(函数名、类名、变量名等)。
342
+
343
+ prompt = f"""分析代码编辑任务,生成5-15个搜索关键词,用于在代码库中查找相关符号。
369
344
 
370
345
  {project_overview}
371
346
 
372
347
  任务描述:{user_input}
373
348
 
374
- 符号名示例:{', '.join(symbol_names_sample[:30])}{'...' if len(symbol_names_sample) > 30 else ''}
349
+ 现有符号名示例:{", ".join(symbol_names_sample[:30])}{"..." if len(symbol_names_sample) > 30 else ""}
375
350
 
376
- 要求:与任务直接相关,符合命名规范,尽量具体。
351
+ 要求:
352
+ 1. 关键词应该是符号名中可能包含的单词或词根(如 "user", "login", "validate", "config")
353
+ 2. 不需要完整的符号名,只需要关键词片段
354
+ 3. 关键词应与任务直接相关
355
+ 4. 可以是单词、缩写或常见的命名片段
377
356
 
378
- 以Jsonnet数组格式返回,用<SYMBOL_NAMES>标签包裹。示例:
379
- <SYMBOL_NAMES>
380
- ["processData", "validateInput", "handleError"]
381
- </SYMBOL_NAMES>
357
+ 以Jsonnet数组格式返回,用<KEYWORDS>标签包裹。示例:
358
+ <KEYWORDS>
359
+ ["user", "login", "auth", "validate", "session"]
360
+ </KEYWORDS>
382
361
  """
383
362
 
384
363
  try:
385
364
  response = self._call_llm(prompt)
386
- # 从<SYMBOL_NAMES>标签中提取内容
365
+ # 从<KEYWORDS>标签中提取内容
387
366
  response = response.strip()
388
- json_match = re.search(r'<SYMBOL_NAMES>\s*(.*?)\s*</SYMBOL_NAMES>', response, re.DOTALL)
367
+ json_match = re.search(
368
+ r"<KEYWORDS>\s*(.*?)\s*</KEYWORDS>", response, re.DOTALL
369
+ )
389
370
  if json_match:
390
371
  json_content = json_match.group(1).strip()
391
372
  else:
@@ -397,76 +378,77 @@ class ContextRecommender:
397
378
  if response.endswith("```"):
398
379
  response = response[:-3]
399
380
  json_content = response.strip()
400
-
401
- symbol_names = json_loads(json_content)
402
- if not isinstance(symbol_names, list):
403
- print("⚠️ LLM返回的符号名格式不正确,期望 Jsonnet 数组格式")
381
+
382
+ keywords = json_loads(json_content)
383
+ if not isinstance(keywords, list):
404
384
  return []
405
-
406
- # 过滤空字符串和过短的符号名
407
- original_count = len(symbol_names)
408
- symbol_names = [name.strip() for name in symbol_names if name and isinstance(name, str) and len(name.strip()) > 0]
409
- if original_count != len(symbol_names):
410
- print(f"📋 过滤后保留 {len(symbol_names)} 个有效符号名(原始 {original_count} 个)")
411
- return symbol_names
412
- except Exception as e:
385
+
386
+ # 过滤空字符串和过短的关键词(至少2个字符)
387
+ keywords = [
388
+ kw.strip().lower()
389
+ for kw in keywords
390
+ if kw and isinstance(kw, str) and len(kw.strip()) >= 2
391
+ ]
392
+ return keywords
393
+ except Exception:
413
394
  # 解析失败,返回空列表
414
- print(f"❌ LLM符号名生成失败: {e}")
415
395
  return []
416
- def _search_symbols_by_names(self, symbol_names: List[str]) -> List[Symbol]:
417
- """基于符号名在符号表中精确查找相关符号
418
-
396
+
397
+ def _search_symbols_by_keywords(self, keywords: List[str]) -> List[Symbol]:
398
+ """基于关键词在符号表中模糊查找相关符号
399
+
419
400
  Args:
420
- symbol_names: 符号名列表
421
-
401
+ keywords: 关键词列表(用于模糊匹配符号名)
402
+
422
403
  Returns:
423
404
  候选符号列表
424
405
  """
425
- if not symbol_names:
406
+ if not keywords:
426
407
  return []
427
-
408
+
428
409
  found_symbols: List[Symbol] = []
429
- found_symbol_keys = set() # 用于去重,使用 (file_path, name, line_start) 作为键
430
-
431
- # 创建符号名映射(支持大小写不敏感匹配)
432
- symbol_names_lower = {name.lower(): name for name in symbol_names}
433
-
434
- # 遍历所有符号,精确匹配符号名
435
- for symbol_name, symbols in self.context_manager.symbol_table.symbols_by_name.items():
410
+ found_symbol_keys: set[tuple[str, str, int]] = set() # 用于去重
411
+
412
+ # 将关键词转为小写用于匹配
413
+ keywords_lower = [kw.lower() for kw in keywords]
414
+
415
+ # 遍历所有符号,模糊匹配符号名
416
+ for (
417
+ symbol_name,
418
+ symbols,
419
+ ) in self.context_manager.symbol_table.symbols_by_name.items():
436
420
  symbol_name_lower = symbol_name.lower()
437
-
438
- # 精确匹配:检查符号名是否在目标列表中(大小写不敏感)
439
- if symbol_name_lower in symbol_names_lower:
421
+
422
+ # 模糊匹配:检查任一关键词是否是符号名的子串(大小写不敏感)
423
+ if any(kw in symbol_name_lower for kw in keywords_lower):
440
424
  # 找到匹配的符号,添加所有同名符号(可能有重载)
441
425
  for symbol in symbols:
442
426
  key = (symbol.file_path, symbol.name, symbol.line_start)
443
427
  if key not in found_symbol_keys:
444
428
  found_symbols.append(symbol)
445
429
  found_symbol_keys.add(key)
446
-
430
+
447
431
  return found_symbols
448
432
 
449
433
  def _select_relevant_symbols_with_llm(
450
- self, user_input: str, symbol_names: List[str], candidate_symbols: List[Symbol]
434
+ self, user_input: str, keywords: List[str], candidate_symbols: List[Symbol]
451
435
  ) -> List[Symbol]:
452
436
  """使用LLM从候选符号中挑选关联度高的条目
453
-
437
+
454
438
  Args:
455
439
  user_input: 用户输入/任务描述
456
- symbol_names: 符号名列表
440
+ keywords: 搜索关键词列表
457
441
  candidate_symbols: 候选符号列表(包含位置信息)
458
-
442
+
459
443
  Returns:
460
444
  选中的符号列表
461
445
  """
462
446
  if not candidate_symbols:
463
447
  return []
464
-
448
+
465
449
  # 限制候选符号数量,避免prompt过长
466
450
  candidates_to_consider = candidate_symbols[:100] # 最多100个候选
467
- if len(candidate_symbols) > 100:
468
- print(f"📌 候选符号数量较多({len(candidate_symbols)} 个),限制为前 100 个进行LLM筛选")
469
-
451
+
470
452
  # 构建带编号的符号信息列表(包含位置信息)
471
453
  symbol_info_list = []
472
454
  for idx, symbol in enumerate(candidates_to_consider, start=1):
@@ -474,21 +456,23 @@ class ContextRecommender:
474
456
  "序号": idx,
475
457
  "name": symbol.name,
476
458
  "kind": symbol.kind,
477
- "file": os.path.relpath(symbol.file_path, self.context_manager.project_root),
459
+ "file": os.path.relpath(
460
+ symbol.file_path, self.context_manager.project_root
461
+ ),
478
462
  "line": symbol.line_start,
479
463
  "signature": symbol.signature or "",
480
464
  }
481
465
  symbol_info_list.append(symbol_info)
482
-
466
+
483
467
  # 获取项目概况
484
468
  project_overview = self._get_project_overview()
485
-
486
- prompt = f"""根据任务描述和生成的符号名,从候选符号列表中选择最相关的10-20个符号。
469
+
470
+ prompt = f"""根据任务描述和搜索关键词,从候选符号列表中选择最相关的10-20个符号。
487
471
 
488
472
  {project_overview}
489
473
 
490
474
  任务描述:{user_input}
491
- 生成的符号名:{', '.join(symbol_names)}
475
+ 搜索关键词:{", ".join(keywords)}
492
476
  候选符号列表(已编号):{json.dumps(symbol_info_list, ensure_ascii=False, indent=2)}
493
477
 
494
478
  返回最相关符号的序号(Jsonnet数组),按相关性排序,用<SELECTED_INDICES>标签包裹。示例:
@@ -501,7 +485,9 @@ class ContextRecommender:
501
485
  response = self._call_llm(prompt)
502
486
  # 从<SELECTED_INDICES>标签中提取内容
503
487
  response = response.strip()
504
- json_match = re.search(r'<SELECTED_INDICES>\s*(.*?)\s*</SELECTED_INDICES>', response, re.DOTALL)
488
+ json_match = re.search(
489
+ r"<SELECTED_INDICES>\s*(.*?)\s*</SELECTED_INDICES>", response, re.DOTALL
490
+ )
505
491
  if json_match:
506
492
  json_content = json_match.group(1).strip()
507
493
  else:
@@ -513,14 +499,11 @@ class ContextRecommender:
513
499
  if response.endswith("```"):
514
500
  response = response[:-3]
515
501
  json_content = response.strip()
516
-
502
+
517
503
  selected_indices = json_loads(json_content)
518
504
  if not isinstance(selected_indices, list):
519
- print("⚠️ LLM返回的符号序号格式不正确,期望 Jsonnet 数组格式")
520
505
  return []
521
-
522
- print(f"📋 LLM返回了 {len(selected_indices)} 个符号序号")
523
-
506
+
524
507
  # 根据序号查找对应的符号对象
525
508
  selected_symbols = []
526
509
  invalid_indices = []
@@ -531,75 +514,95 @@ class ContextRecommender:
531
514
  selected_symbols.append(symbol)
532
515
  else:
533
516
  invalid_indices.append(idx)
534
-
535
- if invalid_indices:
536
- print(f"⚠️ 发现 {len(invalid_indices)} 个无效序号: {invalid_indices[:5]}{'...' if len(invalid_indices) > 5 else ''}")
537
-
538
- if selected_symbols:
539
- # 统计选中的符号类型分布
540
- kind_count = {}
541
- for symbol in selected_symbols:
542
- kind_count[symbol.kind] = kind_count.get(symbol.kind, 0) + 1
543
- kind_summary = ", ".join([f"{kind}: {count}" for kind, count in sorted(kind_count.items())])
544
- print(f"📊 选中符号类型分布: {kind_summary}")
545
-
517
+
546
518
  return selected_symbols
547
- except Exception as e:
519
+ except Exception:
548
520
  # 解析失败,返回空列表
549
- print(f"❌ LLM符号筛选失败: {e}")
550
521
  return []
551
522
 
523
+ def _create_llm_model(self):
524
+ """创建新的 LLM 模型实例
525
+
526
+ 每次调用都创建新的实例,避免上下文窗口累积。
527
+
528
+ Returns:
529
+ LLM 模型实例
530
+
531
+ Raises:
532
+ ValueError: 如果无法创建LLM模型
533
+ """
534
+ try:
535
+ registry = PlatformRegistry.get_global_platform_registry()
536
+
537
+ # 创建平台实例(筛选操作始终使用cheap平台以降低成本)
538
+ if self._platform_name:
539
+ llm_model = registry.create_platform(self._platform_name)
540
+ if llm_model is None:
541
+ # 如果创建失败,使用cheap平台
542
+ llm_model = registry.get_cheap_platform()
543
+ else:
544
+ # 如果没有指定平台,使用cheap平台
545
+ llm_model = registry.get_cheap_platform()
546
+
547
+ if not llm_model:
548
+ raise ValueError("无法创建LLM模型实例")
549
+
550
+ # 先设置模型组(如果从父Agent获取到),因为 model_group 可能会影响模型名称的解析
551
+ if self._model_group:
552
+ try:
553
+ llm_model.set_model_group(self._model_group)
554
+ except Exception:
555
+ pass
556
+
557
+ # 然后设置模型名称(如果从父Agent或model_group获取到)
558
+ if self._model_name:
559
+ try:
560
+ llm_model.set_model_name(self._model_name)
561
+ except Exception:
562
+ pass
563
+
564
+ # 设置抑制输出,因为这是后台任务
565
+ llm_model.set_suppress_output(True)
566
+
567
+ return llm_model
568
+ except Exception as e:
569
+ raise ValueError(f"无法创建LLM模型: {e}")
570
+
552
571
  def _call_llm(self, prompt: str) -> str:
553
572
  """调用LLM生成响应
554
-
573
+
574
+ 每次调用都创建新的 LLM 实例,避免上下文窗口累积。
575
+
555
576
  Args:
556
577
  prompt: 提示词
557
-
578
+
558
579
  Returns:
559
580
  LLM生成的响应文本
560
581
  """
561
- if not self.llm_model:
562
- raise ValueError("LLM model not available")
563
-
582
+ # 每次调用都创建新的 LLM 实例,避免上下文窗口累积
583
+ llm_model = self._create_llm_model()
584
+
564
585
  try:
565
586
  # 使用chat_until_success方法(BasePlatform的标准接口)
566
- if hasattr(self.llm_model, 'chat_until_success'):
567
- response = self.llm_model.chat_until_success(prompt)
587
+ if hasattr(llm_model, "chat_until_success"):
588
+ response = llm_model.chat_until_success(prompt)
568
589
  response_str = str(response)
569
- if response_str:
570
- response_length = len(response_str)
571
- print(f"💬 LLM响应长度: {response_length} 字符")
572
590
  return response_str
573
591
  else:
574
592
  # 如果不支持chat_until_success,抛出异常
575
- raise ValueError("LLM model does not support chat_until_success interface")
576
- except Exception as e:
577
- print(f"❌ LLM调用失败: {e}")
593
+ raise ValueError(
594
+ "LLM model does not support chat_until_success interface"
595
+ )
596
+ except Exception:
578
597
  raise
579
598
 
580
599
  def format_recommendation(self, recommendation: ContextRecommendation) -> str:
581
600
  """格式化推荐结果为可读文本
582
-
601
+
583
602
  Args:
584
603
  recommendation: 推荐结果
585
-
604
+
586
605
  Returns:
587
606
  格式化的文本
588
607
  """
589
- if not recommendation.recommended_symbols:
590
- return ""
591
-
592
- lines = ["\n💡 智能上下文推荐:"]
593
- lines.append("─" * 60)
594
-
595
- # 输出:符号在文件中的位置
596
- symbols_str = "\n ".join(
597
- f"• 符号 `{s.name}` ({s.kind}) 位于文件 {os.path.relpath(s.file_path, self.context_manager.project_root)} 第 {s.line_start} 行"
598
- for s in recommendation.recommended_symbols
599
- )
600
- lines.append(f"🔗 推荐符号位置 ({len(recommendation.recommended_symbols)}个):\n {symbols_str}")
601
-
602
- lines.append("─" * 60)
603
- lines.append("") # 空行
604
-
605
- return "\n".join(lines)
608
+ return ""