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,491 @@
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 Provider 管理 API 路由模块
18
+
19
+ 功能概述:
20
+ - 查询、发现和管理 AI Provider 配置
21
+ - 扫描本地 AI 服务(Ollama、OpenAI 兼容服务)
22
+ - 测试 Provider 连接并获取可用模型列表
23
+ - 设置默认活动 Provider
24
+ - CRUD:创建、更新、删除 Provider
25
+ - 预设:获取内置服务商预设列表
26
+
27
+ 架构设计:
28
+ - 与 CLI 共用同一套 ~/.precis/ai_providers.yaml 配置文件
29
+ - 使用 Provider 注册表创建对应类型的 Provider 实例
30
+ - 发现的服务可直接添加到配置文件并持久化
31
+ - 预设模板定义在 config/presets.py,方便扩展
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import logging
37
+ from typing import Any
38
+
39
+ from fastapi import HTTPException
40
+
41
+ from ....shared.services.llm.config import loader
42
+ from ....shared.services.llm.config.models import AIProvider, DeploymentType, ProviderType
43
+ from ....shared.services.llm.config.presets import get_preset_list
44
+ from ....shared.services.llm.discovery import scanner
45
+ from ....shared.services.llm.providers import create
46
+ from .models import (
47
+ CreateProviderRequest,
48
+ DiscoverResponse,
49
+ ProviderPresetResponse,
50
+ ProviderResponse,
51
+ TestProviderResponse,
52
+ UpdateProviderRequest,
53
+ )
54
+ from .router import router
55
+
56
+
57
+ def _is_configured(p: AIProvider) -> bool:
58
+ """判断 Provider 是否已配置。
59
+
60
+ loader.load() 已完成 env-var 覆盖,直接检查 api_key 即可。
61
+ 本地 Provider(Ollama)不需要 API Key。
62
+ """
63
+ if p.deployment == DeploymentType.LOCAL:
64
+ return bool(p.base_url)
65
+ return bool(p.api_key and p.api_key.strip())
66
+
67
+
68
+ def _validate_base_url(base_url: str | None) -> None:
69
+ """校验 Provider base_url,防止 SSRF(修复 #11)。
70
+
71
+ 允许:http/https scheme;localhost/127.0.0.1(本地 Ollama 合法)。
72
+ 拒绝:非 http(s) scheme;链路本地地址 169.254.x(云元数据端点)。
73
+ """
74
+ if not base_url:
75
+ return # 本地 Provider 可无 base_url
76
+ lowered = base_url.strip().lower()
77
+ # 必须是 http/https(拒绝 file://、gopher://、dict:// 等)
78
+ if not (lowered.startswith("http://") or lowered.startswith("https://")):
79
+ raise HTTPException(400, detail=f"base_url 必须是 http:// 或 https:// 开头: {base_url}")
80
+ # 拒绝链路本地地址(AWS/GCP/Azure 元数据端点 169.254.169.254)
81
+ if "169.254." in lowered:
82
+ raise HTTPException(400, detail="base_url 不允许指向链路本地地址 169.254.x(元数据端点,疑似 SSRF)")
83
+
84
+
85
+ def _get_type_str(p: AIProvider) -> str:
86
+ """安全获取 Provider 类型字符串"""
87
+ return p.type.value if hasattr(p.type, "value") else str(p.type)
88
+
89
+
90
+ def _get_deployment_str(p: AIProvider) -> str:
91
+ """安全获取部署类型字符串"""
92
+ if p.deployment is None:
93
+ return "unknown"
94
+ return p.deployment.value if hasattr(p.deployment, "value") else str(p.deployment)
95
+
96
+
97
+ def _provider_to_response(p: AIProvider, health: dict) -> ProviderResponse:
98
+ """将 AIProvider 转换为 API 响应"""
99
+ return ProviderResponse(
100
+ id=p.id,
101
+ name=p.name,
102
+ provider=_get_type_str(p),
103
+ deployment=_get_deployment_str(p),
104
+ base_url=p.base_url,
105
+ model=p.model,
106
+ context_window=p.context_window,
107
+ health=health,
108
+ is_configured=_is_configured(p),
109
+ )
110
+
111
+
112
+ @router.get(
113
+ "/providers/config-info",
114
+ summary="获取 AI Provider 配置文件信息",
115
+ responses={
116
+ 500: {"description": "服务器内部错误"},
117
+ },
118
+ )
119
+ async def get_config_info() -> dict[str, Any]:
120
+ """
121
+ 获取 AI Provider 配置文件信息
122
+
123
+ 返回配置文件路径、YAML 模板和文件是否存在。
124
+ 前端设置面板通过此接口获取配置引导信息。
125
+ """
126
+ config_path = loader.config_path
127
+ return {
128
+ "path": str(config_path),
129
+ "default_path": str(config_path),
130
+ "template": _DEFAULT_CONFIG_TEMPLATE,
131
+ "exists": config_path.exists(),
132
+ }
133
+
134
+
135
+ _DEFAULT_CONFIG_TEMPLATE = """version: "2.0"
136
+
137
+ providers:
138
+ # DeepSeek
139
+ - id: deepseek
140
+ name: DeepSeek
141
+ type: openai
142
+ base_url: https://api.deepseek.com
143
+ api_key: ${DEEPSEEK_API_KEY}
144
+ model: deepseek-v4-pro
145
+
146
+ # 本地 Ollama(无需 API Key)
147
+ - id: ollama-local
148
+ name: Ollama Local
149
+ type: ollama
150
+ base_url: http://localhost:11434
151
+ api_key: null
152
+ model: llama3.2
153
+
154
+ defaults:
155
+ chat: deepseek"""
156
+
157
+
158
+ @router.get(
159
+ "/providers",
160
+ response_model=list[ProviderResponse],
161
+ summary="获取所有已配置的 Provider",
162
+ responses={
163
+ 500: {"description": "服务器内部错误"},
164
+ },
165
+ )
166
+ async def list_providers() -> list[ProviderResponse]:
167
+ """
168
+ 获取所有已配置的 Provider
169
+
170
+ 只返回配置文件(ai_providers.yaml)中已定义的 Provider,不再合并系统预设。
171
+ 不执行健康检查,直接返回列表。健康检查由 test 端点按需触发。
172
+ """
173
+ config = loader.load()
174
+ return [_provider_to_response(p, {}) for p in config.providers]
175
+
176
+
177
+ @router.get(
178
+ "/providers/active",
179
+ response_model=ProviderResponse | None,
180
+ summary="获取当前活动的 Provider",
181
+ responses={
182
+ 500: {"description": "服务器内部错误"},
183
+ },
184
+ )
185
+ async def get_active_provider() -> ProviderResponse | None:
186
+ """
187
+ 获取当前活动的 Provider
188
+
189
+ 读取 config.defaults["chat"] 并从 config.providers 中查找。
190
+ 只返回已配置文件中存在的 Provider。
191
+ """
192
+ config = loader.load()
193
+
194
+ active_id = config.defaults.get("chat")
195
+ if not active_id:
196
+ if config.providers:
197
+ active_id = config.providers[0].id
198
+ else:
199
+ return None
200
+
201
+ provider_cfg = next((p for p in config.providers if p.id == active_id), None)
202
+ if not provider_cfg:
203
+ return None
204
+
205
+ return _provider_to_response(provider_cfg, {})
206
+
207
+
208
+ @router.post(
209
+ "/providers/discover",
210
+ response_model=DiscoverResponse,
211
+ summary="扫描并发现本地 AI 服务",
212
+ responses={
213
+ 500: {"description": "服务器内部错误"},
214
+ },
215
+ )
216
+ async def discover_local_services() -> DiscoverResponse:
217
+ """
218
+ 扫描并发现本地 AI 服务
219
+
220
+ 扫描 localhost 和 127.0.0.1 的常用端口,
221
+ 检测 Ollama (11434) 和 OpenAI 兼容服务 (1234, 8080, 8000)
222
+ """
223
+ services = await scanner.scan()
224
+ return DiscoverResponse(
225
+ services=[
226
+ {"id": s.id, "name": s.name, "type": s.type, "base_url": s.base_url, "models": s.models, "status": s.status}
227
+ for s in services
228
+ ],
229
+ count=len(services),
230
+ )
231
+
232
+
233
+ @router.post(
234
+ "/providers/discover/add",
235
+ summary="将发现的服务添加到配置",
236
+ responses={
237
+ 404: {"description": "服务未找到"},
238
+ 500: {"description": "服务器内部错误"},
239
+ },
240
+ )
241
+ async def add_discovered_service(service_id: str) -> dict[str, Any]:
242
+ """
243
+ 将发现的服务添加到配置
244
+
245
+ Args:
246
+ service_id: 发现的服务 ID(从 discover 接口获取)
247
+ """
248
+ services = await scanner.scan()
249
+ target = next((s for s in services if s.id == service_id), None)
250
+ if not target:
251
+ raise HTTPException(404, detail="该 AI 服务已不存在或刚刚下线,请刷新服务列表后重试")
252
+
253
+ config = loader.load()
254
+
255
+ provider_type = ProviderType.OLLAMA if target.type == "ollama" else ProviderType.OPENAI
256
+
257
+ new_provider = AIProvider(
258
+ id=target.id,
259
+ name=target.name,
260
+ type=provider_type,
261
+ base_url=target.base_url,
262
+ api_key=None,
263
+ model=target.models[0] if target.models else "",
264
+ )
265
+
266
+ config.providers = [p for p in config.providers if p.id != target.id]
267
+ config.providers.append(new_provider)
268
+
269
+ # 若当前没有默认 Provider,则自动设为默认
270
+ if not config.defaults.get("chat"):
271
+ config.defaults["chat"] = new_provider.id
272
+
273
+ loader.save(config)
274
+
275
+ return {
276
+ "success": True,
277
+ "provider": {
278
+ "id": new_provider.id,
279
+ "name": new_provider.name,
280
+ "type": _get_type_str(new_provider),
281
+ "base_url": new_provider.base_url,
282
+ "model": new_provider.model,
283
+ },
284
+ }
285
+
286
+
287
+ @router.post(
288
+ "/providers/{provider_id}/test",
289
+ response_model=TestProviderResponse,
290
+ summary="测试 Provider 连接",
291
+ responses={
292
+ 404: {"description": "Provider 未找到"},
293
+ 500: {"description": "服务器内部错误"},
294
+ },
295
+ )
296
+ async def test_provider(provider_id: str) -> TestProviderResponse:
297
+ """
298
+ 测试 Provider 连接
299
+
300
+ 执行健康检查并尝试获取可用模型列表
301
+ """
302
+ config = loader.load()
303
+ provider_cfg = next((p for p in config.providers if p.id == provider_id), None)
304
+ if not provider_cfg:
305
+ raise HTTPException(404, detail=f"未找到对应的 AI 模型配置,可能已被删除(ID: {provider_id})")
306
+
307
+ # SSRF 防护:测试连接前校验 base_url(防止 /test 被用作 SSRF 探针)
308
+ _validate_base_url(provider_cfg.base_url)
309
+
310
+ provider = create(provider_cfg)
311
+
312
+ health = await provider.health()
313
+
314
+ models = []
315
+ if health.get("status") == "ok":
316
+ try:
317
+ models = await provider.list_models()
318
+ except Exception:
319
+ logging.exception("获取模型列表失败")
320
+
321
+ return TestProviderResponse(provider_id=provider_id, health=health, available_models=models)
322
+
323
+
324
+ @router.post(
325
+ "/providers/{provider_id}/activate",
326
+ response_model=ProviderResponse,
327
+ summary="设置活动 Provider",
328
+ responses={
329
+ 404: {"description": "Provider 未找到"},
330
+ 500: {"description": "服务器内部错误"},
331
+ },
332
+ )
333
+ async def activate_provider(provider_id: str) -> ProviderResponse:
334
+ """
335
+ 设置活动 Provider
336
+
337
+ 设置 config.defaults["chat"] = provider_id 并保存。
338
+ 只接受已存在于配置文件中的 Provider ID。
339
+ """
340
+ config = loader.load()
341
+
342
+ provider_cfg = next((p for p in config.providers if p.id == provider_id), None)
343
+ if not provider_cfg:
344
+ raise HTTPException(404, detail=f"未找到对应的 AI 模型配置,可能已被删除(ID: {provider_id})")
345
+
346
+ config.defaults["chat"] = provider_id
347
+ loader.save(config)
348
+
349
+ return _provider_to_response(provider_cfg, {})
350
+
351
+
352
+ @router.get(
353
+ "/providers/presets",
354
+ response_model=list[ProviderPresetResponse],
355
+ summary="获取内置服务商预设列表",
356
+ )
357
+ async def list_presets() -> list[dict[str, Any]]:
358
+ """
359
+ 获取内置服务商预设列表
360
+
361
+ 返回后端预定义的服务商模板,包含 base_url、默认模型、可用模型列表。
362
+ 前端可据此构建「选择服务商 → 填 API Key」的交互流程。
363
+ """
364
+ return get_preset_list()
365
+
366
+
367
+ @router.post(
368
+ "/providers",
369
+ response_model=ProviderResponse,
370
+ summary="创建 Provider",
371
+ responses={
372
+ 409: {"description": "Provider ID 已存在"},
373
+ 500: {"description": "服务器内部错误"},
374
+ },
375
+ )
376
+ async def create_provider(req: CreateProviderRequest) -> ProviderResponse:
377
+ """
378
+ 创建一个新的 Provider 并写入配置文件。
379
+
380
+ 自动生成唯一 ID(基于预设 ID + 短随机后缀),或使用请求中的 name 生成。
381
+ """
382
+ config = loader.load()
383
+
384
+ base_id = req.name.lower().replace(" ", "-").replace("_", "-")
385
+ new_id = base_id
386
+ counter = 1
387
+ while any(p.id == new_id for p in config.providers):
388
+ new_id = f"{base_id}-{counter}"
389
+ counter += 1
390
+
391
+ try:
392
+ provider_type = ProviderType(req.type)
393
+ except ValueError:
394
+ raise HTTPException(400, detail=f"不支持的 AI 服务类型「{req.type}」,目前仅支持 openai、ollama 两种接入方式")
395
+
396
+ _validate_base_url(req.base_url) # SSRF 防护
397
+
398
+ new_provider = AIProvider(
399
+ id=new_id,
400
+ name=req.name,
401
+ type=provider_type,
402
+ base_url=req.base_url,
403
+ api_key=req.api_key,
404
+ model=req.model,
405
+ context_window=req.context_window,
406
+ )
407
+
408
+ config.providers.append(new_provider)
409
+
410
+ # 若当前没有默认 Provider,则自动将新 Provider 设为默认,
411
+ # 避免用户添加后仍因 "No default provider configured" 无法对话。
412
+ if not config.defaults.get("chat"):
413
+ config.defaults["chat"] = new_id
414
+
415
+ loader.save(config)
416
+
417
+ return _provider_to_response(new_provider, {})
418
+
419
+
420
+ @router.put(
421
+ "/providers/{provider_id}",
422
+ response_model=ProviderResponse,
423
+ summary="更新 Provider",
424
+ responses={
425
+ 404: {"description": "Provider 未找到"},
426
+ 500: {"description": "服务器内部错误"},
427
+ },
428
+ )
429
+ async def update_provider(provider_id: str, req: UpdateProviderRequest) -> ProviderResponse:
430
+ """
431
+ 更新已有 Provider 的配置。
432
+
433
+ 仅更新请求中传递的非 None 字段。
434
+ """
435
+ config = loader.load()
436
+
437
+ idx = next((i for i, p in enumerate(config.providers) if p.id == provider_id), None)
438
+ if idx is None:
439
+ raise HTTPException(404, detail=f"未找到对应的 AI 模型配置,可能已被删除(ID: {provider_id})")
440
+
441
+ existing = config.providers[idx]
442
+
443
+ update_data = req.model_dump(exclude_none=True)
444
+ if "type" in update_data:
445
+ try:
446
+ update_data["type"] = ProviderType(update_data["type"])
447
+ except ValueError:
448
+ raise HTTPException(400, detail=f"Unsupported provider type: {update_data['type']}")
449
+
450
+ # SSRF 防护:若更新了 base_url 则校验
451
+ if "base_url" in update_data:
452
+ _validate_base_url(update_data.get("base_url"))
453
+
454
+ updated_provider = existing.model_copy(update=update_data)
455
+ config.providers[idx] = updated_provider
456
+ # §2.12: 显式改 api_key 视为手工值——正常落盘,不再按 env 来源剔除
457
+ if "api_key" in update_data:
458
+ loader.mark_api_key_manual(provider_id)
459
+ loader.save(config)
460
+
461
+ return _provider_to_response(updated_provider, {})
462
+
463
+
464
+ @router.delete(
465
+ "/providers/{provider_id}",
466
+ summary="删除 Provider",
467
+ responses={
468
+ 404: {"description": "Provider 未找到"},
469
+ 500: {"description": "服务器内部错误"},
470
+ },
471
+ )
472
+ async def delete_provider(provider_id: str) -> dict[str, Any]:
473
+ """
474
+ 删除指定的 Provider。
475
+
476
+ 如果删除的是当前活跃 Provider,自动清除 defaults.chat。
477
+ """
478
+ config = loader.load()
479
+
480
+ idx = next((i for i, p in enumerate(config.providers) if p.id == provider_id), None)
481
+ if idx is None:
482
+ raise HTTPException(404, detail=f"未找到对应的 AI 模型配置,可能已被删除(ID: {provider_id})")
483
+
484
+ config.providers.pop(idx)
485
+
486
+ if config.defaults.get("chat") == provider_id:
487
+ config.defaults["chat"] = ""
488
+
489
+ loader.save(config)
490
+
491
+ return {"success": True, "deleted": provider_id}
@@ -0,0 +1,44 @@
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/LLM API 路由聚合入口模块
18
+
19
+ 功能概述:
20
+ - 提供前缀为 /ai 的 APIRouter 实例
21
+ - 聚合导入各 AI 子模块(chat、generate、hardware、jobs、ollama、providers、utils)
22
+ - 作为 AI 路由的统一入口,降低单文件体量与耦合度
23
+
24
+ 架构设计:
25
+ - 各子模块通过导入本模块的 router 实例注册端点
26
+ - 本模块仅负责 router 实例创建和聚合导入,不含具体业务逻辑
27
+
28
+ 输入示例:
29
+ 无直接端点,由子模块注册
30
+
31
+ 输出示例:
32
+ 无直接端点,由子模块注册
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ from fastapi import APIRouter
38
+
39
+ router = APIRouter(prefix="/api/latest/ai", tags=["ai"])
40
+
41
+ # 导入各子模块以注册端点
42
+ from . import chat, generate, hardware, jobs, migrate, ollama, providers, stream, utils
43
+
44
+ __all__ = ["router", "chat", "generate", "hardware", "jobs", "migrate", "ollama", "providers", "stream", "utils"]