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,260 @@
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
+ # backend/app/cli/shell/commands/ai/__init__.py
17
+ """
18
+ @fileoverview CLI Shell AI 助手命令模块入口
19
+
20
+ 功能概述:
21
+ - 提供 ai 命令聚合 AI 相关子命令
22
+ - 支持交互式菜单、对话、状态查看、Provider 切换等
23
+ - 支持直接通过自然语言执行 AI 指令
24
+
25
+ 架构设计:
26
+ - AICommand 作为聚合命令,管理 chat、status、switch、delete、setup 等子命令
27
+ - 无参数时显示交互式菜单(支持方向键导航)
28
+ - 第一个参数如果不是子命令,则将其视为直接询问的消息
29
+ - 使用 execute_ai_chat 统一执行 AI 对话
30
+
31
+ 输入示例:
32
+ precis> ai
33
+ precis> ai chat
34
+ precis> ai status
35
+ precis> ai ask "添加非空约束到 users 表的 email 列"
36
+
37
+ 输出示例:
38
+ 交互式菜单或 AI 执行结果
39
+ """
40
+
41
+ # 导入子命令
42
+ from app.cli.shell.commands.ai.chat import AIChatCommand
43
+ from app.cli.shell.commands.ai.delete import AIDeleteCommand
44
+ from app.cli.shell.commands.ai.executor import execute_ai_chat
45
+ from app.cli.shell.commands.ai.generate import AIGenerateCommand
46
+ from app.cli.shell.commands.ai.migrate import AIMigrateCommand
47
+ from app.cli.shell.commands.ai.status import AIStatusCommand
48
+ from app.cli.shell.commands.ai.switch import AISwitchCommand
49
+ from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
50
+ from app.cli.shell.commands.provider import ProviderCommand
51
+ from app.cli.shell.config_storage import get_cli_config
52
+ from app.cli.shell.formatter import Formatter
53
+ from app.cli.shell.interactive_menu import InteractiveMenu
54
+
55
+
56
+ class AICommand(Command):
57
+ """AI 助手命令。
58
+
59
+ 支持交互式对话模式和直接执行 AI 指令。
60
+ 作为聚合命令,包含多个子命令和一个交互式菜单。
61
+ """
62
+
63
+ def __init__(self) -> None:
64
+ super().__init__("ai", aliases=["assistant"])
65
+ self._chat_cmd: AIChatCommand = AIChatCommand()
66
+ self._status_cmd: AIStatusCommand = AIStatusCommand()
67
+ self._switch_cmd: AISwitchCommand = AISwitchCommand()
68
+ self._provider_cmd: ProviderCommand = ProviderCommand()
69
+ self._delete_cmd: AIDeleteCommand = AIDeleteCommand()
70
+ self._generate_cmd: AIGenerateCommand = AIGenerateCommand()
71
+ self._migrate_cmd: AIMigrateCommand = AIMigrateCommand()
72
+ self._cli_config = get_cli_config()
73
+
74
+ # 注册子命令
75
+ self.add_subcommand("chat", self._chat_cmd)
76
+ self.add_subcommand("generate", self._generate_cmd)
77
+ self.add_subcommand("migrate", self._migrate_cmd)
78
+ self.add_subcommand("status", self._status_cmd)
79
+ self.add_subcommand("switch", self._switch_cmd)
80
+ self.add_subcommand("delete", self._delete_cmd)
81
+ self.add_subcommand("provider", self._provider_cmd)
82
+
83
+ @property
84
+ def description(self) -> str:
85
+ return "AI 助手 - 使用自然语言修改项目配置"
86
+
87
+ @property
88
+ def usage(self) -> str:
89
+ return "ai [chat|generate|migrate|status|switch|provider|delete|ask <message>]"
90
+
91
+ @property
92
+ def help_text(self) -> str:
93
+ return """
94
+ 用法: ai [子命令] [参数]
95
+
96
+ 子命令:
97
+ chat 进入交互式 AI 对话模式(默认 Agent 深度模式)
98
+ ask <message> 直接执行 AI 指令(如: ai ask "添加非空约束到用户表的email字段")
99
+ generate 从数据文件生成 Precis V2 配置
100
+ migrate 从旧脚本迁移生成 Precis V2 配置
101
+ status 显示 AI 配置状态
102
+ switch <provider> 切换默认 AI Provider
103
+ provider 管理 AI Provider(添加/编辑/删除/测试)
104
+ delete [provider] 删除已配置的 Provider
105
+
106
+ 示例:
107
+ ai chat # 进入交互式对话(Agent 模式)
108
+ ai chat --no-agent-mode # 关闭 Agent,使用旧 JSON actions 路径
109
+ ai ask "创建非空约束到users表的email列" # 直接执行
110
+ ai generate data/users.xlsx # 预览生成的配置
111
+ ai generate data/*.xlsx --apply # 生成并写入项目
112
+ ai migrate scripts/legacy.sql data/users.xlsx --apply # 迁移并写入项目
113
+ ai status # 查看 AI 配置状态
114
+ ai switch kimi # 切换到 Kimi
115
+ ai provider # 管理 AI Provider
116
+ ai delete kimi # 删除 Kimi
117
+
118
+ 说明:
119
+ AI 助手可以帮你通过自然语言修改项目配置,包括:
120
+ - 添加、更新、删除约束(NOT_NULL, UNIQUE, RANGE, ALLOWED_VALUES, REGEX 等)
121
+ - 查看和解释当前配置
122
+ - 从数据文件生成完整配置
123
+ - 从旧脚本迁移业务规则
124
+
125
+ 首次使用请先运行 'ai provider' 命令添加 Provider
126
+ """.strip()
127
+
128
+ def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
129
+ """执行 AI 命令。
130
+
131
+ 无参数时显示交互式菜单;
132
+ 有参数时根据子命令名分发给对应处理器;
133
+ 如果第一个参数不是已知子命令,则视为直接询问。
134
+
135
+ Args:
136
+ args: 命令参数列表
137
+ context: 命令上下文
138
+
139
+ Returns:
140
+ 子命令执行结果或 AI 对话结果
141
+ """
142
+ if not args:
143
+ # 无参数时显示交互式菜单
144
+ return self._show_interactive_menu(context)
145
+
146
+ subcommand = args[0]
147
+ sub_args = args[1:]
148
+
149
+ if subcommand == "chat":
150
+ return self._chat_cmd.execute(sub_args, context)
151
+ elif subcommand == "ask":
152
+ return self._ask_direct(sub_args, context)
153
+ elif subcommand == "generate":
154
+ return self._generate_cmd.execute(sub_args, context)
155
+ elif subcommand == "migrate":
156
+ return self._migrate_cmd.execute(sub_args, context)
157
+ elif subcommand == "status":
158
+ return self._status_cmd.execute(sub_args, context)
159
+ elif subcommand == "switch":
160
+ return self._switch_cmd.execute(sub_args, context)
161
+ elif subcommand == "delete":
162
+ return self._delete_cmd.execute(sub_args, context)
163
+ elif subcommand == "provider":
164
+ return self._provider_cmd.execute(sub_args, context)
165
+ else:
166
+ # 如果第一个参数不是子命令,则将其视为直接询问的消息
167
+ return self._ask_direct(args, context)
168
+
169
+ def _show_interactive_menu(self, context: ProjectContext) -> CommandResult:
170
+ """显示 AI 助手的交互式菜单(支持方向键导航)。
171
+
172
+ 循环显示菜单,直到用户选择退出或执行需要退出的子命令。
173
+
174
+ Args:
175
+ context: 命令上下文
176
+
177
+ Returns:
178
+ 退出菜单后的结果
179
+ """
180
+ while True:
181
+ status_lines = []
182
+
183
+ # 构建状态行:显示当前 Provider 和项目
184
+ provider = self._cli_config.get_active_provider()
185
+ if provider:
186
+ status_lines.append(Formatter.info(f"当前 Provider: {provider.name} ({provider.model})"))
187
+ else:
188
+ status_lines.append(Formatter.warning("[!] 未配置 AI Provider"))
189
+
190
+ if context.is_project_open:
191
+ # project_config 在 open 后可能尚未加载,做空值守卫避免 AttributeError
192
+ config = context.project_config or {}
193
+ project_name = config.get("project", {}).get("name", "project")
194
+ status_lines.append(Formatter.info(f"当前项目: {project_name}"))
195
+ else:
196
+ status_lines.append(Formatter.warning("[!] 未打开项目"))
197
+
198
+ # 创建交互式菜单
199
+ menu = InteractiveMenu("AI 助手")
200
+ menu.add_item("chat", "chat", "进入交互式对话模式")
201
+ menu.add_item("generate", "generate", "从数据文件生成配置")
202
+ menu.add_item("migrate", "migrate", "从旧脚本迁移配置")
203
+ menu.add_item("status", "status", "查看 AI 配置状态")
204
+ menu.add_item("switch", "switch", "切换 AI Provider")
205
+ menu.add_item("provider", "provider", "管理 AI Provider")
206
+ menu.add_item("delete", "delete", "删除 Provider")
207
+ menu.add_item("help", "help", "显示帮助信息")
208
+
209
+ choice = menu.show_with_status(status_lines)
210
+
211
+ if choice is None:
212
+ return CommandResult.ok("已退出 AI 助手")
213
+ elif choice == "chat":
214
+ result = self._chat_cmd.execute([], context)
215
+ if result.should_exit:
216
+ return result
217
+ elif choice == "generate":
218
+ result = self._generate_cmd.execute([], context)
219
+ if result.should_exit:
220
+ return result
221
+ input(Formatter.info("\n按回车键返回菜单..."))
222
+ elif choice == "migrate":
223
+ result = self._migrate_cmd.execute([], context)
224
+ if result.should_exit:
225
+ return result
226
+ input(Formatter.info("\n按回车键返回菜单..."))
227
+ elif choice == "status":
228
+ result = self._status_cmd.execute([], context)
229
+ print(result.message)
230
+ input(Formatter.info("\n按回车键返回菜单..."))
231
+ elif choice == "switch":
232
+ self._switch_cmd.execute([], context)
233
+ elif choice == "provider":
234
+ self._provider_cmd.execute([], context)
235
+ elif choice == "delete":
236
+ self._delete_cmd.execute([], context)
237
+ elif choice == "help":
238
+ print(Formatter.info(self.help_text))
239
+ input(Formatter.info("\n按回车键继续..."))
240
+
241
+ def _ask_direct(self, args: list[str], context: ProjectContext) -> CommandResult:
242
+ """直接执行 AI 询问。
243
+
244
+ 将参数拼接为完整消息,调用 execute_ai_chat 非交互式执行。
245
+
246
+ Args:
247
+ args: 消息内容(按空格分割的数组)
248
+ context: 命令上下文
249
+
250
+ Returns:
251
+ AI 对话执行结果
252
+ """
253
+ if not args:
254
+ return CommandResult.error("请提供询问内容\n用法: ai ask <message> 或 ai <message>")
255
+
256
+ message = " ".join(args)
257
+ return execute_ai_chat(message, context, interactive=False)
258
+
259
+
260
+ __all__ = ["AICommand"]
@@ -0,0 +1,331 @@
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
+ # backend/app/cli/shell/commands/ai/chat.py
17
+ """
18
+ @fileoverview AI 交互式对话命令模块
19
+
20
+ 功能概述:
21
+ - 提供 ai chat 子命令进入交互式 AI 对话模式
22
+ - 管理对话历史记录,支持清空、查看统计
23
+ - 过滤孤立确认词(如 "ok"、"好的"),避免误触发 AI 调用
24
+ - 支持内置指令:help、clear、history、exit、quit、qq
25
+
26
+ 架构设计:
27
+ - AIChatCommand 继承 Command 基类,实现 execute() 方法
28
+ - execute() 方法启动一个 while True 循环,持续读取用户输入
29
+ - 对话历史以 list[dict] 形式维护,每个元素包含 role(user/assistant)和 content
30
+ - 当历史记录的 Token 数超过限制时,自动截断最早的消息(保留系统提示词)
31
+
32
+ 输入示例:
33
+ precis> ai chat
34
+ 你: 在 users 表添加非空约束
35
+ AI: 已为您添加非空约束...
36
+
37
+ 你: help
38
+ (显示帮助信息)
39
+
40
+ 你: clear
41
+ (清空历史)
42
+
43
+ 输出示例:
44
+ CommandResult.ok("")
45
+ """
46
+
47
+ from app.cli.shell.commands.ai.executor import execute_ai_chat
48
+ from app.cli.shell.commands.ai.interaction import build_context_data
49
+ from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
50
+ from app.cli.shell.config_storage import get_cli_config
51
+ from app.cli.shell.formatter import Colors, Formatter
52
+ from app.shared.services.ai.utils import (
53
+ estimate_tokens,
54
+ truncate_history_by_tokens,
55
+ )
56
+ from app.shared.services.llm.chat.chat_system_prompt import build_system_prompt
57
+ from app.shared.services.llm.providers.base import compute_token_budgets, resolve_context_window
58
+
59
+
60
+ class AIChatCommand(Command):
61
+ """AI 交互式对话命令。
62
+
63
+ 启动一个持续运行的对话会话,用户可以反复输入自然语言指令与 AI 交互。
64
+ 支持历史记录管理、Token 限制保护、内置快捷指令。
65
+
66
+ 上下文窗口大小从 Provider 配置或内置模型表中自动获取,
67
+ 不再硬编码为 120k。
68
+ """
69
+
70
+ # 为模型回复预留的 token 预算
71
+ RESERVED_OUTPUT_TOKENS = 8000
72
+
73
+ def __init__(self) -> None:
74
+ super().__init__("chat")
75
+ self._cli_config = get_cli_config()
76
+
77
+ @property
78
+ def description(self) -> str:
79
+ return "进入 AI 交互式对话模式"
80
+
81
+ @property
82
+ def usage(self) -> str:
83
+ return "ai chat [--no-agent-mode]"
84
+
85
+ def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
86
+ """执行 AI 交互式对话命令。
87
+
88
+ 检查项目和 AI 配置,解析命令行参数,然后进入交互式输入循环。
89
+
90
+ Args:
91
+ args: 命令参数列表,可能包含 --no-agent-mode
92
+ context: 命令上下文,必须包含已打开的项目
93
+
94
+ Returns:
95
+ 命令执行结果,退出对话后返回成功结果
96
+ """
97
+ if not context.is_project_open:
98
+ return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
99
+
100
+ # 检查 AI 配置(使用 CLI 配置)
101
+ provider = self._cli_config.get_active_provider()
102
+ if not provider:
103
+ return CommandResult.error("没有可用的 LLM Provider 配置\n请先运行 'setup' 命令配置 API Key")
104
+
105
+ # 解析命令行参数(--stream/--no-stream 已随 execute_ai_chat 的死参数一并移除)
106
+ agent_mode = True # 默认启用 Agent 深度模式
107
+ filtered_args = []
108
+ for arg in args:
109
+ if arg == "--no-agent-mode":
110
+ agent_mode = False
111
+ else:
112
+ filtered_args.append(arg)
113
+
114
+ if filtered_args:
115
+ return CommandResult.error(f"未知参数: {filtered_args}\n用法: {self.usage}")
116
+
117
+ # 获取项目名称用于显示(project_config 在 open 后可能尚未加载,做空值守卫)
118
+ config = context.project_config or {}
119
+ project_name = config.get("project", {}).get("name", "project")
120
+
121
+ # 根据 Provider 配置和模型名获取上下文窗口(用户输入 > 自动探测 > 全局回退)。
122
+ # 4.24: max_tokens 是输出预算,经 compute_token_budgets 按窗裁剪——原公式
123
+ # 把输入侧公式当输出上限,小窗模型(cw<12k)每次请求必超窗
124
+ self._context_window = resolve_context_window(provider)
125
+ _input_budget, self._max_context_tokens = compute_token_budgets(self._context_window)
126
+
127
+ # 打印会话头信息
128
+ print(Formatter.header("\nAI 助手交互模式"))
129
+ print(Formatter.info(f"项目: {project_name}"))
130
+ print(Formatter.info(f"Provider: {provider.name}"))
131
+ print(Formatter.info(f"Model: {provider.model}"))
132
+ print(Formatter.info(f"Agent 模式: {'开启' if agent_mode else '关闭'}"))
133
+ print(Formatter.info(f"上下文限制: {self._context_window:,} tokens"))
134
+ print(Formatter.info("\n提示: 输入 'exit' 或 'quit' 退出对话,'help' 查看帮助,'clear' 清空历史"))
135
+ print(Formatter.header(""))
136
+
137
+ # 对话历史:存储用户和 AI 的消息,用于多轮对话
138
+ chat_history: list[dict[str, str]] = []
139
+
140
+ while True:
141
+ try:
142
+ # 获取用户输入(带绿色提示符)
143
+ prompt = Formatter.colorize("\n你: ", Colors.GREEN)
144
+ user_input = input(prompt).strip()
145
+
146
+ if not user_input:
147
+ continue
148
+
149
+ # 内置指令处理
150
+ if user_input.lower() in ("exit", "quit", "q"):
151
+ print(Formatter.success("\n再见!"))
152
+ break
153
+
154
+ if user_input.lower() in ("exit!", "quit!", "qq"):
155
+ print(Formatter.success("\n再见!"))
156
+ import sys
157
+
158
+ sys.exit(0)
159
+
160
+ if user_input.lower() == "help":
161
+ self._print_help()
162
+ continue
163
+
164
+ if user_input.lower() == "clear":
165
+ chat_history = []
166
+ print(Formatter.info("对话历史已清空"))
167
+ continue
168
+
169
+ if user_input.lower() == "history":
170
+ self._print_history_stats(chat_history)
171
+ continue
172
+
173
+ # 忽略孤立的确认/回应词(用户可能只是回应 AI,而非执行命令)
174
+ # 但如果是疑问句或包含其他内容,则正常处理
175
+ acknowledgment_words = {
176
+ "ok",
177
+ "okay",
178
+ "yes",
179
+ "yep",
180
+ "yeah",
181
+ "y",
182
+ "好的",
183
+ "知道了",
184
+ "明白",
185
+ "收到",
186
+ "嗯",
187
+ "哦",
188
+ "thanks",
189
+ "thank you",
190
+ "谢谢",
191
+ "谢了",
192
+ "got it",
193
+ "了解了",
194
+ "清楚",
195
+ "好",
196
+ "可以",
197
+ "行",
198
+ }
199
+ input_lower = user_input.lower()
200
+ # 只过滤单独的确认词(不包含问号、不是疑问句)
201
+ if input_lower in acknowledgment_words and len(input_lower) < 20:
202
+ print(Formatter.dim("(已忽略确认词。输入 'help' 查看命令,或输入具体问题与 AI 对话)"))
203
+ continue
204
+
205
+ # 执行 AI 对话(调用统一执行器)
206
+ result = execute_ai_chat(
207
+ user_input,
208
+ context,
209
+ interactive=True,
210
+ history=chat_history,
211
+ agent_mode=agent_mode,
212
+ )
213
+
214
+ if result.success and result.data:
215
+ # 更新历史:将用户消息和 AI 回复加入历史
216
+ reply = result.data.get("reply", "")
217
+ chat_history.append({"role": "user", "content": user_input})
218
+ chat_history.append({"role": "assistant", "content": reply})
219
+
220
+ # 根据 Token 数截断历史(安全机制,防止超出模型上下文窗口)
221
+ context_data = build_context_data("", context)
222
+ system_prompt = build_system_prompt(context_data)
223
+ chat_history = truncate_history_by_tokens(
224
+ chat_history, system_prompt, max_tokens=self._max_context_tokens
225
+ )
226
+ elif not result.success:
227
+ # 处理错误情况
228
+ print(Formatter.error(f"\n请求失败: {result.message}"))
229
+ else:
230
+ # result.success 为 True 但 data 为空(异常情况)
231
+ print(Formatter.warning("\n收到空响应,未更新历史记录"))
232
+
233
+ except KeyboardInterrupt:
234
+ # 用户按 Ctrl+C,提示如何正确退出
235
+ print()
236
+ print(Formatter.info("\n使用 'exit' 或 'quit' 退出对话"))
237
+ except EOFError:
238
+ # 输入流结束(如管道输入),优雅退出
239
+ print()
240
+ print(Formatter.success("\n再见!"))
241
+ break
242
+
243
+ return CommandResult.ok("")
244
+
245
+ def _print_help(self) -> None:
246
+ """打印 AI 交互式对话的帮助信息。
247
+
248
+ 列出所有可用命令、启动选项以及自然语言示例。
249
+ """
250
+ help_text = """
251
+ AI 助手帮助
252
+
253
+ 可用命令:
254
+ help - 显示此帮助信息
255
+ history - 显示对话历史统计
256
+ clear - 清空对话历史
257
+ exit - 退出对话模式(返回 CLI)
258
+ qq - 直接退出整个程序(全局可用)
259
+
260
+ 启动选项:
261
+ --stream - 启用流式输出
262
+ --no-stream - 禁用流式输出(默认,显示 loading spinner)
263
+ --no-agent-mode - 关闭 Agent 深度模式(降级为旧 JSON actions 路径)
264
+
265
+ 你可以用自然语言描述你的需求,例如:
266
+ - "在 users 表的 email 列上添加非空约束"
267
+ - "给 orders 表的 amount 列添加范围约束,最小值 0"
268
+ - "删除 users 表 phone 列上的唯一约束"
269
+ - "显示当前所有的约束配置"
270
+
271
+ 支持的约束类型:
272
+ - NotNull: 非空约束
273
+ - Unique: 唯一约束
274
+ - AllowedValues: 允许值约束 (如: "allowedValues 为 A, B")
275
+ - Range: 范围约束 (如: "最小值 0, 最大值 100")
276
+ - Scripted: 脚本/正则约束 (如: "正则匹配 ^[0-9]+$")
277
+ - ForeignKey: 外键约束 (如: "关联到 orders 表的 user_id 列")
278
+ - Conditional: 条件约束 (如: "如果 type 等于 A 则值为 B")
279
+ - DateLogic: 日期逻辑约束 (如: "日期晚于 2023-01-01")
280
+ """
281
+ print(Formatter.info(help_text))
282
+
283
+ def _print_history_stats(self, chat_history: list[dict[str, str]]) -> None:
284
+ """显示对话历史统计信息。
285
+
286
+ 统计消息总数、用户/AI 消息数量、估算 Token 数及上下文使用率。
287
+
288
+ Args:
289
+ chat_history: 当前对话历史列表
290
+ """
291
+ if not chat_history:
292
+ print(Formatter.info("\n对话历史为空"))
293
+ return
294
+
295
+ # 计算统计信息
296
+ total_messages = len(chat_history)
297
+ user_messages = sum(1 for m in chat_history if m.get("role") == "user")
298
+ assistant_messages = sum(1 for m in chat_history if m.get("role") == "assistant")
299
+
300
+ # 计算 Token 数(估算)
301
+ total_tokens = 0
302
+ for msg in chat_history:
303
+ total_tokens += estimate_tokens(msg.get("content", ""))
304
+
305
+ # 系统提示词 Token 估算(系统提示词不显示在历史中,但占用 Token)
306
+ system_tokens = 2000 # 估算值
307
+ total_with_system = total_tokens + system_tokens
308
+
309
+ # 计算使用率百分比(基于完整上下文窗口)
310
+ context_window = getattr(self, "_context_window", 120000)
311
+ usage_percent = (total_with_system / context_window) * 100
312
+
313
+ print(Formatter.info("\n对话历史统计:"))
314
+ print(f" 消息总数: {total_messages}")
315
+ print(f" - 用户消息: {user_messages}")
316
+ print(f" - AI 回复: {assistant_messages}")
317
+ print(f" 估算 Token: {total_with_system:,} / {context_window:,}")
318
+ print(f" 使用率: {usage_percent:.1f}%")
319
+
320
+ # 显示最近几条消息预览
321
+ preview_count = min(3, len(chat_history) // 2)
322
+ if preview_count > 0:
323
+ print(f"\n 最近 {preview_count} 轮对话:")
324
+ for i in range(-preview_count * 2, 0, 2):
325
+ if abs(i) <= len(chat_history):
326
+ user_msg = chat_history[i] if i < 0 and abs(i) <= len(chat_history) else None
327
+ if user_msg and user_msg.get("role") == "user":
328
+ content = user_msg.get("content", "")[:30]
329
+ if len(user_msg.get("content", "")) > 30:
330
+ content += "..."
331
+ print(f" - {content}")