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,334 @@
|
|
|
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 OpenAI 兼容 Provider
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 支持 OpenAI API 格式的所有服务(云端和本地)
|
|
21
|
+
- 实现非流式对话、流式对话、模型列表、健康检查
|
|
22
|
+
- 自动重试机制(指数退避,最多 3 次)
|
|
23
|
+
- 错误分类处理:网络错误、服务端错误、响应解析错误
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- 继承 BaseProvider,实现统一的 Provider 接口
|
|
27
|
+
- 使用 AsyncOpenAI 客户端进行异步 HTTP 通信
|
|
28
|
+
- 与配置模型联动:从 AIProvider 读取 base_url、api_key、timeout
|
|
29
|
+
|
|
30
|
+
输入示例:
|
|
31
|
+
req = ChatRequest(
|
|
32
|
+
messages=[ChatMessage(role="user", content="你好")],
|
|
33
|
+
temperature=0.7
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
输出示例:
|
|
37
|
+
resp = await provider.chat(req)
|
|
38
|
+
# ChatResponse(content="你好!", model="gpt-4")
|
|
39
|
+
|
|
40
|
+
async for chunk in provider.chat_stream(req):
|
|
41
|
+
print(chunk, end="")
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
import asyncio
|
|
47
|
+
import logging
|
|
48
|
+
from collections.abc import AsyncIterator
|
|
49
|
+
from typing import TYPE_CHECKING, Any
|
|
50
|
+
|
|
51
|
+
if TYPE_CHECKING:
|
|
52
|
+
from openai import APIConnectionError, APIStatusError, AsyncOpenAI
|
|
53
|
+
|
|
54
|
+
from app.shared.services.llm.config.models import AIProvider
|
|
55
|
+
else:
|
|
56
|
+
try:
|
|
57
|
+
from openai import APIConnectionError, APIStatusError, AsyncOpenAI
|
|
58
|
+
except ImportError:
|
|
59
|
+
APIConnectionError = None
|
|
60
|
+
APIStatusError = None
|
|
61
|
+
AsyncOpenAI = None
|
|
62
|
+
|
|
63
|
+
from .base import BaseProvider, ChatRequest, ChatResponse, StreamChunk
|
|
64
|
+
|
|
65
|
+
logger = logging.getLogger(__name__)
|
|
66
|
+
|
|
67
|
+
_MAX_RETRIES = 3
|
|
68
|
+
_RETRY_BASE_DELAY = 1.0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class OpenAIProvider(BaseProvider):
|
|
72
|
+
"""
|
|
73
|
+
@classdesc OpenAI 兼容 Provider
|
|
74
|
+
|
|
75
|
+
支持 OpenAI API 格式的所有服务(云端和本地)。
|
|
76
|
+
实现非流式对话、流式对话、模型列表、健康检查。
|
|
77
|
+
具备自动重试机制(指数退避,最多 3 次)。
|
|
78
|
+
|
|
79
|
+
使用场景:
|
|
80
|
+
- OpenAI 官方 API
|
|
81
|
+
- 本地 OpenAI 兼容服务(如 vLLM、TGI 等)
|
|
82
|
+
- 其他 OpenAI 格式兼容的云端服务
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def name(self) -> str:
|
|
87
|
+
return "OpenAI-Compatible"
|
|
88
|
+
|
|
89
|
+
def __init__(self, config: AIProvider) -> None:
|
|
90
|
+
super().__init__(config)
|
|
91
|
+
if AsyncOpenAI is None:
|
|
92
|
+
raise ImportError("openai 未安装,请运行 pip install openai")
|
|
93
|
+
self.client = AsyncOpenAI(
|
|
94
|
+
base_url=config.base_url,
|
|
95
|
+
api_key=config.api_key or "",
|
|
96
|
+
timeout=config.network.timeout if config.network else 60,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
async def chat(self, req: ChatRequest) -> ChatResponse:
|
|
100
|
+
"""
|
|
101
|
+
@methoddesc 发送非流式对话请求,支持自动重试
|
|
102
|
+
|
|
103
|
+
当遇到网络错误或限流(429)/服务端错误(500, 502, 503)时,
|
|
104
|
+
会进行最多 3 次指数退避重试。
|
|
105
|
+
|
|
106
|
+
参数:
|
|
107
|
+
req: 对话请求对象
|
|
108
|
+
|
|
109
|
+
返回:
|
|
110
|
+
ChatResponse: AI 回复内容
|
|
111
|
+
|
|
112
|
+
异常:
|
|
113
|
+
ValueError: API 响应解析失败
|
|
114
|
+
APIConnectionError: 网络连接持续失败
|
|
115
|
+
APIStatusError: 服务端返回非 2xx 状态码且不可重试
|
|
116
|
+
"""
|
|
117
|
+
for attempt in range(_MAX_RETRIES):
|
|
118
|
+
try:
|
|
119
|
+
# 构造 messages payload,支持 tool_calls 和 tool_call_id
|
|
120
|
+
messages_payload = []
|
|
121
|
+
for m in req.messages:
|
|
122
|
+
msg: dict = {"role": m.role}
|
|
123
|
+
if m.content is not None:
|
|
124
|
+
msg["content"] = m.content
|
|
125
|
+
if m.tool_calls is not None:
|
|
126
|
+
msg["tool_calls"] = m.tool_calls
|
|
127
|
+
if m.tool_call_id is not None:
|
|
128
|
+
msg["tool_call_id"] = m.tool_call_id
|
|
129
|
+
messages_payload.append(msg)
|
|
130
|
+
|
|
131
|
+
kwargs: dict = {
|
|
132
|
+
"model": self._get_model(req.model),
|
|
133
|
+
"messages": messages_payload,
|
|
134
|
+
"temperature": req.temperature,
|
|
135
|
+
"stream": False,
|
|
136
|
+
}
|
|
137
|
+
if req.tools:
|
|
138
|
+
kwargs["tools"] = req.tools
|
|
139
|
+
kwargs["tool_choice"] = req.tool_choice if req.tool_choice is not None else "auto"
|
|
140
|
+
|
|
141
|
+
resp = await self.client.chat.completions.create(**kwargs)
|
|
142
|
+
if not resp.choices:
|
|
143
|
+
raise ValueError("API 返回空的 choices 列表")
|
|
144
|
+
message = resp.choices[0].message
|
|
145
|
+
content = message.content or ""
|
|
146
|
+
|
|
147
|
+
# 解析 tool_calls
|
|
148
|
+
tool_calls = None
|
|
149
|
+
raw_tool_calls = getattr(message, "tool_calls", None)
|
|
150
|
+
if raw_tool_calls:
|
|
151
|
+
tool_calls = []
|
|
152
|
+
for tc in raw_tool_calls:
|
|
153
|
+
tool_calls.append(
|
|
154
|
+
{
|
|
155
|
+
"id": getattr(tc, "id", ""),
|
|
156
|
+
"type": getattr(tc, "type", "function"),
|
|
157
|
+
"function": {
|
|
158
|
+
"name": tc.function.name,
|
|
159
|
+
"arguments": tc.function.arguments,
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return ChatResponse(content=content, tool_calls=tool_calls, model=resp.model)
|
|
165
|
+
except (APIConnectionError, APIStatusError) as e:
|
|
166
|
+
# 非可重试状态码直接抛出异常
|
|
167
|
+
if isinstance(e, APIStatusError) and e.status_code not in (429, 500, 502, 503):
|
|
168
|
+
raise
|
|
169
|
+
# 未达到最大重试次数时,指数退避后重试
|
|
170
|
+
if attempt < _MAX_RETRIES - 1:
|
|
171
|
+
delay = _RETRY_BASE_DELAY * (2**attempt)
|
|
172
|
+
logger.warning(f"[OpenAI] 请求失败(第 {attempt + 1} 次),{delay:.1f}s 后重试: {e}")
|
|
173
|
+
await asyncio.sleep(delay)
|
|
174
|
+
else:
|
|
175
|
+
raise
|
|
176
|
+
except (IndexError, AttributeError, TypeError) as e:
|
|
177
|
+
raise ValueError(f"解析 API 响应失败: {e}") from e
|
|
178
|
+
|
|
179
|
+
# 所有重试已耗尽且未正常返回,理论上不会到达此处
|
|
180
|
+
raise RuntimeError("OpenAI 请求在重试后仍未完成")
|
|
181
|
+
|
|
182
|
+
async def chat_stream(self, req: ChatRequest) -> AsyncIterator[StreamChunk]:
|
|
183
|
+
"""
|
|
184
|
+
@methoddesc 发送流式对话请求(支持 tools 与 tool_calls 分片累积),逐块返回统一 StreamChunk
|
|
185
|
+
|
|
186
|
+
统一输出契约:
|
|
187
|
+
- delta 文本 → StreamChunk(type="delta", text=...)
|
|
188
|
+
- tool_calls(分片累积, finish_reason="tool_calls" 时) → StreamChunk(type="tool_calls", tool_calls=[...原始格式])
|
|
189
|
+
|
|
190
|
+
重试策略(与 chat 对齐):
|
|
191
|
+
- 网络错误(APIConnectionError)或限流/服务端错误(429/500/502/503)触发指数退避重试,最多 _MAX_RETRIES 次。
|
|
192
|
+
- **关键安全门**:只在"尚未 yield 任何 chunk 前"重试。一旦已经向下游输出过 token,
|
|
193
|
+
即使后续流中断也不重试(避免重复输出导致内容错乱,且无法回收已发出的 chunk)。
|
|
194
|
+
- 已 yield 后的异常直接抛出,由上层 executor 处理。
|
|
195
|
+
|
|
196
|
+
参数:
|
|
197
|
+
req: 对话请求对象
|
|
198
|
+
|
|
199
|
+
返回:
|
|
200
|
+
AsyncIterator[StreamChunk]: 统一流式输出单元
|
|
201
|
+
|
|
202
|
+
异常:
|
|
203
|
+
APIConnectionError: 网络连接失败(重试耗尽后)
|
|
204
|
+
APIStatusError: 服务端返回错误(重试耗尽后)
|
|
205
|
+
ValueError: 流式响应解析异常
|
|
206
|
+
"""
|
|
207
|
+
# 构造 messages payload(与非流式 chat 一致,支持 tool_calls/tool_call_id 上下文)
|
|
208
|
+
messages_payload = []
|
|
209
|
+
for m in req.messages:
|
|
210
|
+
msg: dict = {"role": m.role}
|
|
211
|
+
if m.content is not None:
|
|
212
|
+
msg["content"] = m.content
|
|
213
|
+
if m.tool_calls is not None:
|
|
214
|
+
msg["tool_calls"] = m.tool_calls
|
|
215
|
+
if m.tool_call_id is not None:
|
|
216
|
+
msg["tool_call_id"] = m.tool_call_id
|
|
217
|
+
messages_payload.append(msg)
|
|
218
|
+
|
|
219
|
+
kwargs: dict = {
|
|
220
|
+
"model": self._get_model(req.model),
|
|
221
|
+
"messages": messages_payload,
|
|
222
|
+
"temperature": req.temperature,
|
|
223
|
+
"stream": True,
|
|
224
|
+
}
|
|
225
|
+
if req.tools:
|
|
226
|
+
kwargs["tools"] = req.tools
|
|
227
|
+
kwargs["tool_choice"] = req.tool_choice if req.tool_choice is not None else "auto"
|
|
228
|
+
|
|
229
|
+
last_exc: Exception | None = None
|
|
230
|
+
for attempt in range(_MAX_RETRIES):
|
|
231
|
+
# 本轮标志:是否已向下游 yield 过任何 chunk。决定异常时能否重试。
|
|
232
|
+
yielded_any = False
|
|
233
|
+
try:
|
|
234
|
+
stream = await self.client.chat.completions.create(**kwargs)
|
|
235
|
+
|
|
236
|
+
# tool_calls 分片累积器: {index: {"id":"", "name":"", "arguments":""}}
|
|
237
|
+
tc_acc: dict[int, dict[str, str]] = {}
|
|
238
|
+
async for chunk in stream:
|
|
239
|
+
if not chunk.choices:
|
|
240
|
+
continue
|
|
241
|
+
choice = chunk.choices[0]
|
|
242
|
+
delta = choice.delta
|
|
243
|
+
|
|
244
|
+
# 1. 文本增量 → 立即 yield
|
|
245
|
+
if delta and delta.content:
|
|
246
|
+
yielded_any = True
|
|
247
|
+
yield StreamChunk(type="delta", text=delta.content)
|
|
248
|
+
|
|
249
|
+
# 2. tool_calls 分片 → 累积(不立即 yield)
|
|
250
|
+
if delta and delta.tool_calls:
|
|
251
|
+
for tc in delta.tool_calls:
|
|
252
|
+
slot = tc_acc.setdefault(tc.index, {"id": "", "name": "", "arguments": ""})
|
|
253
|
+
if getattr(tc, "id", None):
|
|
254
|
+
slot["id"] = tc.id
|
|
255
|
+
if getattr(tc.function, "name", None):
|
|
256
|
+
slot["name"] = tc.function.name
|
|
257
|
+
slot["arguments"] += tc.function.arguments or ""
|
|
258
|
+
|
|
259
|
+
# 3. finish_reason="tool_calls" → 一次性 yield 完整 tool_calls(OpenAI 原始格式)
|
|
260
|
+
if choice.finish_reason == "tool_calls":
|
|
261
|
+
yielded_any = True
|
|
262
|
+
yield StreamChunk(
|
|
263
|
+
type="tool_calls",
|
|
264
|
+
tool_calls=[
|
|
265
|
+
{
|
|
266
|
+
"id": v["id"],
|
|
267
|
+
"type": "function",
|
|
268
|
+
"function": {
|
|
269
|
+
"name": v["name"],
|
|
270
|
+
"arguments": v["arguments"],
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
for v in tc_acc.values()
|
|
274
|
+
],
|
|
275
|
+
)
|
|
276
|
+
tc_acc.clear()
|
|
277
|
+
# finish_reason="stop" → 流自然结束(循环结束),无需特殊处理
|
|
278
|
+
# 流正常耗尽,返回
|
|
279
|
+
return
|
|
280
|
+
except (APIConnectionError, APIStatusError) as e:
|
|
281
|
+
last_exc = e
|
|
282
|
+
# 非可重试状态码直接抛出(与 chat 一致)
|
|
283
|
+
if isinstance(e, APIStatusError) and e.status_code not in (429, 500, 502, 503):
|
|
284
|
+
raise
|
|
285
|
+
# 已 yield 出 chunk 则不再重试(无法回收已发出的内容)
|
|
286
|
+
if yielded_any:
|
|
287
|
+
raise
|
|
288
|
+
# 未达最大重试次数 → 指数退避后重试
|
|
289
|
+
if attempt < _MAX_RETRIES - 1:
|
|
290
|
+
delay = _RETRY_BASE_DELAY * (2**attempt)
|
|
291
|
+
logger.warning(f"[OpenAI] 流式请求失败(第 {attempt + 1} 次),{delay:.1f}s 后重试: {e}")
|
|
292
|
+
await asyncio.sleep(delay)
|
|
293
|
+
else:
|
|
294
|
+
raise
|
|
295
|
+
except Exception as e:
|
|
296
|
+
# 已 yield 出 chunk 后的非预期异常:不可重试(无法回收)
|
|
297
|
+
if yielded_any:
|
|
298
|
+
raise ValueError(f"流式响应异常(已输出部分内容): {e}") from e
|
|
299
|
+
# 未 yield 前的解析类异常:不重试,直接抛出(与 chat 的 IndexError/AttributeError 分支对齐)
|
|
300
|
+
raise ValueError(f"流式响应异常: {e}") from e
|
|
301
|
+
|
|
302
|
+
# 理论上不会到达(重试耗尽会在循环内 raise)
|
|
303
|
+
if last_exc is not None:
|
|
304
|
+
raise last_exc
|
|
305
|
+
raise RuntimeError("OpenAI 流式请求在重试后仍未完成")
|
|
306
|
+
|
|
307
|
+
async def list_models(self) -> list[str]:
|
|
308
|
+
"""
|
|
309
|
+
@methoddesc 获取当前 Provider 可用的模型列表
|
|
310
|
+
|
|
311
|
+
返回:
|
|
312
|
+
模型 ID 字符串列表
|
|
313
|
+
"""
|
|
314
|
+
models = await self.client.models.list()
|
|
315
|
+
return [m.id for m in models.data]
|
|
316
|
+
|
|
317
|
+
async def health(self) -> dict[str, Any]:
|
|
318
|
+
"""
|
|
319
|
+
@methoddesc 健康检查
|
|
320
|
+
|
|
321
|
+
尝试获取模型列表并计算延迟。
|
|
322
|
+
|
|
323
|
+
返回:
|
|
324
|
+
{"status": "ok", "latency_ms": 延迟毫秒数} 或
|
|
325
|
+
{"status": "error", "error": 错误信息}
|
|
326
|
+
"""
|
|
327
|
+
import time
|
|
328
|
+
|
|
329
|
+
start = time.time()
|
|
330
|
+
try:
|
|
331
|
+
await self.client.models.list()
|
|
332
|
+
return {"status": "ok", "latency_ms": int((time.time() - start) * 1000)}
|
|
333
|
+
except Exception as e:
|
|
334
|
+
return {"status": "error", "error": str(e)}
|
|
@@ -0,0 +1,108 @@
|
|
|
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 Provider 注册表
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 管理 ProviderType 到具体 Provider 实现类的映射
|
|
21
|
+
- 提供统一的 Provider 实例创建接口
|
|
22
|
+
- 自动注册内置 Provider(OpenAI、Ollama)
|
|
23
|
+
|
|
24
|
+
架构设计:
|
|
25
|
+
- 全局字典注册表:运行时动态注册/查询
|
|
26
|
+
- 工厂模式:create(config) 根据配置类型自动实例化对应 Provider
|
|
27
|
+
- 与 base.py 解耦:新增 Provider 只需在此注册即可
|
|
28
|
+
|
|
29
|
+
输入示例:
|
|
30
|
+
from app.shared.services.llm.config.models import AIProvider, ProviderType
|
|
31
|
+
config = AIProvider(id="test", name="Test", type=ProviderType.OPENAI, base_url="...", model="gpt-4")
|
|
32
|
+
|
|
33
|
+
输出示例:
|
|
34
|
+
provider = create(config)
|
|
35
|
+
# 返回 OpenAIProvider 实例(因为 config.type == ProviderType.OPENAI)
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from ..config.models import AIProvider, ProviderType
|
|
39
|
+
from .base import BaseProvider
|
|
40
|
+
from .cached_provider import CachedProvider
|
|
41
|
+
from .ollama import OllamaProvider
|
|
42
|
+
from .openai import OpenAIProvider
|
|
43
|
+
|
|
44
|
+
# 全局注册表:ProviderType -> Provider 类 的映射字典
|
|
45
|
+
_registry: dict[ProviderType, type[BaseProvider]] = {}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def register(type: ProviderType, cls: type[BaseProvider]) -> None:
|
|
49
|
+
"""
|
|
50
|
+
@methoddesc 将 Provider 实现类注册到全局注册表中
|
|
51
|
+
|
|
52
|
+
参数:
|
|
53
|
+
type: Provider 类型枚举值(如 ProviderType.OPENAI)
|
|
54
|
+
cls: 继承自 BaseProvider 的实现类
|
|
55
|
+
|
|
56
|
+
示例:
|
|
57
|
+
>>> register(ProviderType.OPENAI, OpenAIProvider)
|
|
58
|
+
"""
|
|
59
|
+
_registry[type] = cls
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def create(config: AIProvider) -> BaseProvider:
|
|
63
|
+
"""
|
|
64
|
+
@methoddesc 根据配置创建对应的 Provider 实例
|
|
65
|
+
|
|
66
|
+
工厂模式:根据 config.type 从注册表中查找实现类并实例化。
|
|
67
|
+
如果全局 AIConfig 启用了缓存,则用 CachedProvider 包装。
|
|
68
|
+
|
|
69
|
+
参数:
|
|
70
|
+
config: AIProvider 配置对象
|
|
71
|
+
|
|
72
|
+
返回:
|
|
73
|
+
BaseProvider 子类实例(可能被 CachedProvider 包装)
|
|
74
|
+
|
|
75
|
+
异常:
|
|
76
|
+
ValueError: 未注册的 Provider 类型
|
|
77
|
+
|
|
78
|
+
示例:
|
|
79
|
+
>>> provider = create(config)
|
|
80
|
+
"""
|
|
81
|
+
cls = _registry.get(config.type)
|
|
82
|
+
if not cls:
|
|
83
|
+
raise ValueError(f"Unknown provider type: {config.type}")
|
|
84
|
+
real = cls(config)
|
|
85
|
+
|
|
86
|
+
# 尝试从全局配置加载缓存设置
|
|
87
|
+
try:
|
|
88
|
+
from ..config import loader as _loader
|
|
89
|
+
|
|
90
|
+
ai_config = _loader.load()
|
|
91
|
+
cache_cfg = ai_config.cache
|
|
92
|
+
if cache_cfg.enabled:
|
|
93
|
+
from ..cache.response_cache import ResponseCache
|
|
94
|
+
|
|
95
|
+
cache = ResponseCache(
|
|
96
|
+
max_entries=cache_cfg.max_entries,
|
|
97
|
+
ttl_seconds=cache_cfg.ttl_seconds,
|
|
98
|
+
)
|
|
99
|
+
return CachedProvider(real, cache, cache_cfg.cache_temperature_above_zero)
|
|
100
|
+
except Exception:
|
|
101
|
+
pass # 配置加载失败时降级为无缓存
|
|
102
|
+
|
|
103
|
+
return real
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# 注册内置 Provider
|
|
107
|
+
register(ProviderType.OPENAI, OpenAIProvider)
|
|
108
|
+
register(ProviderType.OLLAMA, OllamaProvider)
|
|
@@ -0,0 +1,141 @@
|
|
|
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 Schema 查找与 ID 解析模块
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 在工作区的 schemas 目录中按名称查找匹配的表
|
|
21
|
+
- 支持精确匹配和模糊包含匹配
|
|
22
|
+
- 根据表名/列名反向解析对应的系统 ID
|
|
23
|
+
|
|
24
|
+
输入示例:
|
|
25
|
+
matches = find_matching_schemas("/path/to/project", "users")
|
|
26
|
+
table_id, column_id = _resolve_id_from_name("/path/to/project", "users", "email")
|
|
27
|
+
|
|
28
|
+
输出示例:
|
|
29
|
+
[{"id": "sc_xxx", "name": "系统用户表"}]
|
|
30
|
+
("sc_xxx", "col_email")
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import logging
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
import yaml
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def find_matching_schemas(workspace_path: str, query_name: str) -> list[dict[str, str]]:
|
|
44
|
+
"""
|
|
45
|
+
@methoddesc 在本地 schema 文件中查找匹配的表
|
|
46
|
+
|
|
47
|
+
支持精确匹配和模糊包含匹配,优先返回精确匹配结果。
|
|
48
|
+
|
|
49
|
+
参数:
|
|
50
|
+
workspace_path: 工作区路径
|
|
51
|
+
query_name: 用户提供的名称 (可能是 table_id, tableName 或 targetNodeId)
|
|
52
|
+
|
|
53
|
+
返回:
|
|
54
|
+
匹配到的候选列表: [{"id": "sc_xxx", "name": "系统用户表"}]
|
|
55
|
+
|
|
56
|
+
示例:
|
|
57
|
+
>>> find_matching_schemas("/workspace", "users")
|
|
58
|
+
[{"id": "users", "name": "users"}]
|
|
59
|
+
"""
|
|
60
|
+
if not query_name:
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
schemas_dir = Path(workspace_path) / "schemas"
|
|
64
|
+
if not schemas_dir.exists():
|
|
65
|
+
return []
|
|
66
|
+
|
|
67
|
+
exact_matches = []
|
|
68
|
+
fuzzy_matches = []
|
|
69
|
+
|
|
70
|
+
query_name_lower = query_name.lower()
|
|
71
|
+
|
|
72
|
+
for schema_file in schemas_dir.glob("*.yaml"):
|
|
73
|
+
try:
|
|
74
|
+
with open(schema_file, encoding="utf-8") as f:
|
|
75
|
+
data = yaml.safe_load(f) or {}
|
|
76
|
+
|
|
77
|
+
table_id = data.get("id")
|
|
78
|
+
table_name = data.get("name")
|
|
79
|
+
|
|
80
|
+
if not table_id or not table_name:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
# 1. 精确匹配 (ID 或 Name 完全一致)
|
|
84
|
+
if query_name == table_id or query_name == table_name:
|
|
85
|
+
exact_matches.append({"id": table_id, "name": table_name})
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
# 2. 模糊匹配 (包含关系)
|
|
89
|
+
if query_name_lower in table_name.lower() or query_name_lower in table_id.lower():
|
|
90
|
+
fuzzy_matches.append({"id": table_id, "name": table_name})
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.warning(f"检索 Schema 失败 {schema_file}: {e}")
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
# 如果有精确匹配,优先返回精确匹配
|
|
97
|
+
return exact_matches if exact_matches else fuzzy_matches
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _resolve_id_from_name(
|
|
101
|
+
workspace_path: str, table_name: str, column_name: str | None = None
|
|
102
|
+
) -> tuple[str | None, str | None]:
|
|
103
|
+
"""
|
|
104
|
+
@methoddesc 根据名称在本地 schema 文件中查找对应的 ID
|
|
105
|
+
|
|
106
|
+
仅返回第一个匹配项,用于自动化流程中的名称到 ID 解析。
|
|
107
|
+
|
|
108
|
+
参数:
|
|
109
|
+
workspace_path: 工作区路径
|
|
110
|
+
table_name: 表名或表 ID
|
|
111
|
+
column_name: 列名或列 ID(可选)
|
|
112
|
+
|
|
113
|
+
返回:
|
|
114
|
+
元组 (table_id, column_id),如果未找到则返回 (None, None)
|
|
115
|
+
"""
|
|
116
|
+
matches = find_matching_schemas(workspace_path, table_name)
|
|
117
|
+
if not matches:
|
|
118
|
+
return None, None
|
|
119
|
+
|
|
120
|
+
# 获取第一个匹配的表 ID
|
|
121
|
+
table_id = matches[0]["id"]
|
|
122
|
+
column_id = None
|
|
123
|
+
|
|
124
|
+
# 如果有列名,需要进一步匹配列 ID
|
|
125
|
+
if column_name:
|
|
126
|
+
schemas_dir = Path(workspace_path) / "schemas"
|
|
127
|
+
for schema_file in schemas_dir.glob("*.yaml"):
|
|
128
|
+
try:
|
|
129
|
+
with open(schema_file, encoding="utf-8") as f:
|
|
130
|
+
data = yaml.safe_load(f) or {}
|
|
131
|
+
if data.get("id") == table_id:
|
|
132
|
+
for col in data.get("columns", []):
|
|
133
|
+
if col.get("name") == column_name or col.get("id") == column_name:
|
|
134
|
+
column_id = col.get("id")
|
|
135
|
+
break
|
|
136
|
+
break
|
|
137
|
+
except Exception:
|
|
138
|
+
logger.warning(f"解析 Schema 文件失败: {schema_file}", exc_info=True)
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
return table_id, column_id
|