jarvis-ai-assistant 0.7.8__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +567 -222
- jarvis/jarvis_agent/agent_manager.py +19 -12
- jarvis/jarvis_agent/builtin_input_handler.py +79 -11
- jarvis/jarvis_agent/config_editor.py +7 -2
- jarvis/jarvis_agent/event_bus.py +24 -13
- jarvis/jarvis_agent/events.py +19 -1
- jarvis/jarvis_agent/file_context_handler.py +67 -64
- jarvis/jarvis_agent/file_methodology_manager.py +38 -24
- jarvis/jarvis_agent/jarvis.py +186 -114
- jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
- jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
- jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
- jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
- jarvis/jarvis_agent/language_support_info.py +250 -219
- jarvis/jarvis_agent/main.py +19 -23
- jarvis/jarvis_agent/memory_manager.py +9 -6
- jarvis/jarvis_agent/methodology_share_manager.py +21 -15
- jarvis/jarvis_agent/output_handler.py +4 -2
- jarvis/jarvis_agent/prompt_builder.py +7 -6
- jarvis/jarvis_agent/prompt_manager.py +113 -8
- jarvis/jarvis_agent/prompts.py +317 -85
- jarvis/jarvis_agent/protocols.py +5 -2
- jarvis/jarvis_agent/run_loop.py +192 -32
- jarvis/jarvis_agent/session_manager.py +7 -3
- jarvis/jarvis_agent/share_manager.py +23 -13
- jarvis/jarvis_agent/shell_input_handler.py +12 -8
- jarvis/jarvis_agent/stdio_redirect.py +25 -26
- jarvis/jarvis_agent/task_analyzer.py +29 -23
- jarvis/jarvis_agent/task_list.py +869 -0
- jarvis/jarvis_agent/task_manager.py +26 -23
- jarvis/jarvis_agent/tool_executor.py +6 -5
- jarvis/jarvis_agent/tool_share_manager.py +24 -14
- jarvis/jarvis_agent/user_interaction.py +3 -3
- jarvis/jarvis_agent/utils.py +9 -1
- jarvis/jarvis_agent/web_bridge.py +37 -17
- jarvis/jarvis_agent/web_output_sink.py +5 -2
- jarvis/jarvis_agent/web_server.py +165 -36
- jarvis/jarvis_c2rust/__init__.py +1 -1
- jarvis/jarvis_c2rust/cli.py +260 -141
- jarvis/jarvis_c2rust/collector.py +37 -18
- jarvis/jarvis_c2rust/constants.py +60 -0
- jarvis/jarvis_c2rust/library_replacer.py +242 -1010
- jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
- jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
- jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
- jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
- jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
- jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
- jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
- jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
- jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
- jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
- jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
- jarvis/jarvis_c2rust/loaders.py +28 -10
- jarvis/jarvis_c2rust/models.py +5 -2
- jarvis/jarvis_c2rust/optimizer.py +192 -1974
- jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
- jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
- jarvis/jarvis_c2rust/optimizer_config.py +49 -0
- jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
- jarvis/jarvis_c2rust/optimizer_options.py +48 -0
- jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
- jarvis/jarvis_c2rust/optimizer_report.py +52 -0
- jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
- jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
- jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
- jarvis/jarvis_c2rust/scanner.py +229 -166
- jarvis/jarvis_c2rust/transpiler.py +531 -2732
- jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
- jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
- jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
- jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
- jarvis/jarvis_c2rust/transpiler_config.py +178 -0
- jarvis/jarvis_c2rust/transpiler_context.py +122 -0
- jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
- jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
- jarvis/jarvis_c2rust/transpiler_git.py +163 -0
- jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
- jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
- jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
- jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
- jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
- jarvis/jarvis_c2rust/utils.py +269 -79
- jarvis/jarvis_code_agent/after_change.py +233 -0
- jarvis/jarvis_code_agent/build_validation_config.py +37 -30
- jarvis/jarvis_code_agent/builtin_rules.py +68 -0
- jarvis/jarvis_code_agent/code_agent.py +976 -1517
- jarvis/jarvis_code_agent/code_agent_build.py +227 -0
- jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
- jarvis/jarvis_code_agent/code_agent_git.py +525 -0
- jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
- jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
- jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
- jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
- jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
- jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
- jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
- jarvis/jarvis_code_agent/lint.py +223 -524
- jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
- jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
- jarvis/jarvis_code_agent/rules/code_review.md +115 -0
- jarvis/jarvis_code_agent/rules/documentation.md +165 -0
- jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
- jarvis/jarvis_code_agent/rules/performance.md +158 -0
- jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
- jarvis/jarvis_code_agent/rules/security.md +160 -0
- jarvis/jarvis_code_agent/rules/tdd.md +78 -0
- jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
- jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
- jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
- jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
- jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
- jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
- jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
- jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
- jarvis/jarvis_code_agent/utils.py +36 -26
- jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
- jarvis/jarvis_code_analysis/code_review.py +64 -33
- jarvis/jarvis_data/config_schema.json +285 -192
- jarvis/jarvis_git_squash/main.py +8 -6
- jarvis/jarvis_git_utils/git_commiter.py +53 -76
- jarvis/jarvis_mcp/__init__.py +5 -2
- jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
- jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
- jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
- jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
- jarvis/jarvis_methodology/main.py +48 -39
- jarvis/jarvis_multi_agent/__init__.py +56 -23
- jarvis/jarvis_multi_agent/main.py +15 -18
- jarvis/jarvis_platform/base.py +179 -111
- jarvis/jarvis_platform/human.py +27 -16
- jarvis/jarvis_platform/kimi.py +52 -45
- jarvis/jarvis_platform/openai.py +101 -40
- jarvis/jarvis_platform/registry.py +51 -33
- jarvis/jarvis_platform/tongyi.py +68 -38
- jarvis/jarvis_platform/yuanbao.py +59 -43
- jarvis/jarvis_platform_manager/main.py +68 -76
- jarvis/jarvis_platform_manager/service.py +24 -14
- jarvis/jarvis_rag/README_CONFIG.md +314 -0
- jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
- jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
- jarvis/jarvis_rag/__init__.py +57 -4
- jarvis/jarvis_rag/cache.py +3 -1
- jarvis/jarvis_rag/cli.py +48 -68
- jarvis/jarvis_rag/embedding_interface.py +39 -0
- jarvis/jarvis_rag/embedding_manager.py +7 -230
- jarvis/jarvis_rag/embeddings/__init__.py +41 -0
- jarvis/jarvis_rag/embeddings/base.py +114 -0
- jarvis/jarvis_rag/embeddings/cohere.py +66 -0
- jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
- jarvis/jarvis_rag/embeddings/local.py +260 -0
- jarvis/jarvis_rag/embeddings/openai.py +62 -0
- jarvis/jarvis_rag/embeddings/registry.py +293 -0
- jarvis/jarvis_rag/llm_interface.py +8 -6
- jarvis/jarvis_rag/query_rewriter.py +8 -9
- jarvis/jarvis_rag/rag_pipeline.py +61 -52
- jarvis/jarvis_rag/reranker.py +7 -75
- jarvis/jarvis_rag/reranker_interface.py +32 -0
- jarvis/jarvis_rag/rerankers/__init__.py +41 -0
- jarvis/jarvis_rag/rerankers/base.py +109 -0
- jarvis/jarvis_rag/rerankers/cohere.py +67 -0
- jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
- jarvis/jarvis_rag/rerankers/jina.py +79 -0
- jarvis/jarvis_rag/rerankers/local.py +89 -0
- jarvis/jarvis_rag/rerankers/registry.py +293 -0
- jarvis/jarvis_rag/retriever.py +58 -43
- jarvis/jarvis_sec/__init__.py +66 -141
- jarvis/jarvis_sec/agents.py +21 -17
- jarvis/jarvis_sec/analysis.py +80 -33
- jarvis/jarvis_sec/checkers/__init__.py +7 -13
- jarvis/jarvis_sec/checkers/c_checker.py +356 -164
- jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
- jarvis/jarvis_sec/cli.py +43 -21
- jarvis/jarvis_sec/clustering.py +430 -272
- jarvis/jarvis_sec/file_manager.py +99 -55
- jarvis/jarvis_sec/parsers.py +9 -6
- jarvis/jarvis_sec/prompts.py +4 -3
- jarvis/jarvis_sec/report.py +44 -22
- jarvis/jarvis_sec/review.py +180 -107
- jarvis/jarvis_sec/status.py +50 -41
- jarvis/jarvis_sec/types.py +3 -0
- jarvis/jarvis_sec/utils.py +160 -83
- jarvis/jarvis_sec/verification.py +411 -181
- jarvis/jarvis_sec/workflow.py +132 -21
- jarvis/jarvis_smart_shell/main.py +28 -41
- jarvis/jarvis_stats/cli.py +14 -12
- jarvis/jarvis_stats/stats.py +28 -19
- jarvis/jarvis_stats/storage.py +14 -8
- jarvis/jarvis_stats/visualizer.py +12 -7
- jarvis/jarvis_tools/base.py +5 -2
- jarvis/jarvis_tools/clear_memory.py +13 -9
- jarvis/jarvis_tools/cli/main.py +23 -18
- jarvis/jarvis_tools/edit_file.py +572 -873
- jarvis/jarvis_tools/execute_script.py +10 -7
- jarvis/jarvis_tools/file_analyzer.py +7 -8
- jarvis/jarvis_tools/meta_agent.py +287 -0
- jarvis/jarvis_tools/methodology.py +5 -3
- jarvis/jarvis_tools/read_code.py +305 -1438
- jarvis/jarvis_tools/read_symbols.py +50 -17
- jarvis/jarvis_tools/read_webpage.py +19 -18
- jarvis/jarvis_tools/registry.py +435 -156
- jarvis/jarvis_tools/retrieve_memory.py +16 -11
- jarvis/jarvis_tools/save_memory.py +8 -6
- jarvis/jarvis_tools/search_web.py +31 -31
- jarvis/jarvis_tools/sub_agent.py +32 -28
- jarvis/jarvis_tools/sub_code_agent.py +44 -60
- jarvis/jarvis_tools/task_list_manager.py +1811 -0
- jarvis/jarvis_tools/virtual_tty.py +29 -19
- jarvis/jarvis_utils/__init__.py +4 -0
- jarvis/jarvis_utils/builtin_replace_map.py +2 -1
- jarvis/jarvis_utils/clipboard.py +9 -8
- jarvis/jarvis_utils/collections.py +331 -0
- jarvis/jarvis_utils/config.py +699 -194
- jarvis/jarvis_utils/dialogue_recorder.py +294 -0
- jarvis/jarvis_utils/embedding.py +6 -3
- jarvis/jarvis_utils/file_processors.py +7 -1
- jarvis/jarvis_utils/fzf.py +9 -3
- jarvis/jarvis_utils/git_utils.py +71 -42
- jarvis/jarvis_utils/globals.py +116 -32
- jarvis/jarvis_utils/http.py +6 -2
- jarvis/jarvis_utils/input.py +318 -83
- jarvis/jarvis_utils/jsonnet_compat.py +119 -104
- jarvis/jarvis_utils/methodology.py +37 -28
- jarvis/jarvis_utils/output.py +201 -44
- jarvis/jarvis_utils/utils.py +986 -628
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
- jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
- jarvis/jarvis_tools/generate_new_tool.py +0 -205
- jarvis/jarvis_tools/lsp_client.py +0 -1552
- jarvis/jarvis_tools/rewrite_file.py +0 -105
- jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.7.8.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
|
|
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
|
-
|
|
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__(
|
|
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
|
-
|
|
39
|
-
|
|
42
|
+
parent_model: 父Agent的模型实例(已废弃,保留参数兼容性)
|
|
43
|
+
|
|
44
|
+
Note:
|
|
45
|
+
LLM 模型实例不会在初始化时创建,而是在每次调用时重新创建,
|
|
46
|
+
以避免上下文窗口累积导致的问题。
|
|
47
|
+
模型配置从全局模型组获取,不再从parent_model继承。
|
|
40
48
|
"""
|
|
41
49
|
self.context_manager = context_manager
|
|
42
|
-
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
117
|
-
|
|
79
|
+
|
|
118
80
|
# 0. 检查并填充符号表(如果为空)
|
|
119
81
|
self._ensure_symbol_table_loaded()
|
|
120
|
-
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
134
|
-
if
|
|
135
|
-
# 3.1
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
111
|
+
# 4. 对推荐符号去重(基于符号名称)
|
|
159
112
|
seen = set()
|
|
160
113
|
unique_symbols = []
|
|
161
114
|
for symbol in recommended_symbols:
|
|
162
|
-
key = (symbol.name,
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
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] =
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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数组格式返回,用<
|
|
379
|
-
<
|
|
380
|
-
["
|
|
381
|
-
</
|
|
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
|
-
# 从<
|
|
365
|
+
# 从<KEYWORDS>标签中提取内容
|
|
387
366
|
response = response.strip()
|
|
388
|
-
json_match = re.search(
|
|
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
|
-
|
|
402
|
-
if not isinstance(
|
|
403
|
-
print("⚠️ LLM返回的符号名格式不正确,期望 Jsonnet 数组格式")
|
|
381
|
+
|
|
382
|
+
keywords = json_loads(json_content)
|
|
383
|
+
if not isinstance(keywords, list):
|
|
404
384
|
return []
|
|
405
|
-
|
|
406
|
-
#
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
396
|
+
|
|
397
|
+
def _search_symbols_by_keywords(self, keywords: List[str]) -> List[Symbol]:
|
|
398
|
+
"""基于关键词在符号表中模糊查找相关符号
|
|
399
|
+
|
|
419
400
|
Args:
|
|
420
|
-
|
|
421
|
-
|
|
401
|
+
keywords: 关键词列表(用于模糊匹配符号名)
|
|
402
|
+
|
|
422
403
|
Returns:
|
|
423
404
|
候选符号列表
|
|
424
405
|
"""
|
|
425
|
-
if not
|
|
406
|
+
if not keywords:
|
|
426
407
|
return []
|
|
427
|
-
|
|
408
|
+
|
|
428
409
|
found_symbols: List[Symbol] = []
|
|
429
|
-
found_symbol_keys = set() #
|
|
430
|
-
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
#
|
|
435
|
-
for
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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"""
|
|
469
|
+
|
|
470
|
+
prompt = f"""根据任务描述和搜索关键词,从候选符号列表中选择最相关的10-20个符号。
|
|
487
471
|
|
|
488
472
|
{project_overview}
|
|
489
473
|
|
|
490
474
|
任务描述:{user_input}
|
|
491
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
582
|
+
# 每次调用都创建新的 LLM 实例,避免上下文窗口累积
|
|
583
|
+
llm_model = self._create_llm_model()
|
|
584
|
+
|
|
564
585
|
try:
|
|
565
586
|
# 使用chat_until_success方法(BasePlatform的标准接口)
|
|
566
|
-
if hasattr(
|
|
567
|
-
response =
|
|
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(
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
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 ""
|