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,258 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""
|
|
17
|
+
@fileoverview AI 配置加载器
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 从 ~/.precis/ai_providers.yaml 加载 AI Provider 配置
|
|
21
|
+
- 支持环境变量 ${VAR} 递归替换
|
|
22
|
+
- API Key 加密存储(Fernet 对称加密)
|
|
23
|
+
- 配置版本检查(仅兼容 2.x)
|
|
24
|
+
- 首次使用自动创建默认配置模板
|
|
25
|
+
- mtime 缓存避免重复文件读取
|
|
26
|
+
|
|
27
|
+
设计原则:
|
|
28
|
+
- AI Provider 配置是用户级全局设置,不属于项目仓库
|
|
29
|
+
- 统一固定读取 ~/.precis/ai_providers.yaml
|
|
30
|
+
- save() 始终写入用户级路径
|
|
31
|
+
- api_key 加密存储:YAML 中以 "enc:" 前缀标记加密值
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import os
|
|
37
|
+
import re
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
import yaml
|
|
42
|
+
|
|
43
|
+
from app.shared.core.config import ConfigPaths
|
|
44
|
+
from app.shared.core.io.yaml import write_yaml_atomic
|
|
45
|
+
|
|
46
|
+
from .crypto import decrypt_api_key, encrypt_api_key, is_encrypted
|
|
47
|
+
from .models import AIConfig, AIProvider
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ConfigLoader:
|
|
51
|
+
"""
|
|
52
|
+
@classdesc 配置加载器 - 只支持 v2.0
|
|
53
|
+
|
|
54
|
+
AI Provider 配置固定存放在用户级路径 ~/.precis/ai_providers.yaml,
|
|
55
|
+
不再支持项目级或系统级配置查找。
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
CONFIG_FILENAME = "ai_providers.yaml"
|
|
59
|
+
|
|
60
|
+
_cached_config: AIConfig | None = None
|
|
61
|
+
_cached_mtime: float | None = None
|
|
62
|
+
|
|
63
|
+
def __init__(self, config_path: Path | None = None):
|
|
64
|
+
"""
|
|
65
|
+
@methoddesc 初始化配置加载器
|
|
66
|
+
|
|
67
|
+
参数:
|
|
68
|
+
config_path: 可选,自定义配置文件路径。主要用于测试。
|
|
69
|
+
未提供时固定使用 ~/.precis/ai_providers.yaml。
|
|
70
|
+
"""
|
|
71
|
+
self._config_path = config_path
|
|
72
|
+
# §2.12: env 注入 api_key 的 provider id 集合(save 时剔除不落盘)
|
|
73
|
+
self._env_sourced_providers: set[str] = set()
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def config_path(self) -> Path:
|
|
77
|
+
"""用户级 AI 配置文件路径(唯一真相源)"""
|
|
78
|
+
if self._config_path is not None:
|
|
79
|
+
return self._config_path
|
|
80
|
+
return ConfigPaths.ai_providers()
|
|
81
|
+
|
|
82
|
+
@config_path.setter
|
|
83
|
+
def config_path(self, value: Path) -> None:
|
|
84
|
+
"""允许测试注入自定义路径。"""
|
|
85
|
+
self._config_path = value
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def USER_PATH(self) -> Path: # noqa: N802
|
|
89
|
+
"""用户级配置路径(save 的写入目标)"""
|
|
90
|
+
return self.config_path
|
|
91
|
+
|
|
92
|
+
def _expand_env(self, value: Any) -> Any:
|
|
93
|
+
"""
|
|
94
|
+
@methoddesc 递归替换字符串中的环境变量 ${VAR}
|
|
95
|
+
|
|
96
|
+
支持字符串、字典、列表的递归处理。
|
|
97
|
+
如果环境变量不存在,替换为空字符串。
|
|
98
|
+
"""
|
|
99
|
+
if isinstance(value, str):
|
|
100
|
+
return re.sub(r"\$\{([^}]+)\}", lambda m: os.getenv(m.group(1), ""), value)
|
|
101
|
+
elif isinstance(value, dict):
|
|
102
|
+
return {k: self._expand_env(v) for k, v in value.items()}
|
|
103
|
+
elif isinstance(value, list):
|
|
104
|
+
return [self._expand_env(v) for v in value]
|
|
105
|
+
return value
|
|
106
|
+
|
|
107
|
+
def _decrypt_api_keys(self, data: Any) -> Any:
|
|
108
|
+
"""递归解密 api_key 字段中的加密值。"""
|
|
109
|
+
if isinstance(data, dict):
|
|
110
|
+
result: dict[Any, Any] = {}
|
|
111
|
+
for k, v in data.items():
|
|
112
|
+
if k == "api_key" and isinstance(v, str) and is_encrypted(v):
|
|
113
|
+
result[k] = decrypt_api_key(v)
|
|
114
|
+
else:
|
|
115
|
+
result[k] = self._decrypt_api_keys(v)
|
|
116
|
+
return result
|
|
117
|
+
elif isinstance(data, list):
|
|
118
|
+
return [self._decrypt_api_keys(item) for item in data]
|
|
119
|
+
return data
|
|
120
|
+
|
|
121
|
+
def _encrypt_api_keys(self, data: Any) -> Any:
|
|
122
|
+
"""递归加密 api_key 字段中的明文值。"""
|
|
123
|
+
if isinstance(data, dict):
|
|
124
|
+
result: dict[Any, Any] = {}
|
|
125
|
+
for k, v in data.items():
|
|
126
|
+
if k == "api_key" and isinstance(v, str) and v and not is_encrypted(v) and not re.search(r"\$\{", v):
|
|
127
|
+
result[k] = encrypt_api_key(v)
|
|
128
|
+
else:
|
|
129
|
+
result[k] = self._encrypt_api_keys(v)
|
|
130
|
+
return result
|
|
131
|
+
elif isinstance(data, list):
|
|
132
|
+
return [self._encrypt_api_keys(item) for item in data]
|
|
133
|
+
return data
|
|
134
|
+
|
|
135
|
+
def load(self) -> AIConfig:
|
|
136
|
+
"""
|
|
137
|
+
@methoddesc 加载 AI Provider 配置文件
|
|
138
|
+
|
|
139
|
+
固定读取 ~/.precis/ai_providers.yaml。
|
|
140
|
+
文件不存在时自动创建默认模板并返回。
|
|
141
|
+
"""
|
|
142
|
+
config_path = self.config_path
|
|
143
|
+
|
|
144
|
+
if self._cached_config is not None and config_path.exists():
|
|
145
|
+
try:
|
|
146
|
+
current_mtime = config_path.stat().st_mtime
|
|
147
|
+
if current_mtime == self._cached_mtime:
|
|
148
|
+
return self._cached_config
|
|
149
|
+
except OSError:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
if not config_path.exists():
|
|
153
|
+
config = self._create_default()
|
|
154
|
+
self.save(config)
|
|
155
|
+
return config
|
|
156
|
+
|
|
157
|
+
with open(config_path, encoding="utf-8") as f:
|
|
158
|
+
data = yaml.safe_load(f) or {}
|
|
159
|
+
|
|
160
|
+
version = data.get("version", "unknown")
|
|
161
|
+
if not isinstance(version, str) or not version.startswith("2."):
|
|
162
|
+
raise ValueError(f"Unsupported config version: {version}, expected 2.x")
|
|
163
|
+
|
|
164
|
+
data = self._expand_env(data)
|
|
165
|
+
data = self._decrypt_api_keys(data)
|
|
166
|
+
config = AIConfig(**data)
|
|
167
|
+
self._load_api_keys_from_env(config)
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
self._cached_mtime = config_path.stat().st_mtime
|
|
171
|
+
self._cached_config = config
|
|
172
|
+
except OSError:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
return config
|
|
176
|
+
|
|
177
|
+
def _load_api_keys_from_env(self, config: AIConfig) -> None:
|
|
178
|
+
"""从环境变量读取 API Key(优先级高于配置文件)。
|
|
179
|
+
|
|
180
|
+
§2.12: 注入的同时记录 env 来源的 provider id——save() 据此把 env 来源的
|
|
181
|
+
api_key 从落盘数据剔除,保持"env 覆盖是临时语义";用户显式改 key 的路径
|
|
182
|
+
调 mark_api_key_manual() 后正常落盘。
|
|
183
|
+
"""
|
|
184
|
+
for provider in config.providers:
|
|
185
|
+
env_key = f"{provider.id.upper().replace('-', '_')}_API_KEY"
|
|
186
|
+
api_key = os.getenv(env_key)
|
|
187
|
+
|
|
188
|
+
if not api_key:
|
|
189
|
+
provider_type = provider.type.value if hasattr(provider.type, "value") else str(provider.type)
|
|
190
|
+
env_key = f"{provider_type.upper()}_API_KEY"
|
|
191
|
+
api_key = os.getenv(env_key)
|
|
192
|
+
|
|
193
|
+
if api_key:
|
|
194
|
+
provider.api_key = api_key
|
|
195
|
+
self._env_sourced_providers.add(provider.id)
|
|
196
|
+
|
|
197
|
+
def mark_api_key_manual(self, provider_id: str) -> None:
|
|
198
|
+
"""把 provider 的 api_key 标记为手工设置(save 时正常落盘,不再按 env 来源剔除)。"""
|
|
199
|
+
self._env_sourced_providers.discard(provider_id)
|
|
200
|
+
|
|
201
|
+
def save(self, config: AIConfig) -> None:
|
|
202
|
+
"""
|
|
203
|
+
@methoddesc 将配置保存到用户级 YAML 文件
|
|
204
|
+
|
|
205
|
+
始终写入 ~/.precis/ai_providers.yaml。
|
|
206
|
+
使用原子写入(临时文件 + os.replace):此文件含 API Key 等敏感配置,
|
|
207
|
+
裸 open("w") 在写入中途崩溃/断电会产生半截文件,导致用户配置丢失。
|
|
208
|
+
|
|
209
|
+
§2.12: env 注入的 api_key 不落盘(否则 env 覆盖被一次性写死进配置文件,
|
|
210
|
+
用户此后换 env 变量不再生效还以为 env 是活的)。
|
|
211
|
+
"""
|
|
212
|
+
user_path = self.config_path
|
|
213
|
+
user_path.parent.mkdir(parents=True, exist_ok=True)
|
|
214
|
+
|
|
215
|
+
data = config.model_dump(exclude_none=True)
|
|
216
|
+
data = self._encrypt_api_keys(data)
|
|
217
|
+
|
|
218
|
+
if self._env_sourced_providers:
|
|
219
|
+
for provider_data in data.get("providers", []):
|
|
220
|
+
if provider_data.get("id") in self._env_sourced_providers:
|
|
221
|
+
provider_data.pop("api_key", None)
|
|
222
|
+
|
|
223
|
+
write_yaml_atomic(user_path, data)
|
|
224
|
+
|
|
225
|
+
self.invalidate_cache()
|
|
226
|
+
|
|
227
|
+
def invalidate_cache(self) -> None:
|
|
228
|
+
"""清除缓存,强制下次 load() 重新读取文件"""
|
|
229
|
+
self._cached_config = None
|
|
230
|
+
self._cached_mtime = None
|
|
231
|
+
|
|
232
|
+
def _create_default(self) -> AIConfig:
|
|
233
|
+
"""创建默认配置模板(DeepSeek + Ollama Local)。"""
|
|
234
|
+
return AIConfig(
|
|
235
|
+
providers=[
|
|
236
|
+
AIProvider(
|
|
237
|
+
id="deepseek",
|
|
238
|
+
name="DeepSeek",
|
|
239
|
+
type="openai",
|
|
240
|
+
base_url="https://api.deepseek.com",
|
|
241
|
+
api_key="${DEEPSEEK_API_KEY}",
|
|
242
|
+
model="deepseek-v4-pro",
|
|
243
|
+
),
|
|
244
|
+
AIProvider(
|
|
245
|
+
id="ollama-local",
|
|
246
|
+
name="Ollama Local",
|
|
247
|
+
type="ollama",
|
|
248
|
+
base_url="http://localhost:11434",
|
|
249
|
+
api_key=None,
|
|
250
|
+
model="llama3.2",
|
|
251
|
+
),
|
|
252
|
+
],
|
|
253
|
+
defaults={"chat": "deepseek"},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# 全局实例
|
|
258
|
+
loader = ConfigLoader()
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""
|
|
17
|
+
@fileoverview AI Provider 配置模型
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 定义 AI Provider 的配置结构(AIProvider、AIConfig)
|
|
21
|
+
- 定义 Provider 类型枚举(ProviderType、DeploymentType)
|
|
22
|
+
- 定义网络配置模型(NetworkConfig)
|
|
23
|
+
- 支持根据 base_url 自动推断部署类型(本地/远程)
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- 使用 Pydantic BaseModel 进行数据校验和序列化
|
|
27
|
+
- 字段验证器自动推断 deployment 字段
|
|
28
|
+
- 配置版本固定为 2.0,确保向前兼容
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
# ~/.precis/ai_providers.yaml
|
|
32
|
+
version: "2.0"
|
|
33
|
+
providers:
|
|
34
|
+
- id: ollama-local
|
|
35
|
+
name: Ollama
|
|
36
|
+
type: ollama
|
|
37
|
+
base_url: http://localhost:11434
|
|
38
|
+
api_key: null
|
|
39
|
+
model: llama3.2
|
|
40
|
+
|
|
41
|
+
输出示例:
|
|
42
|
+
AIConfig(
|
|
43
|
+
version="2.0",
|
|
44
|
+
providers=[AIProvider(id="ollama-local", ..., deployment=DeploymentType.LOCAL)],
|
|
45
|
+
defaults={"chat": "ollama-local"}
|
|
46
|
+
)
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
from enum import Enum
|
|
50
|
+
from typing import Any
|
|
51
|
+
|
|
52
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ProviderType(str, Enum):
|
|
56
|
+
"""
|
|
57
|
+
@classdesc Provider 类型枚举
|
|
58
|
+
|
|
59
|
+
定义支持的 AI Provider 类型:
|
|
60
|
+
- OPENAI: OpenAI 兼容 API(云端/本地通用)
|
|
61
|
+
- OLLAMA: Ollama 原生 API
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
OPENAI = "openai" # OpenAI 兼容 API(云端/本地通用)
|
|
65
|
+
OLLAMA = "ollama" # Ollama 原生 API
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class DeploymentType(str, Enum):
|
|
69
|
+
"""
|
|
70
|
+
@classdesc 部署类型枚举
|
|
71
|
+
|
|
72
|
+
根据 base_url 自动推断部署位置:
|
|
73
|
+
- LOCAL: 本地(localhost/127.0.0.1)
|
|
74
|
+
- REMOTE: 远程(内网/公网)
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
LOCAL = "local" # 本地(localhost/127.0.0.1)
|
|
78
|
+
REMOTE = "remote" # 远程(内网/公网)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class NetworkConfig(BaseModel):
|
|
82
|
+
"""
|
|
83
|
+
@classdesc 网络连接配置
|
|
84
|
+
|
|
85
|
+
控制与 AI Provider 通信时的超时和 SSL 校验行为。
|
|
86
|
+
|
|
87
|
+
Attributes:
|
|
88
|
+
timeout: 请求超时时间(秒),范围 5~300,默认 60
|
|
89
|
+
verify_ssl: 是否验证 SSL 证书,默认 True
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
93
|
+
|
|
94
|
+
timeout: int = Field(default=60, ge=5, le=300, description="请求超时时间(秒)")
|
|
95
|
+
verify_ssl: bool = Field(default=True, description="是否验证 SSL 证书")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class AICacheConfig(BaseModel):
|
|
99
|
+
"""
|
|
100
|
+
@classdesc LLM 响应缓存配置
|
|
101
|
+
|
|
102
|
+
控制 CachedProvider 的缓存行为。默认关闭,显式开启才生效。
|
|
103
|
+
|
|
104
|
+
Attributes:
|
|
105
|
+
enabled: 是否启用缓存,默认 False
|
|
106
|
+
ttl_seconds: 缓存条目过期时间(秒),默认 3600
|
|
107
|
+
max_entries: 缓存最大条目数(LRU 驱逐),默认 100
|
|
108
|
+
cache_temperature_above_zero: 是否缓存 temperature > 0 的请求,默认 False
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
112
|
+
|
|
113
|
+
enabled: bool = Field(default=False, description="是否启用 LLM 响应缓存")
|
|
114
|
+
ttl_seconds: float = Field(default=3600, ge=1, description="缓存条目过期时间(秒)")
|
|
115
|
+
max_entries: int = Field(default=100, ge=1, description="缓存最大条目数")
|
|
116
|
+
cache_temperature_above_zero: bool = Field(default=False, description="是否缓存 temperature > 0 的请求")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class AIProvider(BaseModel):
|
|
120
|
+
"""
|
|
121
|
+
@classdesc AI Provider 配置模型
|
|
122
|
+
|
|
123
|
+
定义一个 LLM 服务提供者的完整配置信息。
|
|
124
|
+
设计简化:所有核心字段必填,无向后兼容逻辑。
|
|
125
|
+
|
|
126
|
+
Attributes:
|
|
127
|
+
id: Provider 唯一标识符(如 "openai", "ollama-local")
|
|
128
|
+
name: 显示名称(用于界面展示)
|
|
129
|
+
type: Provider 类型枚举(OPENAI 或 OLLAMA)
|
|
130
|
+
base_url: API 基础 URL(如 https://api.openai.com/v1)
|
|
131
|
+
api_key: API 密钥,本地服务可设为 null
|
|
132
|
+
model: 默认使用的模型名称(如 "gpt-4", "llama3.2")
|
|
133
|
+
context_window: 模型最大上下文窗口(tokens),None 则自动推断
|
|
134
|
+
deployment: 部署类型(LOCAL/REMOTE),根据 base_url 自动推断
|
|
135
|
+
network: 网络连接配置(可选)
|
|
136
|
+
meta: 额外元数据字典(可选)
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
140
|
+
|
|
141
|
+
id: str = Field(..., description="唯一标识")
|
|
142
|
+
name: str = Field(..., description="显示名称")
|
|
143
|
+
type: ProviderType = Field(..., description="Provider 类型")
|
|
144
|
+
|
|
145
|
+
base_url: str = Field(..., description="API 基础 URL")
|
|
146
|
+
api_key: str | None = Field(default=None, description="API 密钥(本地服务可为 null)")
|
|
147
|
+
model: str = Field(..., description="默认模型名称")
|
|
148
|
+
context_window: int | None = Field(
|
|
149
|
+
default=None,
|
|
150
|
+
ge=1024,
|
|
151
|
+
description="模型最大上下文窗口(tokens),None 时由 Provider 根据模型名自动推断",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# 运行时推断字段(不序列化到配置文件)
|
|
155
|
+
deployment: DeploymentType | None = Field(default=None, exclude=True, description="部署类型(自动推断)")
|
|
156
|
+
|
|
157
|
+
# 可选扩展配置
|
|
158
|
+
network: NetworkConfig | None = Field(default=None, description="网络配置")
|
|
159
|
+
meta: dict[str, Any] | None = Field(default=None, description="额外元数据")
|
|
160
|
+
|
|
161
|
+
@model_validator(mode="after")
|
|
162
|
+
def _infer_deployment(self) -> "AIProvider":
|
|
163
|
+
"""
|
|
164
|
+
@methoddesc 根据 base_url 推断部署类型
|
|
165
|
+
|
|
166
|
+
如果 URL 中包含 localhost、127.0.0.1 等本地地址,
|
|
167
|
+
则推断为 LOCAL,否则为 REMOTE。
|
|
168
|
+
使用 model_validator 确保在所有字段初始化后执行。
|
|
169
|
+
"""
|
|
170
|
+
if self.deployment is None:
|
|
171
|
+
local_hosts = ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
|
|
172
|
+
if any(h in self.base_url for h in local_hosts):
|
|
173
|
+
self.deployment = DeploymentType.LOCAL
|
|
174
|
+
else:
|
|
175
|
+
self.deployment = DeploymentType.REMOTE
|
|
176
|
+
return self
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class AIConfig(BaseModel):
|
|
180
|
+
"""
|
|
181
|
+
@classdesc AI 配置根对象
|
|
182
|
+
|
|
183
|
+
对应 ~/.precis/ai_providers.yaml 的完整配置结构。
|
|
184
|
+
|
|
185
|
+
Attributes:
|
|
186
|
+
version: 配置格式版本,固定为 "2.0"
|
|
187
|
+
providers: 所有 AI Provider 的列表
|
|
188
|
+
defaults: 各场景默认使用的 Provider ID,如 {"chat": "ollama-local", "generate": "openai"}
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
192
|
+
|
|
193
|
+
version: str = Field(default="2.0", description="配置版本号")
|
|
194
|
+
providers: list[AIProvider] = Field(default_factory=list, description="Provider 列表")
|
|
195
|
+
|
|
196
|
+
# 系统设置
|
|
197
|
+
defaults: dict[str, str] = Field(
|
|
198
|
+
default_factory=dict, description="各场景默认 Provider,如 {'chat': 'ollama-local'}"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# LLM 响应缓存配置(默认关闭)
|
|
202
|
+
cache: AICacheConfig = Field(default_factory=AICacheConfig, description="LLM 响应缓存配置")
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""
|
|
17
|
+
@fileoverview AI Provider 预设定义
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 定义内置的服务商预设配置(base_url、默认模型、可用模型列表)
|
|
21
|
+
- 所有 OpenAI 兼容服务商统一为 type=openai
|
|
22
|
+
- 后端扩展新服务商时只需在此文件添加预设条目
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- PROVIDER_PRESETS 为字典,key 为预设 ID,value 为预设配置
|
|
26
|
+
- 预设配置包含服务商元信息和可用模型列表
|
|
27
|
+
- API 端点 GET /providers/presets 返回预设列表供前端和 CLI 使用
|
|
28
|
+
- 这是 Provider 元数据的唯一来源,前端和 CLI 共用
|
|
29
|
+
- 字典顺序即前端设置页下拉与 CLI 菜单的展示顺序(国内主流在前、本地服务在后)
|
|
30
|
+
- base_url 必须是 OpenAI 兼容端点(openai SDK 会在其后拼接 /chat/completions),
|
|
31
|
+
含版本路径的厂商必须带全(如 /v1、/compatible-mode/v1、/api/paas/v4、/api/v3)
|
|
32
|
+
|
|
33
|
+
维护约定:
|
|
34
|
+
- 各厂商 base_url / 模型 ID / 鉴权要点 / 更新 SOP 见同目录 AI_PROVIDER_PRESETS.md
|
|
35
|
+
- 模型列表有时效性(厂商滚动升级),更新时须对照官方模型列表并同步维护文档的核对日期
|
|
36
|
+
|
|
37
|
+
输入示例:
|
|
38
|
+
preset = PROVIDER_PRESETS["deepseek"]
|
|
39
|
+
|
|
40
|
+
输出示例:
|
|
41
|
+
ProviderPreset(id="deepseek", name="DeepSeek", type="openai",
|
|
42
|
+
base_url="https://api.deepseek.com",
|
|
43
|
+
default_model="deepseek-v4-flash",
|
|
44
|
+
models=["deepseek-v4-flash", "deepseek-v4-pro"])
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from __future__ import annotations
|
|
48
|
+
|
|
49
|
+
from typing import Any
|
|
50
|
+
|
|
51
|
+
PROVIDER_PRESETS: dict[str, dict[str, Any]] = {
|
|
52
|
+
"deepseek": {
|
|
53
|
+
"id": "deepseek",
|
|
54
|
+
"name": "DeepSeek",
|
|
55
|
+
"type": "openai",
|
|
56
|
+
"base_url": "https://api.deepseek.com",
|
|
57
|
+
"default_model": "deepseek-v4-pro",
|
|
58
|
+
"models": ["deepseek-v4-pro", "deepseek-v4-flash"],
|
|
59
|
+
},
|
|
60
|
+
"qwen": {
|
|
61
|
+
"id": "qwen",
|
|
62
|
+
"name": "通义千问 Qwen",
|
|
63
|
+
"type": "openai",
|
|
64
|
+
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
65
|
+
"default_model": "qwen3.8-max",
|
|
66
|
+
"models": ["qwen3.8-max", "qwen3.7-plus", "qwen3.8-flash"],
|
|
67
|
+
},
|
|
68
|
+
"glm": {
|
|
69
|
+
"id": "glm",
|
|
70
|
+
"name": "智谱 GLM",
|
|
71
|
+
"type": "openai",
|
|
72
|
+
"base_url": "https://open.bigmodel.cn/api/paas/v4",
|
|
73
|
+
"default_model": "glm-5.3",
|
|
74
|
+
"models": ["glm-5.3", "glm-5.3-flash", "glm-5.2"],
|
|
75
|
+
},
|
|
76
|
+
"kimi": {
|
|
77
|
+
"id": "kimi",
|
|
78
|
+
"name": "月之暗面 Kimi",
|
|
79
|
+
"type": "openai",
|
|
80
|
+
"base_url": "https://api.moonshot.cn/v1",
|
|
81
|
+
"default_model": "kimi-k3",
|
|
82
|
+
"models": ["kimi-k3", "kimi-k2.7-code", "kimi-k2.6", "kimi-latest"],
|
|
83
|
+
},
|
|
84
|
+
"minimax": {
|
|
85
|
+
"id": "minimax",
|
|
86
|
+
"name": "MiniMax",
|
|
87
|
+
"type": "openai",
|
|
88
|
+
"base_url": "https://api.minimaxi.com/v1",
|
|
89
|
+
"default_model": "MiniMax-M3",
|
|
90
|
+
"models": ["MiniMax-M3", "MiniMax-M2.5", "MiniMax-M2.7-highspeed"],
|
|
91
|
+
},
|
|
92
|
+
"mimo": {
|
|
93
|
+
"id": "mimo",
|
|
94
|
+
"name": "Xiaomi MiMo",
|
|
95
|
+
"type": "openai",
|
|
96
|
+
"base_url": "https://api.xiaomimimo.com/v1",
|
|
97
|
+
"default_model": "mimo-v2.5",
|
|
98
|
+
"models": ["mimo-v2.5", "mimo-v2.5-pro"],
|
|
99
|
+
},
|
|
100
|
+
"ollama": {
|
|
101
|
+
"id": "ollama-local",
|
|
102
|
+
"name": "Ollama Local",
|
|
103
|
+
"type": "ollama",
|
|
104
|
+
"base_url": "http://localhost:11434",
|
|
105
|
+
"default_model": "llama3.2",
|
|
106
|
+
"models": [],
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_preset_list() -> list[dict[str, Any]]:
|
|
112
|
+
"""返回所有预设的列表(不含内部字段)"""
|
|
113
|
+
return [
|
|
114
|
+
{
|
|
115
|
+
"id": v["id"],
|
|
116
|
+
"name": v["name"],
|
|
117
|
+
"type": v["type"],
|
|
118
|
+
"base_url": v["base_url"],
|
|
119
|
+
"default_model": v["default_model"],
|
|
120
|
+
"models": list(v["models"]),
|
|
121
|
+
}
|
|
122
|
+
for v in PROVIDER_PRESETS.values()
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_preset(preset_id: str) -> dict[str, Any] | None:
|
|
127
|
+
"""按 ID 获取预设,不存在返回 None"""
|
|
128
|
+
return PROVIDER_PRESETS.get(preset_id)
|