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,334 @@
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 OpenAI 兼容 Provider
18
+
19
+ 功能概述:
20
+ - 支持 OpenAI API 格式的所有服务(云端和本地)
21
+ - 实现非流式对话、流式对话、模型列表、健康检查
22
+ - 自动重试机制(指数退避,最多 3 次)
23
+ - 错误分类处理:网络错误、服务端错误、响应解析错误
24
+
25
+ 架构设计:
26
+ - 继承 BaseProvider,实现统一的 Provider 接口
27
+ - 使用 AsyncOpenAI 客户端进行异步 HTTP 通信
28
+ - 与配置模型联动:从 AIProvider 读取 base_url、api_key、timeout
29
+
30
+ 输入示例:
31
+ req = ChatRequest(
32
+ messages=[ChatMessage(role="user", content="你好")],
33
+ temperature=0.7
34
+ )
35
+
36
+ 输出示例:
37
+ resp = await provider.chat(req)
38
+ # ChatResponse(content="你好!", model="gpt-4")
39
+
40
+ async for chunk in provider.chat_stream(req):
41
+ print(chunk, end="")
42
+ """
43
+
44
+ from __future__ import annotations
45
+
46
+ import asyncio
47
+ import logging
48
+ from collections.abc import AsyncIterator
49
+ from typing import TYPE_CHECKING, Any
50
+
51
+ if TYPE_CHECKING:
52
+ from openai import APIConnectionError, APIStatusError, AsyncOpenAI
53
+
54
+ from app.shared.services.llm.config.models import AIProvider
55
+ else:
56
+ try:
57
+ from openai import APIConnectionError, APIStatusError, AsyncOpenAI
58
+ except ImportError:
59
+ APIConnectionError = None
60
+ APIStatusError = None
61
+ AsyncOpenAI = None
62
+
63
+ from .base import BaseProvider, ChatRequest, ChatResponse, StreamChunk
64
+
65
+ logger = logging.getLogger(__name__)
66
+
67
+ _MAX_RETRIES = 3
68
+ _RETRY_BASE_DELAY = 1.0
69
+
70
+
71
+ class OpenAIProvider(BaseProvider):
72
+ """
73
+ @classdesc OpenAI 兼容 Provider
74
+
75
+ 支持 OpenAI API 格式的所有服务(云端和本地)。
76
+ 实现非流式对话、流式对话、模型列表、健康检查。
77
+ 具备自动重试机制(指数退避,最多 3 次)。
78
+
79
+ 使用场景:
80
+ - OpenAI 官方 API
81
+ - 本地 OpenAI 兼容服务(如 vLLM、TGI 等)
82
+ - 其他 OpenAI 格式兼容的云端服务
83
+ """
84
+
85
+ @property
86
+ def name(self) -> str:
87
+ return "OpenAI-Compatible"
88
+
89
+ def __init__(self, config: AIProvider) -> None:
90
+ super().__init__(config)
91
+ if AsyncOpenAI is None:
92
+ raise ImportError("openai 未安装,请运行 pip install openai")
93
+ self.client = AsyncOpenAI(
94
+ base_url=config.base_url,
95
+ api_key=config.api_key or "",
96
+ timeout=config.network.timeout if config.network else 60,
97
+ )
98
+
99
+ async def chat(self, req: ChatRequest) -> ChatResponse:
100
+ """
101
+ @methoddesc 发送非流式对话请求,支持自动重试
102
+
103
+ 当遇到网络错误或限流(429)/服务端错误(500, 502, 503)时,
104
+ 会进行最多 3 次指数退避重试。
105
+
106
+ 参数:
107
+ req: 对话请求对象
108
+
109
+ 返回:
110
+ ChatResponse: AI 回复内容
111
+
112
+ 异常:
113
+ ValueError: API 响应解析失败
114
+ APIConnectionError: 网络连接持续失败
115
+ APIStatusError: 服务端返回非 2xx 状态码且不可重试
116
+ """
117
+ for attempt in range(_MAX_RETRIES):
118
+ try:
119
+ # 构造 messages payload,支持 tool_calls 和 tool_call_id
120
+ messages_payload = []
121
+ for m in req.messages:
122
+ msg: dict = {"role": m.role}
123
+ if m.content is not None:
124
+ msg["content"] = m.content
125
+ if m.tool_calls is not None:
126
+ msg["tool_calls"] = m.tool_calls
127
+ if m.tool_call_id is not None:
128
+ msg["tool_call_id"] = m.tool_call_id
129
+ messages_payload.append(msg)
130
+
131
+ kwargs: dict = {
132
+ "model": self._get_model(req.model),
133
+ "messages": messages_payload,
134
+ "temperature": req.temperature,
135
+ "stream": False,
136
+ }
137
+ if req.tools:
138
+ kwargs["tools"] = req.tools
139
+ kwargs["tool_choice"] = req.tool_choice if req.tool_choice is not None else "auto"
140
+
141
+ resp = await self.client.chat.completions.create(**kwargs)
142
+ if not resp.choices:
143
+ raise ValueError("API 返回空的 choices 列表")
144
+ message = resp.choices[0].message
145
+ content = message.content or ""
146
+
147
+ # 解析 tool_calls
148
+ tool_calls = None
149
+ raw_tool_calls = getattr(message, "tool_calls", None)
150
+ if raw_tool_calls:
151
+ tool_calls = []
152
+ for tc in raw_tool_calls:
153
+ tool_calls.append(
154
+ {
155
+ "id": getattr(tc, "id", ""),
156
+ "type": getattr(tc, "type", "function"),
157
+ "function": {
158
+ "name": tc.function.name,
159
+ "arguments": tc.function.arguments,
160
+ },
161
+ }
162
+ )
163
+
164
+ return ChatResponse(content=content, tool_calls=tool_calls, model=resp.model)
165
+ except (APIConnectionError, APIStatusError) as e:
166
+ # 非可重试状态码直接抛出异常
167
+ if isinstance(e, APIStatusError) and e.status_code not in (429, 500, 502, 503):
168
+ raise
169
+ # 未达到最大重试次数时,指数退避后重试
170
+ if attempt < _MAX_RETRIES - 1:
171
+ delay = _RETRY_BASE_DELAY * (2**attempt)
172
+ logger.warning(f"[OpenAI] 请求失败(第 {attempt + 1} 次),{delay:.1f}s 后重试: {e}")
173
+ await asyncio.sleep(delay)
174
+ else:
175
+ raise
176
+ except (IndexError, AttributeError, TypeError) as e:
177
+ raise ValueError(f"解析 API 响应失败: {e}") from e
178
+
179
+ # 所有重试已耗尽且未正常返回,理论上不会到达此处
180
+ raise RuntimeError("OpenAI 请求在重试后仍未完成")
181
+
182
+ async def chat_stream(self, req: ChatRequest) -> AsyncIterator[StreamChunk]:
183
+ """
184
+ @methoddesc 发送流式对话请求(支持 tools 与 tool_calls 分片累积),逐块返回统一 StreamChunk
185
+
186
+ 统一输出契约:
187
+ - delta 文本 → StreamChunk(type="delta", text=...)
188
+ - tool_calls(分片累积, finish_reason="tool_calls" 时) → StreamChunk(type="tool_calls", tool_calls=[...原始格式])
189
+
190
+ 重试策略(与 chat 对齐):
191
+ - 网络错误(APIConnectionError)或限流/服务端错误(429/500/502/503)触发指数退避重试,最多 _MAX_RETRIES 次。
192
+ - **关键安全门**:只在"尚未 yield 任何 chunk 前"重试。一旦已经向下游输出过 token,
193
+ 即使后续流中断也不重试(避免重复输出导致内容错乱,且无法回收已发出的 chunk)。
194
+ - 已 yield 后的异常直接抛出,由上层 executor 处理。
195
+
196
+ 参数:
197
+ req: 对话请求对象
198
+
199
+ 返回:
200
+ AsyncIterator[StreamChunk]: 统一流式输出单元
201
+
202
+ 异常:
203
+ APIConnectionError: 网络连接失败(重试耗尽后)
204
+ APIStatusError: 服务端返回错误(重试耗尽后)
205
+ ValueError: 流式响应解析异常
206
+ """
207
+ # 构造 messages payload(与非流式 chat 一致,支持 tool_calls/tool_call_id 上下文)
208
+ messages_payload = []
209
+ for m in req.messages:
210
+ msg: dict = {"role": m.role}
211
+ if m.content is not None:
212
+ msg["content"] = m.content
213
+ if m.tool_calls is not None:
214
+ msg["tool_calls"] = m.tool_calls
215
+ if m.tool_call_id is not None:
216
+ msg["tool_call_id"] = m.tool_call_id
217
+ messages_payload.append(msg)
218
+
219
+ kwargs: dict = {
220
+ "model": self._get_model(req.model),
221
+ "messages": messages_payload,
222
+ "temperature": req.temperature,
223
+ "stream": True,
224
+ }
225
+ if req.tools:
226
+ kwargs["tools"] = req.tools
227
+ kwargs["tool_choice"] = req.tool_choice if req.tool_choice is not None else "auto"
228
+
229
+ last_exc: Exception | None = None
230
+ for attempt in range(_MAX_RETRIES):
231
+ # 本轮标志:是否已向下游 yield 过任何 chunk。决定异常时能否重试。
232
+ yielded_any = False
233
+ try:
234
+ stream = await self.client.chat.completions.create(**kwargs)
235
+
236
+ # tool_calls 分片累积器: {index: {"id":"", "name":"", "arguments":""}}
237
+ tc_acc: dict[int, dict[str, str]] = {}
238
+ async for chunk in stream:
239
+ if not chunk.choices:
240
+ continue
241
+ choice = chunk.choices[0]
242
+ delta = choice.delta
243
+
244
+ # 1. 文本增量 → 立即 yield
245
+ if delta and delta.content:
246
+ yielded_any = True
247
+ yield StreamChunk(type="delta", text=delta.content)
248
+
249
+ # 2. tool_calls 分片 → 累积(不立即 yield)
250
+ if delta and delta.tool_calls:
251
+ for tc in delta.tool_calls:
252
+ slot = tc_acc.setdefault(tc.index, {"id": "", "name": "", "arguments": ""})
253
+ if getattr(tc, "id", None):
254
+ slot["id"] = tc.id
255
+ if getattr(tc.function, "name", None):
256
+ slot["name"] = tc.function.name
257
+ slot["arguments"] += tc.function.arguments or ""
258
+
259
+ # 3. finish_reason="tool_calls" → 一次性 yield 完整 tool_calls(OpenAI 原始格式)
260
+ if choice.finish_reason == "tool_calls":
261
+ yielded_any = True
262
+ yield StreamChunk(
263
+ type="tool_calls",
264
+ tool_calls=[
265
+ {
266
+ "id": v["id"],
267
+ "type": "function",
268
+ "function": {
269
+ "name": v["name"],
270
+ "arguments": v["arguments"],
271
+ },
272
+ }
273
+ for v in tc_acc.values()
274
+ ],
275
+ )
276
+ tc_acc.clear()
277
+ # finish_reason="stop" → 流自然结束(循环结束),无需特殊处理
278
+ # 流正常耗尽,返回
279
+ return
280
+ except (APIConnectionError, APIStatusError) as e:
281
+ last_exc = e
282
+ # 非可重试状态码直接抛出(与 chat 一致)
283
+ if isinstance(e, APIStatusError) and e.status_code not in (429, 500, 502, 503):
284
+ raise
285
+ # 已 yield 出 chunk 则不再重试(无法回收已发出的内容)
286
+ if yielded_any:
287
+ raise
288
+ # 未达最大重试次数 → 指数退避后重试
289
+ if attempt < _MAX_RETRIES - 1:
290
+ delay = _RETRY_BASE_DELAY * (2**attempt)
291
+ logger.warning(f"[OpenAI] 流式请求失败(第 {attempt + 1} 次),{delay:.1f}s 后重试: {e}")
292
+ await asyncio.sleep(delay)
293
+ else:
294
+ raise
295
+ except Exception as e:
296
+ # 已 yield 出 chunk 后的非预期异常:不可重试(无法回收)
297
+ if yielded_any:
298
+ raise ValueError(f"流式响应异常(已输出部分内容): {e}") from e
299
+ # 未 yield 前的解析类异常:不重试,直接抛出(与 chat 的 IndexError/AttributeError 分支对齐)
300
+ raise ValueError(f"流式响应异常: {e}") from e
301
+
302
+ # 理论上不会到达(重试耗尽会在循环内 raise)
303
+ if last_exc is not None:
304
+ raise last_exc
305
+ raise RuntimeError("OpenAI 流式请求在重试后仍未完成")
306
+
307
+ async def list_models(self) -> list[str]:
308
+ """
309
+ @methoddesc 获取当前 Provider 可用的模型列表
310
+
311
+ 返回:
312
+ 模型 ID 字符串列表
313
+ """
314
+ models = await self.client.models.list()
315
+ return [m.id for m in models.data]
316
+
317
+ async def health(self) -> dict[str, Any]:
318
+ """
319
+ @methoddesc 健康检查
320
+
321
+ 尝试获取模型列表并计算延迟。
322
+
323
+ 返回:
324
+ {"status": "ok", "latency_ms": 延迟毫秒数} 或
325
+ {"status": "error", "error": 错误信息}
326
+ """
327
+ import time
328
+
329
+ start = time.time()
330
+ try:
331
+ await self.client.models.list()
332
+ return {"status": "ok", "latency_ms": int((time.time() - start) * 1000)}
333
+ except Exception as e:
334
+ return {"status": "error", "error": str(e)}
@@ -0,0 +1,108 @@
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 Provider 注册表
18
+
19
+ 功能概述:
20
+ - 管理 ProviderType 到具体 Provider 实现类的映射
21
+ - 提供统一的 Provider 实例创建接口
22
+ - 自动注册内置 Provider(OpenAI、Ollama)
23
+
24
+ 架构设计:
25
+ - 全局字典注册表:运行时动态注册/查询
26
+ - 工厂模式:create(config) 根据配置类型自动实例化对应 Provider
27
+ - 与 base.py 解耦:新增 Provider 只需在此注册即可
28
+
29
+ 输入示例:
30
+ from app.shared.services.llm.config.models import AIProvider, ProviderType
31
+ config = AIProvider(id="test", name="Test", type=ProviderType.OPENAI, base_url="...", model="gpt-4")
32
+
33
+ 输出示例:
34
+ provider = create(config)
35
+ # 返回 OpenAIProvider 实例(因为 config.type == ProviderType.OPENAI)
36
+ """
37
+
38
+ from ..config.models import AIProvider, ProviderType
39
+ from .base import BaseProvider
40
+ from .cached_provider import CachedProvider
41
+ from .ollama import OllamaProvider
42
+ from .openai import OpenAIProvider
43
+
44
+ # 全局注册表:ProviderType -> Provider 类 的映射字典
45
+ _registry: dict[ProviderType, type[BaseProvider]] = {}
46
+
47
+
48
+ def register(type: ProviderType, cls: type[BaseProvider]) -> None:
49
+ """
50
+ @methoddesc 将 Provider 实现类注册到全局注册表中
51
+
52
+ 参数:
53
+ type: Provider 类型枚举值(如 ProviderType.OPENAI)
54
+ cls: 继承自 BaseProvider 的实现类
55
+
56
+ 示例:
57
+ >>> register(ProviderType.OPENAI, OpenAIProvider)
58
+ """
59
+ _registry[type] = cls
60
+
61
+
62
+ def create(config: AIProvider) -> BaseProvider:
63
+ """
64
+ @methoddesc 根据配置创建对应的 Provider 实例
65
+
66
+ 工厂模式:根据 config.type 从注册表中查找实现类并实例化。
67
+ 如果全局 AIConfig 启用了缓存,则用 CachedProvider 包装。
68
+
69
+ 参数:
70
+ config: AIProvider 配置对象
71
+
72
+ 返回:
73
+ BaseProvider 子类实例(可能被 CachedProvider 包装)
74
+
75
+ 异常:
76
+ ValueError: 未注册的 Provider 类型
77
+
78
+ 示例:
79
+ >>> provider = create(config)
80
+ """
81
+ cls = _registry.get(config.type)
82
+ if not cls:
83
+ raise ValueError(f"Unknown provider type: {config.type}")
84
+ real = cls(config)
85
+
86
+ # 尝试从全局配置加载缓存设置
87
+ try:
88
+ from ..config import loader as _loader
89
+
90
+ ai_config = _loader.load()
91
+ cache_cfg = ai_config.cache
92
+ if cache_cfg.enabled:
93
+ from ..cache.response_cache import ResponseCache
94
+
95
+ cache = ResponseCache(
96
+ max_entries=cache_cfg.max_entries,
97
+ ttl_seconds=cache_cfg.ttl_seconds,
98
+ )
99
+ return CachedProvider(real, cache, cache_cfg.cache_temperature_above_zero)
100
+ except Exception:
101
+ pass # 配置加载失败时降级为无缓存
102
+
103
+ return real
104
+
105
+
106
+ # 注册内置 Provider
107
+ register(ProviderType.OPENAI, OpenAIProvider)
108
+ register(ProviderType.OLLAMA, OllamaProvider)
@@ -0,0 +1,141 @@
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 Schema 查找与 ID 解析模块
18
+
19
+ 功能概述:
20
+ - 在工作区的 schemas 目录中按名称查找匹配的表
21
+ - 支持精确匹配和模糊包含匹配
22
+ - 根据表名/列名反向解析对应的系统 ID
23
+
24
+ 输入示例:
25
+ matches = find_matching_schemas("/path/to/project", "users")
26
+ table_id, column_id = _resolve_id_from_name("/path/to/project", "users", "email")
27
+
28
+ 输出示例:
29
+ [{"id": "sc_xxx", "name": "系统用户表"}]
30
+ ("sc_xxx", "col_email")
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import logging
36
+ from pathlib import Path
37
+
38
+ import yaml
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ def find_matching_schemas(workspace_path: str, query_name: str) -> list[dict[str, str]]:
44
+ """
45
+ @methoddesc 在本地 schema 文件中查找匹配的表
46
+
47
+ 支持精确匹配和模糊包含匹配,优先返回精确匹配结果。
48
+
49
+ 参数:
50
+ workspace_path: 工作区路径
51
+ query_name: 用户提供的名称 (可能是 table_id, tableName 或 targetNodeId)
52
+
53
+ 返回:
54
+ 匹配到的候选列表: [{"id": "sc_xxx", "name": "系统用户表"}]
55
+
56
+ 示例:
57
+ >>> find_matching_schemas("/workspace", "users")
58
+ [{"id": "users", "name": "users"}]
59
+ """
60
+ if not query_name:
61
+ return []
62
+
63
+ schemas_dir = Path(workspace_path) / "schemas"
64
+ if not schemas_dir.exists():
65
+ return []
66
+
67
+ exact_matches = []
68
+ fuzzy_matches = []
69
+
70
+ query_name_lower = query_name.lower()
71
+
72
+ for schema_file in schemas_dir.glob("*.yaml"):
73
+ try:
74
+ with open(schema_file, encoding="utf-8") as f:
75
+ data = yaml.safe_load(f) or {}
76
+
77
+ table_id = data.get("id")
78
+ table_name = data.get("name")
79
+
80
+ if not table_id or not table_name:
81
+ continue
82
+
83
+ # 1. 精确匹配 (ID 或 Name 完全一致)
84
+ if query_name == table_id or query_name == table_name:
85
+ exact_matches.append({"id": table_id, "name": table_name})
86
+ continue
87
+
88
+ # 2. 模糊匹配 (包含关系)
89
+ if query_name_lower in table_name.lower() or query_name_lower in table_id.lower():
90
+ fuzzy_matches.append({"id": table_id, "name": table_name})
91
+
92
+ except Exception as e:
93
+ logger.warning(f"检索 Schema 失败 {schema_file}: {e}")
94
+ continue
95
+
96
+ # 如果有精确匹配,优先返回精确匹配
97
+ return exact_matches if exact_matches else fuzzy_matches
98
+
99
+
100
+ def _resolve_id_from_name(
101
+ workspace_path: str, table_name: str, column_name: str | None = None
102
+ ) -> tuple[str | None, str | None]:
103
+ """
104
+ @methoddesc 根据名称在本地 schema 文件中查找对应的 ID
105
+
106
+ 仅返回第一个匹配项,用于自动化流程中的名称到 ID 解析。
107
+
108
+ 参数:
109
+ workspace_path: 工作区路径
110
+ table_name: 表名或表 ID
111
+ column_name: 列名或列 ID(可选)
112
+
113
+ 返回:
114
+ 元组 (table_id, column_id),如果未找到则返回 (None, None)
115
+ """
116
+ matches = find_matching_schemas(workspace_path, table_name)
117
+ if not matches:
118
+ return None, None
119
+
120
+ # 获取第一个匹配的表 ID
121
+ table_id = matches[0]["id"]
122
+ column_id = None
123
+
124
+ # 如果有列名,需要进一步匹配列 ID
125
+ if column_name:
126
+ schemas_dir = Path(workspace_path) / "schemas"
127
+ for schema_file in schemas_dir.glob("*.yaml"):
128
+ try:
129
+ with open(schema_file, encoding="utf-8") as f:
130
+ data = yaml.safe_load(f) or {}
131
+ if data.get("id") == table_id:
132
+ for col in data.get("columns", []):
133
+ if col.get("name") == column_name or col.get("id") == column_name:
134
+ column_id = col.get("id")
135
+ break
136
+ break
137
+ except Exception:
138
+ logger.warning(f"解析 Schema 文件失败: {schema_file}", exc_info=True)
139
+ continue
140
+
141
+ return table_id, column_id