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,260 @@
|
|
|
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
|
+
# backend/app/cli/shell/commands/ai/__init__.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview CLI Shell AI 助手命令模块入口
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 ai 命令聚合 AI 相关子命令
|
|
22
|
+
- 支持交互式菜单、对话、状态查看、Provider 切换等
|
|
23
|
+
- 支持直接通过自然语言执行 AI 指令
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- AICommand 作为聚合命令,管理 chat、status、switch、delete、setup 等子命令
|
|
27
|
+
- 无参数时显示交互式菜单(支持方向键导航)
|
|
28
|
+
- 第一个参数如果不是子命令,则将其视为直接询问的消息
|
|
29
|
+
- 使用 execute_ai_chat 统一执行 AI 对话
|
|
30
|
+
|
|
31
|
+
输入示例:
|
|
32
|
+
precis> ai
|
|
33
|
+
precis> ai chat
|
|
34
|
+
precis> ai status
|
|
35
|
+
precis> ai ask "添加非空约束到 users 表的 email 列"
|
|
36
|
+
|
|
37
|
+
输出示例:
|
|
38
|
+
交互式菜单或 AI 执行结果
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# 导入子命令
|
|
42
|
+
from app.cli.shell.commands.ai.chat import AIChatCommand
|
|
43
|
+
from app.cli.shell.commands.ai.delete import AIDeleteCommand
|
|
44
|
+
from app.cli.shell.commands.ai.executor import execute_ai_chat
|
|
45
|
+
from app.cli.shell.commands.ai.generate import AIGenerateCommand
|
|
46
|
+
from app.cli.shell.commands.ai.migrate import AIMigrateCommand
|
|
47
|
+
from app.cli.shell.commands.ai.status import AIStatusCommand
|
|
48
|
+
from app.cli.shell.commands.ai.switch import AISwitchCommand
|
|
49
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
50
|
+
from app.cli.shell.commands.provider import ProviderCommand
|
|
51
|
+
from app.cli.shell.config_storage import get_cli_config
|
|
52
|
+
from app.cli.shell.formatter import Formatter
|
|
53
|
+
from app.cli.shell.interactive_menu import InteractiveMenu
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AICommand(Command):
|
|
57
|
+
"""AI 助手命令。
|
|
58
|
+
|
|
59
|
+
支持交互式对话模式和直接执行 AI 指令。
|
|
60
|
+
作为聚合命令,包含多个子命令和一个交互式菜单。
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
super().__init__("ai", aliases=["assistant"])
|
|
65
|
+
self._chat_cmd: AIChatCommand = AIChatCommand()
|
|
66
|
+
self._status_cmd: AIStatusCommand = AIStatusCommand()
|
|
67
|
+
self._switch_cmd: AISwitchCommand = AISwitchCommand()
|
|
68
|
+
self._provider_cmd: ProviderCommand = ProviderCommand()
|
|
69
|
+
self._delete_cmd: AIDeleteCommand = AIDeleteCommand()
|
|
70
|
+
self._generate_cmd: AIGenerateCommand = AIGenerateCommand()
|
|
71
|
+
self._migrate_cmd: AIMigrateCommand = AIMigrateCommand()
|
|
72
|
+
self._cli_config = get_cli_config()
|
|
73
|
+
|
|
74
|
+
# 注册子命令
|
|
75
|
+
self.add_subcommand("chat", self._chat_cmd)
|
|
76
|
+
self.add_subcommand("generate", self._generate_cmd)
|
|
77
|
+
self.add_subcommand("migrate", self._migrate_cmd)
|
|
78
|
+
self.add_subcommand("status", self._status_cmd)
|
|
79
|
+
self.add_subcommand("switch", self._switch_cmd)
|
|
80
|
+
self.add_subcommand("delete", self._delete_cmd)
|
|
81
|
+
self.add_subcommand("provider", self._provider_cmd)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def description(self) -> str:
|
|
85
|
+
return "AI 助手 - 使用自然语言修改项目配置"
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def usage(self) -> str:
|
|
89
|
+
return "ai [chat|generate|migrate|status|switch|provider|delete|ask <message>]"
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def help_text(self) -> str:
|
|
93
|
+
return """
|
|
94
|
+
用法: ai [子命令] [参数]
|
|
95
|
+
|
|
96
|
+
子命令:
|
|
97
|
+
chat 进入交互式 AI 对话模式(默认 Agent 深度模式)
|
|
98
|
+
ask <message> 直接执行 AI 指令(如: ai ask "添加非空约束到用户表的email字段")
|
|
99
|
+
generate 从数据文件生成 Precis V2 配置
|
|
100
|
+
migrate 从旧脚本迁移生成 Precis V2 配置
|
|
101
|
+
status 显示 AI 配置状态
|
|
102
|
+
switch <provider> 切换默认 AI Provider
|
|
103
|
+
provider 管理 AI Provider(添加/编辑/删除/测试)
|
|
104
|
+
delete [provider] 删除已配置的 Provider
|
|
105
|
+
|
|
106
|
+
示例:
|
|
107
|
+
ai chat # 进入交互式对话(Agent 模式)
|
|
108
|
+
ai chat --no-agent-mode # 关闭 Agent,使用旧 JSON actions 路径
|
|
109
|
+
ai ask "创建非空约束到users表的email列" # 直接执行
|
|
110
|
+
ai generate data/users.xlsx # 预览生成的配置
|
|
111
|
+
ai generate data/*.xlsx --apply # 生成并写入项目
|
|
112
|
+
ai migrate scripts/legacy.sql data/users.xlsx --apply # 迁移并写入项目
|
|
113
|
+
ai status # 查看 AI 配置状态
|
|
114
|
+
ai switch kimi # 切换到 Kimi
|
|
115
|
+
ai provider # 管理 AI Provider
|
|
116
|
+
ai delete kimi # 删除 Kimi
|
|
117
|
+
|
|
118
|
+
说明:
|
|
119
|
+
AI 助手可以帮你通过自然语言修改项目配置,包括:
|
|
120
|
+
- 添加、更新、删除约束(NOT_NULL, UNIQUE, RANGE, ALLOWED_VALUES, REGEX 等)
|
|
121
|
+
- 查看和解释当前配置
|
|
122
|
+
- 从数据文件生成完整配置
|
|
123
|
+
- 从旧脚本迁移业务规则
|
|
124
|
+
|
|
125
|
+
首次使用请先运行 'ai provider' 命令添加 Provider
|
|
126
|
+
""".strip()
|
|
127
|
+
|
|
128
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
129
|
+
"""执行 AI 命令。
|
|
130
|
+
|
|
131
|
+
无参数时显示交互式菜单;
|
|
132
|
+
有参数时根据子命令名分发给对应处理器;
|
|
133
|
+
如果第一个参数不是已知子命令,则视为直接询问。
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
args: 命令参数列表
|
|
137
|
+
context: 命令上下文
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
子命令执行结果或 AI 对话结果
|
|
141
|
+
"""
|
|
142
|
+
if not args:
|
|
143
|
+
# 无参数时显示交互式菜单
|
|
144
|
+
return self._show_interactive_menu(context)
|
|
145
|
+
|
|
146
|
+
subcommand = args[0]
|
|
147
|
+
sub_args = args[1:]
|
|
148
|
+
|
|
149
|
+
if subcommand == "chat":
|
|
150
|
+
return self._chat_cmd.execute(sub_args, context)
|
|
151
|
+
elif subcommand == "ask":
|
|
152
|
+
return self._ask_direct(sub_args, context)
|
|
153
|
+
elif subcommand == "generate":
|
|
154
|
+
return self._generate_cmd.execute(sub_args, context)
|
|
155
|
+
elif subcommand == "migrate":
|
|
156
|
+
return self._migrate_cmd.execute(sub_args, context)
|
|
157
|
+
elif subcommand == "status":
|
|
158
|
+
return self._status_cmd.execute(sub_args, context)
|
|
159
|
+
elif subcommand == "switch":
|
|
160
|
+
return self._switch_cmd.execute(sub_args, context)
|
|
161
|
+
elif subcommand == "delete":
|
|
162
|
+
return self._delete_cmd.execute(sub_args, context)
|
|
163
|
+
elif subcommand == "provider":
|
|
164
|
+
return self._provider_cmd.execute(sub_args, context)
|
|
165
|
+
else:
|
|
166
|
+
# 如果第一个参数不是子命令,则将其视为直接询问的消息
|
|
167
|
+
return self._ask_direct(args, context)
|
|
168
|
+
|
|
169
|
+
def _show_interactive_menu(self, context: ProjectContext) -> CommandResult:
|
|
170
|
+
"""显示 AI 助手的交互式菜单(支持方向键导航)。
|
|
171
|
+
|
|
172
|
+
循环显示菜单,直到用户选择退出或执行需要退出的子命令。
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
context: 命令上下文
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
退出菜单后的结果
|
|
179
|
+
"""
|
|
180
|
+
while True:
|
|
181
|
+
status_lines = []
|
|
182
|
+
|
|
183
|
+
# 构建状态行:显示当前 Provider 和项目
|
|
184
|
+
provider = self._cli_config.get_active_provider()
|
|
185
|
+
if provider:
|
|
186
|
+
status_lines.append(Formatter.info(f"当前 Provider: {provider.name} ({provider.model})"))
|
|
187
|
+
else:
|
|
188
|
+
status_lines.append(Formatter.warning("[!] 未配置 AI Provider"))
|
|
189
|
+
|
|
190
|
+
if context.is_project_open:
|
|
191
|
+
# project_config 在 open 后可能尚未加载,做空值守卫避免 AttributeError
|
|
192
|
+
config = context.project_config or {}
|
|
193
|
+
project_name = config.get("project", {}).get("name", "project")
|
|
194
|
+
status_lines.append(Formatter.info(f"当前项目: {project_name}"))
|
|
195
|
+
else:
|
|
196
|
+
status_lines.append(Formatter.warning("[!] 未打开项目"))
|
|
197
|
+
|
|
198
|
+
# 创建交互式菜单
|
|
199
|
+
menu = InteractiveMenu("AI 助手")
|
|
200
|
+
menu.add_item("chat", "chat", "进入交互式对话模式")
|
|
201
|
+
menu.add_item("generate", "generate", "从数据文件生成配置")
|
|
202
|
+
menu.add_item("migrate", "migrate", "从旧脚本迁移配置")
|
|
203
|
+
menu.add_item("status", "status", "查看 AI 配置状态")
|
|
204
|
+
menu.add_item("switch", "switch", "切换 AI Provider")
|
|
205
|
+
menu.add_item("provider", "provider", "管理 AI Provider")
|
|
206
|
+
menu.add_item("delete", "delete", "删除 Provider")
|
|
207
|
+
menu.add_item("help", "help", "显示帮助信息")
|
|
208
|
+
|
|
209
|
+
choice = menu.show_with_status(status_lines)
|
|
210
|
+
|
|
211
|
+
if choice is None:
|
|
212
|
+
return CommandResult.ok("已退出 AI 助手")
|
|
213
|
+
elif choice == "chat":
|
|
214
|
+
result = self._chat_cmd.execute([], context)
|
|
215
|
+
if result.should_exit:
|
|
216
|
+
return result
|
|
217
|
+
elif choice == "generate":
|
|
218
|
+
result = self._generate_cmd.execute([], context)
|
|
219
|
+
if result.should_exit:
|
|
220
|
+
return result
|
|
221
|
+
input(Formatter.info("\n按回车键返回菜单..."))
|
|
222
|
+
elif choice == "migrate":
|
|
223
|
+
result = self._migrate_cmd.execute([], context)
|
|
224
|
+
if result.should_exit:
|
|
225
|
+
return result
|
|
226
|
+
input(Formatter.info("\n按回车键返回菜单..."))
|
|
227
|
+
elif choice == "status":
|
|
228
|
+
result = self._status_cmd.execute([], context)
|
|
229
|
+
print(result.message)
|
|
230
|
+
input(Formatter.info("\n按回车键返回菜单..."))
|
|
231
|
+
elif choice == "switch":
|
|
232
|
+
self._switch_cmd.execute([], context)
|
|
233
|
+
elif choice == "provider":
|
|
234
|
+
self._provider_cmd.execute([], context)
|
|
235
|
+
elif choice == "delete":
|
|
236
|
+
self._delete_cmd.execute([], context)
|
|
237
|
+
elif choice == "help":
|
|
238
|
+
print(Formatter.info(self.help_text))
|
|
239
|
+
input(Formatter.info("\n按回车键继续..."))
|
|
240
|
+
|
|
241
|
+
def _ask_direct(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
242
|
+
"""直接执行 AI 询问。
|
|
243
|
+
|
|
244
|
+
将参数拼接为完整消息,调用 execute_ai_chat 非交互式执行。
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
args: 消息内容(按空格分割的数组)
|
|
248
|
+
context: 命令上下文
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
AI 对话执行结果
|
|
252
|
+
"""
|
|
253
|
+
if not args:
|
|
254
|
+
return CommandResult.error("请提供询问内容\n用法: ai ask <message> 或 ai <message>")
|
|
255
|
+
|
|
256
|
+
message = " ".join(args)
|
|
257
|
+
return execute_ai_chat(message, context, interactive=False)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
__all__ = ["AICommand"]
|
|
@@ -0,0 +1,331 @@
|
|
|
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
|
+
# backend/app/cli/shell/commands/ai/chat.py
|
|
17
|
+
"""
|
|
18
|
+
@fileoverview AI 交互式对话命令模块
|
|
19
|
+
|
|
20
|
+
功能概述:
|
|
21
|
+
- 提供 ai chat 子命令进入交互式 AI 对话模式
|
|
22
|
+
- 管理对话历史记录,支持清空、查看统计
|
|
23
|
+
- 过滤孤立确认词(如 "ok"、"好的"),避免误触发 AI 调用
|
|
24
|
+
- 支持内置指令:help、clear、history、exit、quit、qq
|
|
25
|
+
|
|
26
|
+
架构设计:
|
|
27
|
+
- AIChatCommand 继承 Command 基类,实现 execute() 方法
|
|
28
|
+
- execute() 方法启动一个 while True 循环,持续读取用户输入
|
|
29
|
+
- 对话历史以 list[dict] 形式维护,每个元素包含 role(user/assistant)和 content
|
|
30
|
+
- 当历史记录的 Token 数超过限制时,自动截断最早的消息(保留系统提示词)
|
|
31
|
+
|
|
32
|
+
输入示例:
|
|
33
|
+
precis> ai chat
|
|
34
|
+
你: 在 users 表添加非空约束
|
|
35
|
+
AI: 已为您添加非空约束...
|
|
36
|
+
|
|
37
|
+
你: help
|
|
38
|
+
(显示帮助信息)
|
|
39
|
+
|
|
40
|
+
你: clear
|
|
41
|
+
(清空历史)
|
|
42
|
+
|
|
43
|
+
输出示例:
|
|
44
|
+
CommandResult.ok("")
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from app.cli.shell.commands.ai.executor import execute_ai_chat
|
|
48
|
+
from app.cli.shell.commands.ai.interaction import build_context_data
|
|
49
|
+
from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
|
|
50
|
+
from app.cli.shell.config_storage import get_cli_config
|
|
51
|
+
from app.cli.shell.formatter import Colors, Formatter
|
|
52
|
+
from app.shared.services.ai.utils import (
|
|
53
|
+
estimate_tokens,
|
|
54
|
+
truncate_history_by_tokens,
|
|
55
|
+
)
|
|
56
|
+
from app.shared.services.llm.chat.chat_system_prompt import build_system_prompt
|
|
57
|
+
from app.shared.services.llm.providers.base import compute_token_budgets, resolve_context_window
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AIChatCommand(Command):
|
|
61
|
+
"""AI 交互式对话命令。
|
|
62
|
+
|
|
63
|
+
启动一个持续运行的对话会话,用户可以反复输入自然语言指令与 AI 交互。
|
|
64
|
+
支持历史记录管理、Token 限制保护、内置快捷指令。
|
|
65
|
+
|
|
66
|
+
上下文窗口大小从 Provider 配置或内置模型表中自动获取,
|
|
67
|
+
不再硬编码为 120k。
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
# 为模型回复预留的 token 预算
|
|
71
|
+
RESERVED_OUTPUT_TOKENS = 8000
|
|
72
|
+
|
|
73
|
+
def __init__(self) -> None:
|
|
74
|
+
super().__init__("chat")
|
|
75
|
+
self._cli_config = get_cli_config()
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def description(self) -> str:
|
|
79
|
+
return "进入 AI 交互式对话模式"
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def usage(self) -> str:
|
|
83
|
+
return "ai chat [--no-agent-mode]"
|
|
84
|
+
|
|
85
|
+
def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
|
|
86
|
+
"""执行 AI 交互式对话命令。
|
|
87
|
+
|
|
88
|
+
检查项目和 AI 配置,解析命令行参数,然后进入交互式输入循环。
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
args: 命令参数列表,可能包含 --no-agent-mode
|
|
92
|
+
context: 命令上下文,必须包含已打开的项目
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
命令执行结果,退出对话后返回成功结果
|
|
96
|
+
"""
|
|
97
|
+
if not context.is_project_open:
|
|
98
|
+
return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
|
|
99
|
+
|
|
100
|
+
# 检查 AI 配置(使用 CLI 配置)
|
|
101
|
+
provider = self._cli_config.get_active_provider()
|
|
102
|
+
if not provider:
|
|
103
|
+
return CommandResult.error("没有可用的 LLM Provider 配置\n请先运行 'setup' 命令配置 API Key")
|
|
104
|
+
|
|
105
|
+
# 解析命令行参数(--stream/--no-stream 已随 execute_ai_chat 的死参数一并移除)
|
|
106
|
+
agent_mode = True # 默认启用 Agent 深度模式
|
|
107
|
+
filtered_args = []
|
|
108
|
+
for arg in args:
|
|
109
|
+
if arg == "--no-agent-mode":
|
|
110
|
+
agent_mode = False
|
|
111
|
+
else:
|
|
112
|
+
filtered_args.append(arg)
|
|
113
|
+
|
|
114
|
+
if filtered_args:
|
|
115
|
+
return CommandResult.error(f"未知参数: {filtered_args}\n用法: {self.usage}")
|
|
116
|
+
|
|
117
|
+
# 获取项目名称用于显示(project_config 在 open 后可能尚未加载,做空值守卫)
|
|
118
|
+
config = context.project_config or {}
|
|
119
|
+
project_name = config.get("project", {}).get("name", "project")
|
|
120
|
+
|
|
121
|
+
# 根据 Provider 配置和模型名获取上下文窗口(用户输入 > 自动探测 > 全局回退)。
|
|
122
|
+
# 4.24: max_tokens 是输出预算,经 compute_token_budgets 按窗裁剪——原公式
|
|
123
|
+
# 把输入侧公式当输出上限,小窗模型(cw<12k)每次请求必超窗
|
|
124
|
+
self._context_window = resolve_context_window(provider)
|
|
125
|
+
_input_budget, self._max_context_tokens = compute_token_budgets(self._context_window)
|
|
126
|
+
|
|
127
|
+
# 打印会话头信息
|
|
128
|
+
print(Formatter.header("\nAI 助手交互模式"))
|
|
129
|
+
print(Formatter.info(f"项目: {project_name}"))
|
|
130
|
+
print(Formatter.info(f"Provider: {provider.name}"))
|
|
131
|
+
print(Formatter.info(f"Model: {provider.model}"))
|
|
132
|
+
print(Formatter.info(f"Agent 模式: {'开启' if agent_mode else '关闭'}"))
|
|
133
|
+
print(Formatter.info(f"上下文限制: {self._context_window:,} tokens"))
|
|
134
|
+
print(Formatter.info("\n提示: 输入 'exit' 或 'quit' 退出对话,'help' 查看帮助,'clear' 清空历史"))
|
|
135
|
+
print(Formatter.header(""))
|
|
136
|
+
|
|
137
|
+
# 对话历史:存储用户和 AI 的消息,用于多轮对话
|
|
138
|
+
chat_history: list[dict[str, str]] = []
|
|
139
|
+
|
|
140
|
+
while True:
|
|
141
|
+
try:
|
|
142
|
+
# 获取用户输入(带绿色提示符)
|
|
143
|
+
prompt = Formatter.colorize("\n你: ", Colors.GREEN)
|
|
144
|
+
user_input = input(prompt).strip()
|
|
145
|
+
|
|
146
|
+
if not user_input:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
# 内置指令处理
|
|
150
|
+
if user_input.lower() in ("exit", "quit", "q"):
|
|
151
|
+
print(Formatter.success("\n再见!"))
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
if user_input.lower() in ("exit!", "quit!", "qq"):
|
|
155
|
+
print(Formatter.success("\n再见!"))
|
|
156
|
+
import sys
|
|
157
|
+
|
|
158
|
+
sys.exit(0)
|
|
159
|
+
|
|
160
|
+
if user_input.lower() == "help":
|
|
161
|
+
self._print_help()
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
if user_input.lower() == "clear":
|
|
165
|
+
chat_history = []
|
|
166
|
+
print(Formatter.info("对话历史已清空"))
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
if user_input.lower() == "history":
|
|
170
|
+
self._print_history_stats(chat_history)
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
# 忽略孤立的确认/回应词(用户可能只是回应 AI,而非执行命令)
|
|
174
|
+
# 但如果是疑问句或包含其他内容,则正常处理
|
|
175
|
+
acknowledgment_words = {
|
|
176
|
+
"ok",
|
|
177
|
+
"okay",
|
|
178
|
+
"yes",
|
|
179
|
+
"yep",
|
|
180
|
+
"yeah",
|
|
181
|
+
"y",
|
|
182
|
+
"好的",
|
|
183
|
+
"知道了",
|
|
184
|
+
"明白",
|
|
185
|
+
"收到",
|
|
186
|
+
"嗯",
|
|
187
|
+
"哦",
|
|
188
|
+
"thanks",
|
|
189
|
+
"thank you",
|
|
190
|
+
"谢谢",
|
|
191
|
+
"谢了",
|
|
192
|
+
"got it",
|
|
193
|
+
"了解了",
|
|
194
|
+
"清楚",
|
|
195
|
+
"好",
|
|
196
|
+
"可以",
|
|
197
|
+
"行",
|
|
198
|
+
}
|
|
199
|
+
input_lower = user_input.lower()
|
|
200
|
+
# 只过滤单独的确认词(不包含问号、不是疑问句)
|
|
201
|
+
if input_lower in acknowledgment_words and len(input_lower) < 20:
|
|
202
|
+
print(Formatter.dim("(已忽略确认词。输入 'help' 查看命令,或输入具体问题与 AI 对话)"))
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
# 执行 AI 对话(调用统一执行器)
|
|
206
|
+
result = execute_ai_chat(
|
|
207
|
+
user_input,
|
|
208
|
+
context,
|
|
209
|
+
interactive=True,
|
|
210
|
+
history=chat_history,
|
|
211
|
+
agent_mode=agent_mode,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if result.success and result.data:
|
|
215
|
+
# 更新历史:将用户消息和 AI 回复加入历史
|
|
216
|
+
reply = result.data.get("reply", "")
|
|
217
|
+
chat_history.append({"role": "user", "content": user_input})
|
|
218
|
+
chat_history.append({"role": "assistant", "content": reply})
|
|
219
|
+
|
|
220
|
+
# 根据 Token 数截断历史(安全机制,防止超出模型上下文窗口)
|
|
221
|
+
context_data = build_context_data("", context)
|
|
222
|
+
system_prompt = build_system_prompt(context_data)
|
|
223
|
+
chat_history = truncate_history_by_tokens(
|
|
224
|
+
chat_history, system_prompt, max_tokens=self._max_context_tokens
|
|
225
|
+
)
|
|
226
|
+
elif not result.success:
|
|
227
|
+
# 处理错误情况
|
|
228
|
+
print(Formatter.error(f"\n请求失败: {result.message}"))
|
|
229
|
+
else:
|
|
230
|
+
# result.success 为 True 但 data 为空(异常情况)
|
|
231
|
+
print(Formatter.warning("\n收到空响应,未更新历史记录"))
|
|
232
|
+
|
|
233
|
+
except KeyboardInterrupt:
|
|
234
|
+
# 用户按 Ctrl+C,提示如何正确退出
|
|
235
|
+
print()
|
|
236
|
+
print(Formatter.info("\n使用 'exit' 或 'quit' 退出对话"))
|
|
237
|
+
except EOFError:
|
|
238
|
+
# 输入流结束(如管道输入),优雅退出
|
|
239
|
+
print()
|
|
240
|
+
print(Formatter.success("\n再见!"))
|
|
241
|
+
break
|
|
242
|
+
|
|
243
|
+
return CommandResult.ok("")
|
|
244
|
+
|
|
245
|
+
def _print_help(self) -> None:
|
|
246
|
+
"""打印 AI 交互式对话的帮助信息。
|
|
247
|
+
|
|
248
|
+
列出所有可用命令、启动选项以及自然语言示例。
|
|
249
|
+
"""
|
|
250
|
+
help_text = """
|
|
251
|
+
AI 助手帮助
|
|
252
|
+
|
|
253
|
+
可用命令:
|
|
254
|
+
help - 显示此帮助信息
|
|
255
|
+
history - 显示对话历史统计
|
|
256
|
+
clear - 清空对话历史
|
|
257
|
+
exit - 退出对话模式(返回 CLI)
|
|
258
|
+
qq - 直接退出整个程序(全局可用)
|
|
259
|
+
|
|
260
|
+
启动选项:
|
|
261
|
+
--stream - 启用流式输出
|
|
262
|
+
--no-stream - 禁用流式输出(默认,显示 loading spinner)
|
|
263
|
+
--no-agent-mode - 关闭 Agent 深度模式(降级为旧 JSON actions 路径)
|
|
264
|
+
|
|
265
|
+
你可以用自然语言描述你的需求,例如:
|
|
266
|
+
- "在 users 表的 email 列上添加非空约束"
|
|
267
|
+
- "给 orders 表的 amount 列添加范围约束,最小值 0"
|
|
268
|
+
- "删除 users 表 phone 列上的唯一约束"
|
|
269
|
+
- "显示当前所有的约束配置"
|
|
270
|
+
|
|
271
|
+
支持的约束类型:
|
|
272
|
+
- NotNull: 非空约束
|
|
273
|
+
- Unique: 唯一约束
|
|
274
|
+
- AllowedValues: 允许值约束 (如: "allowedValues 为 A, B")
|
|
275
|
+
- Range: 范围约束 (如: "最小值 0, 最大值 100")
|
|
276
|
+
- Scripted: 脚本/正则约束 (如: "正则匹配 ^[0-9]+$")
|
|
277
|
+
- ForeignKey: 外键约束 (如: "关联到 orders 表的 user_id 列")
|
|
278
|
+
- Conditional: 条件约束 (如: "如果 type 等于 A 则值为 B")
|
|
279
|
+
- DateLogic: 日期逻辑约束 (如: "日期晚于 2023-01-01")
|
|
280
|
+
"""
|
|
281
|
+
print(Formatter.info(help_text))
|
|
282
|
+
|
|
283
|
+
def _print_history_stats(self, chat_history: list[dict[str, str]]) -> None:
|
|
284
|
+
"""显示对话历史统计信息。
|
|
285
|
+
|
|
286
|
+
统计消息总数、用户/AI 消息数量、估算 Token 数及上下文使用率。
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
chat_history: 当前对话历史列表
|
|
290
|
+
"""
|
|
291
|
+
if not chat_history:
|
|
292
|
+
print(Formatter.info("\n对话历史为空"))
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
# 计算统计信息
|
|
296
|
+
total_messages = len(chat_history)
|
|
297
|
+
user_messages = sum(1 for m in chat_history if m.get("role") == "user")
|
|
298
|
+
assistant_messages = sum(1 for m in chat_history if m.get("role") == "assistant")
|
|
299
|
+
|
|
300
|
+
# 计算 Token 数(估算)
|
|
301
|
+
total_tokens = 0
|
|
302
|
+
for msg in chat_history:
|
|
303
|
+
total_tokens += estimate_tokens(msg.get("content", ""))
|
|
304
|
+
|
|
305
|
+
# 系统提示词 Token 估算(系统提示词不显示在历史中,但占用 Token)
|
|
306
|
+
system_tokens = 2000 # 估算值
|
|
307
|
+
total_with_system = total_tokens + system_tokens
|
|
308
|
+
|
|
309
|
+
# 计算使用率百分比(基于完整上下文窗口)
|
|
310
|
+
context_window = getattr(self, "_context_window", 120000)
|
|
311
|
+
usage_percent = (total_with_system / context_window) * 100
|
|
312
|
+
|
|
313
|
+
print(Formatter.info("\n对话历史统计:"))
|
|
314
|
+
print(f" 消息总数: {total_messages}")
|
|
315
|
+
print(f" - 用户消息: {user_messages}")
|
|
316
|
+
print(f" - AI 回复: {assistant_messages}")
|
|
317
|
+
print(f" 估算 Token: {total_with_system:,} / {context_window:,}")
|
|
318
|
+
print(f" 使用率: {usage_percent:.1f}%")
|
|
319
|
+
|
|
320
|
+
# 显示最近几条消息预览
|
|
321
|
+
preview_count = min(3, len(chat_history) // 2)
|
|
322
|
+
if preview_count > 0:
|
|
323
|
+
print(f"\n 最近 {preview_count} 轮对话:")
|
|
324
|
+
for i in range(-preview_count * 2, 0, 2):
|
|
325
|
+
if abs(i) <= len(chat_history):
|
|
326
|
+
user_msg = chat_history[i] if i < 0 and abs(i) <= len(chat_history) else None
|
|
327
|
+
if user_msg and user_msg.get("role") == "user":
|
|
328
|
+
content = user_msg.get("content", "")[:30]
|
|
329
|
+
if len(user_msg.get("content", "")) > 30:
|
|
330
|
+
content += "..."
|
|
331
|
+
print(f" - {content}")
|