zettcode-one-system 0.5.1__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.
- zettcode_one_system-0.5.1/.gitignore +40 -0
- zettcode_one_system-0.5.1/PKG-INFO +19 -0
- zettcode_one_system-0.5.1/pyproject.toml +51 -0
- zettcode_one_system-0.5.1/src/zettcode/one_system/__init__.py +12 -0
- zettcode_one_system-0.5.1/src/zettcode/one_system/extension.py +81 -0
- zettcode_one_system-0.5.1/src/zettcode/one_system/plugin.py +38 -0
- zettcode_one_system-0.5.1/src/zettcode/one_system/py.typed +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
**/.venv/
|
|
2
|
+
**/__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
.smoke/
|
|
8
|
+
.coverage
|
|
9
|
+
**/.pytest_cache/
|
|
10
|
+
**/.ruff_cache/
|
|
11
|
+
.zett-tool-output/
|
|
12
|
+
.env
|
|
13
|
+
.DS_Store
|
|
14
|
+
|
|
15
|
+
# viztracer writes its trace here by default.
|
|
16
|
+
/result.json
|
|
17
|
+
|
|
18
|
+
# The docs site: dependencies, build output, and VitePress' cache.
|
|
19
|
+
node_modules/
|
|
20
|
+
docs/.vitepress/dist/
|
|
21
|
+
docs/.vitepress/cache/
|
|
22
|
+
docs/.vitepress/.temp/
|
|
23
|
+
|
|
24
|
+
# The TypeScript client is built from its sources; only dist/ is published.
|
|
25
|
+
clients/typescript/dist/
|
|
26
|
+
|
|
27
|
+
# The gateway's page is built from clients/gateway into the package when a distribution is made (`make gateway-ui`);
|
|
28
|
+
# the build output is not kept in the repository. pyproject's `artifacts` puts it in the wheel all the same.
|
|
29
|
+
packages/zettcode-gateway/src/zettcode/gateway/static/
|
|
30
|
+
|
|
31
|
+
# The palette gallery is generated by `make palettes`: every widget painted in
|
|
32
|
+
# every palette, written by docs/scripts/palette_gallery.py. Regenerate it
|
|
33
|
+
# rather than committing a 2 MB copy that goes stale the moment a colour moves.
|
|
34
|
+
docs/internal/palettes.html
|
|
35
|
+
|
|
36
|
+
# The release order is generated from the pyproject.toml files (`make order`); it is not kept in git.
|
|
37
|
+
packages/order.txt
|
|
38
|
+
|
|
39
|
+
# electron-builder writes the desktop installers under release/.
|
|
40
|
+
clients/desktop/release/
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: zettcode-one-system
|
|
3
|
+
Version: 0.5.1
|
|
4
|
+
Summary: Every instruction ZettCode gives the model in one system message, as a part of the agent that lives apart from it
|
|
5
|
+
Project-URL: Repository, https://github.com/Chang-LeHung/zettcode
|
|
6
|
+
Author: Chang-LeHung
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: agent,prompt,system-message,zettcode
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: zett-agent<0.3,>=0.2.10
|
|
19
|
+
Requires-Dist: zettcode-engine<0.6,>=0.5.1
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "zettcode-one-system"
|
|
3
|
+
description = "Every instruction ZettCode gives the model in one system message, as a part of the agent that lives apart from it"
|
|
4
|
+
requires-python = ">=3.10"
|
|
5
|
+
dynamic = ["version"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [{name = "Chang-LeHung"}]
|
|
8
|
+
keywords = ["zettcode", "prompt", "system-message", "agent"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 5 - Production/Stable",
|
|
11
|
+
"Programming Language :: Python :: 3.10",
|
|
12
|
+
"Programming Language :: Python :: 3.11",
|
|
13
|
+
"Programming Language :: Python :: 3.12",
|
|
14
|
+
"Programming Language :: Python :: 3.13",
|
|
15
|
+
"Programming Language :: Python :: 3.14",
|
|
16
|
+
"Programming Language :: Python :: 3.15",
|
|
17
|
+
"Typing :: Typed",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
# The plugin base classes.
|
|
21
|
+
"zettcode-engine>=0.5.1,<0.6",
|
|
22
|
+
# The message types and the extension hook the merge runs on.
|
|
23
|
+
"zett-agent>=0.2.10,<0.3",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# A part of the program, not a separately released plugin: the `[tool.zettcode] plugin = true` marker is what the
|
|
27
|
+
# plugin release line looks for, and this ships with the program like the tool guidelines and the long-output cut. It is
|
|
28
|
+
# on unless `[plugins] disable = ["one-system"]` names it.
|
|
29
|
+
[project.entry-points."zettcode.plugin"]
|
|
30
|
+
one-system = "zettcode.one_system.plugin:OneSystem"
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Repository = "https://github.com/Chang-LeHung/zettcode"
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["hatchling"]
|
|
37
|
+
build-backend = "hatchling.build"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.version]
|
|
40
|
+
# One version for the whole repository: the engine ships the file.
|
|
41
|
+
path = "../zettcode-engine/src/zettcode/version.py"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/zettcode"]
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.sdist]
|
|
47
|
+
include = ["/src", "/pyproject.toml"]
|
|
48
|
+
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
testpaths = ["tests"]
|
|
51
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""One system message: every instruction the agent gives the model, joined into the one message before the provider.
|
|
2
|
+
|
|
3
|
+
A part of the program that lives in a distribution of its own, found through the ``zettcode.plugin`` entry point. It is
|
|
4
|
+
on unless ``[plugins] disable = ["one-system"]`` names it: what it changes is the shape of the request, not what the
|
|
5
|
+
model is told, so leaving it on is what an endpoint that accepts one system message expects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .plugin import OneSystem
|
|
11
|
+
|
|
12
|
+
__all__ = ["OneSystem"]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""The hook that turns a request's instructions into one system message, and stops there.
|
|
2
|
+
|
|
3
|
+
A request reaches the provider with several system messages: the agent's own prompt, the working directory, the
|
|
4
|
+
project's instructions, skills, MCP notices, and whatever guidance an extension added for this request. Every one of
|
|
5
|
+
them is an instruction, and every provider has its own way of taking them: the Anthropic-compatible one collects them
|
|
6
|
+
and joins them with a blank line, an OpenAI-compatible one sends a message per entry, and an endpoint that accepts a
|
|
7
|
+
single system message refuses the rest.
|
|
8
|
+
|
|
9
|
+
Joining them here makes the shape the same everywhere, and does it on the text the model was going to read anyway: the
|
|
10
|
+
parts are concatenated in the order they were already in, separated by a blank line, and the result sits where the first
|
|
11
|
+
one was. Nothing is reordered past a conversation message, nothing is dropped, and a request that already has one
|
|
12
|
+
system message is handed on untouched.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import AsyncIterator, Sequence
|
|
18
|
+
from dataclasses import replace
|
|
19
|
+
|
|
20
|
+
from zett_agent.agent import AgentRunContext
|
|
21
|
+
from zett_agent.extensions.base import AgentExtension, ModelRequestNext
|
|
22
|
+
from zett_agent.messages import AnyMessage, SystemMessage
|
|
23
|
+
from zett_agent.model import ModelEvent, ModelRequest
|
|
24
|
+
|
|
25
|
+
__all__ = ["OneSystemMessage", "merge_system"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def merge_system(messages: Sequence[AnyMessage]) -> Sequence[AnyMessage]:
|
|
29
|
+
"""Return ``messages`` with every system message joined into the first one's place.
|
|
30
|
+
|
|
31
|
+
A request with fewer than two system messages, or with nothing but blank text between them, is returned as it was —
|
|
32
|
+
the same object, so a caller can tell the two cases apart without comparing contents.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
messages: The request's messages, in the order the provider would see them.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
The messages with one :class:`~zett_agent.messages.SystemMessage` where the first one was, or ``messages``
|
|
39
|
+
itself when there was nothing to join.
|
|
40
|
+
"""
|
|
41
|
+
found = [(index, message) for index, message in enumerate(messages) if isinstance(message, SystemMessage)]
|
|
42
|
+
if len(found) < 2:
|
|
43
|
+
return messages
|
|
44
|
+
joined = "\n\n".join(message.content.strip() for _index, message in found if message.content.strip())
|
|
45
|
+
if not joined:
|
|
46
|
+
return messages
|
|
47
|
+
# The first message's own flags and attributes are kept: only its text changes. `dataclasses.replace` gives a
|
|
48
|
+
# new message, so the one the runtime holds for later requests is left as it was.
|
|
49
|
+
where, first_message = found[0]
|
|
50
|
+
merged = replace(first_message, content=joined)
|
|
51
|
+
removed = {index for index, _message in found}
|
|
52
|
+
kept = [message for index, message in enumerate(messages) if index not in removed]
|
|
53
|
+
# Every removed place is at or after the first, so the first's index still counts among the ones that remain.
|
|
54
|
+
kept.insert(where, merged)
|
|
55
|
+
return tuple(kept)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class OneSystemMessage(AgentExtension):
|
|
59
|
+
"""Join a request's system messages into one, just before the provider is handed the request.
|
|
60
|
+
|
|
61
|
+
The priority is higher than any other extension's so that this runs innermost: a part that adds an instruction while
|
|
62
|
+
the request is being built — the harness's guidance, for one — has already been added by the time this sees the
|
|
63
|
+
request, and none is left behind to reach the provider on its own.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
#: Innermost: the last extension to change the request, and the one closest to the provider. It has to clear the
|
|
67
|
+
#: two the framework itself runs late — the JIT harness's guidance at 900_000 and plan mode at 1_000_000 — because
|
|
68
|
+
#: both add a system message while the request is being built, and a tie is broken by registration order.
|
|
69
|
+
priority = 1_000_001
|
|
70
|
+
|
|
71
|
+
async def on_model_request(
|
|
72
|
+
self, context: AgentRunContext, request: ModelRequest, call_next: ModelRequestNext
|
|
73
|
+
) -> AsyncIterator[ModelEvent]:
|
|
74
|
+
"""Hand the provider one system message, or the very request it was given when there is nothing to join."""
|
|
75
|
+
merged = merge_system(request.messages)
|
|
76
|
+
if merged is request.messages:
|
|
77
|
+
async for event in call_next(request):
|
|
78
|
+
yield event
|
|
79
|
+
return
|
|
80
|
+
async for event in call_next(replace(request, messages=merged)):
|
|
81
|
+
yield event
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""The plugin that hands the agent the one-system-message hook."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from zettcode.engine.plugins.context import PluginContext
|
|
8
|
+
from zettcode.engine.plugins.plugin import Plugin
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING: # pragma: no cover - annotations only
|
|
11
|
+
from zett_agent.extensions.base import AgentExtension
|
|
12
|
+
|
|
13
|
+
from zettcode.engine.plugins.state import AgentBuild
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OneSystem(Plugin[PluginContext]):
|
|
17
|
+
"""Every instruction in one system message, for the endpoints that take one and for the ones that take many.
|
|
18
|
+
|
|
19
|
+
The merge runs once per request, at the point the provider would see the messages, so it covers the instructions a
|
|
20
|
+
part added for that request as well as the ones the agent carries everywhere. It changes nothing a model reads — the
|
|
21
|
+
same text, in the same order, separated by the blank line the Anthropic-compatible provider already used between
|
|
22
|
+
them — and it is off for `[plugins] disable = ["one-system"]` like any other part.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
name = "one-system"
|
|
26
|
+
description = "All instructions the model is given in one system message"
|
|
27
|
+
|
|
28
|
+
def activate(self, ctx: PluginContext) -> None:
|
|
29
|
+
"""Register the extension as a factory: it is built with the agent, not while the first frame is drawn."""
|
|
30
|
+
|
|
31
|
+
# Imported in the factory: the extension imports the framework's message and hook types, which a launch that
|
|
32
|
+
# never builds an agent does not need.
|
|
33
|
+
def build(_: AgentBuild) -> AgentExtension:
|
|
34
|
+
from .extension import OneSystemMessage
|
|
35
|
+
|
|
36
|
+
return OneSystemMessage()
|
|
37
|
+
|
|
38
|
+
ctx.agent.register_extension_factory("OneSystemMessage", build)
|
|
File without changes
|