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,237 @@
|
|
|
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 ask_user 工具
|
|
17
|
+
|
|
18
|
+
Chat mini-agent 可调用的工具:在 loop 中途向用户提问。
|
|
19
|
+
|
|
20
|
+
支持 4 种 question_type:
|
|
21
|
+
1. free_text — 开放提问,用户输入任意文本
|
|
22
|
+
2. choice — 消歧/选方案,单选或多选
|
|
23
|
+
3. value — 补参数,结构化输入(带类型校验)
|
|
24
|
+
4. confirm — 非写盘意图确认(yes/no)
|
|
25
|
+
|
|
26
|
+
机制:
|
|
27
|
+
- 创建 InteractionController,注册到 pending_interaction_store
|
|
28
|
+
- emit user_input_requested 事件(前端渲染 AskUserCard)
|
|
29
|
+
- await controller.await_response() 挂起(最多 5 分钟)
|
|
30
|
+
- 用户通过 /respond 端点回答 → resolve 唤醒
|
|
31
|
+
- observation {answer: ...} 或 {skipped: true} 回灌 LLM,loop 继续
|
|
32
|
+
|
|
33
|
+
非流式环境(dry_run_enabled=False)fail-closed:不挂起,直接返回 unsupported。
|
|
34
|
+
|
|
35
|
+
注意:InteractionController / get_global_pending_interaction_store 采用延迟导入
|
|
36
|
+
(在 run() 内 import),与 apply_actions.py 一致,避免循环依赖:
|
|
37
|
+
streaming/__init__ → orchestrator → chat_tools/__init__ → ask_user → streaming
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import logging
|
|
43
|
+
from collections.abc import Callable
|
|
44
|
+
from dataclasses import dataclass
|
|
45
|
+
from typing import Any
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
# 允许的提问类型
|
|
50
|
+
_QUESTION_TYPES = ("free_text", "choice", "value", "confirm")
|
|
51
|
+
# value 类型允许的值类型
|
|
52
|
+
_VALUE_TYPES = ("string", "integer", "float", "boolean")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class AskCallbacks:
|
|
57
|
+
"""ask_user 工具的回调集合,由 orchestrator 注入以实现事件桥接。"""
|
|
58
|
+
|
|
59
|
+
on_user_input_requested: Callable[[dict[str, Any]], None] | None = None
|
|
60
|
+
on_user_responded: Callable[[dict[str, Any]], None] | None = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AskUserTool:
|
|
64
|
+
"""让 agent 在 loop 中途向用户提问。"""
|
|
65
|
+
|
|
66
|
+
NAME = "ask_user"
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
job_id: str,
|
|
71
|
+
ask_callbacks: AskCallbacks | None = None,
|
|
72
|
+
dry_run_enabled: bool = False,
|
|
73
|
+
):
|
|
74
|
+
"""
|
|
75
|
+
@methoddesc 初始化工具
|
|
76
|
+
|
|
77
|
+
参数:
|
|
78
|
+
job_id: 当前任务 ID,用于生成 ask_id("{job_id}#ask#{seq}")
|
|
79
|
+
ask_callbacks: 事件回调集合
|
|
80
|
+
dry_run_enabled: 是否启用交互(仅流式路径 True;非流式 fail-closed)
|
|
81
|
+
"""
|
|
82
|
+
self._job_id = job_id
|
|
83
|
+
self._ask_callbacks = ask_callbacks or AskCallbacks()
|
|
84
|
+
self._dry_run_enabled = dry_run_enabled
|
|
85
|
+
self._ask_counter = 0
|
|
86
|
+
|
|
87
|
+
def get_definition(self) -> dict[str, Any]:
|
|
88
|
+
"""返回 OpenAI tool 定义。"""
|
|
89
|
+
return {
|
|
90
|
+
"type": "function",
|
|
91
|
+
"function": {
|
|
92
|
+
"name": self.NAME,
|
|
93
|
+
"description": (
|
|
94
|
+
"向用户提问以获取澄清、消歧或缺失信息。仅当无法通过 read_project/read_table/read_canvas "
|
|
95
|
+
"自行获取信息、或存在多方案需要用户抉择时调用。能自己查到的不要问。"
|
|
96
|
+
"返回用户的回答或 skipped=true(用户跳过/超时/环境不支持)。"
|
|
97
|
+
),
|
|
98
|
+
"parameters": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"properties": {
|
|
101
|
+
"question_type": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"enum": list(_QUESTION_TYPES),
|
|
104
|
+
"description": "提问类型",
|
|
105
|
+
},
|
|
106
|
+
"prompt": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"description": "给用户看的问题文本",
|
|
109
|
+
},
|
|
110
|
+
"options": {
|
|
111
|
+
"type": "array",
|
|
112
|
+
"items": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"properties": {
|
|
115
|
+
"label": {"type": "string"},
|
|
116
|
+
"value": {"type": "string"},
|
|
117
|
+
"description": {"type": "string"},
|
|
118
|
+
},
|
|
119
|
+
"required": ["label", "value"],
|
|
120
|
+
},
|
|
121
|
+
"description": "choice 类型必填:候选项列表",
|
|
122
|
+
},
|
|
123
|
+
"multiple": {
|
|
124
|
+
"type": "boolean",
|
|
125
|
+
"description": "choice 类型:是否多选(默认 false)",
|
|
126
|
+
},
|
|
127
|
+
"value_type": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"enum": list(_VALUE_TYPES),
|
|
130
|
+
"description": "value 类型必填:期望的值类型",
|
|
131
|
+
},
|
|
132
|
+
"placeholder": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"description": "输入框占位提示(可选,free_text/value 用)",
|
|
135
|
+
},
|
|
136
|
+
"optional": {
|
|
137
|
+
"type": "boolean",
|
|
138
|
+
"description": "value 类型:是否允许留空(默认 false)",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
"required": ["question_type", "prompt"],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def _validate_args(self, arguments: dict[str, Any]) -> str | None:
|
|
147
|
+
"""校验参数完整性。返回错误描述或 None。"""
|
|
148
|
+
qt = arguments.get("question_type")
|
|
149
|
+
if qt not in _QUESTION_TYPES:
|
|
150
|
+
return f"非法 question_type: {qt}"
|
|
151
|
+
if qt == "choice":
|
|
152
|
+
opts = arguments.get("options")
|
|
153
|
+
if not opts or not isinstance(opts, list) or len(opts) < 1:
|
|
154
|
+
return "choice 类型必须提供至少一个 options"
|
|
155
|
+
if qt == "value":
|
|
156
|
+
vt = arguments.get("value_type")
|
|
157
|
+
if vt not in _VALUE_TYPES:
|
|
158
|
+
return "value 类型必须提供合法 value_type"
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
async def run(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
162
|
+
"""
|
|
163
|
+
@methoddesc 执行提问
|
|
164
|
+
|
|
165
|
+
参数:
|
|
166
|
+
arguments: tool 参数,含 question_type/prompt 及类型相关字段
|
|
167
|
+
|
|
168
|
+
返回:
|
|
169
|
+
{"success": bool, "answer": {...}}
|
|
170
|
+
answer 为用户回答 {answer: ...} 或跳过 {skipped: true, reason: ...}
|
|
171
|
+
"""
|
|
172
|
+
question_type = arguments.get("question_type")
|
|
173
|
+
prompt = arguments.get("prompt", "")
|
|
174
|
+
|
|
175
|
+
# 参数校验
|
|
176
|
+
err = self._validate_args(arguments)
|
|
177
|
+
if err:
|
|
178
|
+
logger.warning("[ask_user] 参数校验失败: %s", err)
|
|
179
|
+
return {
|
|
180
|
+
"success": False,
|
|
181
|
+
"error": err,
|
|
182
|
+
"answer": {"skipped": True, "reason": "bad_args"},
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# 非流式环境 fail-closed
|
|
186
|
+
if not self._dry_run_enabled:
|
|
187
|
+
return {
|
|
188
|
+
"success": False,
|
|
189
|
+
"unsupported": True,
|
|
190
|
+
"error": "此环境不支持交互问答,请基于已知信息回答",
|
|
191
|
+
"answer": {"skipped": True, "reason": "unsupported_env"},
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# 延迟导入避免循环依赖(streaming/__init__ → orchestrator → chat_tools → streaming)
|
|
195
|
+
# 与 apply_actions.py 的 _run_two_phase 一致:在方法内 import
|
|
196
|
+
from app.shared.services.ai.streaming.pending_interaction_store import (
|
|
197
|
+
InteractionController,
|
|
198
|
+
get_global_pending_interaction_store,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# 创建门控,生成独立 ask_id
|
|
202
|
+
self._ask_counter += 1
|
|
203
|
+
ask_id = f"{self._job_id}#ask#{self._ask_counter}" if self._job_id else f"ask#{self._ask_counter}"
|
|
204
|
+
controller = InteractionController(request_id=ask_id, pending_payload=arguments)
|
|
205
|
+
store = get_global_pending_interaction_store()
|
|
206
|
+
store.put(ask_id, controller)
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
# emit user_input_requested(携带 question schema 给前端)
|
|
210
|
+
request_payload: dict[str, Any] = {
|
|
211
|
+
"ask_id": ask_id,
|
|
212
|
+
"question_type": question_type,
|
|
213
|
+
"prompt": prompt,
|
|
214
|
+
}
|
|
215
|
+
for k in ("options", "multiple", "value_type", "placeholder", "optional"):
|
|
216
|
+
if k in arguments:
|
|
217
|
+
request_payload[k] = arguments[k]
|
|
218
|
+
|
|
219
|
+
if self._ask_callbacks.on_user_input_requested:
|
|
220
|
+
self._ask_callbacks.on_user_input_requested(request_payload)
|
|
221
|
+
|
|
222
|
+
# 挂起等待用户回答
|
|
223
|
+
response = await controller.await_response()
|
|
224
|
+
|
|
225
|
+
# emit user_responded(让前端清态)
|
|
226
|
+
if self._ask_callbacks.on_user_responded:
|
|
227
|
+
self._ask_callbacks.on_user_responded(
|
|
228
|
+
{
|
|
229
|
+
"ask_id": ask_id,
|
|
230
|
+
"response": response,
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
return {"success": True, "answer": response}
|
|
235
|
+
finally:
|
|
236
|
+
# 无论回答/跳过/超时/异常,都清理本次 ask 的 controller
|
|
237
|
+
store.pop(ask_id)
|
|
@@ -0,0 +1,140 @@
|
|
|
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_canvas 工具
|
|
17
|
+
|
|
18
|
+
Chat mini-agent 可调用的工具:读取当前画布上实际显示的节点快照。
|
|
19
|
+
|
|
20
|
+
与 read_project 的关键区别:
|
|
21
|
+
- read_project 读项目配置文件(YAML),返回"项目里配置了什么"。
|
|
22
|
+
- read_canvas 读请求时前端带来的画布快照,返回"画布上现在显示什么"。
|
|
23
|
+
按项目架构,Schema/Constraint/Regex 节点不自动加载到画布,
|
|
24
|
+
故两者会分叉——判断"画布上有没有 X"必须用本工具。
|
|
25
|
+
|
|
26
|
+
无 project_path 依赖:画布数据在 runner 构造时已注入(来自请求体 canvasNodes 字段)。
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import logging
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
# 画布上需要计数的业务节点类型前缀/集合,用于生成 summary
|
|
37
|
+
_CONSTRAINT_TYPE_SUFFIX = "Constraint"
|
|
38
|
+
_SCHEMA_TYPES = frozenset({"schema", "jsonSchema"})
|
|
39
|
+
_SOURCE_TYPES = frozenset({"sourcePreview", "jsonSourcePreview"})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _summarize_canvas(nodes: list[dict[str, Any]]) -> dict[str, int]:
|
|
43
|
+
"""统计画布快照中各类业务节点的数量,便于 LLM 快速判断。"""
|
|
44
|
+
schema_count = 0
|
|
45
|
+
constraint_count = 0
|
|
46
|
+
regex_count = 0
|
|
47
|
+
transform_count = 0
|
|
48
|
+
other_count = 0
|
|
49
|
+
|
|
50
|
+
for n in nodes:
|
|
51
|
+
ntype = n.get("type", "")
|
|
52
|
+
if ntype in _SCHEMA_TYPES:
|
|
53
|
+
schema_count += 1
|
|
54
|
+
elif ntype.endswith(_CONSTRAINT_TYPE_SUFFIX):
|
|
55
|
+
constraint_count += 1
|
|
56
|
+
elif ntype == "regex":
|
|
57
|
+
regex_count += 1
|
|
58
|
+
elif ntype in ("transform", "transformOutput"):
|
|
59
|
+
transform_count += 1
|
|
60
|
+
else:
|
|
61
|
+
other_count += 1
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
"total": len(nodes),
|
|
65
|
+
"schema_count": schema_count,
|
|
66
|
+
"constraint_count": constraint_count,
|
|
67
|
+
"regex_count": regex_count,
|
|
68
|
+
"transform_count": transform_count,
|
|
69
|
+
"other_count": other_count,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ReadCanvasTool:
|
|
74
|
+
"""
|
|
75
|
+
@classdesc 读取画布节点快照工具
|
|
76
|
+
|
|
77
|
+
返回当前画布上实际显示的节点(由前端在请求体携带),
|
|
78
|
+
区别于 read_project 返回的项目配置文件内容。
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
NAME = "read_canvas"
|
|
82
|
+
|
|
83
|
+
def __init__(self, canvas_nodes: list[dict[str, Any]] | None = None):
|
|
84
|
+
"""
|
|
85
|
+
@methoddesc 初始化工具
|
|
86
|
+
|
|
87
|
+
参数:
|
|
88
|
+
canvas_nodes: 前端在请求体携带的画布节点快照(已裁剪)。
|
|
89
|
+
runner 构造时注入;None/空视为空画布。
|
|
90
|
+
"""
|
|
91
|
+
# 防御性拷贝:避免外部 mutate 影响工具内视图
|
|
92
|
+
self._canvas_nodes: list[dict[str, Any]] = list(canvas_nodes) if canvas_nodes else []
|
|
93
|
+
|
|
94
|
+
def get_definition(self) -> dict[str, Any]:
|
|
95
|
+
"""返回 OpenAI tool 定义。"""
|
|
96
|
+
return {
|
|
97
|
+
"type": "function",
|
|
98
|
+
"function": {
|
|
99
|
+
"name": self.NAME,
|
|
100
|
+
"description": (
|
|
101
|
+
"读取当前**画布上实际显示**的节点列表(Schema、约束、正则、转换等),"
|
|
102
|
+
"含每类节点数量摘要。"
|
|
103
|
+
"本工具返回的是画布快照,与 read_project 读项目配置文件不同——"
|
|
104
|
+
"项目配置里有的表/约束不一定已拖到画布上。"
|
|
105
|
+
"当用户说'画布上有没有 X'、'把 Y 放到画布/拖到画布'、"
|
|
106
|
+
"'画布上现在有什么'时,先调用本工具确认画布真实状态,再决定是否需要 ADD 动作。"
|
|
107
|
+
"无需参数。"
|
|
108
|
+
),
|
|
109
|
+
"parameters": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"properties": {},
|
|
112
|
+
"required": [],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async def run(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
118
|
+
"""
|
|
119
|
+
@methoddesc 返回画布节点快照与摘要
|
|
120
|
+
|
|
121
|
+
参数:
|
|
122
|
+
arguments: tool 参数(本工具无参数,保留接口一致性)
|
|
123
|
+
|
|
124
|
+
返回:
|
|
125
|
+
{"success": bool, "canvas_nodes": [...], "summary": {...}, "error": str}
|
|
126
|
+
canvas_nodes 为前端裁剪后的节点摘要列表;空画布返回空列表而非错误。
|
|
127
|
+
"""
|
|
128
|
+
# 本工具无参数,arguments 仅保留接口一致性
|
|
129
|
+
_ = arguments
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
summary = _summarize_canvas(self._canvas_nodes)
|
|
133
|
+
return {
|
|
134
|
+
"success": True,
|
|
135
|
+
"canvas_nodes": self._canvas_nodes,
|
|
136
|
+
"summary": summary,
|
|
137
|
+
}
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.exception("read_canvas 工具执行失败")
|
|
140
|
+
return {"success": False, "error": f"读取画布快照失败: {e}", "canvas_nodes": [], "summary": {}}
|
|
@@ -0,0 +1,160 @@
|
|
|
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_project 工具
|
|
17
|
+
|
|
18
|
+
Chat mini-agent 可调用的工具:读取当前项目的完整概览。
|
|
19
|
+
|
|
20
|
+
返回项目中的所有表结构、约束、转换、正则节点和设置信息,
|
|
21
|
+
供 LLM 了解"当前项目里有什么",从而决定后续操作。
|
|
22
|
+
|
|
23
|
+
体积控制:大项目的 overview 可能几万字符,直接全量返回会被 memory 的 8000 字符硬切
|
|
24
|
+
在 JSON 中途,破坏结构、让 LLM 拿到半截数据。本工具在返回前做语义截断:
|
|
25
|
+
schemas/constraints/transforms/regex_nodes 各保留前 N 项 + 计数,每张表的 columns
|
|
26
|
+
也只列前 N 个,让 LLM 既能判断项目规模,又能在需要详情时调 read_table。
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import asyncio
|
|
32
|
+
import logging
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
from app.shared.services.ai.utils import get_project_overview
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
# 语义截断阈值(参考 validate_table 的"列表前 N 项 + 计数"模式)。
|
|
40
|
+
# 目标:截断后 observation 控制在 memory 8000 字符硬切线以内,且仍是合法 JSON。
|
|
41
|
+
_MAX_SCHEMAS_IN_OBSERVATION = 20
|
|
42
|
+
_MAX_CONSTRAINTS_IN_OBSERVATION = 30
|
|
43
|
+
_MAX_OTHER_NODES_IN_OBSERVATION = 10 # transforms / regex_nodes 共用
|
|
44
|
+
_MAX_COLUMNS_PER_SCHEMA = 50
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ReadProjectTool:
|
|
48
|
+
"""
|
|
49
|
+
@classdesc 读取项目概览工具
|
|
50
|
+
|
|
51
|
+
封装 get_project_overview,返回项目的结构信息(大项目自动语义截断)。
|
|
52
|
+
不接收任何参数——它读取的就是 runner 绑定的当前项目。
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
NAME = "read_project"
|
|
56
|
+
|
|
57
|
+
def __init__(self, project_path: str):
|
|
58
|
+
"""
|
|
59
|
+
@methoddesc 初始化工具
|
|
60
|
+
|
|
61
|
+
参数:
|
|
62
|
+
project_path: 当前项目配置目录路径
|
|
63
|
+
"""
|
|
64
|
+
self.project_path = project_path
|
|
65
|
+
|
|
66
|
+
def get_definition(self) -> dict[str, Any]:
|
|
67
|
+
"""返回 OpenAI tool 定义。"""
|
|
68
|
+
return {
|
|
69
|
+
"type": "function",
|
|
70
|
+
"function": {
|
|
71
|
+
"name": self.NAME,
|
|
72
|
+
"description": (
|
|
73
|
+
"读取当前项目的完整概览,包括所有表结构(schemas)、约束规则(constraints)、"
|
|
74
|
+
"数据转换(transforms)、正则节点(regex)和项目设置(settings)。"
|
|
75
|
+
"当用户询问'有哪些表'、'某个表有哪些约束'、'当前项目配置'等查询类问题时,"
|
|
76
|
+
"先调用此工具获取信息,再用自然语言回答。无需参数。"
|
|
77
|
+
),
|
|
78
|
+
"parameters": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {},
|
|
81
|
+
"required": [],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def _truncate_overview(overview: dict[str, Any]) -> dict[str, Any]:
|
|
88
|
+
"""对 overview 做语义截断,避免 observation 过大被 memory 硬切破坏 JSON 结构。
|
|
89
|
+
|
|
90
|
+
截断策略:各资源列表保留前 N 项,并附 truncated_*_count 告知 LLM "还有更多";
|
|
91
|
+
schemas 的 columns 也只列前 N 个(大宽表的关键体积来源)。
|
|
92
|
+
小项目(各项均未超阈值)原样返回,零影响。
|
|
93
|
+
"""
|
|
94
|
+
schemas = overview.get("schemas", [])
|
|
95
|
+
truncated_schemas: list[dict[str, Any]] = []
|
|
96
|
+
truncated_column_total = 0
|
|
97
|
+
for schema in schemas[:_MAX_SCHEMAS_IN_OBSERVATION]:
|
|
98
|
+
cols = schema.get("columns", [])
|
|
99
|
+
if len(cols) > _MAX_COLUMNS_PER_SCHEMA:
|
|
100
|
+
truncated_column_total += len(cols) - _MAX_COLUMNS_PER_SCHEMA
|
|
101
|
+
schema = {**schema, "columns": cols[:_MAX_COLUMNS_PER_SCHEMA]}
|
|
102
|
+
truncated_schemas.append(schema)
|
|
103
|
+
|
|
104
|
+
constraints = overview.get("constraints", [])
|
|
105
|
+
transforms = overview.get("transforms", [])
|
|
106
|
+
regex_nodes = overview.get("regex_nodes", [])
|
|
107
|
+
|
|
108
|
+
result: dict[str, Any] = {
|
|
109
|
+
"schemas": truncated_schemas,
|
|
110
|
+
"constraints": constraints[:_MAX_CONSTRAINTS_IN_OBSERVATION],
|
|
111
|
+
"transforms": transforms[:_MAX_OTHER_NODES_IN_OBSERVATION],
|
|
112
|
+
"regex_nodes": regex_nodes[:_MAX_OTHER_NODES_IN_OBSERVATION],
|
|
113
|
+
"settings": overview.get("settings", {}),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# 截断计数:让 LLM 知道被省略了多少,需要详情时调 read_table
|
|
117
|
+
result["truncated_schema_count"] = max(0, len(schemas) - _MAX_SCHEMAS_IN_OBSERVATION)
|
|
118
|
+
result["truncated_constraint_count"] = max(0, len(constraints) - _MAX_CONSTRAINTS_IN_OBSERVATION)
|
|
119
|
+
result["truncated_transform_count"] = max(0, len(transforms) - _MAX_OTHER_NODES_IN_OBSERVATION)
|
|
120
|
+
result["truncated_regex_count"] = max(0, len(regex_nodes) - _MAX_OTHER_NODES_IN_OBSERVATION)
|
|
121
|
+
result["truncated_column_count"] = truncated_column_total
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
async def run(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
125
|
+
"""
|
|
126
|
+
@methoddesc 执行项目概览读取
|
|
127
|
+
|
|
128
|
+
参数:
|
|
129
|
+
arguments: tool 参数(本工具无参数)
|
|
130
|
+
|
|
131
|
+
返回:
|
|
132
|
+
{"success": bool, "overview": {...}, "summary": {...}, "error": str}
|
|
133
|
+
overview 形如 {"schemas": [...], "constraints": [...], "transforms": [...],
|
|
134
|
+
"regex_nodes": [...], "settings": {...}, "truncated_*_count": int}
|
|
135
|
+
大项目会语义截断(列表前 N 项 + 计数),详见 _truncate_overview。
|
|
136
|
+
"""
|
|
137
|
+
# arguments 无参数,但保留接口一致性
|
|
138
|
+
_ = arguments
|
|
139
|
+
|
|
140
|
+
if not self.project_path:
|
|
141
|
+
return {"success": False, "error": "未配置项目路径", "overview": {}}
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
# get_project_overview 是同步函数,放到线程池避免阻塞事件循环
|
|
145
|
+
raw_overview = await asyncio.to_thread(get_project_overview, self.project_path)
|
|
146
|
+
|
|
147
|
+
# 语义截断,避免 observation 被 memory 硬切破坏 JSON
|
|
148
|
+
overview = self._truncate_overview(raw_overview)
|
|
149
|
+
|
|
150
|
+
# 统计摘要(基于截断前的真实数量,让 LLM 判断项目真实规模)
|
|
151
|
+
summary = {
|
|
152
|
+
"schema_count": len(raw_overview.get("schemas", [])),
|
|
153
|
+
"constraint_count": len(raw_overview.get("constraints", [])),
|
|
154
|
+
"transform_count": len(raw_overview.get("transforms", [])),
|
|
155
|
+
"regex_count": len(raw_overview.get("regex_nodes", [])),
|
|
156
|
+
}
|
|
157
|
+
return {"success": True, "overview": overview, "summary": summary}
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.exception("read_project 工具执行失败")
|
|
160
|
+
return {"success": False, "error": f"读取项目概览失败: {e}", "overview": {}}
|