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,104 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """
17
+ @fileoverview AI 生成结果数据模型
18
+
19
+ 功能概述:
20
+ - 定义 AI 配置生成返回的数据结构
21
+ - 包括约束建议、正则建议及整体生成响应
22
+
23
+ 架构设计:
24
+ - Pydantic 模型: 使用 BaseModel 进行数据验证和序列化
25
+ - 嵌套结构: AiGenerationResponse 聚合 AiConstraintSuggestion 和 AiRegexSuggestion
26
+
27
+ 输入示例:
28
+ suggestion = AiConstraintSuggestion(
29
+ column_name="age", constraint_type="range", params={"min": 0, "max": 150}
30
+ )
31
+
32
+ 输出示例:
33
+ response = AiGenerationResponse(
34
+ constraints=[suggestion],
35
+ regex_suggestions=[]
36
+ )
37
+ """
38
+
39
+ from typing import Any
40
+
41
+ from pydantic import BaseModel, Field
42
+
43
+
44
+ class AiConstraintSuggestion(BaseModel):
45
+ """
46
+ @classdesc AI 建议的数据约束模型
47
+
48
+ 封装 AI 为某一列建议的约束规则,包括约束类型、参数和说明。
49
+
50
+ 字段:
51
+ table_name: 目标表名(可选)
52
+ column_name: 目标列名(必填)
53
+ constraint_type: 约束类型,如 "unique", "not_null", "range"
54
+ description: 约束规则的描述说明(可选)
55
+ params: 约束参数字典(可选)
56
+ """
57
+
58
+ table_name: str | None = Field(None, description="目标表名")
59
+ column_name: str = Field(..., description="目标列名")
60
+ constraint_type: str = Field(..., description="约束类型 (e.g., unique, not_null, range)")
61
+ description: str | None = Field(None, description="约束规则的描述说明")
62
+ params: dict[str, Any] | None = Field(default_factory=dict, description="约束参数 (e.g., {'min': 0, 'max': 100})")
63
+
64
+
65
+ class AiRegexSuggestion(BaseModel):
66
+ """
67
+ @classdesc AI 建议的正则表达式模型
68
+
69
+ 封装 AI 为某一列建议的正则校验规则。
70
+
71
+ 字段:
72
+ table_name: 目标表名(可选)
73
+ column_name: 目标列名(必填)
74
+ pattern: 正则表达式模式
75
+ regex_type: 正则类型,如 email、phone、custom(可选)
76
+ description: 正则规则的描述说明(可选)
77
+ params: 正则相关参数(可选)
78
+ """
79
+
80
+ table_name: str | None = Field(None, description="目标表名")
81
+ column_name: str = Field(..., description="目标列名")
82
+ pattern: str = Field(..., description="正则表达式模式")
83
+ regex_type: str | None = Field(None, description="正则类型 (e.g., email, phone, custom)")
84
+ description: str | None = Field(None, description="正则规则的描述说明")
85
+ params: dict[str, Any] | None = Field(default_factory=dict, description="正则相关参数")
86
+
87
+
88
+ class AiGenerationResponse(BaseModel):
89
+ """
90
+ @classdesc AI 生成响应模型
91
+
92
+ 聚合 AI 配置生成的完整结果,包含所有建议的约束和正则规则。
93
+
94
+ 字段:
95
+ table_name: 相关的表名(可选)
96
+ description: 生成结果的整体描述(可选)
97
+ constraints: 建议的约束列表
98
+ regex_suggestions: 建议的正则列表
99
+ """
100
+
101
+ table_name: str | None = Field(None, description="相关的表名")
102
+ description: str | None = Field(None, description="生成结果的整体描述")
103
+ constraints: list[AiConstraintSuggestion] = Field(default_factory=list, description="建议的约束列表")
104
+ regex_suggestions: list[AiRegexSuggestion] = Field(default_factory=list, description="建议的正则列表")
@@ -0,0 +1,48 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Copyright 2026 Precis Team
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ """@fileoverview LLM Provider 模块入口
17
+
18
+ 功能概述:
19
+ - 统一导出各 Provider 实现(OpenAIProvider、OllamaProvider)及注册表接口
20
+ - 为上层业务提供统一的 Provider 创建和调用入口
21
+
22
+ 架构设计:
23
+ - 聚合导出 base、openai、ollama、registry 子模块的公共接口
24
+ - 遵循显式导出原则,使用 __all__ 控制公开 API
25
+ - 新增 Provider 只需在 registry.py 注册即可自动可用
26
+
27
+ 输入示例:
28
+ from app.shared.services.llm.providers import create, BaseProvider, ChatRequest
29
+
30
+ 输出示例:
31
+ provider = create(config) # 根据配置自动实例化对应 Provider
32
+ """
33
+
34
+ from .base import BaseProvider, ChatMessage, ChatRequest, ChatResponse
35
+ from .ollama import OllamaProvider
36
+ from .openai import OpenAIProvider
37
+ from .registry import create, register
38
+
39
+ __all__ = [
40
+ "BaseProvider",
41
+ "ChatMessage",
42
+ "ChatRequest",
43
+ "ChatResponse",
44
+ "register",
45
+ "create",
46
+ "OpenAIProvider",
47
+ "OllamaProvider",
48
+ ]
@@ -0,0 +1,310 @@
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
+ - 定义所有 AI Provider 必须实现的抽象接口
21
+ - 定义对话消息、请求、响应的数据结构(ChatMessage、ChatRequest、ChatResponse)
22
+ - 提供模型名称选择逻辑(用户覆盖或配置默认)
23
+
24
+ 架构设计:
25
+ - 抽象基类模式:BaseProvider 为 ABC,子类必须实现 chat、chat_stream、list_models、health
26
+ - Dataclass 定义消息契约,轻量且无外部依赖
27
+ - 与 AIProvider 配置模型解耦:基类只关心运行时行为
28
+
29
+ 输入示例:
30
+ req = ChatRequest(
31
+ messages=[ChatMessage(role="user", content="你好")],
32
+ model="gpt-4",
33
+ temperature=0.7
34
+ )
35
+
36
+ 输出示例:
37
+ resp = await provider.chat(req)
38
+ # ChatResponse(content="你好!有什么可以帮您的?", model="gpt-4")
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ from abc import ABC, abstractmethod
44
+ from collections.abc import AsyncIterator
45
+ from dataclasses import dataclass
46
+ from typing import Any
47
+
48
+ from ..config.models import AIProvider
49
+
50
+ # 用户未指定 context_window 且无法自动探测时的全局回退值。
51
+ # 云厂商的 /v1/models 接口普遍不暴露 context window,因此无法零维护地自动获取;
52
+ # 统一使用一个保守而通用的回退值,避免维护易过期的硬编码模型表。
53
+ DEFAULT_FALLBACK_CONTEXT_WINDOW = 200000
54
+
55
+
56
+ @dataclass
57
+ class ChatMessage:
58
+ """单条对话消息
59
+
60
+ Attributes:
61
+ role: 消息角色,如 "system"(系统)、"user"(用户)、"assistant"(AI助手)、"tool"(工具结果)
62
+ content: 消息文本内容
63
+ tool_calls: assistant 消息中的工具调用请求列表(OpenAI tools 协议)
64
+ tool_call_id: tool 角色消息对应的工具调用 ID
65
+ """
66
+
67
+ role: str
68
+ content: str | None = None
69
+ tool_calls: list[dict[str, Any]] | None = None
70
+ tool_call_id: str | None = None
71
+
72
+
73
+ @dataclass
74
+ class ChatRequest:
75
+ """对话请求参数
76
+
77
+ Attributes:
78
+ messages: 对话消息列表(至少包含一条用户消息)
79
+ model: 指定使用的模型名称,None 则使用 Provider 配置的默认模型
80
+ stream: 是否启用流式输出(True 时逐字返回,False 时一次性返回)
81
+ temperature: 采样温度(0~1 之间),值越低输出越确定,越高越有创造性
82
+ tools: OpenAI tools 定义列表,用于 function calling
83
+ tool_choice: 工具选择策略,如 "auto"、"none" 或指定某个工具
84
+ """
85
+
86
+ messages: list[ChatMessage]
87
+ model: str | None = None
88
+ stream: bool = False
89
+ temperature: float = 0.7
90
+ tools: list[dict[str, Any]] | None = None
91
+ tool_choice: str | dict[str, Any] | None = None
92
+
93
+
94
+ @dataclass
95
+ class ChatResponse:
96
+ """对话响应结果
97
+
98
+ Attributes:
99
+ content: AI 回复的文本内容
100
+ tool_calls: AI 请求调用的工具列表(OpenAI tools 协议)
101
+ model: 实际使用的模型名称
102
+ """
103
+
104
+ content: str | None = None
105
+ tool_calls: list[dict[str, Any]] | None = None
106
+ model: str | None = None
107
+
108
+
109
+ class BaseProvider(ABC):
110
+ """
111
+ @classdesc Provider 抽象基类
112
+
113
+ 定义所有 AI Provider 必须实现的抽象接口。
114
+ 子类必须实现 chat、chat_stream、list_models、health 四个方法。
115
+
116
+ 设计原则:
117
+ - 统一接口:所有 Provider 提供相同的调用方式
118
+ - 与配置解耦:基类只关心运行时行为
119
+ - 模型选择:提供 _get_model 方法处理用户覆盖和配置默认
120
+ """
121
+
122
+ def __init__(self, config: AIProvider):
123
+ self.cfg = config
124
+
125
+ @property
126
+ @abstractmethod
127
+ def name(self) -> str:
128
+ """
129
+ @methoddesc Provider 显示名称
130
+ """
131
+ pass
132
+
133
+ @property
134
+ def model(self) -> str:
135
+ """@methoddesc 当前配置的模型名称。"""
136
+ return self.cfg.model
137
+
138
+ @abstractmethod
139
+ async def chat(self, req: ChatRequest) -> ChatResponse:
140
+ """
141
+ @methoddesc 非流式对话
142
+ """
143
+ pass
144
+
145
+ @abstractmethod
146
+ def chat_stream(self, req: ChatRequest) -> AsyncIterator[StreamChunk]:
147
+ """
148
+ @methoddesc 流式对话
149
+
150
+ 所有 Provider 必须返回 AsyncIterator[StreamChunk] 统一输出契约。
151
+ """
152
+ pass
153
+
154
+ @abstractmethod
155
+ async def list_models(self) -> list[str]:
156
+ """
157
+ @methoddesc 获取可用模型列表
158
+ """
159
+ pass
160
+
161
+ @abstractmethod
162
+ async def health(self) -> dict[str, Any]:
163
+ """
164
+ @methoddesc 健康检查
165
+
166
+ 返回:
167
+ {"status": "ok|error", ...}
168
+ """
169
+ pass
170
+
171
+ def _get_model(self, override: str | None = None) -> str:
172
+ """
173
+ @methoddesc 获取实际使用的模型名称
174
+
175
+ 如果用户指定了覆盖值,则使用覆盖值;否则使用配置中的默认模型。
176
+ """
177
+ return override or self.cfg.model
178
+
179
+ def get_context_window(self, model: str | None = None) -> int:
180
+ """
181
+ @methoddesc 获取指定模型的上下文窗口大小
182
+
183
+ 三层优先级:
184
+ 1. AIProvider 配置中显式指定的 context_window(用户输入,权威)
185
+ 2. 子类实现的 _resolve_context_window() 自动探测(如 Ollama 查询本地服务)
186
+ 3. 全局回退值 DEFAULT_FALLBACK_CONTEXT_WINDOW(200000 tokens)
187
+
188
+ Args:
189
+ model: 模型名称,None 则使用配置中的默认模型
190
+
191
+ Returns:
192
+ 上下文窗口大小(tokens)
193
+ """
194
+ if self.cfg.context_window is not None:
195
+ return self.cfg.context_window
196
+
197
+ resolved = self._resolve_context_window(model)
198
+ if resolved is not None:
199
+ return resolved
200
+ return DEFAULT_FALLBACK_CONTEXT_WINDOW
201
+
202
+ def _resolve_context_window(self, model: str | None) -> int | None:
203
+ """
204
+ @methoddesc 子类钩子:返回自动探测到的上下文窗口大小
205
+
206
+ 基类默认返回 None(不探测),由 get_context_window 回退到全局默认值。
207
+ 子类(如 OllamaProvider)可覆盖此方法,从 Provider 服务实时获取真实值。
208
+
209
+ Args:
210
+ model: 模型名称,None 则使用配置中的默认模型
211
+
212
+ Returns:
213
+ 探测到的上下文窗口大小,探测不到则返回 None
214
+ """
215
+ return None
216
+
217
+
218
+ def get_context_window_for_provider(config: AIProvider, model: str | None = None) -> int:
219
+ """
220
+ @methoddesc 根据 AIProvider 配置获取上下文窗口大小(仅看配置,不探测)
221
+
222
+ 无需实例化 Provider 即可调用,适合 CLI 等只需要读取配置的场景。
223
+ 仅解析两层:用户配置的 context_window > 全局回退值。
224
+ 需要自动探测(如 Ollama)的场景应实例化对应 Provider 并调用 get_context_window()。
225
+
226
+ Args:
227
+ config: AIProvider 配置对象
228
+ model: 模型名称(保留参数以兼容旧调用签名,当前实现未使用)
229
+
230
+ Returns:
231
+ 上下文窗口大小(tokens)
232
+ """
233
+ if config.context_window is not None:
234
+ return config.context_window
235
+ return DEFAULT_FALLBACK_CONTEXT_WINDOW
236
+
237
+
238
+ # 4.24: 预算公式的最小可支撑窗口与保留余量
239
+ MIN_CONTEXT_WINDOW = 4096
240
+ _BUDGET_MARGIN_TOKENS = 512
241
+
242
+
243
+ def compute_token_budgets(context_window: int) -> tuple[int, int]:
244
+ """4.24: 按上下文窗口计算 (输入预算, 输出预算),保证预算和 + 余量 <= 窗口。
245
+
246
+ 原公式 max(cw - 8000, 4096) 在小窗下必超限:cw=8192 时输入 4096 + 输出 8000
247
+ = 12096 > 8192,每次请求必超窗报错,小窗模型完全不可用。现在:
248
+ - 输出预算 = min(8000, max(512, cw // 4))
249
+ - 输入预算 = cw - 输出预算 - 512 余量(下限 512)
250
+ - cw < 4096 视为无法支撑对话,抛 ValueError(调用方明确报错)
251
+
252
+ 返回:
253
+ (input_budget, output_budget)
254
+ """
255
+ if context_window < MIN_CONTEXT_WINDOW:
256
+ raise ValueError(
257
+ f"上下文窗口过小({context_window} tokens),至少需要 {MIN_CONTEXT_WINDOW} tokens 才能支撑对话"
258
+ )
259
+ output_budget = min(8000, max(512, context_window // 4))
260
+ input_budget = max(512, context_window - output_budget - _BUDGET_MARGIN_TOKENS)
261
+ return input_budget, output_budget
262
+
263
+
264
+ def resolve_context_window(config: AIProvider, model: str | None = None) -> int:
265
+ """
266
+ @methoddesc 统一的上下文窗口解析入口(同步)
267
+
268
+ 适用于 CLI 等只持有 AIProvider 配置、无法直接拿到 Provider 实例的场景。
269
+ 根据配置类型实例化对应 Provider 并调用 get_context_window(),
270
+ 自动应用三层优先级(用户输入 > 自动探测 > 全局回退)。
271
+
272
+ Args:
273
+ config: AIProvider 配置对象
274
+ model: 模型名称,None 则使用配置中的默认模型
275
+
276
+ Returns:
277
+ 上下文窗口大小(tokens)
278
+
279
+ 说明:
280
+ 若用户已在配置中显式指定 context_window,直接返回,避免实例化 Provider。
281
+ 延迟导入 registry 以避免循环依赖。
282
+ """
283
+ if config.context_window is not None:
284
+ return config.context_window
285
+
286
+ from .registry import create
287
+
288
+ provider = create(config)
289
+ return provider.get_context_window(model)
290
+
291
+
292
+ @dataclass
293
+ class StreamChunk:
294
+ """@classdesc chat_stream 的统一输出单元
295
+
296
+ 所有 Provider 的 chat_stream 必须返回 AsyncIterator[StreamChunk]。
297
+ 统一两边输出契约,上层 AgentExecutor 只消费此类型,不关心 Provider 差异。
298
+
299
+ Attributes:
300
+ type: "delta"(文本增量) 或 "tool_calls"(完整工具调用集,一次性产出)
301
+ text: type="delta" 时的文本增量,其余为 None
302
+ tool_calls: type="tool_calls" 时的 OpenAI 原始格式 tool_call dict 列表,
303
+ 格式与 parse_tool_call 期望一致: [{"id":..., "function":{"name":..., "arguments":"<json str>"}}],
304
+ 其余为 None。注意: 必须是原始 dict 而非项目内 ToolCall dataclass,
305
+ 以便直接喂给 registry.parse_tool_call() 复用。
306
+ """
307
+
308
+ type: str # Literal["delta", "tool_calls"] 用 str 避免循环导入
309
+ text: str | None = None
310
+ tool_calls: list[dict[str, Any]] | None = None
@@ -0,0 +1,90 @@
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
+ """缓存装饰器 Provider:包装真实 provider 透明缓存 chat/流式响应。"""
17
+
18
+ from __future__ import annotations
19
+
20
+ from collections.abc import AsyncIterator
21
+ from typing import cast
22
+
23
+ from app.shared.services.llm.cache.response_cache import ResponseCache, cache_key
24
+
25
+ from .base import BaseProvider, ChatRequest, ChatResponse, StreamChunk
26
+
27
+
28
+ class CachedProvider(BaseProvider):
29
+ """缓存装饰器:包装真实 provider,透明缓存 chat/chat_stream 响应。"""
30
+
31
+ def __init__(
32
+ self,
33
+ real: BaseProvider,
34
+ cache: ResponseCache,
35
+ cache_temperature_above_zero: bool = False,
36
+ ):
37
+ self._real = real
38
+ self._cache = cache
39
+ self._cache_temp = cache_temperature_above_zero
40
+
41
+ @property
42
+ def name(self) -> str:
43
+ return f"Cached({self._real.name})"
44
+
45
+ @property
46
+ def model(self) -> str:
47
+ return self._real.model
48
+
49
+ def _cacheable(self, req: ChatRequest) -> bool:
50
+ """temperature > 0 默认不缓存(随机性),除非显式开启。"""
51
+ return self._cache_temp or (req.temperature or 0) == 0
52
+
53
+ async def chat(self, req: ChatRequest) -> ChatResponse:
54
+ if not self._cacheable(req):
55
+ return await self._real.chat(req)
56
+ key = cache_key(req)
57
+ cached = self._cache.get(key)
58
+ if cached is not None:
59
+ return cast(ChatResponse, cached)
60
+ resp = await self._real.chat(req)
61
+ if resp.content or resp.tool_calls:
62
+ self._cache.put(key, resp)
63
+ return resp
64
+
65
+ async def chat_stream(self, req: ChatRequest) -> AsyncIterator[StreamChunk]:
66
+ if not self._cacheable(req):
67
+ async for c in self._real.chat_stream(req):
68
+ yield c
69
+ return
70
+ key = cache_key(req)
71
+ cached = self._cache.get(key)
72
+ if cached is not None:
73
+ for c in cached:
74
+ yield c
75
+ return
76
+ chunks: list[StreamChunk] = []
77
+ async for c in self._real.chat_stream(req):
78
+ chunks.append(c)
79
+ yield c
80
+ if chunks:
81
+ self._cache.put(key, chunks)
82
+
83
+ async def list_models(self) -> list[str]:
84
+ return await self._real.list_models()
85
+
86
+ async def health(self) -> dict:
87
+ return await self._real.health()
88
+
89
+ def get_context_window(self, model: str | None = None) -> int:
90
+ return self._real.get_context_window(model)