mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-25 08:04:57 +08:00
core/config: persist provider models as TOML inline tables
This commit is contained in:
@@ -395,8 +395,48 @@ class ConfigStore:
|
|||||||
"""Sync ``[plugins]`` to the document."""
|
"""Sync ``[plugins]`` to the document."""
|
||||||
self._sync_section("plugins", self._plugins)
|
self._sync_section("plugins", self._plugins)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _to_inline_table(mapping: Mapping[str, object]) -> object:
|
||||||
|
"""Encode a flat (or nested-dict) mapping as a TOML inline table."""
|
||||||
|
table = tomlkit.inline_table()
|
||||||
|
for key, value in mapping.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
table[key] = ConfigStore._to_inline_table(cast("Mapping[str, object]", value))
|
||||||
|
else:
|
||||||
|
table[key] = value
|
||||||
|
return table
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _models_to_toml_table(models: Mapping[str, object]) -> object:
|
||||||
|
"""Write each model as ``name = { … }`` under ``[providers.*.models]``."""
|
||||||
|
table = tomlkit.table()
|
||||||
|
for model_id, cfg in models.items():
|
||||||
|
if isinstance(cfg, dict):
|
||||||
|
table[model_id] = ConfigStore._to_inline_table(cast("Mapping[str, object]", cfg))
|
||||||
|
else:
|
||||||
|
table[model_id] = ConfigStore._to_inline_table({})
|
||||||
|
return table
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _provider_to_toml_table(raw: Mapping[str, object]) -> object:
|
||||||
|
"""Build a provider table; nested maps become inline tables except ``models``."""
|
||||||
|
entry = tomlkit.table()
|
||||||
|
for key, value in raw.items():
|
||||||
|
if key == "models" and isinstance(value, dict):
|
||||||
|
entry[key] = ConfigStore._models_to_toml_table(cast("Mapping[str, object]", value))
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
entry[key] = ConfigStore._to_inline_table(cast("Mapping[str, object]", value))
|
||||||
|
else:
|
||||||
|
entry[key] = value
|
||||||
|
return entry
|
||||||
|
|
||||||
def _sync_providers_section(self) -> None:
|
def _sync_providers_section(self) -> None:
|
||||||
"""Sync ``[providers]`` to the document (ready + recoverable)."""
|
"""Sync ``[providers]`` to the document (ready + recoverable).
|
||||||
|
|
||||||
|
Model configs are written as **inline tables** under
|
||||||
|
``[providers.<name>.models]`` (e.g. ``\"gpt-test\" = { text = true }``),
|
||||||
|
not as one dotted section per model id.
|
||||||
|
"""
|
||||||
section = self._toml_table("providers")
|
section = self._toml_table("providers")
|
||||||
keep = set(self._providers) | set(self._recoverable_providers)
|
keep = set(self._providers) | set(self._recoverable_providers)
|
||||||
|
|
||||||
@@ -405,14 +445,8 @@ class ConfigStore:
|
|||||||
del section[name]
|
del section[name]
|
||||||
|
|
||||||
for name, provider in {**self._recoverable_providers, **self._providers}.items():
|
for name, provider in {**self._recoverable_providers, **self._providers}.items():
|
||||||
raw: dict[str, object] = msgspec.to_builtins(provider)
|
raw = cast("dict[str, object]", msgspec.to_builtins(provider))
|
||||||
if name in section:
|
section[name] = self._provider_to_toml_table(raw)
|
||||||
entry = cast("MutableMapping[str, object]", section[name])
|
|
||||||
entry.clear()
|
|
||||||
for k, v in raw.items():
|
|
||||||
entry[k] = v
|
|
||||||
else:
|
|
||||||
section[name] = raw
|
|
||||||
|
|
||||||
def _sync_to_document(self) -> None:
|
def _sync_to_document(self) -> None:
|
||||||
"""Incrementally sync all sections into the document."""
|
"""Incrementally sync all sections into the document."""
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from collections.abc import Mapping
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import tomlkit
|
||||||
|
|
||||||
import plyngent
|
import plyngent
|
||||||
from plyngent.config import (
|
from plyngent.config import (
|
||||||
@@ -12,6 +13,7 @@ from plyngent.config import (
|
|||||||
OpenAICompatibleProvider,
|
OpenAICompatibleProvider,
|
||||||
OpenAIProvider,
|
OpenAIProvider,
|
||||||
)
|
)
|
||||||
|
from plyngent.config.store import ConfigStore
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -252,6 +254,34 @@ url = "https://example/v1"
|
|||||||
assert set(config.providers["local"].models) == {"base", "extra", "remote-a", "remote-b"}
|
assert set(config.providers["local"].models) == {"base", "extra", "remote-a", "remote-b"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_models_as_inline_tables(tmp_path: Path) -> None:
|
||||||
|
"""Persisted models use inline tables, not dotted [providers.x.models.id] sections."""
|
||||||
|
from plyngent.config import ModelConfig, OpenAICompatibleProvider
|
||||||
|
|
||||||
|
path = tmp_path / "inline-models.toml"
|
||||||
|
config = ConfigStore(path=path, document=tomlkit.document())
|
||||||
|
config.providers = {
|
||||||
|
"local": OpenAICompatibleProvider(
|
||||||
|
access_key_or_token="sk-test",
|
||||||
|
url="https://example/v1",
|
||||||
|
models={
|
||||||
|
"base": ModelConfig(),
|
||||||
|
"gpt-test": ModelConfig(text=True, cost_factor=2),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
config.write()
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
assert "[providers.local.models]" in text
|
||||||
|
assert "[providers.local.models.base]" not in text
|
||||||
|
assert "[providers.local.models.gpt-test]" not in text
|
||||||
|
assert "gpt-test" in text and "cost_factor" in text
|
||||||
|
# Round-trip still loads models.
|
||||||
|
again = plyngent.config.load(path)
|
||||||
|
assert set(again.providers["local"].models) == {"base", "gpt-test"}
|
||||||
|
assert again.providers["local"].models["gpt-test"].cost_factor == 2
|
||||||
|
|
||||||
|
|
||||||
def test_update_config() -> None:
|
def test_update_config() -> None:
|
||||||
file = Path(__file__).parent / "plyngent-edit-2.toml"
|
file = Path(__file__).parent / "plyngent-edit-2.toml"
|
||||||
_ = shutil.copy(Path(__file__).parent / "plyngent-valid.toml", file)
|
_ = shutil.copy(Path(__file__).parent / "plyngent-valid.toml", file)
|
||||||
|
|||||||
Reference in New Issue
Block a user