mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-23 05:55:16 +08:00
core/config: add [agent] section for profile defaults
Parse and sync system_prompt, max_tool_result_chars, and parallel_tools.
This commit is contained in:
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
|
|||||||
import tomlkit
|
import tomlkit
|
||||||
from tomlkit.exceptions import TOMLKitError
|
from tomlkit.exceptions import TOMLKitError
|
||||||
|
|
||||||
|
from .models import AgentConfig as AgentConfig
|
||||||
from .models import AnthropicProvider as AnthropicProvider
|
from .models import AnthropicProvider as AnthropicProvider
|
||||||
from .models import DatabaseConfig as DatabaseConfig
|
from .models import DatabaseConfig as DatabaseConfig
|
||||||
from .models import DeepseekProvider as DeepseekProvider
|
from .models import DeepseekProvider as DeepseekProvider
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ class DatabaseConfig(Struct, omit_defaults=True):
|
|||||||
password: str | None = None
|
password: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class AgentConfig(Struct, omit_defaults=True):
|
||||||
|
"""Single-user agent profile defaults."""
|
||||||
|
|
||||||
|
system_prompt: str = ""
|
||||||
|
max_tool_result_chars: int = 32_000
|
||||||
|
parallel_tools: bool = True
|
||||||
|
|
||||||
|
|
||||||
class ModelConfig(Struct, omit_defaults=True):
|
class ModelConfig(Struct, omit_defaults=True):
|
||||||
"""Capability flags for a model within a provider."""
|
"""Capability flags for a model within a provider."""
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, cast
|
|||||||
import msgspec
|
import msgspec
|
||||||
import tomlkit
|
import tomlkit
|
||||||
|
|
||||||
from .models import DatabaseConfig, Provider
|
from .models import AgentConfig, DatabaseConfig, Provider
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Mapping, MutableMapping
|
from collections.abc import Mapping, MutableMapping
|
||||||
@@ -25,6 +25,14 @@ def _parse_database(raw: dict[str, object]) -> DatabaseConfig:
|
|||||||
return DatabaseConfig()
|
return DatabaseConfig()
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_agent(raw: dict[str, object]) -> AgentConfig:
|
||||||
|
"""Parse the ``[agent]`` section, falling back to defaults."""
|
||||||
|
try:
|
||||||
|
return msgspec.convert(raw, AgentConfig)
|
||||||
|
except msgspec.ValidationError:
|
||||||
|
return AgentConfig()
|
||||||
|
|
||||||
|
|
||||||
def _parse_providers(
|
def _parse_providers(
|
||||||
document: tomlkit.TOMLDocument,
|
document: tomlkit.TOMLDocument,
|
||||||
) -> tuple[dict[str, Provider], dict[str, object]]:
|
) -> tuple[dict[str, Provider], dict[str, object]]:
|
||||||
@@ -73,6 +81,7 @@ class ConfigStore:
|
|||||||
_path: Path
|
_path: Path
|
||||||
_document: tomlkit.TOMLDocument
|
_document: tomlkit.TOMLDocument
|
||||||
_database: DatabaseConfig
|
_database: DatabaseConfig
|
||||||
|
_agent: AgentConfig
|
||||||
_providers: dict[str, Provider]
|
_providers: dict[str, Provider]
|
||||||
_bad_providers: dict[str, object]
|
_bad_providers: dict[str, object]
|
||||||
|
|
||||||
@@ -81,6 +90,7 @@ class ConfigStore:
|
|||||||
self._document = document
|
self._document = document
|
||||||
raw: dict[str, object] = document.unwrap()
|
raw: dict[str, object] = document.unwrap()
|
||||||
self._database = _parse_database(cast("dict[str, object]", raw.get("database", {})))
|
self._database = _parse_database(cast("dict[str, object]", raw.get("database", {})))
|
||||||
|
self._agent = _parse_agent(cast("dict[str, object]", raw.get("agent", {})))
|
||||||
self._providers, self._bad_providers = _parse_providers(document)
|
self._providers, self._bad_providers = _parse_providers(document)
|
||||||
|
|
||||||
# -- database (read-only) --
|
# -- database (read-only) --
|
||||||
@@ -90,6 +100,18 @@ class ConfigStore:
|
|||||||
"""Read-only mapping view of database configuration."""
|
"""Read-only mapping view of database configuration."""
|
||||||
return MappingProxyType(msgspec.structs.asdict(self._database))
|
return MappingProxyType(msgspec.structs.asdict(self._database))
|
||||||
|
|
||||||
|
# -- agent (read-only) --
|
||||||
|
|
||||||
|
@property
|
||||||
|
def agent(self) -> MappingProxyType[str, object]:
|
||||||
|
"""Read-only mapping view of agent profile configuration."""
|
||||||
|
return MappingProxyType(msgspec.structs.asdict(self._agent))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def agent_config(self) -> AgentConfig:
|
||||||
|
"""Typed agent profile (system prompt, tool budgets, etc.)."""
|
||||||
|
return self._agent
|
||||||
|
|
||||||
# -- providers (read/write) --
|
# -- providers (read/write) --
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -128,6 +150,7 @@ class ConfigStore:
|
|||||||
self._document = tomlkit.parse(f.read())
|
self._document = tomlkit.parse(f.read())
|
||||||
raw: dict[str, object] = self._document.unwrap()
|
raw: dict[str, object] = self._document.unwrap()
|
||||||
self._database = _parse_database(cast("dict[str, object]", raw.get("database", {})))
|
self._database = _parse_database(cast("dict[str, object]", raw.get("database", {})))
|
||||||
|
self._agent = _parse_agent(cast("dict[str, object]", raw.get("agent", {})))
|
||||||
self._providers, self._bad_providers = _parse_providers(self._document)
|
self._providers, self._bad_providers = _parse_providers(self._document)
|
||||||
|
|
||||||
# -- internal sync helpers --
|
# -- internal sync helpers --
|
||||||
@@ -138,20 +161,26 @@ class ConfigStore:
|
|||||||
self._document[key] = tomlkit.table()
|
self._document[key] = tomlkit.table()
|
||||||
return cast("MutableMapping[str, object]", self._document[key])
|
return cast("MutableMapping[str, object]", self._document[key])
|
||||||
|
|
||||||
|
def _sync_section(self, key: str, data: object) -> None:
|
||||||
|
raw: dict[str, object] = msgspec.to_builtins(data)
|
||||||
|
if not raw:
|
||||||
|
if key in self._document:
|
||||||
|
del self._document[key]
|
||||||
|
return
|
||||||
|
section = self._toml_table(key)
|
||||||
|
for k in list(section.keys()):
|
||||||
|
if k not in raw:
|
||||||
|
del section[k]
|
||||||
|
for k, v in raw.items():
|
||||||
|
section[k] = v
|
||||||
|
|
||||||
def _sync_database_section(self) -> None:
|
def _sync_database_section(self) -> None:
|
||||||
"""Sync ``[database]`` to the document."""
|
"""Sync ``[database]`` to the document."""
|
||||||
raw_db: dict[str, object] = msgspec.to_builtins(self._database)
|
self._sync_section("database", self._database)
|
||||||
if not raw_db:
|
|
||||||
if "database" in self._document:
|
|
||||||
del self._document["database"]
|
|
||||||
return
|
|
||||||
|
|
||||||
section = self._toml_table("database")
|
def _sync_agent_section(self) -> None:
|
||||||
for k in list(section.keys()):
|
"""Sync ``[agent]`` to the document."""
|
||||||
if k not in raw_db:
|
self._sync_section("agent", self._agent)
|
||||||
del section[k]
|
|
||||||
for k, v in raw_db.items():
|
|
||||||
section[k] = v
|
|
||||||
|
|
||||||
def _sync_providers_section(self) -> None:
|
def _sync_providers_section(self) -> None:
|
||||||
"""Sync ``[providers]`` to the document."""
|
"""Sync ``[providers]`` to the document."""
|
||||||
@@ -174,4 +203,5 @@ class ConfigStore:
|
|||||||
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."""
|
||||||
self._sync_database_section()
|
self._sync_database_section()
|
||||||
|
self._sync_agent_section()
|
||||||
self._sync_providers_section()
|
self._sync_providers_section()
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from plyngent.config import load
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_section_defaults(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "c.toml"
|
||||||
|
_ = path.write_text("", encoding="utf-8")
|
||||||
|
store = load(path)
|
||||||
|
assert store.agent_config.system_prompt == ""
|
||||||
|
assert store.agent_config.max_tool_result_chars == 32_000 # noqa: PLR2004
|
||||||
|
assert store.agent_config.parallel_tools is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_section_parse(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "c.toml"
|
||||||
|
_ = path.write_text(
|
||||||
|
"""
|
||||||
|
[agent]
|
||||||
|
system_prompt = "Be brief."
|
||||||
|
max_tool_result_chars = 100
|
||||||
|
parallel_tools = false
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
store = load(path)
|
||||||
|
assert store.agent_config.system_prompt == "Be brief."
|
||||||
|
assert store.agent_config.max_tool_result_chars == 100 # noqa: PLR2004
|
||||||
|
assert store.agent_config.parallel_tools is False
|
||||||
Reference in New Issue
Block a user