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.
- app/__init__.py +16 -0
- app/api/__init__.py +32 -0
- app/api/dependencies.py +185 -0
- app/api/main.py +301 -0
- app/api/middleware/__init__.py +34 -0
- app/api/middleware/exception_handler.py +126 -0
- app/api/middleware/request_logging.py +97 -0
- app/api/middleware/token_auth.py +215 -0
- app/api/models/__init__.py +151 -0
- app/api/models/connection_rules.py +98 -0
- app/api/models/files.py +99 -0
- app/api/models/full_validation.py +554 -0
- app/api/models/project.py +117 -0
- app/api/models/projects.py +80 -0
- app/api/models/schema.py +121 -0
- app/api/models/v2_responses.py +46 -0
- app/api/models/validation.py +303 -0
- app/api/models/workspace.py +118 -0
- app/api/routers/__init__.py +56 -0
- app/api/routers/ai/__init__.py +36 -0
- app/api/routers/ai/chat.py +243 -0
- app/api/routers/ai/generate.py +156 -0
- app/api/routers/ai/hardware.py +199 -0
- app/api/routers/ai/jobs.py +544 -0
- app/api/routers/ai/migrate.py +471 -0
- app/api/routers/ai/models.py +352 -0
- app/api/routers/ai/ollama.py +108 -0
- app/api/routers/ai/providers.py +491 -0
- app/api/routers/ai/router.py +44 -0
- app/api/routers/ai/stream.py +281 -0
- app/api/routers/ai/utils.py +182 -0
- app/api/routers/core/__init__.py +34 -0
- app/api/routers/core/connection_rules.py +188 -0
- app/api/routers/core/data_sources.py +374 -0
- app/api/routers/core/regex.py +333 -0
- app/api/routers/core/reporting.py +263 -0
- app/api/routers/files/__init__.py +24 -0
- app/api/routers/files/ops.py +179 -0
- app/api/routers/files/transfer.py +101 -0
- app/api/routers/preview/__init__.py +35 -0
- app/api/routers/preview/content_mode.py +264 -0
- app/api/routers/preview/header_row.py +154 -0
- app/api/routers/preview/models.py +81 -0
- app/api/routers/preview/path_mode.py +440 -0
- app/api/routers/preview/router.py +30 -0
- app/api/routers/project/__init__.py +61 -0
- app/api/routers/project/base.py +111 -0
- app/api/routers/project/constraint.py +345 -0
- app/api/routers/project/full_config.py +411 -0
- app/api/routers/project/full_config_writer.py +327 -0
- app/api/routers/project/helpers.py +167 -0
- app/api/routers/project/inspection_fix.py +330 -0
- app/api/routers/project/manifest.py +605 -0
- app/api/routers/project/models.py +177 -0
- app/api/routers/project/pattern.py +268 -0
- app/api/routers/project/regex.py +358 -0
- app/api/routers/project/scanner.py +157 -0
- app/api/routers/project/schema.py +567 -0
- app/api/routers/project/schema_helpers.py +149 -0
- app/api/routers/project/settings.py +399 -0
- app/api/routers/project/template.py +325 -0
- app/api/routers/project/validation.py +227 -0
- app/api/routers/project/view.py +177 -0
- app/api/routers/project/workspaces.py +167 -0
- app/api/routers/projects/__init__.py +25 -0
- app/api/routers/projects/create.py +121 -0
- app/api/routers/projects/open.py +103 -0
- app/api/routers/projects/scan.py +100 -0
- app/api/routers/validation/__init__.py +39 -0
- app/api/routers/validation/common.py +263 -0
- app/api/routers/validation/content_mode.py +401 -0
- app/api/routers/validation/history.py +235 -0
- app/api/routers/validation/inline_mode.py +196 -0
- app/api/routers/validation/path_mode.py +314 -0
- app/api/routers/validation/router.py +51 -0
- app/api/services/full_validation_response_builder.py +412 -0
- app/api/services/io_error_messages.py +41 -0
- app/api/services/preview_service.py +248 -0
- app/cli/__init__.py +18 -0
- app/cli/__main__.py +30 -0
- app/cli/shared_services/__init__.py +42 -0
- app/cli/shared_services/config_ops.py +581 -0
- app/cli/shared_services/generation_ops.py +160 -0
- app/cli/shared_services/project_ops.py +288 -0
- app/cli/shell/__init__.py +48 -0
- app/cli/shell/commands/__init__.py +63 -0
- app/cli/shell/commands/ai/__init__.py +260 -0
- app/cli/shell/commands/ai/chat.py +331 -0
- app/cli/shell/commands/ai/delete.py +165 -0
- app/cli/shell/commands/ai/diff.py +70 -0
- app/cli/shell/commands/ai/display.py +428 -0
- app/cli/shell/commands/ai/executor.py +225 -0
- app/cli/shell/commands/ai/executor_utils.py +271 -0
- app/cli/shell/commands/ai/generate.py +285 -0
- app/cli/shell/commands/ai/interaction.py +262 -0
- app/cli/shell/commands/ai/migrate.py +281 -0
- app/cli/shell/commands/ai/resolver.py +224 -0
- app/cli/shell/commands/ai/status.py +101 -0
- app/cli/shell/commands/ai/switch.py +158 -0
- app/cli/shell/commands/ai/utils.py +52 -0
- app/cli/shell/commands/base.py +343 -0
- app/cli/shell/commands/config/__init__.py +131 -0
- app/cli/shell/commands/config/base.py +80 -0
- app/cli/shell/commands/config/check.py +246 -0
- app/cli/shell/commands/config/edit.py +148 -0
- app/cli/shell/commands/config/get.py +111 -0
- app/cli/shell/commands/config/init.py +123 -0
- app/cli/shell/commands/config/inspect.py +164 -0
- app/cli/shell/commands/config/list.py +104 -0
- app/cli/shell/commands/config/set.py +119 -0
- app/cli/shell/commands/config/show.py +150 -0
- app/cli/shell/commands/exit.py +67 -0
- app/cli/shell/commands/help.py +104 -0
- app/cli/shell/commands/infer_schema.py +156 -0
- app/cli/shell/commands/open.py +201 -0
- app/cli/shell/commands/project.py +215 -0
- app/cli/shell/commands/provider.py +619 -0
- app/cli/shell/commands/system.py +170 -0
- app/cli/shell/commands/validate.py +455 -0
- app/cli/shell/completer.py +231 -0
- app/cli/shell/config_storage.py +191 -0
- app/cli/shell/exceptions.py +90 -0
- app/cli/shell/formatter.py +371 -0
- app/cli/shell/interactive_menu.py +348 -0
- app/cli/shell/main.py +286 -0
- app/cli/shell/parser.py +266 -0
- app/cli/start.py +180 -0
- app/cli_main.py +52 -0
- app/mcp_server.py +311 -0
- app/shared/core/__init__.py +18 -0
- app/shared/core/app_version.py +59 -0
- app/shared/core/config/__init__.py +119 -0
- app/shared/core/config/server.py +193 -0
- app/shared/core/data_source/__init__.py +106 -0
- app/shared/core/data_source/loader.py +348 -0
- app/shared/core/data_source/loaders/__init__.py +213 -0
- app/shared/core/data_source/loaders/base.py +215 -0
- app/shared/core/data_source/loaders/converter.py +337 -0
- app/shared/core/data_source/loaders/csv_loader.py +252 -0
- app/shared/core/data_source/loaders/excel_loader.py +666 -0
- app/shared/core/data_source/loaders/extractor.py +268 -0
- app/shared/core/data_source/loaders/json_loader.py +354 -0
- app/shared/core/data_source/loaders/registry.py +88 -0
- app/shared/core/data_source/loaders/sql_loader.py +313 -0
- app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
- app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
- app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
- app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
- app/shared/core/data_source/schema_info.py +50 -0
- app/shared/core/data_source/specs/__init__.py +128 -0
- app/shared/core/data_source/specs/base.py +258 -0
- app/shared/core/data_source/specs/csv_source.py +137 -0
- app/shared/core/data_source/specs/excel_source.py +141 -0
- app/shared/core/data_source/specs/file_base.py +252 -0
- app/shared/core/data_source/specs/json_source.py +226 -0
- app/shared/core/data_source/specs/sql_source.py +161 -0
- app/shared/core/encoding.py +36 -0
- app/shared/core/io/__init__.py +24 -0
- app/shared/core/io/yaml.py +379 -0
- app/shared/core/manifest_schema/__init__.py +52 -0
- app/shared/core/manifest_schema/types.py +99 -0
- app/shared/core/manifest_schema/version.py +175 -0
- app/shared/core/patterns/__init__.py +24 -0
- app/shared/core/patterns/loader.py +134 -0
- app/shared/core/patterns/writer.py +244 -0
- app/shared/core/project/__init__.py +47 -0
- app/shared/core/project/constraint/builders/__init__.py +50 -0
- app/shared/core/project/constraint/builders/base.py +100 -0
- app/shared/core/project/constraint/builders/composite.py +77 -0
- app/shared/core/project/constraint/builders/conditional.py +67 -0
- app/shared/core/project/constraint/builders/foreign_key.py +53 -0
- app/shared/core/project/constraint/builders/registry.py +64 -0
- app/shared/core/project/constraint/builders/scripted.py +51 -0
- app/shared/core/project/constraint/builders/single_column.py +86 -0
- app/shared/core/project/constraint/builders/unique.py +53 -0
- app/shared/core/project/constraint/factory.py +170 -0
- app/shared/core/project/constraint/reader.py +214 -0
- app/shared/core/project/constraint/registry.py +233 -0
- app/shared/core/project/constraint/types/__init__.py +63 -0
- app/shared/core/project/constraint/types/constraint_file.py +261 -0
- app/shared/core/project/constraint/types/refs.py +460 -0
- app/shared/core/project/constraint/types.py +28 -0
- app/shared/core/project/constraint/writer.py +181 -0
- app/shared/core/project/loader/__init__.py +56 -0
- app/shared/core/project/loader/loader.py +30 -0
- app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
- app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
- app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
- app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
- app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
- app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
- app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
- app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
- app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
- app/shared/core/project/loader/loader_parts/main.py +432 -0
- app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
- app/shared/core/project/loader/loader_parts/runtime.py +125 -0
- app/shared/core/project/loader/types.py +246 -0
- app/shared/core/project/manifest/coverage.py +391 -0
- app/shared/core/project/manifest/reader.py +260 -0
- app/shared/core/project/manifest/types.py +91 -0
- app/shared/core/project/manifest/types_parts/__init__.py +60 -0
- app/shared/core/project/manifest/types_parts/constants.py +40 -0
- app/shared/core/project/manifest/types_parts/data_source.py +68 -0
- app/shared/core/project/manifest/types_parts/info.py +59 -0
- app/shared/core/project/manifest/types_parts/manifest.py +294 -0
- app/shared/core/project/manifest/types_parts/refs.py +153 -0
- app/shared/core/project/manifest/types_parts/settings.py +92 -0
- app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
- app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
- app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
- app/shared/core/project/manifest/types_parts/template.py +66 -0
- app/shared/core/project/manifest/writer.py +262 -0
- app/shared/core/project/manual_data/__init__.py +27 -0
- app/shared/core/project/manual_data/types.py +75 -0
- app/shared/core/project/regex/reader.py +197 -0
- app/shared/core/project/regex/types.py +405 -0
- app/shared/core/project/regex/writer.py +123 -0
- app/shared/core/project/schema/reader.py +170 -0
- app/shared/core/project/schema/types.py +47 -0
- app/shared/core/project/schema/types_parts/__init__.py +36 -0
- app/shared/core/project/schema/types_parts/column.py +174 -0
- app/shared/core/project/schema/types_parts/column_utils.py +72 -0
- app/shared/core/project/schema/types_parts/constraint.py +165 -0
- app/shared/core/project/schema/types_parts/schema_id.py +66 -0
- app/shared/core/project/schema/types_parts/source.py +255 -0
- app/shared/core/project/schema/types_parts/source_options.py +347 -0
- app/shared/core/project/schema/types_parts/table.py +230 -0
- app/shared/core/project/schema/writer.py +139 -0
- app/shared/core/project/schema_ref_check.py +95 -0
- app/shared/core/project/template/__init__.py +27 -0
- app/shared/core/project/template/expander.py +263 -0
- app/shared/core/project/template/reader.py +120 -0
- app/shared/core/project/template/types.py +114 -0
- app/shared/core/project/transform/reader.py +76 -0
- app/shared/core/project/transform/types.py +116 -0
- app/shared/core/project/transform/writer.py +84 -0
- app/shared/core/pydantic_messages.py +59 -0
- app/shared/core/reporter/__init__.py +46 -0
- app/shared/core/reporter/reporter.py +220 -0
- app/shared/core/reporter/reporters/__init__.py +65 -0
- app/shared/core/reporter/reporters/base.py +188 -0
- app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
- app/shared/core/reporter/reporters/email_reporter.py +271 -0
- app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
- app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
- app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
- app/shared/core/utils/__init__.py +18 -0
- app/shared/core/utils/path_utils.py +60 -0
- app/shared/core/utils/regex_extract.py +113 -0
- app/shared/domain/__init__.py +114 -0
- app/shared/domain/constraints/__init__.py +76 -0
- app/shared/domain/constraints/allowed_values.py +211 -0
- app/shared/domain/constraints/base.py +141 -0
- app/shared/domain/constraints/charset.py +337 -0
- app/shared/domain/constraints/composite.py +174 -0
- app/shared/domain/constraints/condition_registry.py +130 -0
- app/shared/domain/constraints/conditional.py +629 -0
- app/shared/domain/constraints/date_logic.py +731 -0
- app/shared/domain/constraints/foreign_key.py +261 -0
- app/shared/domain/constraints/key_normalization.py +56 -0
- app/shared/domain/constraints/not_null.py +185 -0
- app/shared/domain/constraints/range.py +360 -0
- app/shared/domain/constraints/regex.py +218 -0
- app/shared/domain/constraints/scripted.py +276 -0
- app/shared/domain/constraints/unique.py +233 -0
- app/shared/domain/data_engine.py +384 -0
- app/shared/domain/data_types.py +113 -0
- app/shared/domain/data_types_parts/__init__.py +67 -0
- app/shared/domain/data_types_parts/base.py +204 -0
- app/shared/domain/data_types_parts/composite.py +250 -0
- app/shared/domain/data_types_parts/expression.py +202 -0
- app/shared/domain/data_types_parts/extracted.py +106 -0
- app/shared/domain/data_types_parts/json_types.py +234 -0
- app/shared/domain/data_types_parts/scalars.py +719 -0
- app/shared/domain/data_types_parts/sequence.py +129 -0
- app/shared/domain/dataset_schema.py +48 -0
- app/shared/domain/eval_sandbox.py +63 -0
- app/shared/domain/expression_system.py +366 -0
- app/shared/domain/regex_flags.py +53 -0
- app/shared/domain/schema/builder.py +223 -0
- app/shared/domain/schema/models.py +339 -0
- app/shared/domain/transforms/__init__.py +28 -0
- app/shared/domain/transforms/aggregate.py +141 -0
- app/shared/domain/transforms/base.py +171 -0
- app/shared/domain/transforms/cast_type.py +120 -0
- app/shared/domain/transforms/concat.py +103 -0
- app/shared/domain/transforms/conditional_assign.py +114 -0
- app/shared/domain/transforms/date_format.py +81 -0
- app/shared/domain/transforms/digits.py +77 -0
- app/shared/domain/transforms/drop_duplicates.py +94 -0
- app/shared/domain/transforms/fill_na.py +94 -0
- app/shared/domain/transforms/filter_rows.py +97 -0
- app/shared/domain/transforms/lookup.py +78 -0
- app/shared/domain/transforms/lower_case.py +70 -0
- app/shared/domain/transforms/map_value.py +90 -0
- app/shared/domain/transforms/math_expr.py +112 -0
- app/shared/domain/transforms/modulo.py +82 -0
- app/shared/domain/transforms/regex_extract.py +103 -0
- app/shared/domain/transforms/registry.py +95 -0
- app/shared/domain/transforms/replace.py +88 -0
- app/shared/domain/transforms/sort_rows.py +94 -0
- app/shared/domain/transforms/string_split.py +84 -0
- app/shared/domain/transforms/strip.py +73 -0
- app/shared/domain/transforms/substring.py +92 -0
- app/shared/domain/transforms/upper_case.py +70 -0
- app/shared/domain/transforms/weighted_sum.py +104 -0
- app/shared/domain/validation_constraints.py +69 -0
- app/shared/services/__init__.py +62 -0
- app/shared/services/ai/__init__.py +48 -0
- app/shared/services/ai/agent/__init__.py +40 -0
- app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
- app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
- app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
- app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
- app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
- app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
- app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
- app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
- app/shared/services/ai/agent/executor.py +554 -0
- app/shared/services/ai/agent/memory.py +256 -0
- app/shared/services/ai/agent/planner.py +282 -0
- app/shared/services/ai/agent/tool_registry.py +296 -0
- app/shared/services/ai/agent/tools/__init__.py +32 -0
- app/shared/services/ai/agent/tools/config_generate.py +133 -0
- app/shared/services/ai/agent/tools/config_refine.py +109 -0
- app/shared/services/ai/agent/tools/config_validate.py +403 -0
- app/shared/services/ai/agent/tools/merge_results.py +136 -0
- app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
- app/shared/services/ai/agent/tools/script_parse.py +359 -0
- app/shared/services/ai/agent/types.py +138 -0
- app/shared/services/ai/chat_agent_runner.py +596 -0
- app/shared/services/ai/chat_orchestrator.py +725 -0
- app/shared/services/ai/failure_messages.py +65 -0
- app/shared/services/ai/job_storage.py +274 -0
- app/shared/services/ai/migrate_service.py +550 -0
- app/shared/services/ai/streaming/__init__.py +65 -0
- app/shared/services/ai/streaming/event_journal.py +197 -0
- app/shared/services/ai/streaming/orchestrator.py +262 -0
- app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
- app/shared/services/ai/streaming/sse_response.py +148 -0
- app/shared/services/ai/streaming/types.py +73 -0
- app/shared/services/ai/types.py +213 -0
- app/shared/services/ai/utils.py +322 -0
- app/shared/services/diff/config_diff.py +320 -0
- app/shared/services/hardware.py +237 -0
- app/shared/services/llm/__init__.py +62 -0
- app/shared/services/llm/actions/__init__.py +42 -0
- app/shared/services/llm/actions/_canvas_validator.py +147 -0
- app/shared/services/llm/actions/_constraint_validator.py +401 -0
- app/shared/services/llm/actions/_regex_validator.py +85 -0
- app/shared/services/llm/actions/_schema_validator.py +82 -0
- app/shared/services/llm/actions/_settings_validator.py +76 -0
- app/shared/services/llm/actions/_transform_validator.py +78 -0
- app/shared/services/llm/actions/action_handlers.py +400 -0
- app/shared/services/llm/actions/action_parser.py +63 -0
- app/shared/services/llm/actions/action_processor.py +468 -0
- app/shared/services/llm/actions/action_validator.py +311 -0
- app/shared/services/llm/actions/diff_compute.py +195 -0
- app/shared/services/llm/actions/regex_handlers.py +284 -0
- app/shared/services/llm/actions/registry.py +336 -0
- app/shared/services/llm/actions/schema_handlers.py +335 -0
- app/shared/services/llm/actions/settings_handlers.py +180 -0
- app/shared/services/llm/actions/specs.py +259 -0
- app/shared/services/llm/actions/transform_handlers.py +279 -0
- app/shared/services/llm/actions/validation_types.py +147 -0
- app/shared/services/llm/cache/__init__.py +18 -0
- app/shared/services/llm/cache/response_cache.py +80 -0
- app/shared/services/llm/chat/__init__.py +35 -0
- app/shared/services/llm/chat/chat_system_prompt.py +561 -0
- app/shared/services/llm/chat/response_parser.py +296 -0
- app/shared/services/llm/config/__init__.py +45 -0
- app/shared/services/llm/config/crypto.py +135 -0
- app/shared/services/llm/config/loader.py +258 -0
- app/shared/services/llm/config/models.py +202 -0
- app/shared/services/llm/config/presets.py +128 -0
- app/shared/services/llm/config_generator.py +163 -0
- app/shared/services/llm/constraints/__init__.py +41 -0
- app/shared/services/llm/constraints/constraint_builder.py +177 -0
- app/shared/services/llm/constraints/constraint_deletion.py +84 -0
- app/shared/services/llm/constraints/constraint_id.py +174 -0
- app/shared/services/llm/constraints/frontend_instructions.py +333 -0
- app/shared/services/llm/constraints/inline_batch.py +279 -0
- app/shared/services/llm/discovery/__init__.py +35 -0
- app/shared/services/llm/discovery/scanner.py +181 -0
- app/shared/services/llm/generation/__init__.py +42 -0
- app/shared/services/llm/generation/agent_wiring.py +156 -0
- app/shared/services/llm/generation/config_builder.py +386 -0
- app/shared/services/llm/generation/errors.py +36 -0
- app/shared/services/llm/generation/existing_config.py +99 -0
- app/shared/services/llm/generation/profiler.py +279 -0
- app/shared/services/llm/generation/prompt_builder.py +199 -0
- app/shared/services/llm/generation/response_parser.py +144 -0
- app/shared/services/llm/generation/service.py +622 -0
- app/shared/services/llm/models.py +104 -0
- app/shared/services/llm/providers/__init__.py +48 -0
- app/shared/services/llm/providers/base.py +310 -0
- app/shared/services/llm/providers/cached_provider.py +90 -0
- app/shared/services/llm/providers/ollama.py +498 -0
- app/shared/services/llm/providers/openai.py +334 -0
- app/shared/services/llm/providers/registry.py +108 -0
- app/shared/services/llm/schema_resolver.py +141 -0
- app/shared/services/llm/suggestion_utils.py +219 -0
- app/shared/services/llm/validate_executor.py +163 -0
- app/shared/services/llm/yaml_io.py +406 -0
- app/shared/services/preview/__init__.py +18 -0
- app/shared/services/preview/loader.py +145 -0
- app/shared/services/preview/path_validation.py +138 -0
- app/shared/services/project_loader.py +57 -0
- app/shared/services/schema_inference.py +260 -0
- app/shared/services/schema_runtime_builder.py +120 -0
- app/shared/services/validation/__init__.py +52 -0
- app/shared/services/validation/chunked_loader.py +509 -0
- app/shared/services/validation/dag/__init__.py +35 -0
- app/shared/services/validation/dag/builder.py +141 -0
- app/shared/services/validation/dag/executor.py +225 -0
- app/shared/services/validation/dag/sorter.py +77 -0
- app/shared/services/validation/data_loader.py +231 -0
- app/shared/services/validation/engine.py +446 -0
- app/shared/services/validation/executor.py +1030 -0
- app/shared/services/validation/extractors.py +266 -0
- app/shared/services/validation/history.py +209 -0
- app/shared/services/validation/json_payload.py +136 -0
- app/shared/services/validation/loader.py +152 -0
- app/shared/services/validation/memory_monitor.py +179 -0
- app/shared/services/validation/postprocess.py +208 -0
- app/shared/services/validation/progress.py +64 -0
- app/shared/services/validation/report_export.py +222 -0
- app/shared/services/validation/resolver.py +180 -0
- app/shared/services/validation/service.py +418 -0
- app/shared/services/validation/types.py +238 -0
- app/shared/services/validation/validators/__init__.py +36 -0
- app/shared/services/validation/validators/adapter.py +182 -0
- app/shared/services/validation/validators/base.py +332 -0
- app/shared/services/validation/validators/composite.py +233 -0
- app/shared/services/validation/validators/date_logic.py +276 -0
- app/start_server.py +133 -0
- precis_cli-0.1.3.dist-info/METADATA +180 -0
- precis_cli-0.1.3.dist-info/RECORD +442 -0
- precis_cli-0.1.3.dist-info/WHEEL +5 -0
- precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
- 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)
|