siada-cli 0.0.1__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 (215) hide show
  1. agent_config.yaml +34 -0
  2. siada/__init__.py +13 -0
  3. siada/agent_hub/__init__.py +10 -0
  4. siada/agent_hub/coder/bug_fix_agent.py +398 -0
  5. siada/agent_hub/coder/code_gen_agent.py +235 -0
  6. siada/agent_hub/coder/fe_gen_agent.py +37 -0
  7. siada/agent_hub/coder/issue_review_agent.py +68 -0
  8. siada/agent_hub/coder/prompt/__init__.py +0 -0
  9. siada/agent_hub/coder/prompt/base/__init__.py +0 -0
  10. siada/agent_hub/coder/prompt/base/capabilities.py +18 -0
  11. siada/agent_hub/coder/prompt/base/non_interactive.py +14 -0
  12. siada/agent_hub/coder/prompt/base/prompt_builder.py +33 -0
  13. siada/agent_hub/coder/prompt/base/rules.py +40 -0
  14. siada/agent_hub/coder/prompt/base/tool_use.py +14 -0
  15. siada/agent_hub/coder/prompt/bug_prompt/bug_fix_prompt.py +51 -0
  16. siada/agent_hub/coder/prompt/bug_prompt/rules.py +53 -0
  17. siada/agent_hub/coder/prompt/code_gen_prompt.py +45 -0
  18. siada/agent_hub/coder/prompt/fe_gen_prompt.py +45 -0
  19. siada/agent_hub/coder/prompt/issue_review_prompt.py +121 -0
  20. siada/agent_hub/coder/tracing/__init__.py +22 -0
  21. siada/agent_hub/coder/tracing/logger_tracing_processor.py +458 -0
  22. siada/agent_hub/hooks/__init__.py +0 -0
  23. siada/agent_hub/hooks/io_hooks.py +30 -0
  24. siada/agent_hub/siada_agent.py +214 -0
  25. siada/config/__init__.py +0 -0
  26. siada/config/config_loader.py +67 -0
  27. siada/entrypoint/__init__.py +3 -0
  28. siada/entrypoint/args_parser/__init__.py +0 -0
  29. siada/entrypoint/args_parser/args.py +195 -0
  30. siada/entrypoint/interaction/__init__.py +0 -0
  31. siada/entrypoint/interaction/base_turn.py +107 -0
  32. siada/entrypoint/interaction/config.py +30 -0
  33. siada/entrypoint/interaction/controller.py +122 -0
  34. siada/entrypoint/interaction/nointeractive_controller.py +27 -0
  35. siada/entrypoint/interaction/turn/interface.py +116 -0
  36. siada/entrypoint/interaction/turn/models.py +34 -0
  37. siada/entrypoint/interaction/turn/run_turn.py +641 -0
  38. siada/entrypoint/interaction/turn_factory.py +35 -0
  39. siada/entrypoint/interaction/turn_models.py +28 -0
  40. siada/entrypoint/siadahub.py +413 -0
  41. siada/foundation/__init__.py +0 -0
  42. siada/foundation/code_agent_context.py +48 -0
  43. siada/foundation/config.py +59 -0
  44. siada/foundation/constants.py +2 -0
  45. siada/foundation/context.py +76 -0
  46. siada/foundation/logging.py +341 -0
  47. siada/foundation/tools/__init__.py +0 -0
  48. siada/foundation/tools/get_git_diff.py +105 -0
  49. siada/io/__init__.py +0 -0
  50. siada/io/banner.py +148 -0
  51. siada/io/color_settings.py +207 -0
  52. siada/io/color_utils.py +16 -0
  53. siada/io/components/__init__.py +0 -0
  54. siada/io/components/mdstream.py +204 -0
  55. siada/io/console_printer.py +68 -0
  56. siada/io/io.py +661 -0
  57. siada/io/key_bindings.py +129 -0
  58. siada/io/notification_command.py +40 -0
  59. siada/io/test_banner.py +64 -0
  60. siada/models/__init__.py +0 -0
  61. siada/models/agent.py +47 -0
  62. siada/models/coder/__init__.py +0 -0
  63. siada/models/converter.py +27 -0
  64. siada/models/model_base_config.py +121 -0
  65. siada/models/model_run_config.py +153 -0
  66. siada/provider/__init__.py +0 -0
  67. siada/provider/client_factory.py +112 -0
  68. siada/provider/llm_client.py +11 -0
  69. siada/provider/openrouter/__init__.py +0 -0
  70. siada/provider/openrouter/coverter.py +17 -0
  71. siada/provider/openrouter/openroute_provider.py +33 -0
  72. siada/provider/provider_factory.py +53 -0
  73. siada/queries/tree-sitter-language-pack/README.md +7 -0
  74. siada/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  75. siada/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  76. siada/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  77. siada/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  78. siada/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  79. siada/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  80. siada/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  81. siada/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  82. siada/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  83. siada/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  84. siada/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  85. siada/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  86. siada/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  87. siada/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  88. siada/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  89. siada/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  90. siada/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  91. siada/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  92. siada/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  93. siada/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  94. siada/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  95. siada/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  96. siada/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  97. siada/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  98. siada/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  99. siada/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  100. siada/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  101. siada/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  102. siada/queries/tree-sitter-languages/README.md +23 -0
  103. siada/queries/tree-sitter-languages/c-tags.scm +9 -0
  104. siada/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  105. siada/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  106. siada/queries/tree-sitter-languages/dart-tags.scm +91 -0
  107. siada/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  108. siada/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  109. siada/queries/tree-sitter-languages/elm-tags.scm +19 -0
  110. siada/queries/tree-sitter-languages/go-tags.scm +30 -0
  111. siada/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  112. siada/queries/tree-sitter-languages/java-tags.scm +20 -0
  113. siada/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  114. siada/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  115. siada/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  116. siada/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  117. siada/queries/tree-sitter-languages/php-tags.scm +26 -0
  118. siada/queries/tree-sitter-languages/python-tags.scm +12 -0
  119. siada/queries/tree-sitter-languages/ql-tags.scm +26 -0
  120. siada/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  121. siada/queries/tree-sitter-languages/rust-tags.scm +60 -0
  122. siada/queries/tree-sitter-languages/scala-tags.scm +65 -0
  123. siada/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  124. siada/services/__init__.py +30 -0
  125. siada/services/at_file_recommendation_design_document.md +1213 -0
  126. siada/services/code_context_manager.py +136 -0
  127. siada/services/enhanced_fix_result_check.py +546 -0
  128. siada/services/execution_trace_collector.py +207 -0
  129. siada/services/file_recommendation/__init__.py +136 -0
  130. siada/services/file_recommendation/core/__init__.py +17 -0
  131. siada/services/file_recommendation/core/completion_engine.py +351 -0
  132. siada/services/file_recommendation/core/config.py +62 -0
  133. siada/services/file_recommendation/core/file_discovery.py +353 -0
  134. siada/services/file_recommendation/core/suggestion.py +110 -0
  135. siada/services/file_recommendation/utils/__init__.py +22 -0
  136. siada/services/file_recommendation/utils/path_utils.py +138 -0
  137. siada/services/file_recommendation/utils/text_utils.py +253 -0
  138. siada/services/fix_result_check.py +377 -0
  139. siada/services/handle_at_command/README.md +247 -0
  140. siada/services/handle_at_command/__init__.py +115 -0
  141. siada/services/handle_at_command/exceptions.py +46 -0
  142. siada/services/handle_at_command/models.py +92 -0
  143. siada/services/handle_at_command/parser.py +189 -0
  144. siada/services/handle_at_command/processor.py +321 -0
  145. siada/services/handle_at_command/resolver.py +318 -0
  146. siada/services/handle_at_command/utils.py +345 -0
  147. siada/services/model_info_service.py +103 -0
  148. siada/services/siada_runner.py +170 -0
  149. siada/session/__init__.py +29 -0
  150. siada/session/session_manager.py +66 -0
  151. siada/session/session_models.py +43 -0
  152. siada/support/__init__.py +0 -0
  153. siada/support/completer.py +204 -0
  154. siada/support/editor.py +146 -0
  155. siada/support/envprocessor.py +54 -0
  156. siada/support/repo.py +51 -0
  157. siada/support/slash_commands.py +410 -0
  158. siada/support/spinner.py +320 -0
  159. siada/tools/__init__.py +0 -0
  160. siada/tools/ast/README.md +345 -0
  161. siada/tools/ast/ast_tool.py +341 -0
  162. siada/tools/ast/models.py +11 -0
  163. siada/tools/coder/__init__.py +0 -0
  164. siada/tools/coder/ask_followup_question.py +70 -0
  165. siada/tools/coder/cmd_runner.py +132 -0
  166. siada/tools/coder/file_operator.py +284 -0
  167. siada/tools/coder/file_search/README.md +223 -0
  168. siada/tools/coder/file_search/__init__.py +10 -0
  169. siada/tools/coder/file_search/bin/rg-linux-arm64 +0 -0
  170. siada/tools/coder/file_search/bin/rg-linux-x64 +0 -0
  171. siada/tools/coder/file_search/bin/rg-macos-arm64 +0 -0
  172. siada/tools/coder/file_search/bin/rg-macos-x64 +0 -0
  173. siada/tools/coder/file_search/bin/rg.exe +0 -0
  174. siada/tools/coder/file_search/search.py +437 -0
  175. siada/tools/coder/file_search/setup_package.py +150 -0
  176. siada/tools/coder/file_search/test_search.py +142 -0
  177. siada/tools/coder/files.py +147 -0
  178. siada/tools/coder/fix_attempt_completion.py +58 -0
  179. siada/tools/coder/issue_review_completion.py +29 -0
  180. siada/tools/coder/observation/__init__.py +0 -0
  181. siada/tools/coder/observation/error.py +22 -0
  182. siada/tools/coder/observation/file_observation.py +216 -0
  183. siada/tools/coder/observation/observation.py +71 -0
  184. siada/tools/coder/repo_map/__init__.py +157 -0
  185. siada/tools/coder/repo_map/dump.py +29 -0
  186. siada/tools/coder/repo_map/io.py +226 -0
  187. siada/tools/coder/repo_map/repo_map.py +881 -0
  188. siada/tools/coder/repo_map/special.py +203 -0
  189. siada/tools/coder/repo_map/token_counter.py +154 -0
  190. siada/tools/coder/repo_map/waiting.py +221 -0
  191. siada/tools/coder/reproduce_completion.py +39 -0
  192. siada/tools/coder/run_cmd.py +60 -0
  193. siada/tools/coder/test_completion.py +25 -0
  194. siada/tools/coder/tool_docs.py +44 -0
  195. siada/tools/compression_tool.py +200 -0
  196. siada/tools/read_many_files/README.md +220 -0
  197. siada/tools/read_many_files/__init__.py +25 -0
  198. siada/tools/read_many_files/file_processor.py +486 -0
  199. siada/tools/read_many_files/filters.py +326 -0
  200. siada/tools/read_many_files/formatters.py +204 -0
  201. siada/tools/read_many_files/models.py +189 -0
  202. siada/tools/read_many_files_tool.py +233 -0
  203. siada/tools/tool_call_format/__init__.py +55 -0
  204. siada/tools/tool_call_format/file_to_language.py +231 -0
  205. siada/tools/tool_call_format/formatter_factory.py +80 -0
  206. siada/tools/tool_call_format/formatters.py +282 -0
  207. siada/tools/tool_call_format/tool_call_formatter.py +54 -0
  208. siada/tools/web/__init__.py +0 -0
  209. siada/tools/web/web_crawl.py +208 -0
  210. siada/utils.py +194 -0
  211. siada_cli-0.0.1.dist-info/LICENSE +201 -0
  212. siada_cli-0.0.1.dist-info/METADATA +457 -0
  213. siada_cli-0.0.1.dist-info/RECORD +215 -0
  214. siada_cli-0.0.1.dist-info/WHEEL +4 -0
  215. siada_cli-0.0.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,253 @@
1
+ """
2
+ Text processing utilities for file recommendation.
3
+ """
4
+
5
+ from typing import List, Tuple, Optional
6
+
7
+
8
+ def is_completion_active(text: str, cursor_row: int, cursor_col: int, lines: List[str]) -> bool:
9
+ """
10
+ Check if @ file recommendation should be activated
11
+
12
+ Args:
13
+ text: Complete input text
14
+ cursor_row: Cursor row number
15
+ cursor_col: Cursor column number
16
+ lines: Text line list
17
+
18
+ Returns:
19
+ bool: Whether to activate recommendation
20
+ """
21
+ # If it's a slash command, handle that first
22
+ if text.strip().startswith('/'):
23
+ return False
24
+
25
+ # Get current line
26
+ current_line = lines[cursor_row] if cursor_row < len(lines) else ""
27
+ code_points = list(current_line) # Unicode code point array
28
+
29
+ # Search backwards from cursor position
30
+ for i in range(cursor_col - 1, -1, -1):
31
+ char = code_points[i]
32
+
33
+ if char == ' ':
34
+ # Check if it's escaped space
35
+ backslash_count = 0
36
+ j = i - 1
37
+ while j >= 0 and code_points[j] == '\\':
38
+ backslash_count += 1
39
+ j -= 1
40
+
41
+ # Even number of backslashes means unescaped space
42
+ if backslash_count % 2 == 0:
43
+ return False
44
+
45
+ elif char == '@':
46
+ # Found @ character, activate recommendation
47
+ return True
48
+
49
+ return False
50
+
51
+
52
+ def parse_at_command_path(text: str) -> Tuple[str, str, str]:
53
+ """
54
+ Parse path information from @ command
55
+
56
+ Args:
57
+ text: Input text
58
+
59
+ Returns:
60
+ Tuple[base_directory, path_prefix, original_partial_path]
61
+
62
+ Note: Modified to remove relative path support and use full path as search prefix
63
+ """
64
+ at_index = text.rfind('@')
65
+ if at_index == -1:
66
+ return ".", "", ""
67
+
68
+ partial_path = text[at_index + 1:]
69
+
70
+ # Use the entire path after @ as search prefix, no directory-based splitting
71
+ # This eliminates relative path navigation issues
72
+ full_prefix = unescape_path(partial_path)
73
+
74
+ return ".", full_prefix, partial_path
75
+
76
+
77
+ def to_code_points(text: str) -> List[str]:
78
+ """
79
+ Convert string to Unicode code point array
80
+
81
+ Args:
82
+ text: Input string
83
+
84
+ Returns:
85
+ List[str]: Unicode code point list
86
+ """
87
+ return list(text)
88
+
89
+
90
+ def cp_len(text: str) -> int:
91
+ """
92
+ Get Unicode code point length of string
93
+
94
+ Args:
95
+ text: Input string
96
+
97
+ Returns:
98
+ int: Code point length
99
+ """
100
+ return len(list(text))
101
+
102
+
103
+ def cp_slice(text: str, start: int, end: Optional[int] = None) -> str:
104
+ """
105
+ Slice string by Unicode code points
106
+
107
+ Args:
108
+ text: Input string
109
+ start: Start position
110
+ end: End position
111
+
112
+ Returns:
113
+ str: Sliced string
114
+ """
115
+ code_points = list(text)
116
+ return ''.join(code_points[start:end])
117
+
118
+
119
+ def unescape_path(file_path: str) -> str:
120
+ """
121
+ Unescape file path spaces
122
+
123
+ Args:
124
+ file_path: Escaped file path
125
+
126
+ Returns:
127
+ str: Unescaped path
128
+ """
129
+ return file_path.replace('\\ ', ' ')
130
+
131
+
132
+ def extract_at_path_from_text(text: str, cursor_pos: int = None) -> Optional[str]:
133
+ """
134
+ Extract the @ path from text at cursor position
135
+
136
+ Args:
137
+ text: Input text
138
+ cursor_pos: Cursor position (default: end of text)
139
+
140
+ Returns:
141
+ Optional[str]: Extracted @ path or None if not found
142
+ """
143
+ if cursor_pos is None:
144
+ cursor_pos = len(text)
145
+
146
+ # Find the last @ before cursor position
147
+ at_pos = -1
148
+ for i in range(cursor_pos - 1, -1, -1):
149
+ if i < len(text) and text[i] == '@':
150
+ # Check if it's escaped
151
+ if i == 0 or text[i-1] != '\\':
152
+ at_pos = i
153
+ break
154
+
155
+ if at_pos == -1:
156
+ return None
157
+
158
+ # Find the end of the @ path (next unescaped space or end of text)
159
+ end_pos = at_pos + 1
160
+ while end_pos < len(text):
161
+ if text[end_pos] == ' ':
162
+ # Check if it's escaped
163
+ if end_pos == 0 or text[end_pos - 1] != '\\':
164
+ break
165
+ elif text[end_pos] == '\\' and end_pos + 1 < len(text):
166
+ # Skip escaped character
167
+ end_pos += 2
168
+ continue
169
+ end_pos += 1
170
+
171
+ # Extract from @ to end of path
172
+ return text[at_pos:end_pos]
173
+
174
+
175
+ def is_at_command_start(text: str, position: int) -> bool:
176
+ """
177
+ Check if position is at the start of an @ command
178
+
179
+ Args:
180
+ text: Input text
181
+ position: Position to check
182
+
183
+ Returns:
184
+ bool: True if position is at @ command start
185
+ """
186
+ if position >= len(text) or text[position] != '@':
187
+ return False
188
+
189
+ # Check if @ is escaped
190
+ if position > 0 and text[position - 1] == '\\':
191
+ return False
192
+
193
+ return True
194
+
195
+
196
+ def find_at_commands_in_text(text: str) -> List[Tuple[int, str]]:
197
+ """
198
+ Find all @ commands in text
199
+
200
+ Args:
201
+ text: Input text
202
+
203
+ Returns:
204
+ List[Tuple[int, str]]: List of (position, at_command) tuples
205
+ """
206
+ commands = []
207
+ i = 0
208
+
209
+ while i < len(text):
210
+ if text[i] == '@' and is_at_command_start(text, i):
211
+ # Find end of command
212
+ j = i + 1
213
+ while j < len(text) and not text[j].isspace():
214
+ if text[j] == '\\' and j + 1 < len(text):
215
+ j += 2 # Skip escaped character
216
+ else:
217
+ j += 1
218
+
219
+ command = text[i:j]
220
+ commands.append((i, command))
221
+ i = j
222
+ else:
223
+ i += 1
224
+
225
+ return commands
226
+
227
+
228
+ def validate_at_path_syntax(at_path: str) -> bool:
229
+ """
230
+ Validate @ path syntax
231
+
232
+ Args:
233
+ at_path: @ path to validate
234
+
235
+ Returns:
236
+ bool: True if valid syntax
237
+ """
238
+ if not at_path or not at_path.startswith('@'):
239
+ return False
240
+
241
+ path_part = at_path[1:]
242
+
243
+ # Empty path after @ is only valid for lone @
244
+ if not path_part and at_path != '@':
245
+ return False
246
+
247
+ # Check for invalid characters
248
+ invalid_chars = ['<', '>', '|', '"', '*', '?']
249
+ for char in invalid_chars:
250
+ if char in path_part:
251
+ return False
252
+
253
+ return True
@@ -0,0 +1,377 @@
1
+ """
2
+ Fix Result Checker - 通过模型判断代码修复是否真正解决了问题
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import logging
8
+ from typing import Dict, Any, Union
9
+
10
+ from openai.types.chat import ChatCompletionMessageParam
11
+
12
+ from siada.provider.client_factory import get_client_with_kwargs
13
+ from siada.foundation.config import settings
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class FixResultChecker:
18
+ """修复结果检查器
19
+
20
+ 使用模型分析代码修复是否真正解决了描述的问题
21
+ """
22
+ async def check(self, issue_desc: str, fix_code: str, context: Any) -> Dict[str, Any]:
23
+ """检查修复代码是否真正解决了问题
24
+
25
+ Args:
26
+ issue_desc: 问题描述
27
+ fix_code: 修复代码
28
+ context: 上下文对象,包含provider等信息
29
+
30
+ Returns:
31
+ Dict[str, Any]: 包含检查结果的字典
32
+ {
33
+ "is_fixed": bool, # 是否修复(部分修复算作未修复)
34
+ "check_summary": str, # 检查摘要,说明修复状态和原因
35
+ "analysis": str # 完整的分析过程
36
+ }
37
+ """
38
+ try:
39
+ analysis_result = await self._call_model_for_analysis(issue_desc, fix_code, context)
40
+ return self._parse_analysis_result(analysis_result)
41
+ except Exception as e:
42
+ logger.error(f"Fix result check failed: {e}", exc_info=True)
43
+ return {
44
+ "is_fixed": False,
45
+ "check_summary": f"分析过程中发生错误: {str(e)}",
46
+ "analysis": f"错误详情: {str(e)}"
47
+ }
48
+
49
+ async def _call_model_for_analysis(self, issue_desc: str, fix_code: str, context: Any) -> str:
50
+ """调用模型进行分析
51
+
52
+ Args:
53
+ issue_desc: 问题描述
54
+ fix_code: 修复代码
55
+ context: 上下文对象,包含provider等信息
56
+
57
+ Returns:
58
+ str: 模型分析结果
59
+ """
60
+ # 构建用户任务提示词
61
+ user_task = self.build_prompt(issue_desc, fix_code)
62
+
63
+ # 构建请求消息
64
+ model_messages: list[ChatCompletionMessageParam] = [
65
+ {"role": "user", "content": user_task},
66
+ ]
67
+
68
+
69
+ # Call the model
70
+ default_kwargs = {
71
+ "model": settings.Claude_4_0_SONNET,
72
+ "messages": model_messages,
73
+ "stream": False,
74
+ "temperature": 0.2, # Lower temperature for accuracy and consistency
75
+ }
76
+
77
+ # Use get_client_with_kwargs to support context parameter overrides
78
+ client, complete_kwargs = get_client_with_kwargs(context, default_kwargs)
79
+ response = await client.chat_complete(**complete_kwargs)
80
+
81
+ # 提取分析结果
82
+ if response and response.choices and response.choices[0].message:
83
+ analysis = response.choices[0].message.content
84
+ if analysis:
85
+ return analysis.strip()
86
+
87
+ # 如果无法获取有效分析结果,返回错误信息
88
+ raise Exception("无法从模型获取有效的分析结果")
89
+
90
+ def build_prompt(self, issue_desc: str, fix_code: str) -> str:
91
+ return f"""**Please systematically analyze whether the code modification truly resolves the issue by following the steps below and return your analysis in JSON format:**
92
+
93
+ ---
94
+ Please systematically analyze whether the code modifications truly fix the problem by following these steps:
95
+
96
+ ## Step 1: Deep Root Cause Analysis
97
+ 1. **Core Problem Identification**: Extract the fundamental cause of the problem from the issue description, distinguishing between symptoms and true root causes
98
+ 2. **Problem Impact Scope**: List all affected code paths, usage scenarios, and boundary conditions
99
+ 3. **Problem Trigger Conditions**: Clarify under what conditions this problem will be triggered, with special attention to edge cases
100
+ 4. **Expected Behavior Definition**: Based on the problem description, clearly define the specific behavior that should be achieved after the fix
101
+ 5. **Reverse Logic Check**: Confirm whether the fix direction is correct, avoiding going in the opposite direction of expectations
102
+
103
+ ## Step 2: Fix Strategy Rationality Assessment
104
+ 1. **Fix Type Classification**:
105
+ - Fundamental fix: Directly addresses the root cause
106
+ - Symptomatic fix: Only masks or bypasses the error phenomenon
107
+ - Compensatory fix: Avoids the problem through other mechanisms
108
+ 2. **Solution Alignment**: Whether the fix solution directly targets the root cause
109
+ 3. **Complexity Rationality**: Assess whether there is over-complication or over-engineering
110
+ 4. **Minimal Intrusion Principle**: Whether it follows the principle of minimal changes, avoiding unnecessary modifications
111
+
112
+ ## Step 3: Fix Code Implementation Quality Analysis
113
+ ### 3.1 Coverage Assessment
114
+ 1. **Modification Point Mapping**: Map each code modification point to specific problem scenarios
115
+ 2. **Coverage Range Check**: Verify whether modifications cover all problem scenarios
116
+ 3. **Missing Scenario Identification**: Identify uncovered scenarios that may have the same problem
117
+
118
+ ### 3.2 Implementation Detail Analysis
119
+ 1. **API Usage Appropriateness**: Verify whether the APIs used are the most direct and standard methods
120
+ 2. **Code Execution Path**: Analyze whether there are unnecessary intermediate steps or roundabout implementations
121
+ 3. **Error Handling Completeness**: Check whether all possible exception situations are correctly handled
122
+ 4. **Performance Impact Assessment**: Analyze whether the fix introduces unnecessary performance overhead
123
+
124
+ ## Step 4: Data Security and System Stability Check
125
+ 1. **Data Security Risk**: Whether modifications may lead to data loss or inconsistency
126
+ 2. **State Consistency**: Whether system state remains consistent after modifications
127
+ 3. **Side Effect Assessment**: Evaluate whether modifications may introduce new problems
128
+ 4. **Backward Compatibility**: Whether modifications maintain backward compatibility
129
+ 5. **Rollback Safety**: Whether modifications support safe rollback
130
+
131
+ ## Step 5: Design Principles and Architecture Consistency
132
+ 1. **Architecture Alignment**: Whether modifications align with existing architecture and design patterns
133
+ 2. **Framework Best Practices**: Whether they conform to the design philosophy and best practices of relevant frameworks
134
+ 3. **Code Simplicity**: Whether the solution is concise, clear, easy to understand and maintain
135
+ 4. **Maintainability Assessment**: Analyze the long-term maintainability and extensibility of the fix code
136
+
137
+ ## Step 6: Test Verification Completeness
138
+ 1. **Test Scenario Coverage**: Whether test cases cover all problem scenarios and boundary conditions
139
+ 2. **Failed Case Analysis**: If there are test failures, analyze whether they indicate incomplete fixes
140
+ 3. **Regression Test Verification**: Whether it's verified that modifications don't break existing functionality
141
+ 4. **Performance Test Consideration**: Assess whether performance-related tests are needed to verify fix quality
142
+
143
+ ## Step 7: Comprehensive Judgment and Recommendations
144
+ Based on the above analysis, provide clear conclusions:
145
+
146
+ ### Required Output Fields:
147
+ 1. **is_fixed**: true/false (partial fixes count as false)
148
+ 2. **check_summary**: Detailed analysis summary, must include:
149
+ - Specific basis for fix status judgment
150
+ - If not fixed, clearly explain reasons for non-fix
151
+ - If fixed, assess implementation quality and potential risks
152
+ - Specific improvement suggestions or alternative solutions
153
+
154
+ ## Key Analysis Focus:
155
+ - Whether the fundamental problem is truly solved rather than just making errors disappear
156
+ - Whether the fix direction is correct, avoiding directional errors
157
+ - Whether there's a tendency toward over-engineering
158
+ - Whether API usage is appropriate, avoiding roundabout or inefficient implementations
159
+ - Whether data security and system stability are ensured
160
+ - Long-term maintainability and extensibility of the code
161
+ ---
162
+
163
+ ## **Required JSON Output Format**
164
+
165
+ You must return your analysis in the following JSON format:
166
+
167
+ ```json
168
+ {{
169
+ "analysis": "The analysis results of each step",
170
+ "result": {{
171
+ "is_fixed": True,
172
+ "check_summary": "Summary of each step of the analysis"
173
+ }}
174
+ }}
175
+ ```
176
+ ---
177
+
178
+ **Problem Description:**
179
+ {issue_desc}
180
+
181
+ **Code Change:**
182
+ {fix_code}
183
+ """
184
+
185
+ def _parse_analysis_result(self, analysis_result: str) -> Dict[str, Any]:
186
+ """解析模型分析结果
187
+
188
+ Args:
189
+ analysis_result: 模型返回的分析结果(应为JSON格式)
190
+
191
+ Returns:
192
+ Dict[str, Any]: 解析后的结果
193
+ """
194
+ try:
195
+ # 尝试直接解析JSON,处理可能的markdown包装
196
+ json_content = analysis_result.strip()
197
+
198
+ # 如果响应被包装在markdown代码块中,提取JSON部分
199
+ if json_content.startswith('```json') or json_content.startswith('```'):
200
+ lines = json_content.split('\n')
201
+ json_lines = []
202
+ in_json_block = False
203
+ for line in lines:
204
+ line_stripped = line.strip()
205
+ if line_stripped in ['```json', '```']:
206
+ if not in_json_block:
207
+ in_json_block = True
208
+ else:
209
+ break
210
+ continue
211
+ elif in_json_block:
212
+ json_lines.append(line)
213
+ json_content = '\n'.join(json_lines)
214
+
215
+ parsed_json = json.loads(json_content)
216
+
217
+ # 验证JSON结构
218
+ if not isinstance(parsed_json, dict):
219
+ raise ValueError("返回的不是有效的JSON对象")
220
+
221
+ # 提取结果信息
222
+ result = parsed_json.get("result", {})
223
+ analysis = parsed_json.get("analysis", "")
224
+
225
+ # 如果analysis是字典类型,构建完整的分析文本
226
+ if isinstance(analysis, dict):
227
+ analysis_text = self._build_analysis_text(analysis)
228
+ else:
229
+ analysis_text = str(analysis)
230
+
231
+ return {
232
+ "is_fixed": result.get("is_fixed", False),
233
+ "check_summary": result.get("check_summary", "未提供原因说明"),
234
+ "analysis": analysis_text
235
+ }
236
+
237
+ except (json.JSONDecodeError, ValueError, KeyError) as e:
238
+ # JSON解析失败,回退到文本解析
239
+ return self._fallback_text_parsing(analysis_result, str(e))
240
+
241
+ def _build_analysis_text(self, analysis: Dict[str, Any]) -> str:
242
+ """从JSON分析数据构建完整的分析文本
243
+
244
+ Args:
245
+ analysis: 分析数据字典
246
+
247
+ Returns:
248
+ str: 格式化的分析文本
249
+ """
250
+ # 如果analysis是字符串,直接返回
251
+ if isinstance(analysis, str):
252
+ return analysis
253
+
254
+ # 如果analysis是字典,尝试构建结构化文本
255
+ if isinstance(analysis, dict):
256
+ # 预定义的步骤名称映射
257
+ step_mappings = [
258
+ ("step1_problem_scope", "Step 1: Deep Root Cause Analysis"),
259
+ ("step2_fix_coverage", "Step 2: Fix Strategy Rationality Assessment"),
260
+ ("step3_test_validation", "Step 3: Fix Code Implementation Quality Analysis"),
261
+ ("step4_logical_consistency", "Step 4: Data Security and System Stability Check"),
262
+ ("step5_final_assessment", "Step 5: Design Principles and Architecture Consistency"),
263
+ ("step6_test_verification", "Step 6: Test Verification Completeness"),
264
+ ("step7_comprehensive_judgment", "Step 7: Comprehensive Judgment and Recommendations")
265
+ ]
266
+
267
+ formatted_sections = []
268
+
269
+ # 处理结构化步骤
270
+ for key, title in step_mappings:
271
+ content = analysis.get(key, "")
272
+ if content:
273
+ formatted_sections.append(f"## {title}\n{content}")
274
+
275
+ # 如果没有找到标准步骤,处理其他键值对
276
+ if not formatted_sections:
277
+ for key, value in analysis.items():
278
+ if value: # 只处理非空值
279
+ # 格式化键名为更可读的标题
280
+ title = key.replace("_", " ").title()
281
+ formatted_sections.append(f"## {title}\n{str(value)}")
282
+
283
+ return "\n\n".join(formatted_sections) if formatted_sections else str(analysis)
284
+
285
+ # 其他类型转换为字符串
286
+ return str(analysis)
287
+
288
+ def _fallback_text_parsing(self, analysis_result: str, error_msg: str) -> Dict[str, Any]:
289
+ """当JSON解析失败时的回退文本解析方法
290
+
291
+ Args:
292
+ analysis_result: 原始分析结果
293
+ error_msg: 错误信息
294
+
295
+ Returns:
296
+ Dict[str, Any]: 解析后的结果
297
+ """
298
+ # 使用原有的文本解析方法作为回退
299
+ is_fixed = self._extract_fix_status(analysis_result)
300
+ check_summary = self._extract_check_summary(analysis_result, is_fixed)
301
+
302
+ # 在分析文本中添加解析错误信息
303
+ analysis_with_error = f"[JSON解析失败: {error_msg}]\n\n{analysis_result}"
304
+
305
+ return {
306
+ "is_fixed": is_fixed,
307
+ "check_summary": check_summary,
308
+ "analysis": analysis_with_error
309
+ }
310
+
311
+ def _extract_fix_status(self, analysis: str) -> bool:
312
+ """从分析结果中提取修复状态
313
+
314
+ Args:
315
+ analysis: 分析结果文本
316
+
317
+ Returns:
318
+ bool: 是否已修复
319
+ """
320
+ analysis_lower = analysis.lower()
321
+
322
+ # 查找明确的修复状态指示
323
+ if "fully fixed" in analysis_lower:
324
+ return True
325
+ elif "partially fixed" in analysis_lower or "not fixed" in analysis_lower:
326
+ return False
327
+
328
+ # 查找是否修复的明确回答
329
+ if "is the issue fixed: yes" in analysis_lower:
330
+ return True
331
+ elif "is the issue fixed: no" in analysis_lower:
332
+ return False
333
+
334
+ # 如果没有明确指示,查找其他关键词
335
+ positive_indicators = ["resolved", "addressed", "fixed", "solved"]
336
+ negative_indicators = ["not resolved", "not addressed", "not fixed", "incomplete", "missing"]
337
+
338
+ positive_count = sum(1 for indicator in positive_indicators if indicator in analysis_lower)
339
+ negative_count = sum(1 for indicator in negative_indicators if indicator in analysis_lower)
340
+
341
+ # 如果负面指示更多,认为未修复
342
+ if negative_count > positive_count:
343
+ return False
344
+
345
+ # 默认情况下,如果有正面指示且没有明确的负面结论,认为已修复
346
+ return positive_count > 0
347
+
348
+ def _extract_check_summary(self, analysis: str, is_fixed: bool) -> str:
349
+ """提取未修复的原因
350
+
351
+ Args:
352
+ analysis: 分析结果文本
353
+ is_fixed: 是否已修复
354
+
355
+ Returns:
356
+ str: 原因说明
357
+ """
358
+ if is_fixed:
359
+ return "问题已完全修复"
360
+
361
+ # 尝试提取原因
362
+ lines = analysis.split('\n')
363
+ check_summary_lines = []
364
+
365
+ # 查找包含原因的行
366
+ for line in lines:
367
+ line_lower = line.lower().strip()
368
+ if any(keyword in line_lower for keyword in [
369
+ "check_summary", "because", "however", "but", "missing",
370
+ "not covered", "incomplete", "still exists"
371
+ ]):
372
+ check_summary_lines.append(line.strip())
373
+
374
+ if check_summary_lines:
375
+ return " ".join(check_summary_lines)
376
+
377
+ return "分析表明问题未完全修复,但未明确说明具体原因"