pygent 0.1.11__py3-none-any.whl → 0.1.12__py3-none-any.whl
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.
- pygent/__init__.py +11 -1
- pygent/agent.py +12 -7
- pygent/tools.py +88 -59
- {pygent-0.1.11.dist-info → pygent-0.1.12.dist-info}/METADATA +1 -1
- {pygent-0.1.11.dist-info → pygent-0.1.12.dist-info}/RECORD +9 -9
- {pygent-0.1.11.dist-info → pygent-0.1.12.dist-info}/WHEEL +0 -0
- {pygent-0.1.11.dist-info → pygent-0.1.12.dist-info}/entry_points.txt +0 -0
- {pygent-0.1.11.dist-info → pygent-0.1.12.dist-info}/licenses/LICENSE +0 -0
- {pygent-0.1.11.dist-info → pygent-0.1.12.dist-info}/top_level.txt +0 -0
pygent/__init__.py
CHANGED
@@ -9,5 +9,15 @@ except _metadata.PackageNotFoundError: # pragma: no cover - fallback for tests
|
|
9
9
|
from .agent import Agent, run_interactive # noqa: E402,F401, must come after __version__
|
10
10
|
from .models import Model, OpenAIModel # noqa: E402,F401
|
11
11
|
from .errors import PygentError, APIError # noqa: E402,F401
|
12
|
+
from .tools import register_tool, tool # noqa: E402,F401
|
12
13
|
|
13
|
-
__all__ = [
|
14
|
+
__all__ = [
|
15
|
+
"Agent",
|
16
|
+
"run_interactive",
|
17
|
+
"Model",
|
18
|
+
"OpenAIModel",
|
19
|
+
"PygentError",
|
20
|
+
"APIError",
|
21
|
+
"register_tool",
|
22
|
+
"tool",
|
23
|
+
]
|
pygent/agent.py
CHANGED
@@ -13,7 +13,7 @@ from rich.panel import Panel
|
|
13
13
|
from rich.markdown import Markdown
|
14
14
|
|
15
15
|
from .runtime import Runtime
|
16
|
-
from .
|
16
|
+
from . import tools
|
17
17
|
from .models import Model, OpenAIModel
|
18
18
|
|
19
19
|
DEFAULT_MODEL = os.getenv("PYGENT_MODEL", "gpt-4.1-mini")
|
@@ -22,7 +22,7 @@ SYSTEM_MSG = (
|
|
22
22
|
"Respond with JSON when you need to use a tool."
|
23
23
|
"If you need to stop, call the `stop` tool.\n"
|
24
24
|
"You can use the following tools:\n"
|
25
|
-
f"{json.dumps(TOOL_SCHEMAS, indent=2)}\n"
|
25
|
+
f"{json.dumps(tools.TOOL_SCHEMAS, indent=2)}\n"
|
26
26
|
"You can also use the `continue` tool to continue the conversation.\n"
|
27
27
|
)
|
28
28
|
|
@@ -36,18 +36,23 @@ class Agent:
|
|
36
36
|
runtime: Runtime = field(default_factory=Runtime)
|
37
37
|
model: Model = field(default_factory=OpenAIModel)
|
38
38
|
model_name: str = DEFAULT_MODEL
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
system_msg: str = SYSTEM_MSG
|
40
|
+
history: List[Dict[str, Any]] = field(default_factory=list)
|
41
|
+
|
42
|
+
def __post_init__(self) -> None:
|
43
|
+
if not self.history:
|
44
|
+
self.history.append({"role": "system", "content": self.system_msg})
|
42
45
|
|
43
46
|
def step(self, user_msg: str):
|
44
47
|
self.history.append({"role": "user", "content": user_msg})
|
45
|
-
assistant_msg = self.model.chat(
|
48
|
+
assistant_msg = self.model.chat(
|
49
|
+
self.history, self.model_name, tools.TOOL_SCHEMAS
|
50
|
+
)
|
46
51
|
self.history.append(assistant_msg)
|
47
52
|
|
48
53
|
if assistant_msg.tool_calls:
|
49
54
|
for call in assistant_msg.tool_calls:
|
50
|
-
output = execute_tool(call, self.runtime)
|
55
|
+
output = tools.execute_tool(call, self.runtime)
|
51
56
|
self.history.append({"role": "tool", "content": output, "tool_call_id": call.id})
|
52
57
|
console.print(Panel(output, title=f"tool:{call.function.name}"))
|
53
58
|
else:
|
pygent/tools.py
CHANGED
@@ -1,70 +1,99 @@
|
|
1
|
-
"""
|
1
|
+
"""Tool registry and helper utilities."""
|
2
2
|
from __future__ import annotations
|
3
|
+
|
3
4
|
import json
|
4
|
-
from typing import Any, Dict
|
5
|
+
from typing import Any, Callable, Dict, List
|
5
6
|
|
6
7
|
from .runtime import Runtime
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
"parameters": {
|
29
|
-
"type": "object",
|
30
|
-
"properties": {
|
31
|
-
"path": {"type": "string"},
|
32
|
-
"content": {"type": "string"},
|
33
|
-
},
|
34
|
-
"required": ["path", "content"],
|
9
|
+
|
10
|
+
# ---- registry ----
|
11
|
+
TOOLS: Dict[str, Callable[..., str]] = {}
|
12
|
+
TOOL_SCHEMAS: List[Dict[str, Any]] = []
|
13
|
+
|
14
|
+
|
15
|
+
def register_tool(
|
16
|
+
name: str, description: str, parameters: Dict[str, Any], func: Callable[..., str]
|
17
|
+
) -> None:
|
18
|
+
"""Register a new callable tool."""
|
19
|
+
if name in TOOLS:
|
20
|
+
raise ValueError(f"tool {name} already registered")
|
21
|
+
TOOLS[name] = func
|
22
|
+
TOOL_SCHEMAS.append(
|
23
|
+
{
|
24
|
+
"type": "function",
|
25
|
+
"function": {
|
26
|
+
"name": name,
|
27
|
+
"description": description,
|
28
|
+
"parameters": parameters,
|
35
29
|
},
|
36
|
-
}
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
"function": {
|
49
|
-
"name": "continue",
|
50
|
-
"description": "Continue the conversation.",
|
51
|
-
"parameters": {"type": "object", "properties": {}},
|
52
|
-
},
|
53
|
-
},
|
54
|
-
]
|
30
|
+
}
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
def tool(name: str, description: str, parameters: Dict[str, Any]):
|
35
|
+
"""Decorator for registering a tool."""
|
36
|
+
|
37
|
+
def decorator(func: Callable[..., str]) -> Callable[..., str]:
|
38
|
+
register_tool(name, description, parameters, func)
|
39
|
+
return func
|
40
|
+
|
41
|
+
return decorator
|
55
42
|
|
56
|
-
# --------------- dispatcher ---------------
|
57
43
|
|
58
|
-
def execute_tool(call: Any, rt: Runtime) -> str: # pragma: no cover
|
44
|
+
def execute_tool(call: Any, rt: Runtime) -> str: # pragma: no cover
|
45
|
+
"""Dispatch a tool call."""
|
59
46
|
name = call.function.name
|
60
47
|
args: Dict[str, Any] = json.loads(call.function.arguments)
|
48
|
+
func = TOOLS.get(name)
|
49
|
+
if func is None:
|
50
|
+
return f"⚠️ unknown tool {name}"
|
51
|
+
return func(rt, **args)
|
52
|
+
|
53
|
+
|
54
|
+
# ---- built-ins ----
|
55
|
+
|
56
|
+
|
57
|
+
@tool(
|
58
|
+
name="bash",
|
59
|
+
description="Run a shell command inside the sandboxed container.",
|
60
|
+
parameters={
|
61
|
+
"type": "object",
|
62
|
+
"properties": {"cmd": {"type": "string", "description": "Command to execute"}},
|
63
|
+
"required": ["cmd"],
|
64
|
+
},
|
65
|
+
)
|
66
|
+
def _bash(rt: Runtime, cmd: str) -> str:
|
67
|
+
return rt.bash(cmd)
|
68
|
+
|
69
|
+
|
70
|
+
@tool(
|
71
|
+
name="write_file",
|
72
|
+
description="Create or overwrite a file in the workspace.",
|
73
|
+
parameters={
|
74
|
+
"type": "object",
|
75
|
+
"properties": {"path": {"type": "string"}, "content": {"type": "string"}},
|
76
|
+
"required": ["path", "content"],
|
77
|
+
},
|
78
|
+
)
|
79
|
+
def _write_file(rt: Runtime, path: str, content: str) -> str:
|
80
|
+
return rt.write_file(path, content)
|
81
|
+
|
82
|
+
|
83
|
+
@tool(
|
84
|
+
name="stop",
|
85
|
+
description="Stop the autonomous loop.",
|
86
|
+
parameters={"type": "object", "properties": {}},
|
87
|
+
)
|
88
|
+
def _stop(rt: Runtime) -> str: # pragma: no cover - side-effect free
|
89
|
+
return "Stopping."
|
90
|
+
|
91
|
+
|
92
|
+
@tool(
|
93
|
+
name="continue",
|
94
|
+
description="Continue the conversation.",
|
95
|
+
parameters={"type": "object", "properties": {}},
|
96
|
+
)
|
97
|
+
def _continue(rt: Runtime) -> str: # pragma: no cover - side-effect free
|
98
|
+
return "Continuing the conversation."
|
61
99
|
|
62
|
-
if name == "bash":
|
63
|
-
return rt.bash(**args)
|
64
|
-
if name == "write_file":
|
65
|
-
return rt.write_file(**args)
|
66
|
-
if name == "stop":
|
67
|
-
return "Stopping."
|
68
|
-
if name == "continue":
|
69
|
-
return "Continuing the conversation."
|
70
|
-
return f"⚠️ unknown tool {name}"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pygent
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.12
|
4
4
|
Summary: Pygent is a minimalist coding assistant that runs commands in a Docker container when available and falls back to local execution. See https://marianochaves.github.io/pygent for documentation and https://github.com/marianochaves/pygent for the source code.
|
5
5
|
Author-email: Mariano Chaves <mchaves.software@gmail.com>
|
6
6
|
Project-URL: Documentation, https://marianochaves.github.io/pygent
|
@@ -1,17 +1,17 @@
|
|
1
|
-
pygent/__init__.py,sha256=
|
1
|
+
pygent/__init__.py,sha256=L4IRxHYfzucHUjSMvYHG-1uoTe6XaYJkaHLXuGs4CI8,648
|
2
2
|
pygent/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
3
|
-
pygent/agent.py,sha256=
|
3
|
+
pygent/agent.py,sha256=ZyY9dEvB6bnM1ZFLh4XKQJ68v8xpRlP3HM2RRm6Y6Q0,3030
|
4
4
|
pygent/cli.py,sha256=Hz2FZeNMVhxoT5DjCqphXla3TisGJtPEz921LEcpxrA,527
|
5
5
|
pygent/errors.py,sha256=s5FBg_v94coSgMh7cfkP4hVXafViGYgCY8QiT698-c4,155
|
6
6
|
pygent/models.py,sha256=j3670gjUtvQRGZ5wqGDcQ7ZJVTdT5WiwL7nWTokeYzg,1141
|
7
7
|
pygent/openai_compat.py,sha256=cyWFtXt6sDfOlsZd3FuRxbcZMm3WU-DLPBQpbmcuiW8,2617
|
8
8
|
pygent/py.typed,sha256=0Wh72UpGSn4lSGW-u3xMV9kxcBHMdwE15IGUqiJTwqo,52
|
9
9
|
pygent/runtime.py,sha256=xD77DPFUfmbAwsIA5SjlEmj_TimFtN3j-6h53aUVdF0,3821
|
10
|
-
pygent/tools.py,sha256=
|
10
|
+
pygent/tools.py,sha256=H2oice1Dyl-rw6Rq9hgWxpGSlxDD3ylCpxha8-4dkZQ,2568
|
11
11
|
pygent/ui.py,sha256=xqPAvweghPOBBvoD72HzhN6zlXew_3inb8AN7Ck2zpQ,1328
|
12
|
-
pygent-0.1.
|
13
|
-
pygent-0.1.
|
14
|
-
pygent-0.1.
|
15
|
-
pygent-0.1.
|
16
|
-
pygent-0.1.
|
17
|
-
pygent-0.1.
|
12
|
+
pygent-0.1.12.dist-info/licenses/LICENSE,sha256=rIktBU2VR4kHzsWul64cbom2zHIgGqYmABoZwSur6T8,1071
|
13
|
+
pygent-0.1.12.dist-info/METADATA,sha256=896zkz6xObtzwzVoM77ts8BYMgx-lNQzuUCe8YkBv74,878
|
14
|
+
pygent-0.1.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
pygent-0.1.12.dist-info/entry_points.txt,sha256=b9j216E5UpuMrQWRZrwyEmacNEAYvw1tCKkZqdIVIOc,70
|
16
|
+
pygent-0.1.12.dist-info/top_level.txt,sha256=P26IYsb-ThK5IkGP_bRuGJQ0Q_Y8JCcbYqVpvULdxDw,7
|
17
|
+
pygent-0.1.12.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|