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,296 @@
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
+ """
17
+ @fileoverview LLM 响应解析器模块
18
+
19
+ 功能概述:
20
+ - 解析 LLM 返回的 JSON 响应文本(健壮版本)
21
+ - 支持多种格式:标准 JSON、Markdown 代码块、单引号伪 JSON、截断 JSON
22
+ - 验证响应格式和动作有效性
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import logging
29
+ import re
30
+ from typing import Any
31
+
32
+ from app.shared.services.llm.actions.registry import (
33
+ ALL_ACTION_TYPES,
34
+ ALL_CONSTRAINT_TYPES,
35
+ SPEC_FIELD_FOR,
36
+ TRANSFORM_SUB_TYPES,
37
+ )
38
+ from app.shared.services.llm.suggestion_utils import normalize_constraint_type
39
+ from app.shared.services.llm.yaml_io import ActionParseError
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+
44
+ class ActionParser:
45
+ """LLM 响应动作解析器。"""
46
+
47
+ # 以下白名单均从动作注册表(单一事实源)派生,禁止本地硬编码。
48
+ VALID_CONSTRAINT_TYPES = sorted(ALL_CONSTRAINT_TYPES)
49
+ VALID_TRANSFORM_TYPES = sorted(TRANSFORM_SUB_TYPES)
50
+ VALID_ACTION_TYPES = list(ALL_ACTION_TYPES)
51
+
52
+ @staticmethod
53
+ def parse_llm_response(response_text: str) -> dict[str, Any]:
54
+ """
55
+ @methoddesc 解析 LLM 响应文本为结构化字典
56
+
57
+ 业务用途:
58
+ - 接收 LLM 返回的原始字符串,依次尝试 5 种解析策略:
59
+ 直接解析 → 清理 Markdown → 提取 JSON 块 → 修复单引号 → 截断恢复
60
+ - 只要任一策略成功即返回结果;全部失败抛出 ActionParseError
61
+
62
+ 参数:
63
+ response_text: LLM 原始响应文本
64
+
65
+ 返回:
66
+ 解析后的字典
67
+
68
+ 异常:
69
+ ActionParseError: 响应文本为空或所有策略都失败
70
+ """
71
+ if not response_text or not response_text.strip():
72
+ raise ActionParseError("响应文本为空")
73
+
74
+ original_text = response_text.strip()
75
+ parse_attempts = [
76
+ ("直接解析", lambda t: t),
77
+ ("清理 Markdown", ActionParser._clean_json_text),
78
+ ("提取 JSON 块", ActionParser._extract_json_block),
79
+ ("修复单引号", ActionParser._fix_single_quotes),
80
+ ("截断恢复", lambda t: ActionParser._try_recover_truncated(t)[0]),
81
+ ]
82
+
83
+ last_error = None
84
+ for attempt_name, cleaner in parse_attempts:
85
+ try:
86
+ cleaned = cleaner(original_text)
87
+ if not cleaned:
88
+ continue
89
+ parsed = json.loads(cleaned)
90
+ if isinstance(parsed, dict):
91
+ if attempt_name != "直接解析":
92
+ logger.debug(f"JSON 解析成功使用策略: {attempt_name}")
93
+ return parsed
94
+ except (json.JSONDecodeError, ValueError) as e:
95
+ last_error = e
96
+ continue
97
+ except Exception as e:
98
+ logger.debug(f"解析策略 '{attempt_name}' 失败: {e}")
99
+ continue
100
+
101
+ logger.error(f"原始文本前 500 字符: {original_text[:500]}")
102
+ raise ActionParseError(f"无法解析 AI 响应: {last_error}") from last_error
103
+
104
+ @staticmethod
105
+ def _clean_json_text(text: str) -> str:
106
+ """
107
+ @methoddesc 清理 Markdown 包裹
108
+
109
+ 业务用途:
110
+ - 去除 ```json ... ``` 或 ``` ... ``` 的 Markdown 代码块包裹
111
+ - 不处理 Markdown 内部的内容
112
+
113
+ 参数:
114
+ text: 原始文本
115
+
116
+ 返回:
117
+ 去除 Markdown 包裹后的文本
118
+ """
119
+ text = text.strip()
120
+ if text.startswith("```"):
121
+ lines = text.split("\n")
122
+ if len(lines) >= 2:
123
+ text = "\n".join(lines[1:])
124
+ if text.endswith("```"):
125
+ text = text[:-3]
126
+ text = text.strip()
127
+ return text
128
+
129
+ @staticmethod
130
+ def _extract_json_block(text: str) -> str:
131
+ """
132
+ @methoddesc 从文本中提取 JSON 块
133
+
134
+ 业务用途:
135
+ - 优先匹配 ```json ... ``` 或 ``` ... ``` 包裹的代码块
136
+ - 若无代码块,则提取最外层的 { } 或 [ ] 子串
137
+ - 用于处理 LLM 在 JSON 外附带解释文字的场景
138
+
139
+ 参数:
140
+ text: 原始文本
141
+
142
+ 返回:
143
+ 提取出的 JSON 文本
144
+ """
145
+ text = text.strip()
146
+ code_block_pattern = r"```(?:json)?\s*\n?(.*?)\n?```"
147
+ matches = re.findall(code_block_pattern, text, re.DOTALL)
148
+ if matches:
149
+ return str(max(matches, key=len)).strip()
150
+ first_brace = text.find("{")
151
+ last_brace = text.rfind("}")
152
+ if first_brace != -1 and last_brace != -1 and first_brace < last_brace:
153
+ return text[first_brace : last_brace + 1]
154
+ first_bracket = text.find("[")
155
+ last_bracket = text.rfind("]")
156
+ if first_bracket != -1 and last_bracket != -1 and first_bracket < last_bracket:
157
+ return text[first_bracket : last_bracket + 1]
158
+ return text
159
+
160
+ @staticmethod
161
+ def _fix_single_quotes(text: str) -> str:
162
+ """
163
+ @methoddesc 将 JSON 中的单引号替换为双引号
164
+
165
+ 业务用途:
166
+ - 部分 LLM 会输出 Python 风格单引号 JSON(标准 JSON 要求双引号)
167
+ - 仅在内容中不包含双引号时才替换,避免破坏正常字段
168
+
169
+ 参数:
170
+ text: 原始文本
171
+
172
+ 返回:
173
+ 修复单引号后的文本
174
+ """
175
+ text = ActionParser._extract_json_block(text)
176
+
177
+ def replace_quotes(match: re.Match[str]) -> str:
178
+ content = match.group(1)
179
+ if '"' not in content:
180
+ return f'"{content}"'
181
+ return match.group(0)
182
+
183
+ return re.sub(r"'([^']*)'", replace_quotes, text)
184
+
185
+ @staticmethod
186
+ def _try_recover_truncated(text: str) -> tuple[str, bool]:
187
+ """
188
+ @methoddesc 尝试恢复被 LLM 截断的 JSON 响应
189
+
190
+ 业务用途:
191
+ - 当 LLM 输出超过 token 限制被截断时,自动补全缺失的 }, ], "
192
+ - 启发式策略:补齐未闭合的大括号/方括号;若引号成奇数,则截断到最近的 , : { [ 之后
193
+
194
+ 参数:
195
+ text: 截断的文本
196
+
197
+ 返回:
198
+ (恢复后的文本, 是否进行了恢复) 二元组
199
+ """
200
+ text = text.strip()
201
+ open_braces = text.count("{") - text.count("}")
202
+ open_brackets = text.count("[") - text.count("]")
203
+ result = text
204
+ recovered = False
205
+ if open_braces > 0:
206
+ result += "}" * open_braces
207
+ recovered = True
208
+ if open_brackets > 0:
209
+ result += "]" * open_brackets
210
+ recovered = True
211
+ if result.count('"') % 2 == 1:
212
+ last_quote = result.rfind('"')
213
+ if last_quote > 0:
214
+ for char in [",", ":", "{", "[", " "]:
215
+ pos = result.rfind(char, 0, last_quote)
216
+ if pos > 0:
217
+ result = result[: pos + 1]
218
+ recovered = True
219
+ break
220
+ if recovered:
221
+ logger.warning("[ResponseParser] 响应被截断,已自动恢复。恢复后的内容可能与 LLM 原始输出不同。")
222
+ return result, recovered
223
+
224
+ @staticmethod
225
+ def validate_response(response: dict[str, Any]) -> bool:
226
+ """
227
+ @methoddesc 校验解析后的响应字典结构
228
+
229
+ 业务用途:
230
+ - 确保 response 包含必要的 "reply" 和 "actions" 字段
231
+ - 若 actions 缺失或为 None,则规范化为空列表
232
+ - 调用方可基于校验结果决定是否进入后续动作执行
233
+
234
+ 参数:
235
+ response: 解析后的字典(会被就地修改)
236
+
237
+ 返回:
238
+ True 表示通过校验
239
+ """
240
+ if "reply" not in response:
241
+ return False
242
+ if "actions" not in response:
243
+ return False
244
+ actions = response.get("actions", [])
245
+ if actions is None:
246
+ response["actions"] = []
247
+ actions = []
248
+ elif not isinstance(actions, list):
249
+ return False
250
+ for action in actions:
251
+ if not ActionParser._validate_action(action):
252
+ return False
253
+ return True
254
+
255
+ @staticmethod
256
+ def _validate_action(action: dict[str, Any]) -> bool:
257
+ if not isinstance(action, dict):
258
+ return False
259
+ if "actionType" not in action:
260
+ return False
261
+ action_type = action.get("actionType")
262
+ if action_type not in ActionParser.VALID_ACTION_TYPES:
263
+ return False
264
+ # VALIDATE_PROJECT / UPDATE_SETTINGS 的 spec 可选(校验可不带 tableName,设置可空壳)
265
+ # 其余动作必须携带对应 spec 字段
266
+ if action_type in ("VALIDATE_PROJECT", "UPDATE_SETTINGS"):
267
+ return True
268
+ # 从注册表查 spec 字段名(None 表示无 spec 要求)
269
+ spec_field = SPEC_FIELD_FOR.get(action_type)
270
+ if spec_field is None:
271
+ return True
272
+ if spec_field not in action:
273
+ return False
274
+ # 约束操作额外校验 type 字段在白名单内
275
+ if spec_field == "constraintSpec":
276
+ spec = action.get("constraintSpec", {})
277
+ # §2.9: 校验前过 normalize_constraint_type 归一(与写盘路径同一函数,
278
+ # 52662373 引入)——legacy 路径对大小写/别名零容忍会把小写 not_null 的
279
+ # 整条响应判非法。归一后写回正名,后续 handler 零改动拿到 PascalCase。
280
+ normalized = normalize_constraint_type(str(spec.get("type") or ""))
281
+ spec["type"] = normalized
282
+ if normalized not in ActionParser.VALID_CONSTRAINT_TYPES:
283
+ return False
284
+ return True
285
+
286
+ @staticmethod
287
+ def parse_actions(llm_response: dict[str, Any]) -> list[dict[str, Any]]:
288
+ actions = llm_response.get("actions", [])
289
+ if not isinstance(actions, list):
290
+ return []
291
+ return [a for a in actions if ActionParser._validate_action(a)]
292
+
293
+ @staticmethod
294
+ def extract_reply(llm_response: dict[str, Any]) -> str:
295
+ reply = llm_response.get("reply", "")
296
+ return str(reply) if reply is not None else ""
@@ -0,0 +1,45 @@
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
+ """
17
+ @fileoverview AI 配置模块聚合导出
18
+
19
+ 功能概述:
20
+ - 聚合导出 AI 配置相关的模型、加载器
21
+ - 为上层 LLM 服务提供统一的配置入口
22
+
23
+ 架构设计:
24
+ - 通过 __init__.py 聚合子模块公共接口,降低外部导入复杂度
25
+ - 遵循显式导出原则,使用 __all__ 控制公开 API
26
+
27
+ 输入示例:
28
+ from app.shared.services.llm.config import AIConfig, ConfigLoader
29
+
30
+ 输出示例:
31
+ AIConfig 实例与 ConfigLoader 实例可直接用于 provider 配置加载
32
+ """
33
+
34
+ from .loader import ConfigLoader, loader
35
+ from .models import AIConfig, AIProvider, DeploymentType, NetworkConfig, ProviderType
36
+
37
+ __all__ = [
38
+ "AIConfig",
39
+ "AIProvider",
40
+ "ProviderType",
41
+ "DeploymentType",
42
+ "NetworkConfig",
43
+ "ConfigLoader",
44
+ "loader",
45
+ ]
@@ -0,0 +1,135 @@
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
+ """
17
+ @fileoverview API Key 加密存储模块
18
+
19
+ 功能概述:
20
+ - 使用 Fernet 对称加密保护 api_key 字段
21
+ - 密钥文件存储在 ~/.precis/.precis_key
22
+ - 加密后的值以 "enc:" 前缀标记,与明文和环境变量引用区分
23
+ - 首次使用自动生成密钥文件
24
+ - 向后兼容:明文 api_key 在 load 时正常读取,下次 save 时自动加密
25
+
26
+ 架构设计:
27
+ - 加密/解密对 ConfigLoader 透明:save 时加密,load 时解密
28
+ - 密钥与用户绑定,不同操作系统的用户各有独立密钥
29
+ - _ENC_PREFIX = "enc:" 前缀用于区分加密值与明文值
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import logging
35
+ import os
36
+ from pathlib import Path
37
+
38
+ from cryptography.fernet import Fernet
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+ _ENC_PREFIX = "enc:"
43
+
44
+ _KEY_FILE_NAME = ".precis_key"
45
+
46
+
47
+ def _key_file_path() -> Path:
48
+ """密钥文件路径: ~/.precis/.precis_key"""
49
+ return Path.home() / ".precis" / _KEY_FILE_NAME
50
+
51
+
52
+ def _get_or_create_key() -> bytes:
53
+ """
54
+ @methoddesc 获取或创建加密密钥
55
+
56
+ 首次使用时自动生成 Fernet 密钥并保存到 ~/.precis/.precis_key。
57
+ 密钥文件权限设为 0600(仅用户可读写),Windows 上无此限制。
58
+
59
+ 返回:
60
+ Fernet 密钥字节(32 字节,base64 编码为 44 字节)
61
+ """
62
+ key_path = _key_file_path()
63
+
64
+ if key_path.exists():
65
+ try:
66
+ key_data = key_path.read_bytes().strip()
67
+ if key_data:
68
+ return key_data
69
+ except Exception:
70
+ logger.warning("读取密钥文件失败,将重新生成")
71
+
72
+ key_path.parent.mkdir(parents=True, exist_ok=True)
73
+ new_key = Fernet.generate_key()
74
+ key_path.write_bytes(new_key)
75
+
76
+ try:
77
+ os.chmod(str(key_path), 0o600)
78
+ except OSError:
79
+ pass
80
+
81
+ logger.info("已生成新的 API Key 加密密钥: %s", key_path)
82
+ return new_key
83
+
84
+
85
+ def _get_fernet() -> Fernet:
86
+ """获取 Fernet 实例(懒加载密钥)"""
87
+ key = _get_or_create_key()
88
+ return Fernet(key)
89
+
90
+
91
+ def encrypt_api_key(plain: str) -> str:
92
+ """
93
+ @methoddesc 加密 API Key
94
+
95
+ 参数:
96
+ plain: 明文 API Key
97
+
98
+ 返回:
99
+ "enc:" 前缀 + base64 编码的加密值
100
+ """
101
+ if not plain:
102
+ return plain
103
+ f = _get_fernet()
104
+ encrypted = f.encrypt(plain.encode("utf-8"))
105
+ return _ENC_PREFIX + encrypted.decode("ascii")
106
+
107
+
108
+ def decrypt_api_key(encrypted: str) -> str:
109
+ """
110
+ @methoddesc 解密 API Key
111
+
112
+ 参数:
113
+ encrypted: "enc:" 前缀的加密值,或明文值(向后兼容)
114
+
115
+ 返回:
116
+ 明文 API Key
117
+ """
118
+ if not encrypted:
119
+ return encrypted
120
+
121
+ if not encrypted.startswith(_ENC_PREFIX):
122
+ return encrypted
123
+
124
+ cipher_text = encrypted[len(_ENC_PREFIX) :]
125
+ try:
126
+ f = _get_fernet()
127
+ return f.decrypt(cipher_text.encode("ascii")).decode("utf-8")
128
+ except Exception:
129
+ logger.warning("API Key 解密失败,可能密钥文件已更换")
130
+ return ""
131
+
132
+
133
+ def is_encrypted(value: str | None) -> bool:
134
+ """判断值是否为加密格式"""
135
+ return value is not None and value.startswith(_ENC_PREFIX)