sqlseed-ai 0.2.4__tar.gz → 0.2.6__tar.gz
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.
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/.gitignore +10 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/PKG-INFO +77 -11
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/README.md +74 -8
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/README.zh-CN.md +57 -10
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/pyproject.toml +2 -2
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/_client.py +38 -2
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/_hardware.py +65 -14
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/_json_utils.py +69 -29
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/_prompts.py +19 -3
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/analyzer/_caller.py +69 -15
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/analyzer/_context.py +17 -9
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/analyzer/_json_parser.py +3 -2
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/analyzer/_streaming.py +103 -21
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/analyzer/_tool_calling.py +49 -26
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/cli/ai_commands.py +59 -27
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/config.py +6 -1
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/errors.py +10 -1
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/level2_column_healer.py +7 -6
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/mcp.py +64 -31
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/refiner.py +73 -14
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/runtime.py +2 -2
- sqlseed_ai-0.2.6/tests/healer/conftest.py +66 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/healer/test_heal_orchestrator_real.py +2 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/healer/test_level1_subgraph_healer_real.py +2 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/healer/test_level2_column_healer_real.py +1 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/healer/test_level3_compact_healer_real.py +3 -0
- sqlseed_ai-0.2.6/tests/healer/test_llm_availability.py +112 -0
- sqlseed_ai-0.2.6/tests/http_helpers.py +23 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_caller.py +20 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_client.py +3 -3
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_hardware.py +154 -4
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_json_utils.py +59 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_plugin.py +9 -6
- sqlseed_ai-0.2.6/tests/test_ai_preserve_names.py +465 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_prompts_tools.py +16 -0
- sqlseed_ai-0.2.6/tests/test_analyzer_client_lifecycle.py +122 -0
- sqlseed_ai-0.2.6/tests/test_cli_suggestion_diagnostics.py +211 -0
- sqlseed_ai-0.2.6/tests/test_cli_suggestion_identity.py +139 -0
- sqlseed_ai-0.2.6/tests/test_client_loopback.py +170 -0
- sqlseed_ai-0.2.6/tests/test_http_probe_lifecycle.py +66 -0
- sqlseed_ai-0.2.6/tests/test_mcp.py +228 -0
- sqlseed_ai-0.2.6/tests/test_mcp_error_redaction.py +29 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_mcp_stdio.py +9 -11
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_nonweb_ai_boundaries.py +23 -3
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_quality_error_boundaries.py +24 -0
- sqlseed_ai-0.2.6/tests/test_real_llm_environment.py +58 -0
- sqlseed_ai-0.2.6/tests/test_refiner_json_recovery.py +200 -0
- sqlseed_ai-0.2.6/tests/test_refiner_table_boundaries.py +317 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_runtime.py +12 -9
- sqlseed_ai-0.2.4/tests/healer/conftest.py +0 -57
- sqlseed_ai-0.2.4/tests/healer/test_llm_availability.py +0 -40
- sqlseed_ai-0.2.4/tests/test_mcp.py +0 -121
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/LICENSE +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/_generator_names.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/_model_selector.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/_tools.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/ai_mediator.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/analyzer/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/auto_heal/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/auto_heal/_check_inference.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/auto_heal/_cross_column_checks.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/auto_heal/orchestrator.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/auto_heal/time_budget.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/cli/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/contracts/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/contracts/builtin_violations.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/contracts/matrix.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/contracts/registry.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/examples.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/exceptions.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/_client.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/_llm_call.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/candidate_validation.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/context_detector.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/degrader.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/diff_learner.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/failure_classifier.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/level1_subgraph_healer.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/level3_compact_healer.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/models.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/orchestrator.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/oscillation.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/post_repair.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/healer/subgraph.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/repair/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/repair/executor.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/repair/models.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/repair/pipeline.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/repair/strategies.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/validator/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/validator/composite_fk.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/validator/cross_column.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/validator/dialect_parser.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/validator/main.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/validator/models.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/validator/schema_snapshot.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/validator/shadow_fk_scan.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/src/sqlseed_ai/validator/single_column.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/.pylintrc +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/conftest.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/healer/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/healer/scenario_helpers.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/healer/test_context_detector.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/healer/test_failure_classifier.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/healer/test_level2_context_builder.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/healer/test_llm_call_boundary.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/property/__init__.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/property/test_matrix_completeness.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/schema_helpers.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_analyzer_streaming.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_commands.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_config.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_errors.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_mediator.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_model_selector.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_plugin_init.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_ai_tool_calling.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_auto_heal_orchestrator.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_auto_heal_sonar_boundaries.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_auto_heal_time_budget.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_cli_auto_heal.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_cli_input_contract.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_contracts_builtin.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_contracts_matrix.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_contracts_registry.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_healer_candidate_contract.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_healer_degrader.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_healer_diff_learner.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_healer_models.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_healer_oscillation.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_healer_post_repair.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_healer_subgraph.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_prompts_p0_p3.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_refiner.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_repair_executor.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_repair_models.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_repair_pipeline.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_repair_strategies.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_schema_snapshot.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_validator_composite_fk.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_validator_cross_column.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_validator_dialect_parser.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_validator_main.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_validator_models.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_validator_shadow_fk_scan.py +0 -0
- {sqlseed_ai-0.2.4 → sqlseed_ai-0.2.6}/tests/test_validator_single_column.py +0 -0
|
@@ -59,6 +59,16 @@ dmypy.json
|
|
|
59
59
|
*.db
|
|
60
60
|
*.sqlite
|
|
61
61
|
*.sqlite3
|
|
62
|
+
# SQLite runtime sidecars
|
|
63
|
+
*.db-wal
|
|
64
|
+
*.db-shm
|
|
65
|
+
*.db-journal
|
|
66
|
+
*.sqlite-wal
|
|
67
|
+
*.sqlite-shm
|
|
68
|
+
*.sqlite-journal
|
|
69
|
+
*.sqlite3-wal
|
|
70
|
+
*.sqlite3-shm
|
|
71
|
+
*.sqlite3-journal
|
|
62
72
|
.sqlseed_cache/
|
|
63
73
|
snapshots/
|
|
64
74
|
.env
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sqlseed-ai
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Optional LLM schema analysis and contract-driven configuration repair for sqlseed
|
|
5
5
|
Project-URL: Documentation, https://sunbos.github.io/sqlseed/gemma4-integration/
|
|
6
6
|
Project-URL: Homepage, https://github.com/sunbos/sqlseed
|
|
@@ -18,9 +18,9 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
19
|
Requires-Dist: httpx>=0.24.0
|
|
20
20
|
Requires-Dist: networkx>=3.0
|
|
21
|
-
Requires-Dist: openai>=1.
|
|
21
|
+
Requires-Dist: openai>=1.55.3
|
|
22
22
|
Requires-Dist: sqlseed-cli<0.3,>=0.2.4.dev0
|
|
23
|
-
Requires-Dist: sqlseed<0.3,>=0.2.
|
|
23
|
+
Requires-Dist: sqlseed<0.3,>=0.2.5.dev0
|
|
24
24
|
Provides-Extra: dev
|
|
25
25
|
Requires-Dist: hypothesis>=6.100; extra == 'dev'
|
|
26
26
|
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
@@ -46,13 +46,15 @@ backend test; installing the plugin does not perform one.
|
|
|
46
46
|
|
|
47
47
|
## Installation
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
These instructions target version 0.2.6. Check [Releases](https://github.com/sunbos/sqlseed/releases)
|
|
50
|
+
for publication status; use the source installation below to test an unpublished candidate.
|
|
51
|
+
Use a Python 3.10+ virtual environment:
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
|
-
python -m pip install "sqlseed-ai==0.2.
|
|
54
|
+
python -m pip install "sqlseed-ai==0.2.6"
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
Core 0.2.
|
|
57
|
+
Core 0.2.4 and older lack the shared diagnostic interfaces required by this version.
|
|
56
58
|
For development, install Core and the required local plugins together from the
|
|
57
59
|
repository root:
|
|
58
60
|
|
|
@@ -100,6 +102,28 @@ names fail before output is written. `--merge` requires `--output`; it replaces
|
|
|
100
102
|
selected tables, retains existing dependency and unrelated tables plus root settings,
|
|
101
103
|
and appends missing generated tables.
|
|
102
104
|
|
|
105
|
+
Single-table `ai-suggest` checks that the target exists before contacting the
|
|
106
|
+
model. Suggestions and cached results must name that same table, including when
|
|
107
|
+
using `--no-verify` or `--max-retries 0`. SQLite case aliases remain supported;
|
|
108
|
+
export preserves real table and column names, including leading punctuation.
|
|
109
|
+
Rejected suggestions leave existing output files unchanged.
|
|
110
|
+
|
|
111
|
+
The prompt requests one JSON object configuring only the named table, preserving
|
|
112
|
+
its table and column names. Other table names are reference context, not additional
|
|
113
|
+
output targets; the response still passes target validation.
|
|
114
|
+
|
|
115
|
+
Direct analysis (`--no-verify` or `--max-retries 0`), streaming or non-streaming,
|
|
116
|
+
reports empty replies, invalid JSON, output-limit truncation, and empty configuration
|
|
117
|
+
objects separately, without
|
|
118
|
+
echoing the model response in these diagnostics. It announces a retry only when
|
|
119
|
+
another existing shorter-prompt level remains. Once those levels are exhausted,
|
|
120
|
+
it reports the final cause and exits unsuccessfully; it does not increase the
|
|
121
|
+
request budget or change the existing output YAML or database.
|
|
122
|
+
|
|
123
|
+
Direct Python callers can pass `preserve_names=True` to
|
|
124
|
+
`SchemaAnalyzer.call_llm()` or `call_llm_streaming()` before validating against
|
|
125
|
+
their schema. The default retains the existing leading-punctuation cleanup.
|
|
126
|
+
|
|
103
127
|
`auto-heal --config` reads that document. An explicit `--db` or `--url` selects the
|
|
104
128
|
output connection. Invalid YAML, configuration structure, or unknown input tables
|
|
105
129
|
fail without replacing the output file. Candidate repairs are checked for config
|
|
@@ -112,7 +136,7 @@ actual generated values and database constraints.
|
|
|
112
136
|
The AI MCP entry point requires the `mcp` extra:
|
|
113
137
|
|
|
114
138
|
```bash
|
|
115
|
-
python -m pip install "sqlseed-ai[mcp]==0.2.
|
|
139
|
+
python -m pip install "sqlseed-ai[mcp]==0.2.6"
|
|
116
140
|
mcp-server-sqlseed-ai
|
|
117
141
|
```
|
|
118
142
|
|
|
@@ -145,15 +169,57 @@ explicit backend, then known URL patterns, then OpenAI-compatible behavior. It d
|
|
|
145
169
|
not probe every service as a fallback chain. The `tool_calling_protocol` setting and
|
|
146
170
|
its resolver choose the response protocol; a model name alone is insufficient.
|
|
147
171
|
|
|
148
|
-
AI
|
|
149
|
-
|
|
172
|
+
AI requests to `localhost` and loopback IP addresses connect directly even when
|
|
173
|
+
an HTTP proxy is configured. Remote services keep the environment proxy settings;
|
|
174
|
+
`SSL_CERT_FILE` and `SSL_CERT_DIR` remain effective for HTTPS certificate validation.
|
|
175
|
+
|
|
176
|
+
For Python callers, `SchemaAnalyzer.call_llm(..., strict_json=True)` and
|
|
177
|
+
`call_llm_streaming(..., strict_json=True)` distinguish empty replies, invalid JSON,
|
|
178
|
+
and output-limit truncation using content-free `JSONResponseError.code` values.
|
|
179
|
+
JSON parsing can complete missing final `}` or `]`
|
|
180
|
+
delimiters, including inside code fences, but never fills missing values or strings.
|
|
181
|
+
An output-limit response is rejected even if its prefix parses, including a streaming
|
|
182
|
+
length marker in a separate empty terminal chunk. Both methods default to
|
|
183
|
+
`strict_json=False`. Strict local calls request JSON sampling constraints: LM Studio
|
|
184
|
+
uses its JSON-schema grammar interface, and Ollama uses JSON object mode. A server
|
|
185
|
+
that explicitly rejects the format gets one text-mode compatibility attempt;
|
|
186
|
+
the same strict parser still rejects invalid or truncated output. The CLI's direct
|
|
187
|
+
path and both refiner modes keep their existing prompt/refinement retry budgets.
|
|
188
|
+
Strict tool calling also rejects arrays, scalars and `null` arguments as `invalid_json`;
|
|
189
|
+
compatibility mode may still fall back to response text. Parsed suggestions still
|
|
190
|
+
require scope and rule validation.
|
|
191
|
+
|
|
192
|
+
Single-table suggestion caches use a versioned hash of sorted column names, encoded
|
|
193
|
+
as a JSON array so names containing delimiters remain distinct. The check detects
|
|
194
|
+
added, removed, or renamed columns; column order, types, and constraints are not
|
|
195
|
+
included. Caches using the older delimiter encoding are ignored and regenerated.
|
|
196
|
+
Use `--no-cache` to analyze again without cached suggestions. This cache check is
|
|
197
|
+
separate from AutoHeal's full schema fingerprint. Malformed cache metadata
|
|
198
|
+
or configuration containers are treated as cache misses. Review model output before
|
|
199
|
+
writing data.
|
|
200
|
+
|
|
201
|
+
## Hardware estimates on macOS
|
|
202
|
+
|
|
203
|
+
The AI MCP model list distinguishes Apple unified memory, Intel shared graphics
|
|
204
|
+
memory, and dedicated GPU memory. Apple unified RAM is not reported as dedicated
|
|
205
|
+
VRAM or added to system RAM. `unified_memory_budget_gb` is a static heuristic:
|
|
206
|
+
`max(0, min(total_ram_gb * 0.75, total_ram_gb - 4))`, reserving at least 4 GiB or
|
|
207
|
+
25% for the system. It is not measured free memory or a Metal allocation limit.
|
|
208
|
+
|
|
209
|
+
For an identified Apple GPU on macOS, model screening compares that budget with
|
|
210
|
+
both existing minimum RAM and VRAM estimates and reports at most `capable`.
|
|
211
|
+
This does not verify Metal acceleration, backend/model support, or successful
|
|
212
|
+
inference; loaded applications and context size can require more memory.
|
|
150
213
|
|
|
151
214
|
## Requirements
|
|
152
215
|
|
|
216
|
+
These metadata requirements apply to version 0.2.6 and its source candidates.
|
|
217
|
+
Use local Core and plugins together when developing from source.
|
|
218
|
+
|
|
153
219
|
- Python `>=3.10`
|
|
154
|
-
- `sqlseed>=0.2.
|
|
220
|
+
- `sqlseed>=0.2.5.dev0,<0.3`
|
|
155
221
|
- `sqlseed-cli>=0.2.4.dev0,<0.3`
|
|
156
|
-
- `openai>=1.0
|
|
222
|
+
- `openai>=1.55.3` (SDK transport defaults with HTTPX 0.28 compatibility)
|
|
157
223
|
- `httpx>=0.24.0`
|
|
158
224
|
- `networkx>=3.0`
|
|
159
225
|
- Optional `mcp` extra: `mcp>=1.0,<2`
|
|
@@ -14,13 +14,15 @@ backend test; installing the plugin does not perform one.
|
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
These instructions target version 0.2.6. Check [Releases](https://github.com/sunbos/sqlseed/releases)
|
|
18
|
+
for publication status; use the source installation below to test an unpublished candidate.
|
|
19
|
+
Use a Python 3.10+ virtual environment:
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
|
-
python -m pip install "sqlseed-ai==0.2.
|
|
22
|
+
python -m pip install "sqlseed-ai==0.2.6"
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
Core 0.2.
|
|
25
|
+
Core 0.2.4 and older lack the shared diagnostic interfaces required by this version.
|
|
24
26
|
For development, install Core and the required local plugins together from the
|
|
25
27
|
repository root:
|
|
26
28
|
|
|
@@ -68,6 +70,28 @@ names fail before output is written. `--merge` requires `--output`; it replaces
|
|
|
68
70
|
selected tables, retains existing dependency and unrelated tables plus root settings,
|
|
69
71
|
and appends missing generated tables.
|
|
70
72
|
|
|
73
|
+
Single-table `ai-suggest` checks that the target exists before contacting the
|
|
74
|
+
model. Suggestions and cached results must name that same table, including when
|
|
75
|
+
using `--no-verify` or `--max-retries 0`. SQLite case aliases remain supported;
|
|
76
|
+
export preserves real table and column names, including leading punctuation.
|
|
77
|
+
Rejected suggestions leave existing output files unchanged.
|
|
78
|
+
|
|
79
|
+
The prompt requests one JSON object configuring only the named table, preserving
|
|
80
|
+
its table and column names. Other table names are reference context, not additional
|
|
81
|
+
output targets; the response still passes target validation.
|
|
82
|
+
|
|
83
|
+
Direct analysis (`--no-verify` or `--max-retries 0`), streaming or non-streaming,
|
|
84
|
+
reports empty replies, invalid JSON, output-limit truncation, and empty configuration
|
|
85
|
+
objects separately, without
|
|
86
|
+
echoing the model response in these diagnostics. It announces a retry only when
|
|
87
|
+
another existing shorter-prompt level remains. Once those levels are exhausted,
|
|
88
|
+
it reports the final cause and exits unsuccessfully; it does not increase the
|
|
89
|
+
request budget or change the existing output YAML or database.
|
|
90
|
+
|
|
91
|
+
Direct Python callers can pass `preserve_names=True` to
|
|
92
|
+
`SchemaAnalyzer.call_llm()` or `call_llm_streaming()` before validating against
|
|
93
|
+
their schema. The default retains the existing leading-punctuation cleanup.
|
|
94
|
+
|
|
71
95
|
`auto-heal --config` reads that document. An explicit `--db` or `--url` selects the
|
|
72
96
|
output connection. Invalid YAML, configuration structure, or unknown input tables
|
|
73
97
|
fail without replacing the output file. Candidate repairs are checked for config
|
|
@@ -80,7 +104,7 @@ actual generated values and database constraints.
|
|
|
80
104
|
The AI MCP entry point requires the `mcp` extra:
|
|
81
105
|
|
|
82
106
|
```bash
|
|
83
|
-
python -m pip install "sqlseed-ai[mcp]==0.2.
|
|
107
|
+
python -m pip install "sqlseed-ai[mcp]==0.2.6"
|
|
84
108
|
mcp-server-sqlseed-ai
|
|
85
109
|
```
|
|
86
110
|
|
|
@@ -113,15 +137,57 @@ explicit backend, then known URL patterns, then OpenAI-compatible behavior. It d
|
|
|
113
137
|
not probe every service as a fallback chain. The `tool_calling_protocol` setting and
|
|
114
138
|
its resolver choose the response protocol; a model name alone is insufficient.
|
|
115
139
|
|
|
116
|
-
AI
|
|
117
|
-
|
|
140
|
+
AI requests to `localhost` and loopback IP addresses connect directly even when
|
|
141
|
+
an HTTP proxy is configured. Remote services keep the environment proxy settings;
|
|
142
|
+
`SSL_CERT_FILE` and `SSL_CERT_DIR` remain effective for HTTPS certificate validation.
|
|
143
|
+
|
|
144
|
+
For Python callers, `SchemaAnalyzer.call_llm(..., strict_json=True)` and
|
|
145
|
+
`call_llm_streaming(..., strict_json=True)` distinguish empty replies, invalid JSON,
|
|
146
|
+
and output-limit truncation using content-free `JSONResponseError.code` values.
|
|
147
|
+
JSON parsing can complete missing final `}` or `]`
|
|
148
|
+
delimiters, including inside code fences, but never fills missing values or strings.
|
|
149
|
+
An output-limit response is rejected even if its prefix parses, including a streaming
|
|
150
|
+
length marker in a separate empty terminal chunk. Both methods default to
|
|
151
|
+
`strict_json=False`. Strict local calls request JSON sampling constraints: LM Studio
|
|
152
|
+
uses its JSON-schema grammar interface, and Ollama uses JSON object mode. A server
|
|
153
|
+
that explicitly rejects the format gets one text-mode compatibility attempt;
|
|
154
|
+
the same strict parser still rejects invalid or truncated output. The CLI's direct
|
|
155
|
+
path and both refiner modes keep their existing prompt/refinement retry budgets.
|
|
156
|
+
Strict tool calling also rejects arrays, scalars and `null` arguments as `invalid_json`;
|
|
157
|
+
compatibility mode may still fall back to response text. Parsed suggestions still
|
|
158
|
+
require scope and rule validation.
|
|
159
|
+
|
|
160
|
+
Single-table suggestion caches use a versioned hash of sorted column names, encoded
|
|
161
|
+
as a JSON array so names containing delimiters remain distinct. The check detects
|
|
162
|
+
added, removed, or renamed columns; column order, types, and constraints are not
|
|
163
|
+
included. Caches using the older delimiter encoding are ignored and regenerated.
|
|
164
|
+
Use `--no-cache` to analyze again without cached suggestions. This cache check is
|
|
165
|
+
separate from AutoHeal's full schema fingerprint. Malformed cache metadata
|
|
166
|
+
or configuration containers are treated as cache misses. Review model output before
|
|
167
|
+
writing data.
|
|
168
|
+
|
|
169
|
+
## Hardware estimates on macOS
|
|
170
|
+
|
|
171
|
+
The AI MCP model list distinguishes Apple unified memory, Intel shared graphics
|
|
172
|
+
memory, and dedicated GPU memory. Apple unified RAM is not reported as dedicated
|
|
173
|
+
VRAM or added to system RAM. `unified_memory_budget_gb` is a static heuristic:
|
|
174
|
+
`max(0, min(total_ram_gb * 0.75, total_ram_gb - 4))`, reserving at least 4 GiB or
|
|
175
|
+
25% for the system. It is not measured free memory or a Metal allocation limit.
|
|
176
|
+
|
|
177
|
+
For an identified Apple GPU on macOS, model screening compares that budget with
|
|
178
|
+
both existing minimum RAM and VRAM estimates and reports at most `capable`.
|
|
179
|
+
This does not verify Metal acceleration, backend/model support, or successful
|
|
180
|
+
inference; loaded applications and context size can require more memory.
|
|
118
181
|
|
|
119
182
|
## Requirements
|
|
120
183
|
|
|
184
|
+
These metadata requirements apply to version 0.2.6 and its source candidates.
|
|
185
|
+
Use local Core and plugins together when developing from source.
|
|
186
|
+
|
|
121
187
|
- Python `>=3.10`
|
|
122
|
-
- `sqlseed>=0.2.
|
|
188
|
+
- `sqlseed>=0.2.5.dev0,<0.3`
|
|
123
189
|
- `sqlseed-cli>=0.2.4.dev0,<0.3`
|
|
124
|
-
- `openai>=1.0
|
|
190
|
+
- `openai>=1.55.3` (SDK transport defaults with HTTPX 0.28 compatibility)
|
|
125
191
|
- `httpx>=0.24.0`
|
|
126
192
|
- `networkx>=3.0`
|
|
127
193
|
- Optional `mcp` extra: `mcp>=1.0,<2`
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[English](https://github.com/sunbos/sqlseed/blob/main/plugins/sqlseed-ai/README.md) |
|
|
4
4
|
**[中文](https://github.com/sunbos/sqlseed/blob/main/plugins/sqlseed-ai/README.zh-CN.md)**
|
|
5
5
|
|
|
6
|
-
[sqlseed](https://sunbos.github.io/sqlseed/) 的可选 LLM Schema 分析与契约驱动配置修复插件。
|
|
6
|
+
[sqlseed](https://sunbos.github.io/sqlseed/zh-CN/) 的可选 LLM Schema 分析与契约驱动配置修复插件。
|
|
7
7
|
提供列规则建议、配置校验与修复,以及模板候选值生成。接受的配置可交由 Core 离线执行。
|
|
8
8
|
|
|
9
9
|
支持 Google AI Studio、LM Studio、Ollama 和 OpenAI-compatible API 后端。
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
## 安装
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
本文安装说明对应 0.2.6 版本;发布状态以 [Releases](https://github.com/sunbos/sqlseed/releases) 为准。测试尚未发布的候选版本时,使用下方的源码安装方式。请先创建并激活 Python 3.10+ 虚拟环境:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
python -m pip install "sqlseed-ai==0.2.
|
|
17
|
+
python -m pip install "sqlseed-ai==0.2.6"
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Core 0.2.
|
|
20
|
+
Core 0.2.4 及更早版本缺少本版本使用的共享诊断接口。
|
|
21
21
|
开发源码时,从仓库根一次安装本地 Core、CLI 和 AI:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
@@ -91,7 +91,7 @@ native/custom 方法、实际生成值及依赖数据库状态的约束需另行
|
|
|
91
91
|
AI MCP 入口要求本包的 `mcp` extra:
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
python -m pip install "sqlseed-ai[mcp]==0.2.
|
|
94
|
+
python -m pip install "sqlseed-ai[mcp]==0.2.6"
|
|
95
95
|
mcp-server-sqlseed-ai
|
|
96
96
|
```
|
|
97
97
|
|
|
@@ -114,6 +114,9 @@ mcp-server-sqlseed-ai
|
|
|
114
114
|
后端解析顺序是显式 `SQLSEED_AI_BACKEND`、已知 URL 模式、最后 `openai_compat`。
|
|
115
115
|
这不是逐个探测所有服务的 fallback 链。
|
|
116
116
|
|
|
117
|
+
AI 请求访问 `localhost` 或回环 IP 地址时直接连接,不经过环境代理;远程服务仍使用
|
|
118
|
+
原有代理配置。HTTPS 证书校验继续遵循 `SSL_CERT_FILE` 和 `SSL_CERT_DIR`。
|
|
119
|
+
|
|
117
120
|
| 变量 | 用途 |
|
|
118
121
|
| --- | --- |
|
|
119
122
|
| `SQLSEED_AI_BACKEND` | `google_ai_studio`、`lm_studio`、`ollama` 或 `openai_compat` |
|
|
@@ -148,6 +151,33 @@ Gemma 26B ID。注册的模型名称不保证服务当前提供该模型,请
|
|
|
148
151
|
修正;默认最多重试 3 次,仍失败时报告 `AISuggestionFailedError`。
|
|
149
152
|
`ai-analyze` 和 `auto-heal` 则使用 v4 `AutoHealOrchestrator` 的契约驱动路径。
|
|
150
153
|
|
|
154
|
+
单表 `ai-suggest` 在请求模型前检查目标表是否存在;建议与缓存必须指向同一张表。
|
|
155
|
+
`--no-verify` 和 `--max-retries 0` 只跳过生成校验,仍保留目标保护。
|
|
156
|
+
SQLite 表名大小写别名继续可用;导出保留真实表名和列名,包括前导 `.` 或 `:`。
|
|
157
|
+
拒绝的建议不会覆盖已有输出文件。
|
|
158
|
+
|
|
159
|
+
Prompt 明确要求只为指定表返回一个 JSON 对象,并保留表名和列名原文。上下文中的
|
|
160
|
+
其他表名只供参考,不是额外输出目标;这一提示不能替代响应后的目标校验。
|
|
161
|
+
|
|
162
|
+
直接分析(`--no-verify` 或 `--max-retries 0`)的流式和非流式路径均分别说明空回答、无效 JSON、输出长度
|
|
163
|
+
截断和空配置对象,这些诊断不回显模型原文。仅在既定流程中还有更短提示层时才提示
|
|
164
|
+
并继续重试;最后一层失败会报告具体原因并以失败退出,不再声称正在重试,也不增加
|
|
165
|
+
请求预算。拒绝响应时,已有输出 YAML 与数据库均保持不变。
|
|
166
|
+
|
|
167
|
+
Python 调用可使用 `SchemaAnalyzer.call_llm(..., strict_json=True)` 或
|
|
168
|
+
`call_llm_streaming(..., strict_json=True)`,通过不含原文的
|
|
169
|
+
`JSONResponseError.code` 区分空回答、无效 JSON 和输出长度截断。解析器可补齐末尾缺失的
|
|
170
|
+
`}` / `]`,包括代码围栏内的 JSON,但不会补值或字符串;达到输出长度上限时,即使前缀
|
|
171
|
+
可解析也会拒绝,包括流式独立空终止帧中的长度截断标记。两种 Python 方法默认均为
|
|
172
|
+
`strict_json=False`。严格本地调用请求 JSON 采样约束:LM Studio 使用 JSON Schema 语法接口,
|
|
173
|
+
Ollama 使用 JSON 对象模式。服务明确拒绝该格式时,只进行一次文本模式兼容请求,仍由同一
|
|
174
|
+
严格解析器拒绝无效或截断输出。CLI 直接分析与 refiner 保留各自现有提示与自纠正重试预算。
|
|
175
|
+
严格工具调用还会拒绝数组、标量和 `null` 参数,
|
|
176
|
+
返回 `invalid_json`;兼容模式仍可回退到响应正文。解析后的建议仍需验证范围和业务规则。
|
|
177
|
+
|
|
178
|
+
直接 Python 调用可给 `SchemaAnalyzer.call_llm()` 或 `call_llm_streaming()` 传入
|
|
179
|
+
`preserve_names=True`,保留标识符后再按实际 schema 校验;默认仍保留既有的前导标点清理。
|
|
180
|
+
|
|
151
181
|
开启 AI 生成路径时,`sqlseed_pre_generate_templates` 可为符合条件的未匹配字符串列
|
|
152
182
|
准备候选值。用户明确配置、UNIQUE、默认值或主键等条件会影响是否使用模板池,
|
|
153
183
|
不保证每个复杂字段都会调用模型。
|
|
@@ -164,7 +194,11 @@ Gemma 26B ID。注册的模型名称不保证服务当前提供该模型,请
|
|
|
164
194
|
|
|
165
195
|
### 文件缓存
|
|
166
196
|
|
|
167
|
-
|
|
197
|
+
单表建议缓存使用带版本标识的 schema hash,将排序后的列名编码为 JSON 数组,
|
|
198
|
+
避免含分隔符的不同列名集合混淆。校验覆盖列的增删和重命名,不包含列顺序、类型或约束。
|
|
199
|
+
旧分隔符编码的缓存会被忽略并重新生成;可用 `--no-cache` 跳过缓存重新分析。
|
|
200
|
+
此处的缓存校验与 AutoHeal 使用的完整 schema 指纹不同。
|
|
201
|
+
缓存元数据或配置容器类型无效时,按缓存未命中重新分析。
|
|
168
202
|
默认路径为 macOS 的 `~/Library/Caches/sqlseed/ai_configs/`、Linux 的
|
|
169
203
|
`$XDG_CACHE_HOME/sqlseed/ai_configs/`(未设置时为 `~/.cache/sqlseed/ai_configs/`),
|
|
170
204
|
以及 Windows 的 `%LOCALAPPDATA%/sqlseed/ai_configs/`。
|
|
@@ -184,19 +218,32 @@ AI 配置缓存包含 schema hash,结构变化会使旧建议失效;`--no-ca
|
|
|
184
218
|
CLI 命令另由 `sqlseed.cli_commands` entry point 注册。本插件不实现 provider 或
|
|
185
219
|
column-mapper 注册 hooks,也不要求 Core 导入 AI 实现。
|
|
186
220
|
|
|
221
|
+
## macOS 硬件估算
|
|
222
|
+
|
|
223
|
+
AI MCP 模型列表区分 Apple 统一内存、Intel 共享显存与独立显存。Apple 统一内存
|
|
224
|
+
不会被报告为独立显存,也不会与系统 RAM 相加。`unified_memory_budget_gb` 是静态
|
|
225
|
+
启发式预算:`max(0, min(total_ram_gb * 0.75, total_ram_gb - 4))`,为系统预留
|
|
226
|
+
至少 4 GiB 或 25% 内存。它不是实测可用内存,也不是 Metal 分配上限。
|
|
227
|
+
|
|
228
|
+
仅在 macOS 上识别出 Apple GPU 后,模型筛选才使用该预算与既有最低 RAM、VRAM
|
|
229
|
+
估算同时比较,最高返回 `capable`。该结果不代表已验证 Metal 加速、后端或模型支持,
|
|
230
|
+
也不保证推理成功;其他应用占用和上下文大小可能增加实际内存需求。
|
|
231
|
+
|
|
187
232
|
## 依赖
|
|
188
233
|
|
|
234
|
+
以下为 0.2.6 版本及其源码候选的依赖要求。源码开发时请在同一次解析中安装本地 Core 和插件。
|
|
235
|
+
|
|
189
236
|
- Python `>=3.10`
|
|
190
|
-
- `sqlseed>=0.2.
|
|
237
|
+
- `sqlseed>=0.2.5.dev0,<0.3`
|
|
191
238
|
- `sqlseed-cli>=0.2.4.dev0,<0.3`
|
|
192
|
-
- `openai>=1.0
|
|
239
|
+
- `openai>=1.55.3`(保留 SDK 传输默认值并兼容 HTTPX 0.28)
|
|
193
240
|
- `httpx>=0.24.0`
|
|
194
241
|
- `networkx>=3.0`
|
|
195
242
|
- 可选 `mcp` extra:`mcp>=1.0,<2`
|
|
196
243
|
- 实际模型请求需要已配置且可达的后端
|
|
197
244
|
|
|
198
|
-
更多信息见[AI 集成指南](https://sunbos.github.io/sqlseed/
|
|
199
|
-
[升级说明](https://sunbos.github.io/sqlseed/
|
|
245
|
+
更多信息见[AI 集成指南](https://sunbos.github.io/sqlseed/zh-CN/gemma4-integration/)、
|
|
246
|
+
[升级说明](https://sunbos.github.io/sqlseed/zh-CN/migration/)和
|
|
200
247
|
[配置源码](https://github.com/sunbos/sqlseed/blob/main/plugins/sqlseed-ai/src/sqlseed_ai/config.py)。
|
|
201
248
|
|
|
202
249
|
许可证:[AGPL-3.0-or-later](https://github.com/sunbos/sqlseed/blob/main/LICENSE)。
|
|
@@ -23,9 +23,9 @@ classifiers = [
|
|
|
23
23
|
"Programming Language :: Python :: 3.13",
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"sqlseed>=0.2.
|
|
26
|
+
"sqlseed>=0.2.5.dev0,<0.3",
|
|
27
27
|
"sqlseed-cli>=0.2.4.dev0,<0.3",
|
|
28
|
-
"openai>=1.
|
|
28
|
+
"openai>=1.55.3",
|
|
29
29
|
"httpx>=0.24.0",
|
|
30
30
|
"networkx>=3.0",
|
|
31
31
|
]
|
|
@@ -7,18 +7,25 @@ unified httpx timeouts suitable for both cloud and local (GPU) inference.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
from ipaddress import ip_address
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
10
13
|
import httpx
|
|
11
|
-
from openai import APIConnectionError, APIError, APITimeoutError
|
|
14
|
+
from openai import APIConnectionError, APIError, APITimeoutError
|
|
12
15
|
from sqlseed_ai.config import AIConfig
|
|
13
16
|
|
|
14
17
|
from sqlseed._utils.logger import get_logger
|
|
15
18
|
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from openai import OpenAI
|
|
21
|
+
|
|
16
22
|
logger = get_logger(__name__)
|
|
17
23
|
|
|
18
24
|
__all__ = [
|
|
19
25
|
"APIConnectionError",
|
|
20
26
|
"APIError",
|
|
21
27
|
"APITimeoutError",
|
|
28
|
+
"build_openai_client",
|
|
22
29
|
"get_openai_client",
|
|
23
30
|
"httpx_timeout",
|
|
24
31
|
]
|
|
@@ -49,7 +56,36 @@ def get_openai_client(config: AIConfig | None = None) -> OpenAI:
|
|
|
49
56
|
# - pool=10s: connection-pool acquisition timeout
|
|
50
57
|
kwargs["timeout"] = httpx_timeout(config.resolve_timeout())
|
|
51
58
|
logger.info("Creating OpenAI client", **{"backend": config.backend.value, "base_url": kwargs["base_url"]})
|
|
52
|
-
return
|
|
59
|
+
return build_openai_client(**kwargs)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def build_openai_client(*, api_key: str, base_url: str, timeout: float | httpx.Timeout | None) -> OpenAI:
|
|
63
|
+
"""Create a client with direct loopback routing and SDK transport defaults.
|
|
64
|
+
|
|
65
|
+
Exact loopback hosts bypass environment proxies. TLS certificate settings,
|
|
66
|
+
remote proxy routes, timeouts, redirects, and pool limits remain unchanged.
|
|
67
|
+
The returned SDK client owns its optional HTTP client.
|
|
68
|
+
"""
|
|
69
|
+
from openai import DefaultHttpxClient, OpenAI, Timeout
|
|
70
|
+
|
|
71
|
+
resolved_timeout = Timeout(**timeout.as_dict()) if isinstance(timeout, httpx.Timeout) else timeout
|
|
72
|
+
if (host := httpx.URL(base_url).host) != "localhost":
|
|
73
|
+
try:
|
|
74
|
+
is_loopback = ip_address(host).is_loopback
|
|
75
|
+
except ValueError:
|
|
76
|
+
is_loopback = False
|
|
77
|
+
else:
|
|
78
|
+
is_loopback = True
|
|
79
|
+
if not is_loopback:
|
|
80
|
+
return OpenAI(api_key=api_key, base_url=base_url, timeout=resolved_timeout)
|
|
81
|
+
|
|
82
|
+
authority = f"[{host}]" if ":" in host else host
|
|
83
|
+
transport = DefaultHttpxClient(mounts={f"all://{authority}": None})
|
|
84
|
+
try:
|
|
85
|
+
return OpenAI(api_key=api_key, base_url=base_url, timeout=resolved_timeout, http_client=transport)
|
|
86
|
+
except BaseException:
|
|
87
|
+
transport.close()
|
|
88
|
+
raise
|
|
53
89
|
|
|
54
90
|
|
|
55
91
|
def httpx_timeout(total: float) -> httpx.Timeout:
|
|
@@ -11,8 +11,10 @@ from __future__ import annotations
|
|
|
11
11
|
import ctypes
|
|
12
12
|
import json
|
|
13
13
|
import platform
|
|
14
|
+
import re
|
|
14
15
|
import subprocess
|
|
15
16
|
import time
|
|
17
|
+
from math import isfinite
|
|
16
18
|
from typing import Any, NamedTuple
|
|
17
19
|
|
|
18
20
|
from sqlseed._utils.logger import get_logger
|
|
@@ -200,8 +202,34 @@ def _detect_gpu_nvidia() -> list[dict[str, Any]]:
|
|
|
200
202
|
return []
|
|
201
203
|
|
|
202
204
|
|
|
205
|
+
def _video_memory_mb(value: object) -> int:
|
|
206
|
+
"""Parse a profiler memory quantity without trusting malformed card fields."""
|
|
207
|
+
if not isinstance(value, str):
|
|
208
|
+
return 0
|
|
209
|
+
if (match := re.fullmatch(r"(\d+(?:\.\d+)?)\s*(MB|GB)", value.strip(), re.IGNORECASE)) is None:
|
|
210
|
+
return 0
|
|
211
|
+
size_mb = float(match[1]) * (1024 if match[2].upper() == "GB" else 1)
|
|
212
|
+
return int(size_mb) if isfinite(size_mb) else 0
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _macos_gpu_memory(gpu_info: dict[str, object], vendor: str) -> tuple[str, int]:
|
|
216
|
+
"""Separate shared and dedicated video memory from Apple unified RAM."""
|
|
217
|
+
if vendor == "apple":
|
|
218
|
+
return "unified", 0
|
|
219
|
+
memory_type = "shared" if "spdisplays_vram_shared" in gpu_info else "dedicated"
|
|
220
|
+
vram_mb = next(
|
|
221
|
+
(
|
|
222
|
+
size
|
|
223
|
+
for key in ("spdisplays_vram", "spdisplays_vram_shared", "_spdisplays_vram")
|
|
224
|
+
if (size := _video_memory_mb(gpu_info.get(key))) > 0
|
|
225
|
+
),
|
|
226
|
+
0,
|
|
227
|
+
)
|
|
228
|
+
return memory_type, vram_mb
|
|
229
|
+
|
|
230
|
+
|
|
203
231
|
def _detect_gpu_macos() -> list[dict[str, Any]]:
|
|
204
|
-
"""Detect Apple
|
|
232
|
+
"""Detect Apple, Intel and discrete GPUs via macOS system_profiler."""
|
|
205
233
|
try:
|
|
206
234
|
result = subprocess.run(
|
|
207
235
|
["system_profiler", "SPDisplaysDataType", "-json"],
|
|
@@ -214,26 +242,30 @@ def _detect_gpu_macos() -> list[dict[str, Any]]:
|
|
|
214
242
|
return []
|
|
215
243
|
|
|
216
244
|
data = json.loads(result.stdout)
|
|
245
|
+
if not isinstance(data, dict):
|
|
246
|
+
return []
|
|
217
247
|
displays = data.get("SPDisplaysDataType", [])
|
|
248
|
+
if not isinstance(displays, list):
|
|
249
|
+
return []
|
|
218
250
|
gpus: list[dict[str, Any]] = []
|
|
219
251
|
for gpu_info in displays:
|
|
252
|
+
if not isinstance(gpu_info, dict):
|
|
253
|
+
continue
|
|
220
254
|
name = gpu_info.get("sppci_model", "Unknown GPU")
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
val = int(parts[0])
|
|
227
|
-
unit = parts[1].upper()
|
|
228
|
-
vram_mb = val * 1024 if "GB" in unit else val
|
|
255
|
+
if not isinstance(name, str):
|
|
256
|
+
name = "Unknown GPU"
|
|
257
|
+
vendor_label = str(gpu_info.get("spdisplays_vendor") or name).lower()
|
|
258
|
+
vendor = next((v for v in ("apple", "intel", "amd", "nvidia") if v in vendor_label), "unknown")
|
|
259
|
+
memory_type, vram_mb = _macos_gpu_memory(gpu_info, vendor)
|
|
229
260
|
|
|
230
261
|
gpus.append(
|
|
231
262
|
{
|
|
232
263
|
"name": name,
|
|
233
264
|
"vram_total_mb": vram_mb,
|
|
234
|
-
"vram_free_mb": 0, #
|
|
265
|
+
"vram_free_mb": 0, # system_profiler does not report free VRAM
|
|
235
266
|
"vram_total_gb": round(vram_mb / 1024, 1),
|
|
236
|
-
"vendor":
|
|
267
|
+
"vendor": vendor,
|
|
268
|
+
"memory_type": memory_type,
|
|
237
269
|
}
|
|
238
270
|
)
|
|
239
271
|
return gpus
|
|
@@ -252,6 +284,13 @@ def _detect_gpus() -> list[dict[str, Any]]:
|
|
|
252
284
|
return []
|
|
253
285
|
|
|
254
286
|
|
|
287
|
+
def _has_apple_unified_memory(hw: dict[str, Any]) -> bool:
|
|
288
|
+
"""Require an identified Apple GPU on macOS, including under Rosetta."""
|
|
289
|
+
return hw.get("platform", {}).get("system") == "Darwin" and any(
|
|
290
|
+
gpu.get("vendor") == "apple" and gpu.get("memory_type") == "unified" for gpu in hw.get("gpus", [])
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
255
294
|
# ── Public API ───────────────────────────────────────────────────────
|
|
256
295
|
|
|
257
296
|
|
|
@@ -262,7 +301,8 @@ def detect_hardware() -> dict[str, Any]:
|
|
|
262
301
|
platform: {system, release, machine}
|
|
263
302
|
ram: {total_gb, available_gb}
|
|
264
303
|
gpus: [{name, vram_total_mb, vram_free_mb, vram_total_gb, vendor, ...}]
|
|
265
|
-
max_vram_gb: float
|
|
304
|
+
max_vram_gb: float (largest reported non-unified VRAM quantity)
|
|
305
|
+
unified_memory_budget_gb: float (heuristic budget, not measured free VRAM)
|
|
266
306
|
"""
|
|
267
307
|
if _HardwareCache.data is not None:
|
|
268
308
|
cached_time, cached_result = _HardwareCache.data
|
|
@@ -271,9 +311,9 @@ def detect_hardware() -> dict[str, Any]:
|
|
|
271
311
|
|
|
272
312
|
ram = _detect_system_ram()
|
|
273
313
|
gpus = _detect_gpus()
|
|
274
|
-
max_vram = max((g.get("vram_total_gb", 0) for g in gpus), default=0)
|
|
314
|
+
max_vram = max((g.get("vram_total_gb", 0) for g in gpus if g.get("memory_type") != "unified"), default=0)
|
|
275
315
|
|
|
276
|
-
result = {
|
|
316
|
+
result: dict[str, Any] = {
|
|
277
317
|
"platform": {
|
|
278
318
|
"system": platform.system(),
|
|
279
319
|
"release": platform.release(),
|
|
@@ -282,7 +322,13 @@ def detect_hardware() -> dict[str, Any]:
|
|
|
282
322
|
"ram": ram,
|
|
283
323
|
"gpus": gpus,
|
|
284
324
|
"max_vram_gb": max_vram,
|
|
325
|
+
"unified_memory_budget_gb": 0.0,
|
|
285
326
|
}
|
|
327
|
+
if _has_apple_unified_memory(result):
|
|
328
|
+
# A static screening heuristic, not Metal's runtime allocation limit.
|
|
329
|
+
# Reserve at least 4 GiB / 25% for the OS and other applications.
|
|
330
|
+
total_ram = ram["total_gb"]
|
|
331
|
+
result["unified_memory_budget_gb"] = max(0.0, min(total_ram * 0.75, total_ram - 4.0))
|
|
286
332
|
|
|
287
333
|
_HardwareCache.data = (time.monotonic(), result)
|
|
288
334
|
logger.info(
|
|
@@ -340,6 +386,11 @@ def evaluate_model_status(
|
|
|
340
386
|
return "recommended"
|
|
341
387
|
if max_vram >= req.min_vram_gb:
|
|
342
388
|
return "capable"
|
|
389
|
+
if _has_apple_unified_memory(hw):
|
|
390
|
+
budget = hw.get("unified_memory_budget_gb", 0)
|
|
391
|
+
# RAM and GPU allocations share this budget; never add them or grant
|
|
392
|
+
# a recommendation without verifying the backend and actual model.
|
|
393
|
+
return "capable" if budget >= max(req.min_ram_gb, req.min_vram_gb) else "insufficient"
|
|
343
394
|
if total_ram >= req.min_ram_gb and max_vram == 0:
|
|
344
395
|
return "cpu_only"
|
|
345
396
|
if total_ram >= req.min_ram_gb:
|