precis-cli 0.1.3__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 (442) hide show
  1. app/__init__.py +16 -0
  2. app/api/__init__.py +32 -0
  3. app/api/dependencies.py +185 -0
  4. app/api/main.py +301 -0
  5. app/api/middleware/__init__.py +34 -0
  6. app/api/middleware/exception_handler.py +126 -0
  7. app/api/middleware/request_logging.py +97 -0
  8. app/api/middleware/token_auth.py +215 -0
  9. app/api/models/__init__.py +151 -0
  10. app/api/models/connection_rules.py +98 -0
  11. app/api/models/files.py +99 -0
  12. app/api/models/full_validation.py +554 -0
  13. app/api/models/project.py +117 -0
  14. app/api/models/projects.py +80 -0
  15. app/api/models/schema.py +121 -0
  16. app/api/models/v2_responses.py +46 -0
  17. app/api/models/validation.py +303 -0
  18. app/api/models/workspace.py +118 -0
  19. app/api/routers/__init__.py +56 -0
  20. app/api/routers/ai/__init__.py +36 -0
  21. app/api/routers/ai/chat.py +243 -0
  22. app/api/routers/ai/generate.py +156 -0
  23. app/api/routers/ai/hardware.py +199 -0
  24. app/api/routers/ai/jobs.py +544 -0
  25. app/api/routers/ai/migrate.py +471 -0
  26. app/api/routers/ai/models.py +352 -0
  27. app/api/routers/ai/ollama.py +108 -0
  28. app/api/routers/ai/providers.py +491 -0
  29. app/api/routers/ai/router.py +44 -0
  30. app/api/routers/ai/stream.py +281 -0
  31. app/api/routers/ai/utils.py +182 -0
  32. app/api/routers/core/__init__.py +34 -0
  33. app/api/routers/core/connection_rules.py +188 -0
  34. app/api/routers/core/data_sources.py +374 -0
  35. app/api/routers/core/regex.py +333 -0
  36. app/api/routers/core/reporting.py +263 -0
  37. app/api/routers/files/__init__.py +24 -0
  38. app/api/routers/files/ops.py +179 -0
  39. app/api/routers/files/transfer.py +101 -0
  40. app/api/routers/preview/__init__.py +35 -0
  41. app/api/routers/preview/content_mode.py +264 -0
  42. app/api/routers/preview/header_row.py +154 -0
  43. app/api/routers/preview/models.py +81 -0
  44. app/api/routers/preview/path_mode.py +440 -0
  45. app/api/routers/preview/router.py +30 -0
  46. app/api/routers/project/__init__.py +61 -0
  47. app/api/routers/project/base.py +111 -0
  48. app/api/routers/project/constraint.py +345 -0
  49. app/api/routers/project/full_config.py +411 -0
  50. app/api/routers/project/full_config_writer.py +327 -0
  51. app/api/routers/project/helpers.py +167 -0
  52. app/api/routers/project/inspection_fix.py +330 -0
  53. app/api/routers/project/manifest.py +605 -0
  54. app/api/routers/project/models.py +177 -0
  55. app/api/routers/project/pattern.py +268 -0
  56. app/api/routers/project/regex.py +358 -0
  57. app/api/routers/project/scanner.py +157 -0
  58. app/api/routers/project/schema.py +567 -0
  59. app/api/routers/project/schema_helpers.py +149 -0
  60. app/api/routers/project/settings.py +399 -0
  61. app/api/routers/project/template.py +325 -0
  62. app/api/routers/project/validation.py +227 -0
  63. app/api/routers/project/view.py +177 -0
  64. app/api/routers/project/workspaces.py +167 -0
  65. app/api/routers/projects/__init__.py +25 -0
  66. app/api/routers/projects/create.py +121 -0
  67. app/api/routers/projects/open.py +103 -0
  68. app/api/routers/projects/scan.py +100 -0
  69. app/api/routers/validation/__init__.py +39 -0
  70. app/api/routers/validation/common.py +263 -0
  71. app/api/routers/validation/content_mode.py +401 -0
  72. app/api/routers/validation/history.py +235 -0
  73. app/api/routers/validation/inline_mode.py +196 -0
  74. app/api/routers/validation/path_mode.py +314 -0
  75. app/api/routers/validation/router.py +51 -0
  76. app/api/services/full_validation_response_builder.py +412 -0
  77. app/api/services/io_error_messages.py +41 -0
  78. app/api/services/preview_service.py +248 -0
  79. app/cli/__init__.py +18 -0
  80. app/cli/__main__.py +30 -0
  81. app/cli/shared_services/__init__.py +42 -0
  82. app/cli/shared_services/config_ops.py +581 -0
  83. app/cli/shared_services/generation_ops.py +160 -0
  84. app/cli/shared_services/project_ops.py +288 -0
  85. app/cli/shell/__init__.py +48 -0
  86. app/cli/shell/commands/__init__.py +63 -0
  87. app/cli/shell/commands/ai/__init__.py +260 -0
  88. app/cli/shell/commands/ai/chat.py +331 -0
  89. app/cli/shell/commands/ai/delete.py +165 -0
  90. app/cli/shell/commands/ai/diff.py +70 -0
  91. app/cli/shell/commands/ai/display.py +428 -0
  92. app/cli/shell/commands/ai/executor.py +225 -0
  93. app/cli/shell/commands/ai/executor_utils.py +271 -0
  94. app/cli/shell/commands/ai/generate.py +285 -0
  95. app/cli/shell/commands/ai/interaction.py +262 -0
  96. app/cli/shell/commands/ai/migrate.py +281 -0
  97. app/cli/shell/commands/ai/resolver.py +224 -0
  98. app/cli/shell/commands/ai/status.py +101 -0
  99. app/cli/shell/commands/ai/switch.py +158 -0
  100. app/cli/shell/commands/ai/utils.py +52 -0
  101. app/cli/shell/commands/base.py +343 -0
  102. app/cli/shell/commands/config/__init__.py +131 -0
  103. app/cli/shell/commands/config/base.py +80 -0
  104. app/cli/shell/commands/config/check.py +246 -0
  105. app/cli/shell/commands/config/edit.py +148 -0
  106. app/cli/shell/commands/config/get.py +111 -0
  107. app/cli/shell/commands/config/init.py +123 -0
  108. app/cli/shell/commands/config/inspect.py +164 -0
  109. app/cli/shell/commands/config/list.py +104 -0
  110. app/cli/shell/commands/config/set.py +119 -0
  111. app/cli/shell/commands/config/show.py +150 -0
  112. app/cli/shell/commands/exit.py +67 -0
  113. app/cli/shell/commands/help.py +104 -0
  114. app/cli/shell/commands/infer_schema.py +156 -0
  115. app/cli/shell/commands/open.py +201 -0
  116. app/cli/shell/commands/project.py +215 -0
  117. app/cli/shell/commands/provider.py +619 -0
  118. app/cli/shell/commands/system.py +170 -0
  119. app/cli/shell/commands/validate.py +455 -0
  120. app/cli/shell/completer.py +231 -0
  121. app/cli/shell/config_storage.py +191 -0
  122. app/cli/shell/exceptions.py +90 -0
  123. app/cli/shell/formatter.py +371 -0
  124. app/cli/shell/interactive_menu.py +348 -0
  125. app/cli/shell/main.py +286 -0
  126. app/cli/shell/parser.py +266 -0
  127. app/cli/start.py +180 -0
  128. app/cli_main.py +52 -0
  129. app/mcp_server.py +311 -0
  130. app/shared/core/__init__.py +18 -0
  131. app/shared/core/app_version.py +59 -0
  132. app/shared/core/config/__init__.py +119 -0
  133. app/shared/core/config/server.py +193 -0
  134. app/shared/core/data_source/__init__.py +106 -0
  135. app/shared/core/data_source/loader.py +348 -0
  136. app/shared/core/data_source/loaders/__init__.py +213 -0
  137. app/shared/core/data_source/loaders/base.py +215 -0
  138. app/shared/core/data_source/loaders/converter.py +337 -0
  139. app/shared/core/data_source/loaders/csv_loader.py +252 -0
  140. app/shared/core/data_source/loaders/excel_loader.py +666 -0
  141. app/shared/core/data_source/loaders/extractor.py +268 -0
  142. app/shared/core/data_source/loaders/json_loader.py +354 -0
  143. app/shared/core/data_source/loaders/registry.py +88 -0
  144. app/shared/core/data_source/loaders/sql_loader.py +313 -0
  145. app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
  146. app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
  147. app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
  148. app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
  149. app/shared/core/data_source/schema_info.py +50 -0
  150. app/shared/core/data_source/specs/__init__.py +128 -0
  151. app/shared/core/data_source/specs/base.py +258 -0
  152. app/shared/core/data_source/specs/csv_source.py +137 -0
  153. app/shared/core/data_source/specs/excel_source.py +141 -0
  154. app/shared/core/data_source/specs/file_base.py +252 -0
  155. app/shared/core/data_source/specs/json_source.py +226 -0
  156. app/shared/core/data_source/specs/sql_source.py +161 -0
  157. app/shared/core/encoding.py +36 -0
  158. app/shared/core/io/__init__.py +24 -0
  159. app/shared/core/io/yaml.py +379 -0
  160. app/shared/core/manifest_schema/__init__.py +52 -0
  161. app/shared/core/manifest_schema/types.py +99 -0
  162. app/shared/core/manifest_schema/version.py +175 -0
  163. app/shared/core/patterns/__init__.py +24 -0
  164. app/shared/core/patterns/loader.py +134 -0
  165. app/shared/core/patterns/writer.py +244 -0
  166. app/shared/core/project/__init__.py +47 -0
  167. app/shared/core/project/constraint/builders/__init__.py +50 -0
  168. app/shared/core/project/constraint/builders/base.py +100 -0
  169. app/shared/core/project/constraint/builders/composite.py +77 -0
  170. app/shared/core/project/constraint/builders/conditional.py +67 -0
  171. app/shared/core/project/constraint/builders/foreign_key.py +53 -0
  172. app/shared/core/project/constraint/builders/registry.py +64 -0
  173. app/shared/core/project/constraint/builders/scripted.py +51 -0
  174. app/shared/core/project/constraint/builders/single_column.py +86 -0
  175. app/shared/core/project/constraint/builders/unique.py +53 -0
  176. app/shared/core/project/constraint/factory.py +170 -0
  177. app/shared/core/project/constraint/reader.py +214 -0
  178. app/shared/core/project/constraint/registry.py +233 -0
  179. app/shared/core/project/constraint/types/__init__.py +63 -0
  180. app/shared/core/project/constraint/types/constraint_file.py +261 -0
  181. app/shared/core/project/constraint/types/refs.py +460 -0
  182. app/shared/core/project/constraint/types.py +28 -0
  183. app/shared/core/project/constraint/writer.py +181 -0
  184. app/shared/core/project/loader/__init__.py +56 -0
  185. app/shared/core/project/loader/loader.py +30 -0
  186. app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
  187. app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
  188. app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
  189. app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
  190. app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
  191. app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
  192. app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
  193. app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
  194. app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
  195. app/shared/core/project/loader/loader_parts/main.py +432 -0
  196. app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
  197. app/shared/core/project/loader/loader_parts/runtime.py +125 -0
  198. app/shared/core/project/loader/types.py +246 -0
  199. app/shared/core/project/manifest/coverage.py +391 -0
  200. app/shared/core/project/manifest/reader.py +260 -0
  201. app/shared/core/project/manifest/types.py +91 -0
  202. app/shared/core/project/manifest/types_parts/__init__.py +60 -0
  203. app/shared/core/project/manifest/types_parts/constants.py +40 -0
  204. app/shared/core/project/manifest/types_parts/data_source.py +68 -0
  205. app/shared/core/project/manifest/types_parts/info.py +59 -0
  206. app/shared/core/project/manifest/types_parts/manifest.py +294 -0
  207. app/shared/core/project/manifest/types_parts/refs.py +153 -0
  208. app/shared/core/project/manifest/types_parts/settings.py +92 -0
  209. app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
  210. app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
  211. app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
  212. app/shared/core/project/manifest/types_parts/template.py +66 -0
  213. app/shared/core/project/manifest/writer.py +262 -0
  214. app/shared/core/project/manual_data/__init__.py +27 -0
  215. app/shared/core/project/manual_data/types.py +75 -0
  216. app/shared/core/project/regex/reader.py +197 -0
  217. app/shared/core/project/regex/types.py +405 -0
  218. app/shared/core/project/regex/writer.py +123 -0
  219. app/shared/core/project/schema/reader.py +170 -0
  220. app/shared/core/project/schema/types.py +47 -0
  221. app/shared/core/project/schema/types_parts/__init__.py +36 -0
  222. app/shared/core/project/schema/types_parts/column.py +174 -0
  223. app/shared/core/project/schema/types_parts/column_utils.py +72 -0
  224. app/shared/core/project/schema/types_parts/constraint.py +165 -0
  225. app/shared/core/project/schema/types_parts/schema_id.py +66 -0
  226. app/shared/core/project/schema/types_parts/source.py +255 -0
  227. app/shared/core/project/schema/types_parts/source_options.py +347 -0
  228. app/shared/core/project/schema/types_parts/table.py +230 -0
  229. app/shared/core/project/schema/writer.py +139 -0
  230. app/shared/core/project/schema_ref_check.py +95 -0
  231. app/shared/core/project/template/__init__.py +27 -0
  232. app/shared/core/project/template/expander.py +263 -0
  233. app/shared/core/project/template/reader.py +120 -0
  234. app/shared/core/project/template/types.py +114 -0
  235. app/shared/core/project/transform/reader.py +76 -0
  236. app/shared/core/project/transform/types.py +116 -0
  237. app/shared/core/project/transform/writer.py +84 -0
  238. app/shared/core/pydantic_messages.py +59 -0
  239. app/shared/core/reporter/__init__.py +46 -0
  240. app/shared/core/reporter/reporter.py +220 -0
  241. app/shared/core/reporter/reporters/__init__.py +65 -0
  242. app/shared/core/reporter/reporters/base.py +188 -0
  243. app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
  244. app/shared/core/reporter/reporters/email_reporter.py +271 -0
  245. app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
  246. app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
  247. app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
  248. app/shared/core/utils/__init__.py +18 -0
  249. app/shared/core/utils/path_utils.py +60 -0
  250. app/shared/core/utils/regex_extract.py +113 -0
  251. app/shared/domain/__init__.py +114 -0
  252. app/shared/domain/constraints/__init__.py +76 -0
  253. app/shared/domain/constraints/allowed_values.py +211 -0
  254. app/shared/domain/constraints/base.py +141 -0
  255. app/shared/domain/constraints/charset.py +337 -0
  256. app/shared/domain/constraints/composite.py +174 -0
  257. app/shared/domain/constraints/condition_registry.py +130 -0
  258. app/shared/domain/constraints/conditional.py +629 -0
  259. app/shared/domain/constraints/date_logic.py +731 -0
  260. app/shared/domain/constraints/foreign_key.py +261 -0
  261. app/shared/domain/constraints/key_normalization.py +56 -0
  262. app/shared/domain/constraints/not_null.py +185 -0
  263. app/shared/domain/constraints/range.py +360 -0
  264. app/shared/domain/constraints/regex.py +218 -0
  265. app/shared/domain/constraints/scripted.py +276 -0
  266. app/shared/domain/constraints/unique.py +233 -0
  267. app/shared/domain/data_engine.py +384 -0
  268. app/shared/domain/data_types.py +113 -0
  269. app/shared/domain/data_types_parts/__init__.py +67 -0
  270. app/shared/domain/data_types_parts/base.py +204 -0
  271. app/shared/domain/data_types_parts/composite.py +250 -0
  272. app/shared/domain/data_types_parts/expression.py +202 -0
  273. app/shared/domain/data_types_parts/extracted.py +106 -0
  274. app/shared/domain/data_types_parts/json_types.py +234 -0
  275. app/shared/domain/data_types_parts/scalars.py +719 -0
  276. app/shared/domain/data_types_parts/sequence.py +129 -0
  277. app/shared/domain/dataset_schema.py +48 -0
  278. app/shared/domain/eval_sandbox.py +63 -0
  279. app/shared/domain/expression_system.py +366 -0
  280. app/shared/domain/regex_flags.py +53 -0
  281. app/shared/domain/schema/builder.py +223 -0
  282. app/shared/domain/schema/models.py +339 -0
  283. app/shared/domain/transforms/__init__.py +28 -0
  284. app/shared/domain/transforms/aggregate.py +141 -0
  285. app/shared/domain/transforms/base.py +171 -0
  286. app/shared/domain/transforms/cast_type.py +120 -0
  287. app/shared/domain/transforms/concat.py +103 -0
  288. app/shared/domain/transforms/conditional_assign.py +114 -0
  289. app/shared/domain/transforms/date_format.py +81 -0
  290. app/shared/domain/transforms/digits.py +77 -0
  291. app/shared/domain/transforms/drop_duplicates.py +94 -0
  292. app/shared/domain/transforms/fill_na.py +94 -0
  293. app/shared/domain/transforms/filter_rows.py +97 -0
  294. app/shared/domain/transforms/lookup.py +78 -0
  295. app/shared/domain/transforms/lower_case.py +70 -0
  296. app/shared/domain/transforms/map_value.py +90 -0
  297. app/shared/domain/transforms/math_expr.py +112 -0
  298. app/shared/domain/transforms/modulo.py +82 -0
  299. app/shared/domain/transforms/regex_extract.py +103 -0
  300. app/shared/domain/transforms/registry.py +95 -0
  301. app/shared/domain/transforms/replace.py +88 -0
  302. app/shared/domain/transforms/sort_rows.py +94 -0
  303. app/shared/domain/transforms/string_split.py +84 -0
  304. app/shared/domain/transforms/strip.py +73 -0
  305. app/shared/domain/transforms/substring.py +92 -0
  306. app/shared/domain/transforms/upper_case.py +70 -0
  307. app/shared/domain/transforms/weighted_sum.py +104 -0
  308. app/shared/domain/validation_constraints.py +69 -0
  309. app/shared/services/__init__.py +62 -0
  310. app/shared/services/ai/__init__.py +48 -0
  311. app/shared/services/ai/agent/__init__.py +40 -0
  312. app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
  313. app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
  314. app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
  315. app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
  316. app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
  317. app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
  318. app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
  319. app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
  320. app/shared/services/ai/agent/executor.py +554 -0
  321. app/shared/services/ai/agent/memory.py +256 -0
  322. app/shared/services/ai/agent/planner.py +282 -0
  323. app/shared/services/ai/agent/tool_registry.py +296 -0
  324. app/shared/services/ai/agent/tools/__init__.py +32 -0
  325. app/shared/services/ai/agent/tools/config_generate.py +133 -0
  326. app/shared/services/ai/agent/tools/config_refine.py +109 -0
  327. app/shared/services/ai/agent/tools/config_validate.py +403 -0
  328. app/shared/services/ai/agent/tools/merge_results.py +136 -0
  329. app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
  330. app/shared/services/ai/agent/tools/script_parse.py +359 -0
  331. app/shared/services/ai/agent/types.py +138 -0
  332. app/shared/services/ai/chat_agent_runner.py +596 -0
  333. app/shared/services/ai/chat_orchestrator.py +725 -0
  334. app/shared/services/ai/failure_messages.py +65 -0
  335. app/shared/services/ai/job_storage.py +274 -0
  336. app/shared/services/ai/migrate_service.py +550 -0
  337. app/shared/services/ai/streaming/__init__.py +65 -0
  338. app/shared/services/ai/streaming/event_journal.py +197 -0
  339. app/shared/services/ai/streaming/orchestrator.py +262 -0
  340. app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
  341. app/shared/services/ai/streaming/sse_response.py +148 -0
  342. app/shared/services/ai/streaming/types.py +73 -0
  343. app/shared/services/ai/types.py +213 -0
  344. app/shared/services/ai/utils.py +322 -0
  345. app/shared/services/diff/config_diff.py +320 -0
  346. app/shared/services/hardware.py +237 -0
  347. app/shared/services/llm/__init__.py +62 -0
  348. app/shared/services/llm/actions/__init__.py +42 -0
  349. app/shared/services/llm/actions/_canvas_validator.py +147 -0
  350. app/shared/services/llm/actions/_constraint_validator.py +401 -0
  351. app/shared/services/llm/actions/_regex_validator.py +85 -0
  352. app/shared/services/llm/actions/_schema_validator.py +82 -0
  353. app/shared/services/llm/actions/_settings_validator.py +76 -0
  354. app/shared/services/llm/actions/_transform_validator.py +78 -0
  355. app/shared/services/llm/actions/action_handlers.py +400 -0
  356. app/shared/services/llm/actions/action_parser.py +63 -0
  357. app/shared/services/llm/actions/action_processor.py +468 -0
  358. app/shared/services/llm/actions/action_validator.py +311 -0
  359. app/shared/services/llm/actions/diff_compute.py +195 -0
  360. app/shared/services/llm/actions/regex_handlers.py +284 -0
  361. app/shared/services/llm/actions/registry.py +336 -0
  362. app/shared/services/llm/actions/schema_handlers.py +335 -0
  363. app/shared/services/llm/actions/settings_handlers.py +180 -0
  364. app/shared/services/llm/actions/specs.py +259 -0
  365. app/shared/services/llm/actions/transform_handlers.py +279 -0
  366. app/shared/services/llm/actions/validation_types.py +147 -0
  367. app/shared/services/llm/cache/__init__.py +18 -0
  368. app/shared/services/llm/cache/response_cache.py +80 -0
  369. app/shared/services/llm/chat/__init__.py +35 -0
  370. app/shared/services/llm/chat/chat_system_prompt.py +561 -0
  371. app/shared/services/llm/chat/response_parser.py +296 -0
  372. app/shared/services/llm/config/__init__.py +45 -0
  373. app/shared/services/llm/config/crypto.py +135 -0
  374. app/shared/services/llm/config/loader.py +258 -0
  375. app/shared/services/llm/config/models.py +202 -0
  376. app/shared/services/llm/config/presets.py +128 -0
  377. app/shared/services/llm/config_generator.py +163 -0
  378. app/shared/services/llm/constraints/__init__.py +41 -0
  379. app/shared/services/llm/constraints/constraint_builder.py +177 -0
  380. app/shared/services/llm/constraints/constraint_deletion.py +84 -0
  381. app/shared/services/llm/constraints/constraint_id.py +174 -0
  382. app/shared/services/llm/constraints/frontend_instructions.py +333 -0
  383. app/shared/services/llm/constraints/inline_batch.py +279 -0
  384. app/shared/services/llm/discovery/__init__.py +35 -0
  385. app/shared/services/llm/discovery/scanner.py +181 -0
  386. app/shared/services/llm/generation/__init__.py +42 -0
  387. app/shared/services/llm/generation/agent_wiring.py +156 -0
  388. app/shared/services/llm/generation/config_builder.py +386 -0
  389. app/shared/services/llm/generation/errors.py +36 -0
  390. app/shared/services/llm/generation/existing_config.py +99 -0
  391. app/shared/services/llm/generation/profiler.py +279 -0
  392. app/shared/services/llm/generation/prompt_builder.py +199 -0
  393. app/shared/services/llm/generation/response_parser.py +144 -0
  394. app/shared/services/llm/generation/service.py +622 -0
  395. app/shared/services/llm/models.py +104 -0
  396. app/shared/services/llm/providers/__init__.py +48 -0
  397. app/shared/services/llm/providers/base.py +310 -0
  398. app/shared/services/llm/providers/cached_provider.py +90 -0
  399. app/shared/services/llm/providers/ollama.py +498 -0
  400. app/shared/services/llm/providers/openai.py +334 -0
  401. app/shared/services/llm/providers/registry.py +108 -0
  402. app/shared/services/llm/schema_resolver.py +141 -0
  403. app/shared/services/llm/suggestion_utils.py +219 -0
  404. app/shared/services/llm/validate_executor.py +163 -0
  405. app/shared/services/llm/yaml_io.py +406 -0
  406. app/shared/services/preview/__init__.py +18 -0
  407. app/shared/services/preview/loader.py +145 -0
  408. app/shared/services/preview/path_validation.py +138 -0
  409. app/shared/services/project_loader.py +57 -0
  410. app/shared/services/schema_inference.py +260 -0
  411. app/shared/services/schema_runtime_builder.py +120 -0
  412. app/shared/services/validation/__init__.py +52 -0
  413. app/shared/services/validation/chunked_loader.py +509 -0
  414. app/shared/services/validation/dag/__init__.py +35 -0
  415. app/shared/services/validation/dag/builder.py +141 -0
  416. app/shared/services/validation/dag/executor.py +225 -0
  417. app/shared/services/validation/dag/sorter.py +77 -0
  418. app/shared/services/validation/data_loader.py +231 -0
  419. app/shared/services/validation/engine.py +446 -0
  420. app/shared/services/validation/executor.py +1030 -0
  421. app/shared/services/validation/extractors.py +266 -0
  422. app/shared/services/validation/history.py +209 -0
  423. app/shared/services/validation/json_payload.py +136 -0
  424. app/shared/services/validation/loader.py +152 -0
  425. app/shared/services/validation/memory_monitor.py +179 -0
  426. app/shared/services/validation/postprocess.py +208 -0
  427. app/shared/services/validation/progress.py +64 -0
  428. app/shared/services/validation/report_export.py +222 -0
  429. app/shared/services/validation/resolver.py +180 -0
  430. app/shared/services/validation/service.py +418 -0
  431. app/shared/services/validation/types.py +238 -0
  432. app/shared/services/validation/validators/__init__.py +36 -0
  433. app/shared/services/validation/validators/adapter.py +182 -0
  434. app/shared/services/validation/validators/base.py +332 -0
  435. app/shared/services/validation/validators/composite.py +233 -0
  436. app/shared/services/validation/validators/date_logic.py +276 -0
  437. app/start_server.py +133 -0
  438. precis_cli-0.1.3.dist-info/METADATA +180 -0
  439. precis_cli-0.1.3.dist-info/RECORD +442 -0
  440. precis_cli-0.1.3.dist-info/WHEEL +5 -0
  441. precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
  442. precis_cli-0.1.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,622 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """@fileoverview AI 配置生成服务(编排层)
17
+
18
+ 功能概述:
19
+ - 基于 AI 分析数据文件自动生成 Precis 项目配置
20
+ - 支持生成 Schema、Constraint、Regex Node 等多种配置
21
+ - 支持进度回调和取消操作
22
+ - Agent 模式:多轮自迭代优化 + 大数据分块 + 旧脚本迁移
23
+
24
+ 架构设计(2026-09 职责拆分,本模块仅保留编排):
25
+ - errors.py: 生成链路共享异常(GenerationParseError / CancelledError)
26
+ - response_parser.py: LLM 响应 JSON 提取与解析(字符串感知配平)
27
+ - profiler.py: 数据文件画像 + ProfilingOptions(采样与列信息提取)
28
+ - existing_config.py: 既有项目配置加载(keep_existing 合并基线)
29
+ - agent_wiring.py: Agent 工具注册表与系统/任务提示词构建
30
+ - 本模块: ConfigGenerationService 编排单次生成(generate)与
31
+ Agent 多轮生成(generate_with_agent),以及供子类(ConfigMigrationService)
32
+ 与 Agent 工具复用的受保护方法(scope 生成 / LLM 精修 / 配置构建)
33
+
34
+ 输入示例:
35
+ service = ConfigGenerationService(provider_id="openai")
36
+ result = await service.generate_with_agent(
37
+ file_paths=["data/users.xlsx", "data/orders.xlsx"],
38
+ project_name="电商数据校验",
39
+ project_id="ecommerce",
40
+ max_iterations=2,
41
+ )
42
+
43
+ 输出示例:
44
+ {
45
+ "success": True,
46
+ "manifest": {...},
47
+ "schemas": {...},
48
+ "constraints": {...},
49
+ "regex_nodes": {...},
50
+ "yaml_preview": "...",
51
+ "iterations": 2,
52
+ "metrics": {"total_rules": 10, "passed_rules": 9, "failed_rules": 1},
53
+ "warnings": []
54
+ }
55
+ """
56
+
57
+ from __future__ import annotations
58
+
59
+ import asyncio
60
+ import logging
61
+ from collections.abc import Callable
62
+ from dataclasses import dataclass
63
+ from typing import TYPE_CHECKING, Any
64
+
65
+ import yaml
66
+
67
+ from ..config import loader
68
+ from ..providers import ChatMessage, ChatRequest, create
69
+ from .agent_wiring import build_agent_system_prompt, build_agent_task_message, create_agent_registry
70
+ from .config_builder import build_config
71
+ from .errors import CancelledError, GenerationParseError
72
+ from .existing_config import load_existing_config
73
+ from .profiler import ProfilingOptions, filter_profiling, format_profiling_for_agent, profile_files
74
+ from .prompt_builder import build_prompt
75
+ from .response_parser import find_json_object_end, parse_llm_response, try_extract_json_object
76
+
77
+ if TYPE_CHECKING:
78
+ from app.shared.services.ai.agent.types import ToolResult
79
+
80
+ from ..providers.base import BaseProvider
81
+
82
+ logger = logging.getLogger(__name__)
83
+
84
+ # 历史导出名兼容:测试直接从本模块导入 _find_json_object_end(2026-09 拆分前为本模块函数)
85
+ _find_json_object_end = find_json_object_end
86
+
87
+ __all__ = [
88
+ "ConfigGenerationService",
89
+ "GenerationOptions",
90
+ "ProfilingOptions",
91
+ "CancelledError",
92
+ "GenerationParseError",
93
+ ]
94
+
95
+
96
+ @dataclass
97
+ class GenerationOptions:
98
+ """生成选项"""
99
+
100
+ generate_schemas: bool = True
101
+ generate_constraints: bool = True
102
+ generate_regex_nodes: bool = True
103
+ keep_existing: bool = True
104
+
105
+
106
+ class ConfigGenerationService:
107
+ """
108
+ @classdesc AI 配置生成服务(编排层)
109
+
110
+ 基于 AI 分析数据文件自动生成 Precis 项目配置。
111
+ 支持单次快速生成和 Agent 多轮优化生成两种模式;
112
+ 数据画像、响应解析、Agent 装配等具体能力委托给同包子模块。
113
+ """
114
+
115
+ def __init__(self, provider_id: str | None = None):
116
+ """
117
+ @methoddesc 初始化配置生成服务
118
+
119
+ 参数:
120
+ provider_id: 指定 Provider ID,None 则使用默认
121
+ """
122
+ self.provider_id = provider_id
123
+ self._provider: BaseProvider | None = None
124
+ self._cancelled = False
125
+
126
+ # Agent 模式运行时状态
127
+ self._file_paths: list[str] = []
128
+ self._project_name: str = ""
129
+ self._project_id: str = ""
130
+ self._config_path: str | None = None
131
+ self._profiling_options: ProfilingOptions = ProfilingOptions()
132
+ self._generation_options: GenerationOptions = GenerationOptions()
133
+ self._profiling_data: list[dict] = []
134
+ self._existing_config: dict[str, Any] | None = None
135
+ self._last_metrics: dict[str, Any] | None = None
136
+ self._current_plan: list[dict[str, Any]] | None = None
137
+
138
+ def _get_provider(self) -> BaseProvider:
139
+ """
140
+ @methoddesc 获取 Provider 实例
141
+
142
+ 懒加载:首次调用时从用户级 AI 配置中查找并创建 Provider 实例。
143
+ 优先使用显式指定的 provider_id,其次 defaults.generate / defaults.chat。
144
+ 如果默认指向的 Provider 不存在,则回退到第一个已配置 Provider。
145
+ """
146
+ if self._provider is None:
147
+ config = loader.load()
148
+ pid = self.provider_id
149
+
150
+ if not pid:
151
+ pid = config.defaults.get("generate") or config.defaults.get("chat")
152
+
153
+ provider_cfg = next((p for p in config.providers if p.id == pid), None) if pid else None
154
+ if provider_cfg is None and config.providers:
155
+ provider_cfg = config.providers[0]
156
+ pid = provider_cfg.id
157
+
158
+ if not provider_cfg:
159
+ raise ValueError("No provider configured")
160
+
161
+ self._provider = create(provider_cfg)
162
+ return self._provider
163
+
164
+ async def generate(
165
+ self,
166
+ file_paths: list[str],
167
+ project_name: str,
168
+ project_id: str,
169
+ config_path: str | None = None,
170
+ profiling_options: ProfilingOptions | None = None,
171
+ generation_options: GenerationOptions | None = None,
172
+ progress_callback: Callable[[str, float], None] | None = None,
173
+ ) -> dict[str, Any]:
174
+ """
175
+ @methoddesc 单次快速生成配置
176
+
177
+ 完整流程:数据画像 -> 构建 Prompt -> 调用 LLM -> 解析响应 -> 构建最终配置。
178
+ """
179
+ self._setup_run(
180
+ file_paths=file_paths,
181
+ project_name=project_name,
182
+ project_id=project_id,
183
+ config_path=config_path,
184
+ profiling_options=profiling_options,
185
+ generation_options=generation_options,
186
+ )
187
+
188
+ # 阶段 1: 数据画像
189
+ self._check_cancelled(progress_callback, "profiling", 10)
190
+ self._profiling_data = await self._profile_files(file_paths, self._profiling_options)
191
+
192
+ # 阶段 2: 构建提示词
193
+ self._check_cancelled(progress_callback, "building_prompt", 20)
194
+ prompt, prompt_warnings = build_prompt(self._profiling_data, project_name)
195
+
196
+ # 阶段 3: 调用 LLM
197
+ self._check_cancelled(progress_callback, "generating", 40)
198
+ provider = self._get_provider()
199
+ chat_req = ChatRequest(
200
+ messages=[
201
+ ChatMessage(role="system", content="你是一个数据治理专家,擅长分析数据文件并生成数据验证配置。"),
202
+ ChatMessage(role="user", content=prompt),
203
+ ],
204
+ temperature=0.3,
205
+ )
206
+ response = await provider.chat(chat_req)
207
+
208
+ # 阶段 4: 解析响应
209
+ self._check_cancelled(progress_callback, "parsing", 80)
210
+ result = self._parse_response(response.content or "")
211
+
212
+ # 阶段 5: 构建最终配置
213
+ self._check_cancelled(progress_callback, "finalizing", 95)
214
+ config = self._build_config_from_llm_result(result)
215
+ config["warnings"] = config.get("warnings", []) + prompt_warnings
216
+
217
+ self._notify_progress(progress_callback, "completed", 100)
218
+ return config
219
+
220
+ async def generate_with_agent(
221
+ self,
222
+ file_paths: list[str],
223
+ project_name: str,
224
+ project_id: str,
225
+ config_path: str | None = None,
226
+ profiling_options: ProfilingOptions | None = None,
227
+ generation_options: GenerationOptions | None = None,
228
+ max_iterations: int = 2,
229
+ validation_sample_size: int = 1000,
230
+ auto_chunking: bool = True,
231
+ chunk_max_columns: int = 20,
232
+ chunk_max_files: int = 5,
233
+ progress_callback: Callable[[str, float, dict[str, Any] | None], None] | None = None,
234
+ checkpoint_callback: Callable[[dict[str, Any]], None] | None = None,
235
+ initial_checkpoint: dict[str, Any] | None = None,
236
+ ) -> dict[str, Any]:
237
+ """
238
+ @methoddesc Agent 多轮优化生成配置
239
+
240
+ 流程:数据画像 -> Agent 规划/生成 -> 校验 -> 精修 -> 输出最终配置。
241
+ """
242
+ from app.shared.services.ai.agent import AgentExecutor
243
+
244
+ self._setup_run(
245
+ file_paths=file_paths,
246
+ project_name=project_name,
247
+ project_id=project_id,
248
+ config_path=config_path,
249
+ profiling_options=profiling_options,
250
+ generation_options=generation_options,
251
+ )
252
+
253
+ # 阶段 1: 数据画像
254
+ self._check_cancelled(progress_callback, "profiling", 5)
255
+ self._profiling_data = await self._profile_files(file_paths, self._profiling_options)
256
+
257
+ # 阶段 2: 加载现有配置
258
+ self._check_cancelled(progress_callback, "loading_existing", 10)
259
+ if self._generation_options.keep_existing and config_path:
260
+ self._existing_config = self._load_existing_config(config_path)
261
+
262
+ # 阶段 3: Agent 执行
263
+ self._check_cancelled(progress_callback, "agent_planning", 15)
264
+
265
+ # 注册工具(Agent 装配细节见 agent_wiring.py)
266
+ registry = create_agent_registry(
267
+ service=self,
268
+ validation_sample_size=validation_sample_size,
269
+ chunk_max_columns=chunk_max_columns,
270
+ chunk_max_files=chunk_max_files,
271
+ )
272
+
273
+ provider = self._get_provider()
274
+ # get_context_window 内部可能调用 Ollama 的同步 urllib 探测,放到线程池避免阻塞事件循环
275
+ context_window = await asyncio.to_thread(provider.get_context_window)
276
+ max_tokens = max(context_window - 8000, 4096)
277
+ executor = AgentExecutor(
278
+ provider=provider,
279
+ registry=registry,
280
+ system_prompt=build_agent_system_prompt(),
281
+ max_iterations=max_iterations,
282
+ max_tokens=max_tokens,
283
+ progress_callback=lambda stage, progress, extra: self._agent_progress_callback(
284
+ progress_callback, stage, progress, extra
285
+ ),
286
+ checkpoint_callback=checkpoint_callback,
287
+ cancelled_callback=lambda: self._cancelled,
288
+ on_tool_result=lambda tr: self._on_agent_tool_result(tr),
289
+ )
290
+
291
+ task_message = build_agent_task_message(
292
+ profiling_data=self._profiling_data,
293
+ project_name=self._project_name,
294
+ project_id=self._project_id,
295
+ keep_existing=self._generation_options.keep_existing,
296
+ auto_chunking=auto_chunking,
297
+ chunk_max_columns=chunk_max_columns,
298
+ chunk_max_files=chunk_max_files,
299
+ )
300
+
301
+ agent_result = await executor.run(task_message, initial_checkpoint=initial_checkpoint)
302
+
303
+ if not agent_result.success:
304
+ return {
305
+ "success": False,
306
+ "error": agent_result.error,
307
+ "yaml_preview": "",
308
+ "manifest": None,
309
+ "schemas": {},
310
+ "constraints": {},
311
+ "regex_nodes": {},
312
+ "warnings": [],
313
+ "iterations": agent_result.iterations,
314
+ "metrics": agent_result.metrics.to_dict() if agent_result.metrics else None,
315
+ }
316
+
317
+ # 阶段 4: 构建最终返回
318
+ config = agent_result.config or {}
319
+ if not config:
320
+ # 尝试从最后一轮 content 中解析
321
+ config = self._try_parse_config_from_content(agent_result.content) or {}
322
+
323
+ if not config.get("schemas") and not config.get("constraints"):
324
+ return {
325
+ "success": False,
326
+ "error": "Agent 未生成有效配置(缺少 schemas/constraints)",
327
+ "yaml_preview": "",
328
+ "manifest": None,
329
+ "schemas": {},
330
+ "constraints": {},
331
+ "regex_nodes": {},
332
+ "warnings": [],
333
+ "iterations": agent_result.iterations,
334
+ "metrics": agent_result.metrics.to_dict() if agent_result.metrics else None,
335
+ }
336
+
337
+ # 补充 YAML 预览和统一字段
338
+ if "yaml_preview" not in config:
339
+ preview = {
340
+ "manifest": config.get("manifest", {}),
341
+ "schemas": config.get("schemas", {}),
342
+ "constraints": config.get("constraints", {}),
343
+ "regex_nodes": config.get("regex_nodes", {}),
344
+ }
345
+ config["yaml_preview"] = yaml.safe_dump(preview, sort_keys=False, allow_unicode=True)
346
+
347
+ config["success"] = True
348
+ config["iterations"] = agent_result.iterations
349
+ config["metrics"] = agent_result.metrics.to_dict() if agent_result.metrics else self._last_metrics
350
+ config["warnings"] = config.get("warnings", [])
351
+
352
+ self._notify_progress(progress_callback, "completed", 100)
353
+ return config
354
+
355
+ def _setup_run(
356
+ self,
357
+ file_paths: list[str],
358
+ project_name: str,
359
+ project_id: str,
360
+ config_path: str | None,
361
+ profiling_options: ProfilingOptions | None,
362
+ generation_options: GenerationOptions | None,
363
+ ) -> None:
364
+ """设置运行时状态。"""
365
+ self._file_paths = file_paths
366
+ self._project_name = project_name
367
+ self._project_id = project_id
368
+ self._config_path = config_path
369
+ self._profiling_options = profiling_options or ProfilingOptions()
370
+ self._generation_options = generation_options or GenerationOptions()
371
+ self._profiling_data = []
372
+ self._existing_config = None
373
+ self._cancelled = False
374
+ self._current_plan = None
375
+
376
+ def _check_cancelled(
377
+ self,
378
+ progress_callback: Callable[..., None] | None,
379
+ stage: str,
380
+ progress: float,
381
+ ) -> None:
382
+ """检查取消状态并上报进度。"""
383
+ if self._cancelled:
384
+ raise CancelledError("Generation cancelled")
385
+ self._notify_progress(progress_callback, stage, progress)
386
+
387
+ def _notify_progress(
388
+ self,
389
+ progress_callback: Callable[..., None] | None,
390
+ stage: str,
391
+ progress: float,
392
+ ) -> None:
393
+ """触发进度回调。"""
394
+ if progress_callback:
395
+ progress_callback(stage, progress)
396
+
397
+ def _agent_progress_callback(
398
+ self,
399
+ progress_callback: Callable[[str, float, dict[str, Any] | None], None] | None,
400
+ stage: str,
401
+ progress: float,
402
+ extra: dict[str, Any] | None,
403
+ ) -> None:
404
+ """Agent 进度映射到 0.15-0.95 区间,并透传 current_plan。"""
405
+ mapped = 15 + progress * 80
406
+ merged_extra = dict(extra or {})
407
+ if self._current_plan is not None:
408
+ merged_extra["current_plan"] = self._current_plan
409
+ if progress_callback:
410
+ progress_callback(stage, mapped / 100.0, merged_extra)
411
+
412
+ def _on_agent_tool_result(self, tr: ToolResult) -> None:
413
+ """从工具结果中提取 metrics 和 current_plan。"""
414
+ from app.shared.services.ai.agent.types import AgentMetrics, ToolResult
415
+
416
+ if not isinstance(tr, ToolResult):
417
+ return
418
+ if tr.name == "validate_config" and isinstance(tr.observation, dict):
419
+ obs = tr.observation
420
+ metrics = AgentMetrics(
421
+ total_rules=obs.get("total_rules", 0),
422
+ passed_rules=obs.get("passed", 0),
423
+ failed_rules=obs.get("failed", 0),
424
+ issues=obs.get("issues", []),
425
+ )
426
+ # 暂时保存到 service 实例,后续可以传入 result
427
+ self._last_metrics = metrics.to_dict()
428
+ elif tr.name == "plan_chunks" and isinstance(tr.observation, dict):
429
+ plan = tr.observation.get("plan")
430
+ if isinstance(plan, dict):
431
+ self._current_plan = [plan]
432
+ elif isinstance(plan, list):
433
+ self._current_plan = plan
434
+
435
+ async def _generate_config_for_scope(
436
+ self,
437
+ file_paths: list[str],
438
+ instructions: str,
439
+ previous_config: dict[str, Any] | None,
440
+ scope: dict[str, Any],
441
+ ) -> dict[str, Any]:
442
+ """
443
+ @methoddesc 为指定 scope 生成配置(Agent 工具内部使用)
444
+
445
+ 参数:
446
+ file_paths: 数据文件路径
447
+ instructions: 生成指令
448
+ previous_config: 上一轮配置
449
+ scope: 生成范围
450
+
451
+ 返回:
452
+ 完整配置字典
453
+ """
454
+ # 如果 scope 指定了 table_names/columns,做局部画像
455
+ profiling_data = self._profiling_data
456
+ table_names = scope.get("table_names")
457
+ columns_filter = scope.get("columns")
458
+ if table_names or columns_filter:
459
+ profiling_data = filter_profiling(profiling_data, table_names, columns_filter)
460
+
461
+ # 如果没画像过,临时画像
462
+ if not profiling_data:
463
+ profiling_data = await self._profile_files(file_paths, self._profiling_options)
464
+
465
+ extra_context: list[str] = []
466
+ if instructions:
467
+ extra_context.append(f"生成指令: {instructions}")
468
+ if previous_config:
469
+ extra_context.append("上一轮配置摘要:\n" + self._summarize_config(previous_config))
470
+
471
+ prompt, prompt_warnings = build_prompt(
472
+ profiling_data,
473
+ self._project_name,
474
+ extra_context=extra_context,
475
+ )
476
+
477
+ provider = self._get_provider()
478
+ chat_req = ChatRequest(
479
+ messages=[
480
+ ChatMessage(role="system", content="你是一个数据治理专家,擅长分析数据文件并生成数据验证配置。"),
481
+ ChatMessage(role="user", content=prompt),
482
+ ],
483
+ temperature=0.3,
484
+ )
485
+ response = await provider.chat(chat_req)
486
+ result = self._parse_response(response.content or "")
487
+ config = self._build_config_from_llm_result(result)
488
+ config["warnings"] = config.get("warnings", []) + prompt_warnings
489
+ return config
490
+
491
+ async def _refine_config_with_llm(
492
+ self,
493
+ config: dict[str, Any],
494
+ issues: list[dict[str, Any]],
495
+ strategy: str,
496
+ ) -> dict[str, Any]:
497
+ """
498
+ @methoddesc 使用 LLM 精修配置
499
+
500
+ 参数:
501
+ config: 当前配置
502
+ issues: 校验问题
503
+ strategy: 修正策略
504
+
505
+ 返回:
506
+ {"config": {...}, "removed_rules": [...], "modified_rules": [...], "warnings": [...]}
507
+ """
508
+ issues_text = yaml.safe_dump(issues, sort_keys=False, allow_unicode=True)
509
+ config_text = yaml.safe_dump(
510
+ {
511
+ "schemas": config.get("schemas", {}),
512
+ "constraints": config.get("constraints", {}),
513
+ "regex_nodes": config.get("regex_nodes", {}),
514
+ },
515
+ sort_keys=False,
516
+ allow_unicode=True,
517
+ )
518
+
519
+ prompt = f"""根据以下校验问题修正数据验证配置。
520
+
521
+ ## 修正策略
522
+ {strategy}
523
+
524
+ ## 当前配置
525
+ {config_text}
526
+
527
+ ## 校验问题
528
+ {issues_text}
529
+
530
+ ## 要求
531
+ - 移除明显错误的规则
532
+ - 放宽过于严格的规则参数
533
+ - 保留合理的规则
534
+ - 直接返回修正后的完整 JSON 配置,包含 schemas、constraints、regex_nodes
535
+ """
536
+
537
+ provider = self._get_provider()
538
+ chat_req = ChatRequest(
539
+ messages=[
540
+ ChatMessage(role="system", content="你是一个数据治理专家,擅长修正数据验证配置。"),
541
+ ChatMessage(role="user", content=prompt),
542
+ ],
543
+ temperature=0.2,
544
+ )
545
+ response = await provider.chat(chat_req)
546
+ result = self._parse_response(response.content or "")
547
+ refined_config = self._build_config_from_llm_result(result)
548
+
549
+ # 对比找出 removed/modified
550
+ original_ids = set(config.get("constraints", {}).keys()) | set(config.get("regex_nodes", {}).keys())
551
+ refined_ids = set(refined_config.get("constraints", {}).keys()) | set(
552
+ refined_config.get("regex_nodes", {}).keys()
553
+ )
554
+ removed = list(original_ids - refined_ids)
555
+ modified = list(original_ids & refined_ids)
556
+
557
+ return {
558
+ "config": refined_config,
559
+ "removed_rules": removed,
560
+ "modified_rules": modified,
561
+ "warnings": refined_config.get("warnings", []),
562
+ }
563
+
564
+ def _summarize_config(self, config: dict[str, Any]) -> str:
565
+ """生成配置摘要(用于 prompt)。"""
566
+ schemas = config.get("schemas", {})
567
+ constraints = config.get("constraints", {})
568
+ regex_nodes = config.get("regex_nodes", {})
569
+ lines = [
570
+ f"schemas: {len(schemas)}",
571
+ f"constraints: {len(constraints)}",
572
+ f"regex_nodes: {len(regex_nodes)}",
573
+ ]
574
+ for cid, cdef in list(constraints.items())[:10]:
575
+ lines.append(f"- {cid}: {cdef.get('type')} -> {cdef.get('refs', {})}")
576
+ return "\n".join(lines)
577
+
578
+ def _build_config_from_llm_result(self, result: dict[str, Any]) -> dict[str, Any]:
579
+ """使用 build_config 从 LLM 结果构建配置。"""
580
+ return build_config(
581
+ project_id=self._project_id,
582
+ project_name=self._project_name,
583
+ config_path=self._config_path,
584
+ profiling_data=self._profiling_data,
585
+ llm_result=result,
586
+ options=self._generation_options,
587
+ existing_config=self._existing_config,
588
+ )
589
+
590
+ # ---- 以下为委托方法:实现已拆分至同包子模块,保留方法签名以维持
591
+ # ---- 子类(ConfigMigrationService)、Agent 工具与既有测试的调用面不变。
592
+
593
+ def _parse_response(self, content: str) -> dict:
594
+ """解析 LLM 响应文本(委托 response_parser.parse_llm_response)。"""
595
+ return parse_llm_response(content)
596
+
597
+ def _try_parse_config_from_content(self, content: str | None) -> dict[str, Any] | None:
598
+ """从文本解析配置(JSON 提取委托 response_parser,构建走 build_config)。"""
599
+ parsed = try_extract_json_object(content)
600
+ if parsed is None:
601
+ return None
602
+ return self._build_config_from_llm_result(parsed)
603
+
604
+ async def _profile_files(self, file_paths: list[str], options: ProfilingOptions) -> list[dict]:
605
+ """分析数据文件获取画像(委托 profiler.profile_files,注入取消探测)。"""
606
+ return await profile_files(file_paths, options, lambda: self._cancelled)
607
+
608
+ def _format_profiling_for_agent(self) -> str:
609
+ """画像数据格式化(委托 profiler.format_profiling_for_agent)。"""
610
+ return format_profiling_for_agent(self._profiling_data)
611
+
612
+ def _load_existing_config(self, config_path: str) -> dict[str, Any] | None:
613
+ """加载既有配置(委托 existing_config.load_existing_config)。"""
614
+ return load_existing_config(config_path)
615
+
616
+ def cancel(self) -> None:
617
+ """
618
+ @methoddesc 标记生成任务为已取消状态
619
+
620
+ 异步任务会在合适的检查点检测到取消标记并抛出 CancelledError。
621
+ """
622
+ self._cancelled = True