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,301 @@
|
|
|
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 read_table 工具
|
|
17
|
+
|
|
18
|
+
Chat mini-agent 可调用的工具:读取指定表的数据样本。
|
|
19
|
+
|
|
20
|
+
根据表名定位 schema 文件,解析其 source 配置找到数据文件,
|
|
21
|
+
加载前 N 行采样数据返回给 LLM。让 LLM 在设计约束规则前能"看到真实数据",
|
|
22
|
+
从而做出更准确的判断(如合理的 Range 范围、AllowedValues 取值等)。
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import asyncio
|
|
28
|
+
import logging
|
|
29
|
+
import os
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
import pandas as pd
|
|
34
|
+
import yaml
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
# read_table 最多返回的单元格字符数,避免 observation 过长
|
|
39
|
+
_MAX_CELL_CHARS = 200
|
|
40
|
+
# 默认采样行数
|
|
41
|
+
_DEFAULT_SAMPLE_ROWS = 10
|
|
42
|
+
# 最大采样行数上限,防止 LLM 传入过大值
|
|
43
|
+
_MAX_SAMPLE_ROWS = 100
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ReadTableTool:
|
|
47
|
+
"""
|
|
48
|
+
@classdesc 读取表数据样本工具
|
|
49
|
+
|
|
50
|
+
根据表名查找 schema,加载对应数据文件的前 N 行。
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
NAME = "read_table"
|
|
54
|
+
|
|
55
|
+
def __init__(self, project_path: str):
|
|
56
|
+
"""
|
|
57
|
+
@methoddesc 初始化工具
|
|
58
|
+
|
|
59
|
+
参数:
|
|
60
|
+
project_path: 当前项目配置目录路径
|
|
61
|
+
"""
|
|
62
|
+
self.project_path = project_path
|
|
63
|
+
|
|
64
|
+
# schema 索引缓存:避免每次 read_table 都 glob + 逐个解析 YAML
|
|
65
|
+
# 结构: {table_name_lower: (schema_path, source_config)}
|
|
66
|
+
# _schema_index_mtime 记录构建缓存时 schemas 目录的 mtime,用于失效检测
|
|
67
|
+
self._schema_index: dict[str, tuple[str, dict[str, Any]]] = {}
|
|
68
|
+
self._schema_index_mtime: float | None = None
|
|
69
|
+
|
|
70
|
+
def get_definition(self) -> dict[str, Any]:
|
|
71
|
+
"""返回 OpenAI tool 定义。"""
|
|
72
|
+
return {
|
|
73
|
+
"type": "function",
|
|
74
|
+
"function": {
|
|
75
|
+
"name": self.NAME,
|
|
76
|
+
"description": (
|
|
77
|
+
"读取指定表的数据样本(前 N 行),展示列结构和真实数据值。"
|
|
78
|
+
"当需要为某列设计约束规则(如 Range/AllowedValues)时,"
|
|
79
|
+
"先调用此工具查看真实数据分布,再决定合理的参数。"
|
|
80
|
+
"表名可以是 schema 的 name 或 id。"
|
|
81
|
+
),
|
|
82
|
+
"parameters": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"properties": {
|
|
85
|
+
"table_name": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"description": "要查看的表名(schema 的 name 或 id)",
|
|
88
|
+
},
|
|
89
|
+
"sample_rows": {
|
|
90
|
+
"type": "integer",
|
|
91
|
+
"description": f"采样行数,默认 {_DEFAULT_SAMPLE_ROWS},最大 {_MAX_SAMPLE_ROWS}",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
"required": ["table_name"],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async def run(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
100
|
+
"""
|
|
101
|
+
@methoddesc 执行表数据采样读取
|
|
102
|
+
|
|
103
|
+
参数:
|
|
104
|
+
arguments: tool 参数,包含 table_name 和可选 sample_rows
|
|
105
|
+
|
|
106
|
+
返回:
|
|
107
|
+
{"success": bool, "table_name": str, "columns": [...], "sample_rows": [...], "error": str}
|
|
108
|
+
"""
|
|
109
|
+
table_name = arguments.get("table_name", "")
|
|
110
|
+
if not table_name:
|
|
111
|
+
return {"success": False, "error": "未指定 table_name", "table_name": ""}
|
|
112
|
+
|
|
113
|
+
if not self.project_path:
|
|
114
|
+
return {"success": False, "error": "未配置项目路径", "table_name": table_name}
|
|
115
|
+
|
|
116
|
+
sample_rows = arguments.get("sample_rows", _DEFAULT_SAMPLE_ROWS)
|
|
117
|
+
sample_rows = max(1, min(int(sample_rows), _MAX_SAMPLE_ROWS))
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
# 同步文件读取 + DataFrame 加载,放到线程池
|
|
121
|
+
result = await asyncio.to_thread(self._load_table_sample, table_name, sample_rows)
|
|
122
|
+
return result
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.exception("read_table 工具执行失败")
|
|
125
|
+
return {"success": False, "error": f"读取表数据失败: {e}", "table_name": table_name}
|
|
126
|
+
|
|
127
|
+
def _load_table_sample(self, table_name: str, sample_rows: int) -> dict[str, Any]:
|
|
128
|
+
"""
|
|
129
|
+
@methoddesc 同步加载表数据样本(在线程池中执行)
|
|
130
|
+
|
|
131
|
+
参数:
|
|
132
|
+
table_name: 表名
|
|
133
|
+
sample_rows: 采样行数
|
|
134
|
+
|
|
135
|
+
返回:
|
|
136
|
+
结果字典
|
|
137
|
+
"""
|
|
138
|
+
# 步骤 1+2: 查 schema 文件路径 + source 配置(带缓存,合并原两步)
|
|
139
|
+
entry = self._get_schema_entry(table_name)
|
|
140
|
+
if not entry:
|
|
141
|
+
return {"success": False, "error": f"未找到表 '{table_name}' 的 schema 文件", "table_name": table_name}
|
|
142
|
+
|
|
143
|
+
_schema_path, source_config = entry
|
|
144
|
+
if not source_config.get("path"):
|
|
145
|
+
return {
|
|
146
|
+
"success": False,
|
|
147
|
+
"error": f"表 '{table_name}' 的 schema 未配置 source.path,无法读取数据",
|
|
148
|
+
"table_name": table_name,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# 步骤 3: 解析数据文件绝对路径
|
|
152
|
+
data_file = source_config["path"]
|
|
153
|
+
if not os.path.isabs(data_file):
|
|
154
|
+
# relative_file 模式:路径相对于项目根目录
|
|
155
|
+
data_file = os.path.join(self.project_path, data_file)
|
|
156
|
+
|
|
157
|
+
if not os.path.isfile(data_file):
|
|
158
|
+
return {
|
|
159
|
+
"success": False,
|
|
160
|
+
"error": f"数据文件不存在: {data_file}",
|
|
161
|
+
"table_name": table_name,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# 步骤 4: 加载数据文件
|
|
165
|
+
df = self._load_data_file(data_file, source_config)
|
|
166
|
+
if df is None:
|
|
167
|
+
return {
|
|
168
|
+
"success": False,
|
|
169
|
+
"error": f"无法加载数据文件: {data_file}",
|
|
170
|
+
"table_name": table_name,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# 步骤 5: 采样并格式化
|
|
174
|
+
df_sample = df.head(sample_rows)
|
|
175
|
+
|
|
176
|
+
# 列结构
|
|
177
|
+
columns = []
|
|
178
|
+
for col in df.columns:
|
|
179
|
+
col_str = str(col)
|
|
180
|
+
dtype = str(df[col].dtype)
|
|
181
|
+
null_count = int(df[col].isna().sum())
|
|
182
|
+
columns.append({"name": col_str, "dtype": dtype, "null_count": null_count})
|
|
183
|
+
|
|
184
|
+
# 采样行(转为可 JSON 序列化的列表,处理 NaN)
|
|
185
|
+
sample_records = self._dataframe_to_records(df_sample)
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
"success": True,
|
|
189
|
+
"table_name": table_name,
|
|
190
|
+
"file_path": data_file,
|
|
191
|
+
"total_rows": int(len(df)),
|
|
192
|
+
"columns": columns,
|
|
193
|
+
"sample_rows": sample_records,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
def _ensure_schema_index(self) -> None:
|
|
197
|
+
"""
|
|
198
|
+
@methoddesc 构建/刷新 schema 索引缓存
|
|
199
|
+
|
|
200
|
+
首次调用或 schemas 目录 mtime 变化时,扫描所有 *.yaml 文件,
|
|
201
|
+
构建 {table_name_lower: (schema_path, source_config)} 映射。
|
|
202
|
+
之后同一 tool 实例内的 read_table 调用直接查缓存,避免重复 glob + 解析。
|
|
203
|
+
|
|
204
|
+
失效策略:比较目录 mtime。单文件内容变更(不触发目录 mtime)极少见,
|
|
205
|
+
且 agent 会话内通常不编辑 schema 文件,该策略足够可靠。
|
|
206
|
+
"""
|
|
207
|
+
import os
|
|
208
|
+
|
|
209
|
+
schemas_dir = Path(self.project_path) / "schemas"
|
|
210
|
+
if not schemas_dir.exists():
|
|
211
|
+
self._schema_index = {}
|
|
212
|
+
self._schema_index_mtime = None
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
current_mtime = os.path.getmtime(schemas_dir)
|
|
217
|
+
except OSError:
|
|
218
|
+
current_mtime = 0.0
|
|
219
|
+
|
|
220
|
+
# mtime 未变 → 缓存有效,直接返回
|
|
221
|
+
if self._schema_index_mtime is not None and current_mtime == self._schema_index_mtime:
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
# 重建索引:一次扫描收集所有 schema 的 id/name → (path, source)
|
|
225
|
+
index: dict[str, tuple[str, dict[str, Any]]] = {}
|
|
226
|
+
for schema_file in schemas_dir.glob("*.yaml"):
|
|
227
|
+
try:
|
|
228
|
+
with open(schema_file, encoding="utf-8") as f:
|
|
229
|
+
data = yaml.safe_load(f) or {}
|
|
230
|
+
schema_id = str(data.get("id", "")).strip().lower()
|
|
231
|
+
schema_name = str(data.get("name", "")).strip().lower()
|
|
232
|
+
source_config = data.get("source") or {}
|
|
233
|
+
entry = (str(schema_file), source_config)
|
|
234
|
+
# id 和 name 都建索引(两者都可能作为 table_name 传入)
|
|
235
|
+
if schema_id:
|
|
236
|
+
index[schema_id] = entry
|
|
237
|
+
if schema_name:
|
|
238
|
+
index[schema_name] = entry
|
|
239
|
+
except Exception:
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
self._schema_index = index
|
|
243
|
+
self._schema_index_mtime = current_mtime
|
|
244
|
+
logger.debug(f"[read_table] schema 索引已重建: {len(index)} 个条目")
|
|
245
|
+
|
|
246
|
+
def _get_schema_entry(self, table_name: str) -> tuple[str, dict[str, Any]] | None:
|
|
247
|
+
"""
|
|
248
|
+
@methoddesc 查询 schema 文件路径和 source 配置(带缓存)
|
|
249
|
+
|
|
250
|
+
合并了原 _find_schema_file + _parse_schema_source 两步:
|
|
251
|
+
首次扫描 schemas 目录构建索引,之后直接查内存缓存。
|
|
252
|
+
|
|
253
|
+
参数:
|
|
254
|
+
table_name: 表名(schema 的 id 或 name)
|
|
255
|
+
|
|
256
|
+
返回:
|
|
257
|
+
(schema_path, source_config) 或 None(未找到)
|
|
258
|
+
"""
|
|
259
|
+
self._ensure_schema_index()
|
|
260
|
+
target = table_name.strip().lower()
|
|
261
|
+
return self._schema_index.get(target)
|
|
262
|
+
|
|
263
|
+
def _load_data_file(self, data_file: str, source_config: dict[str, Any]) -> pd.DataFrame | None:
|
|
264
|
+
"""
|
|
265
|
+
@methoddesc 根据文件扩展名加载数据文件
|
|
266
|
+
|
|
267
|
+
使用 load_file_data 统一入口,避免重复实现格式适配逻辑。
|
|
268
|
+
"""
|
|
269
|
+
try:
|
|
270
|
+
from app.shared.services.validation.loader import load_file_data
|
|
271
|
+
|
|
272
|
+
sheet_name = source_config.get("sheet")
|
|
273
|
+
header_row = source_config.get("header_row", 0)
|
|
274
|
+
return load_file_data(
|
|
275
|
+
source_file_path=data_file,
|
|
276
|
+
sheet_name=sheet_name,
|
|
277
|
+
header_row=header_row,
|
|
278
|
+
)
|
|
279
|
+
except Exception as e:
|
|
280
|
+
logger.warning(f"load_file_data 加载失败 {data_file}: {e}")
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
@staticmethod
|
|
284
|
+
def _dataframe_to_records(df: pd.DataFrame) -> list[dict[str, Any]]:
|
|
285
|
+
"""将 DataFrame 转为可 JSON 序列化的记录列表,处理 NaN 和超长值。"""
|
|
286
|
+
records: list[dict[str, Any]] = []
|
|
287
|
+
for _, row in df.iterrows():
|
|
288
|
+
record: dict[str, Any] = {}
|
|
289
|
+
for col in df.columns:
|
|
290
|
+
value = row[col]
|
|
291
|
+
# NaN/None 转为 null
|
|
292
|
+
if pd.isna(value):
|
|
293
|
+
record[str(col)] = None
|
|
294
|
+
continue
|
|
295
|
+
# 截断超长字符串
|
|
296
|
+
value_str = str(value)
|
|
297
|
+
if len(value_str) > _MAX_CELL_CHARS:
|
|
298
|
+
value_str = value_str[:_MAX_CELL_CHARS] + "..."
|
|
299
|
+
record[str(col)] = value_str
|
|
300
|
+
records.append(record)
|
|
301
|
+
return records
|
|
@@ -0,0 +1,105 @@
|
|
|
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 chat 工具入参的 Pydantic 校验模型
|
|
17
|
+
|
|
18
|
+
P1-1:为每个 chat 工具定义入参模型,由 ToolRegistry 在 execute 入口做结构校验,
|
|
19
|
+
统一拦截"必填字段缺失/类型错误",把结构化错误回灌给 LLM(配合 P0-1 的错误回灌机制)。
|
|
20
|
+
|
|
21
|
+
设计原则:
|
|
22
|
+
- extra="allow":LLM 偶尔会带额外字段(如调试信息、变体参数),不为此拒绝合法调用。
|
|
23
|
+
- 复杂嵌套结构(如 apply_actions 的 *Spec)不在这一层校验,留给业务层的 ActionValidator,
|
|
24
|
+
避免两处校验逻辑漂移。这里只校验"工具入参骨架"。
|
|
25
|
+
- MODEL_FOR_TOOL 是工具名 → 模型类的映射,ToolRegistry.register 时传入。
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from typing import Any, Literal
|
|
31
|
+
|
|
32
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _ToolArgsBase(BaseModel):
|
|
36
|
+
"""所有工具入参模型的基类:允许额外字段,避免误拒 LLM 的合法变体调用。"""
|
|
37
|
+
|
|
38
|
+
model_config = ConfigDict(extra="allow")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ReadProjectArgs(_ToolArgsBase):
|
|
42
|
+
"""read_project 无参数(空模型仍可拦截"传了非 dict 入参"的极端情况)。"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ReadTableArgs(_ToolArgsBase):
|
|
46
|
+
"""read_table 入参:table_name 必填,sample_rows 可选。"""
|
|
47
|
+
|
|
48
|
+
table_name: str = Field(..., description="要查看的表名(schema 的 name 或 id)")
|
|
49
|
+
sample_rows: int | None = Field(default=None, description="采样行数")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ReadCanvasArgs(_ToolArgsBase):
|
|
53
|
+
"""read_canvas 无参数。"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ValidateTableArgs(_ToolArgsBase):
|
|
57
|
+
"""validate_table 入参:table_name 可选(不传则校验所有表)。"""
|
|
58
|
+
|
|
59
|
+
table_name: str | None = Field(default=None, description="要校验的表名;不传则校验所有表")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ApplyActionsArgs(_ToolArgsBase):
|
|
63
|
+
"""apply_actions 入参:actions 必填(非空数组)。
|
|
64
|
+
|
|
65
|
+
actions 的元素是 dict(每个含 actionType + 对应 *Spec),元素内部结构由 ActionValidator
|
|
66
|
+
校验,这里只保证 actions 是非空 list[dict],避免重复校验。
|
|
67
|
+
intent_scope 字段为 P2-1 意图校验改造预留(本轮可选)。
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
actions: list[dict[str, Any]] = Field(..., description="要执行的动作列表(非空)")
|
|
71
|
+
intent_scope: dict[str, Any] | None = Field(default=None, description="意图范围声明(预留)")
|
|
72
|
+
|
|
73
|
+
@field_validator("actions")
|
|
74
|
+
@classmethod
|
|
75
|
+
def _actions_non_empty(cls, v: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
76
|
+
# OpenAI schema 的 required 只保证字段存在,空数组会漏过;这里补非空校验
|
|
77
|
+
if not v:
|
|
78
|
+
raise ValueError("actions 不能为空数组")
|
|
79
|
+
return v
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class AskUserArgs(_ToolArgsBase):
|
|
83
|
+
"""ask_user 入参:question_type 和 prompt 必填,其余按类型可选。"""
|
|
84
|
+
|
|
85
|
+
question_type: Literal["free_text", "choice", "value", "confirm"] = Field(..., description="提问类型")
|
|
86
|
+
prompt: str = Field(..., description="给用户看的问题文本")
|
|
87
|
+
options: list[dict[str, Any]] | None = Field(default=None, description="choice 类型必填:候选项列表")
|
|
88
|
+
multiple: bool | None = Field(default=None, description="choice 类型:是否多选")
|
|
89
|
+
value_type: Literal["string", "integer", "float", "boolean"] | None = Field(
|
|
90
|
+
default=None, description="value 类型必填:期望的值类型"
|
|
91
|
+
)
|
|
92
|
+
placeholder: str | None = Field(default=None, description="输入框占位提示")
|
|
93
|
+
optional: bool | None = Field(default=None, description="value 类型:是否允许留空")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# 工具名 → 入参模型 的注册表。ToolRegistry.register 时按名查表。
|
|
97
|
+
# 未在此注册的工具(如未来新工具)保持原透传行为,向后兼容。
|
|
98
|
+
MODEL_FOR_TOOL: dict[str, type[_ToolArgsBase]] = {
|
|
99
|
+
"read_project": ReadProjectArgs,
|
|
100
|
+
"read_table": ReadTableArgs,
|
|
101
|
+
"read_canvas": ReadCanvasArgs,
|
|
102
|
+
"validate_table": ValidateTableArgs,
|
|
103
|
+
"apply_actions": ApplyActionsArgs,
|
|
104
|
+
"ask_user": AskUserArgs,
|
|
105
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
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 validate_table 工具
|
|
17
|
+
|
|
18
|
+
Chat mini-agent 可调用的工具:执行项目数据校验。
|
|
19
|
+
|
|
20
|
+
复用 execute_validate_project,支持校验全部表或单个表。
|
|
21
|
+
让 LLM 在执行配置修改后,能自主验证改动效果,
|
|
22
|
+
若发现问题可进一步调整 actions,实现"改-验-修"自我纠错循环。
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import asyncio
|
|
28
|
+
import logging
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
from app.shared.services.llm.validate_executor import execute_validate_project
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
# 返回给 LLM 的错误条目上限,避免 observation 过长
|
|
36
|
+
_MAX_ERRORS_IN_OBSERVATION = 15
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ValidateTableTool:
|
|
40
|
+
"""
|
|
41
|
+
@classdesc 数据校验工具
|
|
42
|
+
|
|
43
|
+
封装 execute_validate_project,支持全表或单表校验。
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
NAME = "validate_table"
|
|
47
|
+
|
|
48
|
+
def __init__(self, project_path: str):
|
|
49
|
+
"""
|
|
50
|
+
@methoddesc 初始化工具
|
|
51
|
+
|
|
52
|
+
参数:
|
|
53
|
+
project_path: 当前项目配置目录路径
|
|
54
|
+
"""
|
|
55
|
+
self.project_path = project_path
|
|
56
|
+
|
|
57
|
+
def get_definition(self) -> dict[str, Any]:
|
|
58
|
+
"""返回 OpenAI tool 定义。"""
|
|
59
|
+
return {
|
|
60
|
+
"type": "function",
|
|
61
|
+
"function": {
|
|
62
|
+
"name": self.NAME,
|
|
63
|
+
"description": (
|
|
64
|
+
"执行项目数据校验,检查当前约束规则是否通过。"
|
|
65
|
+
"可校验全部表(不传 table_name)或指定单表(传 table_name)。"
|
|
66
|
+
"建议在 apply_actions 修改约束后调用此工具验证效果;"
|
|
67
|
+
"用户明确要求'校验项目'或'校验某表'时也应调用。"
|
|
68
|
+
"返回错误数量和具体错误列表。"
|
|
69
|
+
),
|
|
70
|
+
"parameters": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": {
|
|
73
|
+
"table_name": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "要校验的表名(schema 的 name 或 id)。不传则校验所有表。",
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"required": [],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async def run(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
84
|
+
"""
|
|
85
|
+
@methoddesc 执行数据校验
|
|
86
|
+
|
|
87
|
+
参数:
|
|
88
|
+
arguments: tool 参数,包含可选的 table_name
|
|
89
|
+
|
|
90
|
+
返回:
|
|
91
|
+
{"success": bool, "error_count": int, "errors": [...], "message": str}
|
|
92
|
+
"""
|
|
93
|
+
if not self.project_path:
|
|
94
|
+
return {"success": False, "error": "未配置项目路径", "error_count": 0, "errors": []}
|
|
95
|
+
|
|
96
|
+
table_name = arguments.get("table_name")
|
|
97
|
+
# 空字符串视为不指定表
|
|
98
|
+
table_filter = table_name if table_name else None
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# execute_validate_project 是同步函数(内部跑校验引擎),放到线程池
|
|
102
|
+
result = await asyncio.to_thread(
|
|
103
|
+
execute_validate_project,
|
|
104
|
+
self.project_path,
|
|
105
|
+
table_filter,
|
|
106
|
+
)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.exception("validate_table 工具执行失败")
|
|
109
|
+
return {"success": False, "error": f"校验执行失败: {e}", "error_count": 0, "errors": []}
|
|
110
|
+
|
|
111
|
+
# execute_validate_project 返回 {success, message, details}
|
|
112
|
+
# success=True 表示校验流程跑通(不代表数据无错),details 含 error_count
|
|
113
|
+
details = result.get("details") or {}
|
|
114
|
+
error_count = details.get("error_count", 0)
|
|
115
|
+
raw_errors = details.get("errors", []) or []
|
|
116
|
+
|
|
117
|
+
# 截断错误列表,避免 observation 过长
|
|
118
|
+
truncated_errors = raw_errors[:_MAX_ERRORS_IN_OBSERVATION]
|
|
119
|
+
if len(raw_errors) > _MAX_ERRORS_IN_OBSERVATION:
|
|
120
|
+
truncated_count = len(raw_errors) - _MAX_ERRORS_IN_OBSERVATION
|
|
121
|
+
else:
|
|
122
|
+
truncated_count = 0
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
"success": result.get("success", False),
|
|
126
|
+
"error_count": error_count,
|
|
127
|
+
"errors": truncated_errors,
|
|
128
|
+
"truncated_error_count": truncated_count,
|
|
129
|
+
"table_filter": table_filter,
|
|
130
|
+
"message": result.get("message", ""),
|
|
131
|
+
}
|