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
|
||||
from tomlkit.exceptions import TOMLKitError
|
||||
|
||||
from .models import AgentConfig as AgentConfig
|
||||
from .models import AnthropicProvider as AnthropicProvider
|
||||
from .models import DatabaseConfig as DatabaseConfig
|
||||
from .models import DeepseekProvider as DeepseekProvider
|
||||
|
||||
@@ -10,6 +10,14 @@ class DatabaseConfig(Struct, omit_defaults=True):
|
||||
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):
|
||||
"""Capability flags for a model within a provider."""
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, cast
|
||||
import msgspec
|
||||
import tomlkit
|
||||
|
||||
from .models import DatabaseConfig, Provider
|
||||
from .models import AgentConfig, DatabaseConfig, Provider
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping, MutableMapping
|
||||
@@ -25,6 +25,14 @@ def _parse_database(raw: dict[str, object]) -> 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(
|
||||
document: tomlkit.TOMLDocument,
|
||||
) -> tuple[dict[str, Provider], dict[str, object]]:
|
||||
@@ -73,6 +81,7 @@ class ConfigStore:
|
||||
_path: Path
|
||||
_document: tomlkit.TOMLDocument
|
||||
_database: DatabaseConfig
|
||||
_agent: AgentConfig
|
||||
_providers: dict[str, Provider]
|
||||
_bad_providers: dict[str, object]
|
||||
|
||||
@@ -81,6 +90,7 @@ class ConfigStore:
|
||||
self._document = document
|
||||
raw: dict[str, object] = document.unwrap()
|
||||
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)
|
||||
|
||||
# -- database (read-only) --
|
||||
@@ -90,6 +100,18 @@ class ConfigStore:
|
||||
"""Read-only mapping view of database configuration."""
|
||||
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) --
|
||||
|
||||
@property
|
||||
@@ -128,6 +150,7 @@ class ConfigStore:
|
||||
self._document = tomlkit.parse(f.read())
|
||||
raw: dict[str, object] = self._document.unwrap()
|
||||
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)
|
||||
|
||||
# -- internal sync helpers --
|
||||
@@ -138,20 +161,26 @@ class ConfigStore:
|
||||
self._document[key] = tomlkit.table()
|
||||
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:
|
||||
"""Sync ``[database]`` to the document."""
|
||||
raw_db: dict[str, object] = msgspec.to_builtins(self._database)
|
||||
if not raw_db:
|
||||
if "database" in self._document:
|
||||
del self._document["database"]
|
||||
return
|
||||
self._sync_section("database", self._database)
|
||||
|
||||
section = self._toml_table("database")
|
||||
for k in list(section.keys()):
|
||||
if k not in raw_db:
|
||||
del section[k]
|
||||
for k, v in raw_db.items():
|
||||
section[k] = v
|
||||
def _sync_agent_section(self) -> None:
|
||||
"""Sync ``[agent]`` to the document."""
|
||||
self._sync_section("agent", self._agent)
|
||||
|
||||
def _sync_providers_section(self) -> None:
|
||||
"""Sync ``[providers]`` to the document."""
|
||||
@@ -174,4 +203,5 @@ class ConfigStore:
|
||||
def _sync_to_document(self) -> None:
|
||||
"""Incrementally sync all sections into the document."""
|
||||
self._sync_database_section()
|
||||
self._sync_agent_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