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,596 @@
|
|
|
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 Agent Runner 模块
|
|
17
|
+
|
|
18
|
+
Chat mini-agent 的编排器。在 agent_mode=true 时,
|
|
19
|
+
让 Chat 路径真正跑起 plan→act→observe 工具循环。
|
|
20
|
+
|
|
21
|
+
核心职责:
|
|
22
|
+
- 组装 5 个 chat 专用工具(read_project/read_table/apply_actions/validate_table/read_canvas)
|
|
23
|
+
- 构建 chat agent 系统提示词
|
|
24
|
+
- 调用 AgentExecutor 跑工具循环
|
|
25
|
+
- 从循环结果提取 reply + 旁路收集的 frontend_instructions
|
|
26
|
+
- 对外保持 ChatExecutionResult 契约不变(前端零改动)
|
|
27
|
+
|
|
28
|
+
设计要点:
|
|
29
|
+
- 复用 AgentExecutor/Memory/ToolRegistry 通用内核,不改动它们
|
|
30
|
+
- apply_actions 工具持有的 collected_instructions 列表由 runner 创建并共享,
|
|
31
|
+
工具 append、runner 最终读取,实现 frontend_instructions 的旁路累积
|
|
32
|
+
- 当 LLM 不再调用工具、输出纯文本时,循环自然终止,该文本即为 reply
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import logging
|
|
38
|
+
from dataclasses import dataclass, field
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
from app.shared.services.ai.agent.chat_tools import (
|
|
42
|
+
ApplyActionsTool,
|
|
43
|
+
AskUserTool,
|
|
44
|
+
ReadCanvasTool,
|
|
45
|
+
ReadProjectTool,
|
|
46
|
+
ReadTableTool,
|
|
47
|
+
ValidateTableTool,
|
|
48
|
+
)
|
|
49
|
+
from app.shared.services.ai.agent.chat_tools.apply_actions import ApplyCallbacks
|
|
50
|
+
from app.shared.services.ai.agent.chat_tools.ask_user import AskCallbacks
|
|
51
|
+
|
|
52
|
+
# P1-1:各工具入参的 Pydantic 校验模型,注册时按工具名查表绑定
|
|
53
|
+
from app.shared.services.ai.agent.chat_tools.schemas import MODEL_FOR_TOOL
|
|
54
|
+
from app.shared.services.ai.agent.executor import AgentExecutor
|
|
55
|
+
from app.shared.services.ai.agent.tool_registry import ToolRegistry
|
|
56
|
+
from app.shared.services.llm.actions.registry import (
|
|
57
|
+
ACTION_COUNT,
|
|
58
|
+
READ_ONLY_ACTION_TYPES,
|
|
59
|
+
build_action_type_list_text,
|
|
60
|
+
build_spec_field_mapping_text,
|
|
61
|
+
)
|
|
62
|
+
from app.shared.services.llm.chat.chat_system_prompt import SYSTEM_PROMPT_CORE
|
|
63
|
+
|
|
64
|
+
logger = logging.getLogger(__name__)
|
|
65
|
+
|
|
66
|
+
# 只读动作类型集合(用于动态标签判断,从注册表派生)
|
|
67
|
+
_READ_ONLY_LABEL_TYPES = READ_ONLY_ACTION_TYPES
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# =============================================================================
|
|
71
|
+
# 系统提示词
|
|
72
|
+
# =============================================================================
|
|
73
|
+
|
|
74
|
+
# 工具使用指引:定义 LLM 如何使用 5 个工具完成查-改-验闭环
|
|
75
|
+
_CHAT_AGENT_TOOL_GUIDE = """## 工具使用指引
|
|
76
|
+
|
|
77
|
+
你有以下 5 个工具可用。请根据用户需求自主决定调用顺序和次数:
|
|
78
|
+
|
|
79
|
+
### 1. read_project(查询,无参数)
|
|
80
|
+
读取当前项目的完整概览:所有表结构、约束、转换、正则节点、设置。
|
|
81
|
+
**使用时机**:用户询问"有哪些表"、"某表有哪些约束"、"当前配置"等查询类问题时,先调用此工具。
|
|
82
|
+
|
|
83
|
+
### 2. read_table(查询,参数: table_name, sample_rows?)
|
|
84
|
+
读取指定表的数据样本(前 N 行)和列结构。
|
|
85
|
+
**使用时机**:需要为某列设计约束(如 Range/AllowedValues)时,先看真实数据分布再决定参数。
|
|
86
|
+
|
|
87
|
+
### 3. apply_actions(修改,参数: actions)
|
|
88
|
+
执行配置修改动作。actions 是动作列表,每个动作含 actionType 和对应 spec。
|
|
89
|
+
**使用时机**:用户明确要求添加/修改/删除约束、表结构、正则、转换或设置时。
|
|
90
|
+
**关键区分**:
|
|
91
|
+
- 想创建新配置文件(磁盘上没有)→ 用 ADD_SCHEMA/ADD_REGEX 等。
|
|
92
|
+
- 想"把已存在的资源显示到画布上"(配置文件已有,但画布上没显示)→ 用 **ADD_TO_CANVAS**
|
|
93
|
+
(actionType=ADD_TO_CANVAS,spec 含 resourceKind: schema/regex/constraint/transform
|
|
94
|
+
和 resourceId/resourceName)。ADD_TO_CANVAS 不写盘,只把现有配置显示到画布。
|
|
95
|
+
**注意**:纯查询类问题绝不调用此工具。
|
|
96
|
+
|
|
97
|
+
### 4. validate_table(校验,参数: table_name?)
|
|
98
|
+
执行数据校验,返回错误数量和列表。不传 table_name 校验所有表。
|
|
99
|
+
**使用时机**:用户要求"校验项目/表",或在 apply_actions 后想验证改动效果。
|
|
100
|
+
|
|
101
|
+
### 5. read_canvas(查询,无参数)
|
|
102
|
+
读取当前**画布上实际显示**的节点列表(Schema、约束、正则、转换等),含各类数量摘要。
|
|
103
|
+
**与 read_project 的关键区别**:read_project 读项目配置文件,read_canvas 读画布快照——
|
|
104
|
+
项目配置里有的表/约束不一定已拖到画布上,两者会不一致。
|
|
105
|
+
**使用时机**:当用户说"画布上有没有 X"、"把 Y 放到画布/拖到画布"、"画布上现在有什么"、
|
|
106
|
+
或你需要判断某节点是否已在画布上显示时,先调用本工具确认画布真实状态,再决定是否需要 ADD 动作。
|
|
107
|
+
判断"画布上是否存在某节点"必须用 read_canvas,不能用 read_project。
|
|
108
|
+
|
|
109
|
+
## 工作流程
|
|
110
|
+
|
|
111
|
+
1. **查询类问题**(如"有哪些表"):read_project → 用自然语言回答,不要 apply_actions。
|
|
112
|
+
2. **修改类问题**(如"给 email 加唯一约束"):
|
|
113
|
+
- 如需确认结构:先 read_project 或 read_table
|
|
114
|
+
- apply_actions 执行修改
|
|
115
|
+
- 可选:validate_table 验证效果
|
|
116
|
+
- 用自然语言总结结果
|
|
117
|
+
3. **校验类问题**(如"校验数据"):直接 validate_table → 用自然语言汇报结果。
|
|
118
|
+
4. **画布显示类问题**(如"把 users 表拖到画布"、"显示 orders 约束"):
|
|
119
|
+
- 先 read_canvas 确认画布真实状态(可能已经显示了)
|
|
120
|
+
- 若画布上没有但配置里有(read_project 确认)→ apply_actions 用 **ADD_TO_CANVAS** 显示
|
|
121
|
+
- 若配置里也没有 → 用 ADD_SCHEMA 等先创建
|
|
122
|
+
- 不要用 read_project 推断画布内容
|
|
123
|
+
|
|
124
|
+
## 终止条件
|
|
125
|
+
|
|
126
|
+
当你准备好回答用户、不再需要调用任何工具时,直接输出自然语言文本(不带 tool_calls),
|
|
127
|
+
循环即结束,该文本会作为最终回复返回给用户。回答应简洁明了。"""
|
|
128
|
+
|
|
129
|
+
# 措辞规范(复用自原 chat_system_prompt)
|
|
130
|
+
_WORDING_RULES = """## 措辞规范
|
|
131
|
+
|
|
132
|
+
- 当执行了修改操作时:使用"我将..."、"准备..."等未来时态描述即将执行的动作。
|
|
133
|
+
❌ 错误:"已为 email 添加唯一约束"
|
|
134
|
+
✅ 正确:"我将为 email 添加唯一约束"
|
|
135
|
+
- 当汇报校验结果时:客观陈述错误数量和内容,不夸大不缩小。"""
|
|
136
|
+
|
|
137
|
+
# ask_user 工具使用指引
|
|
138
|
+
_ASK_USER_GUIDE = """## 何时使用 ask_user
|
|
139
|
+
|
|
140
|
+
ask_user 用于获取无法自行查到的信息或让用户做决策。**能自己查到的不要问**。
|
|
141
|
+
|
|
142
|
+
该问的情况:
|
|
143
|
+
- 用户意图存在多方案需要抉择("用 A 还是 B?")→ choice 类型
|
|
144
|
+
- 关键参数缺失且无法从 read_project/read_table/read_canvas 推断(如目标列名歧义)→ value 或 choice 类型
|
|
145
|
+
- 执行不可逆的批量非写盘操作前确认意图 → confirm 类型
|
|
146
|
+
- 需要用户提供开放式信息(如业务规则说明)→ free_text 类型
|
|
147
|
+
|
|
148
|
+
不该问的情况:
|
|
149
|
+
- 能通过 read_project 查到的表/列信息
|
|
150
|
+
- 能通过 read_table 推断的数据特征
|
|
151
|
+
- 答案在 context.selectedNodes 或 canvas 已有信息里
|
|
152
|
+
|
|
153
|
+
返回值:observation 含 answer 字段。用户可能跳过(skipped:true)——此时不要反复追问,
|
|
154
|
+
基于已知信息尽力继续或明确说明无法完成的原因。"""
|
|
155
|
+
|
|
156
|
+
# 完整的 chat agent 系统提示词
|
|
157
|
+
# 注意:SYSTEM_PROMPT_CORE 现已剥离 JSON 输出指令(移至 SYSTEM_PROMPT_JSON_OUTPUT,
|
|
158
|
+
# 仅非 Agent 路径用),故 Agent 路径无需 verbal override,直接继承 CORE 的领域能力描述。
|
|
159
|
+
CHAT_AGENT_SYSTEM_PROMPT = f"""{SYSTEM_PROMPT_CORE}
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
# Chat Agent 模式说明
|
|
164
|
+
|
|
165
|
+
你现在处于 Agent 工具调用模式。你不直接输出 JSON,而是:
|
|
166
|
+
- 调用工具完成查-改-验,最后用**自然语言文本**(不带 tool_calls)回复用户。
|
|
167
|
+
|
|
168
|
+
你可以通过调用工具查询项目信息、修改配置、校验数据。请根据用户需求自主决定如何组合使用工具。
|
|
169
|
+
|
|
170
|
+
{_CHAT_AGENT_TOOL_GUIDE}
|
|
171
|
+
|
|
172
|
+
{_WORDING_RULES}
|
|
173
|
+
|
|
174
|
+
{_ASK_USER_GUIDE}
|
|
175
|
+
|
|
176
|
+
## actions 格式说明
|
|
177
|
+
|
|
178
|
+
调用 apply_actions 时,actions 数组中每个元素必须含 actionType 和对应的 spec 字段。
|
|
179
|
+
actionType 可选值({ACTION_COUNT}种):
|
|
180
|
+
{build_action_type_list_text()}
|
|
181
|
+
|
|
182
|
+
每个动作需带对应 spec 字段:
|
|
183
|
+
{build_spec_field_mapping_text()}
|
|
184
|
+
|
|
185
|
+
## ADD_TO_CANVAS vs ADD_* 的关键区分(最容易出错,务必牢记)
|
|
186
|
+
|
|
187
|
+
- **ADD_TO_CANVAS**:项目配置文件里**已有**该资源,只是没显示在画布上 → 只读,不写盘。
|
|
188
|
+
- **ADD_SCHEMA / ADD_REGEX 等**:项目配置文件里**没有**该资源,需要**新建文件** → 会写盘。
|
|
189
|
+
|
|
190
|
+
判断流程(用户说"把 X 拖到/放到/显示在画布"时):
|
|
191
|
+
1. 先 read_project 确认 X 在配置文件里是否已存在。
|
|
192
|
+
2. 已存在 → 用 **ADD_TO_CANVAS**(绝不写盘,不弹写盘确认)。
|
|
193
|
+
3. 不存在 → 用 ADD_SCHEMA 等创建(会写盘,需用户确认)。
|
|
194
|
+
|
|
195
|
+
❌ 错误:配置里已有 users 表,用户说"拖到画布",却调 ADD_SCHEMA(会触发"文件已存在"失败或无谓的写盘确认)。
|
|
196
|
+
✅ 正确:配置里已有 users 表,用户说"拖到画布" → 调 ADD_TO_CANVAS。
|
|
197
|
+
|
|
198
|
+
## 约束类型与参数说明(关键)
|
|
199
|
+
|
|
200
|
+
调用 ADD_CONSTRAINT_NODE / UPDATE_CONSTRAINT_NODE 时,constraintSpec.type 必须是以下之一,
|
|
201
|
+
constraintSpec.params 按类型填充对应字段:
|
|
202
|
+
|
|
203
|
+
- **NotNull**: 非空约束。参数:无。
|
|
204
|
+
- **Unique**: 唯一约束。参数:无。
|
|
205
|
+
- **AllowedValues**: 允许值约束。参数:`allowedValues` (List[Any])。
|
|
206
|
+
- **Range**: 范围约束。参数:`min` (float/int), `max` (float/int)。
|
|
207
|
+
- **Scripted**: 脚本/正则约束。二选一:`expression` (str, 代码表达式) 或 `pattern` (str, 正则)。
|
|
208
|
+
- **ForeignKey**: 外键约束。参数:`toTableId` (str), `toColumnId` (str)。
|
|
209
|
+
- **Conditional**: 条件约束。参数:`ifConditions` (List), `thenValue` (Any)。
|
|
210
|
+
- `ifConditions` 结构:`[{{"ifColumnId": "列名", "operator": "eq/ne/gt/lt/in", "value": "比较值"}}]`
|
|
211
|
+
- **DateLogic**: 日期逻辑约束。参数:`logicMode` ("compare"/"calculation"), `compareOp` ("gt/lt/eq/gte/lte/range"), `referenceDate` (str, "YYYY-MM-DD"), `referenceColumn` (str)。当 `compareOp` 为 "range" 时,必须同时提供 `referenceDateEnd` 或 `referenceColumnEnd`。
|
|
212
|
+
- **Charset**: 字符集约束。
|
|
213
|
+
- **Composite**: 组合约束(多列联合)。
|
|
214
|
+
|
|
215
|
+
## 字段解析约定
|
|
216
|
+
|
|
217
|
+
- `tableName` / `targetColumn`:可使用表名/列名(中文或英文),系统会自动解析为对应 ID。
|
|
218
|
+
- 如不确定 ID,留空 `targetNodeId` / `targetColumnId`,系统从 `tableName` / `targetColumn` 解析。
|
|
219
|
+
- `isInline`:默认 false(创建独立约束文件)。仅当用户明确要求"内联约束/存入表配置"时设 true。
|
|
220
|
+
|
|
221
|
+
## 使用策略
|
|
222
|
+
|
|
223
|
+
- **默认创建独立约束文件** (`isInline: false`):独立文件是独立可引用的配置实体,画布节点与文件一一对应
|
|
224
|
+
- **只有当用户明确要求"内联约束"、"存在表配置里"时**,才设置 `isInline: true`
|
|
225
|
+
- 如果用户说"删除 XXX 约束",请使用 DELETE_CONSTRAINT_NODE,且 `isInline` 必须与该约束的实际存储形态一致(内联约束 → true,独立约束 → false)
|
|
226
|
+
- 必须确保 `tableName` 和 `targetColumn` 准确无误
|
|
227
|
+
|
|
228
|
+
## 防止误操作(务必遵守)
|
|
229
|
+
|
|
230
|
+
- **只改用户明确要求的资源**:不要主动添加、修改或删除无关的约束、表结构、正则节点、转换节点或设置。
|
|
231
|
+
- **不要重命名或重建 schema 文件**:除非用户明确要求重命名,否则使用现有 schema 的 tableName/id,直接修改对应文件。
|
|
232
|
+
- **不要删除已有约束**:除非用户明确说"删除"、"替换"或"去掉",否则保留已有约束。
|
|
233
|
+
- **不要重复创建**:如果某列已存在同类型约束,请在回复中说明,不要再次创建。
|
|
234
|
+
- **格式校验优先用约束**:"为 X 添加格式校验"应使用 ADD_CONSTRAINT_NODE(type=Scripted,params.pattern 为正则)或 ADD_REGEX,仅操作目标列。
|
|
235
|
+
- **一次只做一个明确修改**:如果用户只提到一个字段(如"为 email 添加格式校验"),你的 actions 列表中只能包含针对该字段的写操作。严禁同时添加 age 的 Range 约束、重建 users schema 或删除其他约束。若该字段已存在同类型约束,直接说明即可,不要生成新动作。
|
|
236
|
+
- **禁止照搬示例参数**:示例中的 `min: 0, max: 100` 只是参数格式说明,不要为未提及的字段创建 Range 约束。
|
|
237
|
+
- **不要把"拖到画布"误用为 ADD_SCHEMA**:当用户想把已存在的资源显示到画布时,使用 ADD_TO_CANVAS;只有资源不存在时才使用 ADD_SCHEMA/ADD_REGEX/ADD_TRANSFORM。
|
|
238
|
+
- **填写 intent_scope(写动作必填)**:调用 apply_actions 时,凡含写动作(ADD/UPDATE/DELETE_*),必须在 intent_scope 中声明你理解的用户意图所涉及的表和列。这是防止越界修改的安全门——后端会校验 actions 的写目标是否全部在 intent_scope 内,越界将被拒绝。例:用户说"给邮箱加格式校验"(邮箱=email),intent_scope 填 `{{"tables":["users"],"columns":[{{"table":"users","column":"email"}}]}}`;用户说"删除 users 表的所有约束",intent_scope 填 `{{"tables":["users"]}}`。中文到字段名的映射(邮箱→email)由你完成,后端只做精确比对。"""
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# =============================================================================
|
|
242
|
+
# 运行结果
|
|
243
|
+
# =============================================================================
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@dataclass
|
|
247
|
+
class ChatAgentRunResult:
|
|
248
|
+
"""Chat Agent 单次运行的产出
|
|
249
|
+
|
|
250
|
+
reply: 最终回复给用户的自然语言文本
|
|
251
|
+
frontend_instructions: apply_actions 旁路累积的前端指令列表
|
|
252
|
+
actions: 已执行的动作列表(用于审计,可空)
|
|
253
|
+
tool_steps: 工具调用轨迹(有序的工具名 + 简要描述列表,供前端展示)
|
|
254
|
+
iterations: 实际迭代轮数
|
|
255
|
+
success: 是否成功完成
|
|
256
|
+
error: 失败时的错误信息
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
reply: str
|
|
260
|
+
frontend_instructions: list[Any] = field(default_factory=list)
|
|
261
|
+
actions: list[dict[str, Any]] = field(default_factory=list)
|
|
262
|
+
tool_steps: list[dict[str, Any]] = field(default_factory=list)
|
|
263
|
+
iterations: int = 0
|
|
264
|
+
success: bool = True
|
|
265
|
+
error: str | None = None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# =============================================================================
|
|
269
|
+
# Runner 核心
|
|
270
|
+
# =============================================================================
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class ChatAgentRunner:
|
|
274
|
+
"""
|
|
275
|
+
@classdesc Chat Agent 编排器
|
|
276
|
+
|
|
277
|
+
组装工具 → 构建提示词 → 调用 AgentExecutor → 提取结果。
|
|
278
|
+
对 ChatOrchestrator 屏蔽 AgentExecutor 的内部细节。
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
def __init__(
|
|
282
|
+
self,
|
|
283
|
+
provider: Any,
|
|
284
|
+
project_path: str,
|
|
285
|
+
context_nodes: list[dict[str, Any]],
|
|
286
|
+
max_iterations: int = 5,
|
|
287
|
+
max_history_tokens: int = 120000,
|
|
288
|
+
confirm_controller: Any | None = None,
|
|
289
|
+
apply_callbacks: ApplyCallbacks | None = None,
|
|
290
|
+
ask_callbacks: AskCallbacks | None = None,
|
|
291
|
+
dry_run_enabled: bool = False,
|
|
292
|
+
job_id: str = "",
|
|
293
|
+
canvas_nodes: list[dict[str, Any]] | None = None,
|
|
294
|
+
):
|
|
295
|
+
"""
|
|
296
|
+
@methoddesc 初始化 Chat Agent Runner
|
|
297
|
+
|
|
298
|
+
参数:
|
|
299
|
+
provider: Provider 实例(BaseProvider 子类,有 chat 方法),由调用方通过 create() 创建
|
|
300
|
+
project_path: 项目配置目录路径
|
|
301
|
+
context_nodes: 前端选中的上下文节点列表
|
|
302
|
+
max_iterations: Agent 最大迭代轮数
|
|
303
|
+
max_history_tokens: 历史消息 token 预算
|
|
304
|
+
confirm_controller: (已废弃)旧的单 job 控制器;保留兼容但不再用于门控
|
|
305
|
+
apply_callbacks: apply_* 事件回调集合
|
|
306
|
+
ask_callbacks: ask_user 事件回调集合(仅流式路径启用交互)
|
|
307
|
+
dry_run_enabled: 是否启用两阶段确认模式
|
|
308
|
+
job_id: 当前任务 ID,供 ApplyActionsTool 生成 apply_id
|
|
309
|
+
canvas_nodes: 前端请求体携带的画布节点快照(已裁剪),供 read_canvas 工具查询。
|
|
310
|
+
区别于 context_nodes(用户右键选中的少数节点),canvas_nodes 是全部画布业务节点。
|
|
311
|
+
"""
|
|
312
|
+
self.provider = provider
|
|
313
|
+
self.project_path = project_path
|
|
314
|
+
self.context_nodes = context_nodes
|
|
315
|
+
self.max_iterations = max_iterations
|
|
316
|
+
self.max_history_tokens = max_history_tokens
|
|
317
|
+
self.confirm_controller = confirm_controller
|
|
318
|
+
self.apply_callbacks = apply_callbacks or ApplyCallbacks()
|
|
319
|
+
self.ask_callbacks = ask_callbacks or AskCallbacks()
|
|
320
|
+
self.dry_run_enabled = dry_run_enabled
|
|
321
|
+
self.job_id = job_id
|
|
322
|
+
self.canvas_nodes = canvas_nodes or []
|
|
323
|
+
|
|
324
|
+
# 关键:frontend_instructions 的旁路累积容器
|
|
325
|
+
# apply_actions 工具持有此列表引用,append 后 runner 最终读取
|
|
326
|
+
self.collected_instructions: list[Any] = []
|
|
327
|
+
|
|
328
|
+
# 流式回调容器:由 StreamingOrchestrator 通过 configure_callbacks 注入,
|
|
329
|
+
# run 时透传给 AgentExecutor(未配置时为空,executor 用默认 noop)。
|
|
330
|
+
self._callbacks: dict[str, Any] = {}
|
|
331
|
+
|
|
332
|
+
# 组装系统提示词(含上下文)
|
|
333
|
+
self.system_prompt = self._build_system_prompt()
|
|
334
|
+
|
|
335
|
+
def configure_callbacks(self, **kwargs: Any) -> None:
|
|
336
|
+
"""@methoddesc 配置流式回调,run 时透传给 AgentExecutor。
|
|
337
|
+
|
|
338
|
+
由 StreamingOrchestrator 调用,把 on_chunk/on_turn/on_tool_call/on_tool_result/cancelled
|
|
339
|
+
注入,实现 service 输出 → 事件流的桥接。未配置的回调在 executor 内部使用默认 noop。
|
|
340
|
+
|
|
341
|
+
参数:
|
|
342
|
+
**kwargs: 回调键值对,支持的键: on_chunk, on_turn, on_tool_call, on_tool_result, cancelled
|
|
343
|
+
"""
|
|
344
|
+
self._callbacks = kwargs
|
|
345
|
+
|
|
346
|
+
def _build_system_prompt(self) -> str:
|
|
347
|
+
"""
|
|
348
|
+
@methoddesc 构建完整的 chat agent 系统提示词
|
|
349
|
+
|
|
350
|
+
在基础 agent 提示词后,附加当前项目概览和选中上下文,
|
|
351
|
+
让 LLM 开局即了解项目状态(但仍可用 read_project 刷新)。
|
|
352
|
+
"""
|
|
353
|
+
from app.shared.services.ai.utils import get_project_overview
|
|
354
|
+
from app.shared.services.llm.chat.chat_system_prompt import (
|
|
355
|
+
build_context_section,
|
|
356
|
+
build_project_overview_section,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
parts = [CHAT_AGENT_SYSTEM_PROMPT]
|
|
360
|
+
|
|
361
|
+
# 附加项目概览(让 LLM 无需首轮必调 read_project)
|
|
362
|
+
try:
|
|
363
|
+
overview = get_project_overview(self.project_path)
|
|
364
|
+
overview_section = build_project_overview_section(overview)
|
|
365
|
+
if overview_section:
|
|
366
|
+
parts.append(overview_section)
|
|
367
|
+
except Exception as e:
|
|
368
|
+
logger.warning(f"构建项目概览失败,LLM 可自行调用 read_project: {e}")
|
|
369
|
+
|
|
370
|
+
# 附加选中上下文节点
|
|
371
|
+
context_section = build_context_section(self.context_nodes)
|
|
372
|
+
if context_section:
|
|
373
|
+
parts.append(context_section)
|
|
374
|
+
|
|
375
|
+
return "\n\n".join(parts)
|
|
376
|
+
|
|
377
|
+
def _create_registry(self, user_message: str = "") -> ToolRegistry:
|
|
378
|
+
"""
|
|
379
|
+
@methoddesc 创建并注册 chat 工具集
|
|
380
|
+
|
|
381
|
+
6 个工具:read_project/read_table/apply_actions/validate_table 注入 project_path,
|
|
382
|
+
read_canvas 注入画布节点快照,ask_user 注入交互回调(仅流式路径启用)。
|
|
383
|
+
apply_actions 额外注入 collected_instructions 共享引用 + 当前用户消息(用于意图范围校验)。
|
|
384
|
+
"""
|
|
385
|
+
registry = ToolRegistry()
|
|
386
|
+
|
|
387
|
+
# 工具注册统一走 register_tool;只读工具显式标 read_only(execute_many 并发分流)
|
|
388
|
+
registry.register_tool(
|
|
389
|
+
ReadProjectTool(project_path=self.project_path),
|
|
390
|
+
read_only=True,
|
|
391
|
+
args_model=MODEL_FOR_TOOL.get(ReadProjectTool.NAME),
|
|
392
|
+
)
|
|
393
|
+
registry.register_tool(
|
|
394
|
+
ReadTableTool(project_path=self.project_path),
|
|
395
|
+
read_only=True,
|
|
396
|
+
args_model=MODEL_FOR_TOOL.get(ReadTableTool.NAME),
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# 关键:apply_actions 注入 collected_instructions 共享引用 + 两阶段确认参数
|
|
400
|
+
# 同时传入 user_message,用于工具内部做意图范围校验,防止 LLM 越界修改。
|
|
401
|
+
# job_id 用于生成 apply_id("{job_id}#{seq}"),每次 apply 创建独立确认控制器
|
|
402
|
+
# apply_actions 是写盘工具(read_only 默认 False),execute_many 会串行化同轮多个 apply
|
|
403
|
+
registry.register_tool(
|
|
404
|
+
ApplyActionsTool(
|
|
405
|
+
project_path=self.project_path,
|
|
406
|
+
collected_instructions=self.collected_instructions,
|
|
407
|
+
dry_run_enabled=self.dry_run_enabled,
|
|
408
|
+
apply_callbacks=self.apply_callbacks,
|
|
409
|
+
job_id=self.job_id,
|
|
410
|
+
user_message=user_message,
|
|
411
|
+
),
|
|
412
|
+
args_model=MODEL_FOR_TOOL.get(ApplyActionsTool.NAME),
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
registry.register_tool(
|
|
416
|
+
ValidateTableTool(project_path=self.project_path),
|
|
417
|
+
read_only=True,
|
|
418
|
+
args_model=MODEL_FOR_TOOL.get(ValidateTableTool.NAME),
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# read_canvas:注入前端请求体携带的画布节点快照,供 LLM 查询画布真实状态
|
|
422
|
+
registry.register_tool(
|
|
423
|
+
ReadCanvasTool(canvas_nodes=self.canvas_nodes),
|
|
424
|
+
read_only=True,
|
|
425
|
+
args_model=MODEL_FOR_TOOL.get(ReadCanvasTool.NAME),
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# ask_user:交互问答工具,注入 ask_callbacks 与 dry_run_enabled
|
|
429
|
+
# ask_user 不写盘(标 read_only),与其他工具同轮调用时并发安全
|
|
430
|
+
registry.register_tool(
|
|
431
|
+
AskUserTool(
|
|
432
|
+
job_id=self.job_id,
|
|
433
|
+
ask_callbacks=self.ask_callbacks,
|
|
434
|
+
dry_run_enabled=self.dry_run_enabled,
|
|
435
|
+
),
|
|
436
|
+
read_only=True,
|
|
437
|
+
args_model=MODEL_FOR_TOOL.get(AskUserTool.NAME),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
return registry
|
|
441
|
+
|
|
442
|
+
# 工具名到人类可读标签的映射,用于前端展示轨迹
|
|
443
|
+
_TOOL_LABELS = {
|
|
444
|
+
ReadProjectTool.NAME: "读取项目",
|
|
445
|
+
ReadTableTool.NAME: "查看数据",
|
|
446
|
+
ApplyActionsTool.NAME: "修改配置",
|
|
447
|
+
ValidateTableTool.NAME: "校验数据",
|
|
448
|
+
ReadCanvasTool.NAME: "读取画布",
|
|
449
|
+
AskUserTool.NAME: "询问用户",
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
def _collect_audit_trail(self, agent_result: Any) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
453
|
+
"""
|
|
454
|
+
@methoddesc 从 agent 执行记录中收集审计轨迹
|
|
455
|
+
|
|
456
|
+
返回两个列表:
|
|
457
|
+
- executed_actions: 所有 apply_actions 工具调用中的 actions 参数(扁平化)
|
|
458
|
+
- tool_steps: 工具调用轨迹(每步含 tool 名、标签、轮次、成败),供前端展示
|
|
459
|
+
|
|
460
|
+
成败信息取自与 tool_calls 同序的 tool_results(executor 用 asyncio.gather
|
|
461
|
+
保持顺序挂载)。按下标对齐匹配,而非 name/call_id——因为 execute_many 的
|
|
462
|
+
异常兜底分支产生的 ToolResult 其 call_id/name 为空,无法用字段匹配。
|
|
463
|
+
缺失对应 result 时(边界情况)回退为 success,避免历史数据显示未知状态。
|
|
464
|
+
"""
|
|
465
|
+
executed: list[dict[str, Any]] = []
|
|
466
|
+
tool_steps: list[dict[str, Any]] = []
|
|
467
|
+
for turn in agent_result.turns:
|
|
468
|
+
results = turn.tool_results
|
|
469
|
+
for i, tc in enumerate(turn.tool_calls):
|
|
470
|
+
label = self._TOOL_LABELS.get(tc.name, tc.name)
|
|
471
|
+
step: dict[str, Any] = {"tool": tc.name, "label": label, "turn": turn.turn}
|
|
472
|
+
# 按下标取同序 tool_result,提取成败(B3 路径 A:后端保留成败)
|
|
473
|
+
if i < len(results):
|
|
474
|
+
tr = results[i]
|
|
475
|
+
step["status"] = "success" if tr.success else "failed"
|
|
476
|
+
if tr.error:
|
|
477
|
+
step["error"] = tr.error
|
|
478
|
+
else:
|
|
479
|
+
# 缺失 result 回退为 success(边界兜底)
|
|
480
|
+
step["status"] = "success"
|
|
481
|
+
# apply_actions 额外记录动作数量与动态标签
|
|
482
|
+
if tc.name == ApplyActionsTool.NAME:
|
|
483
|
+
args = tc.arguments
|
|
484
|
+
if isinstance(args, dict):
|
|
485
|
+
actions = args.get("actions", [])
|
|
486
|
+
if isinstance(actions, list):
|
|
487
|
+
executed.extend(actions)
|
|
488
|
+
step["action_count"] = len(actions)
|
|
489
|
+
# 动态标签:全是只读动作时显示"显示到画布",而非笼统的"修改配置"
|
|
490
|
+
# 避免用户看到"拖入画布"操作却显示"修改配置"的困惑
|
|
491
|
+
action_types = [a.get("actionType", "") for a in actions if isinstance(a, dict)]
|
|
492
|
+
if action_types and all(t in _READ_ONLY_LABEL_TYPES for t in action_types):
|
|
493
|
+
# 进一步细分:全是 ADD_TO_CANVAS 显示"显示到画布",全是 VALIDATE 显示"校验数据"
|
|
494
|
+
if all(t == "ADD_TO_CANVAS" for t in action_types):
|
|
495
|
+
step["label"] = "显示到画布"
|
|
496
|
+
elif all(t == "VALIDATE_PROJECT" for t in action_types):
|
|
497
|
+
step["label"] = "校验数据"
|
|
498
|
+
else:
|
|
499
|
+
step["label"] = "查询操作"
|
|
500
|
+
tool_steps.append(step)
|
|
501
|
+
return executed, tool_steps
|
|
502
|
+
|
|
503
|
+
async def run(self, message: str, history: list[dict[str, str]] | None = None) -> ChatAgentRunResult:
|
|
504
|
+
"""
|
|
505
|
+
@methoddesc 运行 Chat Agent
|
|
506
|
+
|
|
507
|
+
组装 registry + executor,跑工具循环,提取结果。
|
|
508
|
+
|
|
509
|
+
参数:
|
|
510
|
+
message: 用户消息
|
|
511
|
+
history: 对话历史(可选,用于多轮上下文)
|
|
512
|
+
|
|
513
|
+
返回:
|
|
514
|
+
ChatAgentRunResult: 含 reply、frontend_instructions、actions 等
|
|
515
|
+
"""
|
|
516
|
+
registry = self._create_registry(user_message=message)
|
|
517
|
+
|
|
518
|
+
# 构建任务消息:用户消息 + 历史摘要
|
|
519
|
+
task_message = self._build_task_message(message, history)
|
|
520
|
+
|
|
521
|
+
executor = AgentExecutor(
|
|
522
|
+
provider=self.provider,
|
|
523
|
+
registry=registry,
|
|
524
|
+
system_prompt=self.system_prompt,
|
|
525
|
+
max_iterations=self.max_iterations,
|
|
526
|
+
max_tokens=self.max_history_tokens,
|
|
527
|
+
# 流式回调透传(未配置时为 None,AgentExecutor 内部用默认 noop)
|
|
528
|
+
on_chunk=self._callbacks.get("on_chunk"),
|
|
529
|
+
on_turn=self._callbacks.get("on_turn"),
|
|
530
|
+
on_tool_call=self._callbacks.get("on_tool_call"),
|
|
531
|
+
on_tool_result=self._callbacks.get("on_tool_result"),
|
|
532
|
+
cancelled_callback=self._callbacks.get("cancelled"),
|
|
533
|
+
# chat agent 无最终输出工具,跳过最终配置提取(chat 靠无 tool_calls 自然终止)
|
|
534
|
+
final_output_tool=None,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
try:
|
|
538
|
+
agent_result = await executor.run(task_message)
|
|
539
|
+
except Exception as e:
|
|
540
|
+
logger.exception("ChatAgentRunner 执行失败")
|
|
541
|
+
return ChatAgentRunResult(
|
|
542
|
+
reply="抱歉,我在处理时遇到了问题,请稍后重试。",
|
|
543
|
+
frontend_instructions=[],
|
|
544
|
+
success=False,
|
|
545
|
+
error=f"Agent 执行失败: {e}",
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# 提取最终回复(无 tool_calls 的最后一轮 content)
|
|
549
|
+
reply = agent_result.content or ""
|
|
550
|
+
if not reply:
|
|
551
|
+
# 兜底:循环用尽但仍无文本回复
|
|
552
|
+
if agent_result.error:
|
|
553
|
+
reply = f"处理未能完成:{agent_result.error}"
|
|
554
|
+
else:
|
|
555
|
+
reply = "我已完成处理,但没有生成回复文本。"
|
|
556
|
+
|
|
557
|
+
# 收集审计轨迹(已执行动作 + 工具步骤)
|
|
558
|
+
executed_actions, tool_steps = self._collect_audit_trail(agent_result)
|
|
559
|
+
|
|
560
|
+
return ChatAgentRunResult(
|
|
561
|
+
reply=reply,
|
|
562
|
+
frontend_instructions=list(self.collected_instructions),
|
|
563
|
+
actions=executed_actions,
|
|
564
|
+
tool_steps=tool_steps,
|
|
565
|
+
iterations=agent_result.iterations,
|
|
566
|
+
success=agent_result.success,
|
|
567
|
+
error=agent_result.error,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
def _build_task_message(self, message: str, history: list[dict[str, str]] | None) -> str:
|
|
571
|
+
"""
|
|
572
|
+
@methoddesc 构建 Agent 任务消息
|
|
573
|
+
|
|
574
|
+
AgentExecutor.run 的 task_message 会作为初始 user 消息。
|
|
575
|
+
在用户原始消息前,附加简要的历史摘要(如有),
|
|
576
|
+
让 Agent 在多轮对话中保持上下文。
|
|
577
|
+
"""
|
|
578
|
+
if not history:
|
|
579
|
+
return message
|
|
580
|
+
|
|
581
|
+
# 截取最近几轮历史作为摘要(避免 task_message 过长)
|
|
582
|
+
recent = history[-6:] # 最近 3 轮(user+assistant 各算一条)
|
|
583
|
+
if not recent:
|
|
584
|
+
return message
|
|
585
|
+
|
|
586
|
+
history_lines = []
|
|
587
|
+
for msg in recent:
|
|
588
|
+
role = msg.get("role", "")
|
|
589
|
+
content = msg.get("content", "")
|
|
590
|
+
if role == "user":
|
|
591
|
+
history_lines.append(f"用户: {content}")
|
|
592
|
+
elif role == "assistant":
|
|
593
|
+
history_lines.append(f"助手: {content}")
|
|
594
|
+
|
|
595
|
+
history_text = "\n".join(history_lines)
|
|
596
|
+
return f"## 对话历史摘要\n{history_text}\n\n## 当前用户需求\n{message}"
|