mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-23 05:55:16 +08:00
core/agent+cli: wire max_context_chars and re-raise CancelledError
Propagate context budget from [agent] config; preserve cancel through tool invoke for mid-turn interruption.
This commit is contained in:
@@ -54,10 +54,10 @@ Async SQLAlchemy + aiosqlite. `MemoryStore`: schema init, default local user, se
|
|||||||
|
|
||||||
- **`ChatClient`** Protocol for `chat_completions`.
|
- **`ChatClient`** Protocol for `chat_completions`.
|
||||||
- **`@tool` / `ToolRegistry`**: decorator infers JSON Schema from type hints; execute tools by name.
|
- **`@tool` / `ToolRegistry`**: decorator infers JSON Schema from type hints; execute tools by name.
|
||||||
- **`run_chat_loop`**: multi-round tool loop; default **streaming** text deltas + stream tool-call merge; parallel tools; tool-result char budget; optional `on_limit`.
|
- **`run_chat_loop`**: multi-round tool loop; default **streaming** text deltas + stream tool-call merge; parallel tools; tool-result char budget; soft context compact on request; cooperative cancel points; optional `on_limit`.
|
||||||
- **`ChatAgent`**: optional `MemoryStore` (persist on success only); `stream`; system prompt; `pending_retry_text` + `retry()`.
|
- **`ChatAgent`**: optional `MemoryStore` (persist on success only); `stream`; system prompt; `pending_retry_text` + `retry()`.
|
||||||
- Events: text_delta, assistant_message, tool_call/result, max_rounds, **error**, **cancelled**.
|
- Events: text_delta, assistant_message, tool_call/result, max_rounds, **error** (`retryable`/`source`), **cancelled** (`reason`).
|
||||||
- Config ``[agent]``: `system_prompt`, `max_tool_result_chars`, `parallel_tools`, `confirm_destructive`, `path_denylist`.
|
- Config ``[agent]``: `system_prompt`, `max_tool_result_chars`, `parallel_tools`, `confirm_destructive`, `path_denylist`, `max_context_chars`.
|
||||||
|
|
||||||
### Tools (`tools/`)
|
### Tools (`tools/`)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from plyngent.lmproto.openai_compatible.model import SystemChatMessage, UserChatMessage
|
from plyngent.lmproto.openai_compatible.model import SystemChatMessage, UserChatMessage
|
||||||
|
|
||||||
from .budget import DEFAULT_TOOL_RESULT_MAX_CHARS
|
from .budget import DEFAULT_CONTEXT_MAX_CHARS, DEFAULT_TOOL_RESULT_MAX_CHARS
|
||||||
from .loop import DEFAULT_MAX_ROUNDS, run_chat_loop
|
from .loop import DEFAULT_MAX_ROUNDS, run_chat_loop
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -35,6 +35,7 @@ class ChatAgent:
|
|||||||
system_prompt: str | None
|
system_prompt: str | None
|
||||||
max_tool_result_chars: int
|
max_tool_result_chars: int
|
||||||
parallel_tools: bool
|
parallel_tools: bool
|
||||||
|
max_context_chars: int
|
||||||
messages: list[AnyChatMessage]
|
messages: list[AnyChatMessage]
|
||||||
pending_retry_text: str | None
|
pending_retry_text: str | None
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ class ChatAgent:
|
|||||||
system_prompt: str | None = None,
|
system_prompt: str | None = None,
|
||||||
max_tool_result_chars: int = DEFAULT_TOOL_RESULT_MAX_CHARS,
|
max_tool_result_chars: int = DEFAULT_TOOL_RESULT_MAX_CHARS,
|
||||||
parallel_tools: bool = True,
|
parallel_tools: bool = True,
|
||||||
|
max_context_chars: int = DEFAULT_CONTEXT_MAX_CHARS,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.client = client
|
self.client = client
|
||||||
self.model = model
|
self.model = model
|
||||||
@@ -67,6 +69,7 @@ class ChatAgent:
|
|||||||
self.system_prompt = system_prompt
|
self.system_prompt = system_prompt
|
||||||
self.max_tool_result_chars = max_tool_result_chars
|
self.max_tool_result_chars = max_tool_result_chars
|
||||||
self.parallel_tools = parallel_tools
|
self.parallel_tools = parallel_tools
|
||||||
|
self.max_context_chars = max_context_chars
|
||||||
self.messages = list(messages) if messages is not None else []
|
self.messages = list(messages) if messages is not None else []
|
||||||
self.pending_retry_text = None
|
self.pending_retry_text = None
|
||||||
self._ensure_system_prompt()
|
self._ensure_system_prompt()
|
||||||
@@ -125,6 +128,7 @@ class ChatAgent:
|
|||||||
stream=self.stream,
|
stream=self.stream,
|
||||||
max_tool_result_chars=self.max_tool_result_chars,
|
max_tool_result_chars=self.max_tool_result_chars,
|
||||||
parallel_tools=self.parallel_tools,
|
parallel_tools=self.parallel_tools,
|
||||||
|
max_context_chars=self.max_context_chars,
|
||||||
):
|
):
|
||||||
yield event
|
yield event
|
||||||
completed = True
|
completed = True
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ class ToolRegistry:
|
|||||||
result = definition.handler(**args)
|
result = definition.handler(**args)
|
||||||
if inspect.isawaitable(result):
|
if inspect.isawaitable(result):
|
||||||
result = await result
|
result = await result
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
raise
|
||||||
except TypeError as exc:
|
except TypeError as exc:
|
||||||
return f"error: invalid tool arguments: {exc}"
|
return f"error: invalid tool arguments: {exc}"
|
||||||
except Exception as exc: # noqa: BLE001 — surface tool failures to the model
|
except Exception as exc: # noqa: BLE001 — surface tool failures to the model
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ _MINIMAL_CONFIG = """\
|
|||||||
# parallel_tools = true
|
# parallel_tools = true
|
||||||
# confirm_destructive = true
|
# confirm_destructive = true
|
||||||
# path_denylist = ["/secrets/", ".ssh/"]
|
# path_denylist = ["/secrets/", ".ssh/"]
|
||||||
|
# max_context_chars = 200000
|
||||||
|
|
||||||
# [providers.example]
|
# [providers.example]
|
||||||
# preset = "openai-compatible"
|
# preset = "openai-compatible"
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ class ReplState:
|
|||||||
system_prompt=system_prompt,
|
system_prompt=system_prompt,
|
||||||
max_tool_result_chars=agent_cfg.max_tool_result_chars,
|
max_tool_result_chars=agent_cfg.max_tool_result_chars,
|
||||||
parallel_tools=agent_cfg.parallel_tools,
|
parallel_tools=agent_cfg.parallel_tools,
|
||||||
|
max_context_chars=agent_cfg.max_context_chars,
|
||||||
)
|
)
|
||||||
|
|
||||||
def rebuild_client(self) -> None:
|
def rebuild_client(self) -> None:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class AgentConfig(Struct, omit_defaults=True):
|
|||||||
parallel_tools: bool = True
|
parallel_tools: bool = True
|
||||||
confirm_destructive: bool = True
|
confirm_destructive: bool = True
|
||||||
path_denylist: list[str] = field(default_factory=list)
|
path_denylist: list[str] = field(default_factory=list)
|
||||||
|
max_context_chars: int = 200_000
|
||||||
|
|
||||||
|
|
||||||
class ModelConfig(Struct, omit_defaults=True):
|
class ModelConfig(Struct, omit_defaults=True):
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ def test_agent_section_defaults(tmp_path: Path) -> None:
|
|||||||
assert store.agent_config.parallel_tools is True
|
assert store.agent_config.parallel_tools is True
|
||||||
assert store.agent_config.confirm_destructive is True
|
assert store.agent_config.confirm_destructive is True
|
||||||
assert store.agent_config.path_denylist == []
|
assert store.agent_config.path_denylist == []
|
||||||
|
assert store.agent_config.max_context_chars == 200_000 # noqa: PLR2004
|
||||||
|
|
||||||
|
|
||||||
def test_agent_section_parse(tmp_path: Path) -> None:
|
def test_agent_section_parse(tmp_path: Path) -> None:
|
||||||
@@ -29,6 +30,7 @@ max_tool_result_chars = 100
|
|||||||
parallel_tools = false
|
parallel_tools = false
|
||||||
confirm_destructive = false
|
confirm_destructive = false
|
||||||
path_denylist = ["/secrets/", ".ssh/"]
|
path_denylist = ["/secrets/", ".ssh/"]
|
||||||
|
max_context_chars = 5000
|
||||||
""",
|
""",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
@@ -38,3 +40,4 @@ path_denylist = ["/secrets/", ".ssh/"]
|
|||||||
assert store.agent_config.parallel_tools is False
|
assert store.agent_config.parallel_tools is False
|
||||||
assert store.agent_config.confirm_destructive is False
|
assert store.agent_config.confirm_destructive is False
|
||||||
assert store.agent_config.path_denylist == ["/secrets/", ".ssh/"]
|
assert store.agent_config.path_denylist == ["/secrets/", ".ssh/"]
|
||||||
|
assert store.agent_config.max_context_chars == 5000 # noqa: PLR2004
|
||||||
|
|||||||
Reference in New Issue
Block a user