agentic-python-coder 3.4.1__tar.gz → 3.4.3__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.
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/PKG-INFO +24 -18
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/README.md +18 -14
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/agent.py +51 -5
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/cli.py +6 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/kernel.py +7 -3
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/llm.py +1 -1
- agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/gemini4argon.json +6 -0
- agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/gpt6astra.json +5 -0
- agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/gpt6sol.json +5 -0
- agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/grok47.json +5 -0
- agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/opus55.json +5 -0
- agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/sonnet55.json +5 -0
- agentic_python_coder-3.4.3/coder/tests/test_kernel.py +212 -0
- agentic_python_coder-3.4.3/coder/tests/test_library_api.py +384 -0
- agentic_python_coder-3.4.3/coder/tests/test_llm_config.py +90 -0
- agentic_python_coder-3.4.3/coder/tests/test_mcp_hardening.py +275 -0
- agentic_python_coder-3.4.3/coder/tests/test_mcp_server.py +429 -0
- agentic_python_coder-3.4.3/coder/tests/test_react_loop.py +306 -0
- agentic_python_coder-3.4.3/coder/tests/test_todo_flag_integration.py +131 -0
- agentic_python_coder-3.4.3/coder/tests/test_todo_tool_availability.py +58 -0
- agentic_python_coder-3.4.3/coder/tests/test_tool_descriptions.py +74 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/pyproject.toml +5 -3
- agentic_python_coder-3.4.1/coder/src/agentic_python_coder/models/gemini3pro.json +0 -7
- agentic_python_coder-3.4.1/coder/src/agentic_python_coder/models/grok41.json +0 -6
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/.gitignore +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/LICENSE +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/.gitignore +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/prompts/system.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/prompts/system_todo.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/__init__.py +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/__init__.py +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/README.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/clingo.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/bird_reasoning.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/diagnosis.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/simple_coloring.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/stable_marriage.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/sudoku_mini.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/cpmpy/README.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/cpmpy/cpmpy.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/cpmpy/sample_tasks/magic_square.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/cpmpy/sample_tasks/n_queens.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/regex/README.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/regex/regex.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/regex/sample_tasks/email_extraction.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/regex/sample_tasks/phone_validation.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/regex/sample_tasks/url_parsing.md +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/mcp_server.py +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/deepseek31.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gemini25.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gemini31.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gemini3flash.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gpt52.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gpt56sol.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gpt56terra.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/opus45.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/opus48.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/qwen3.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/sonnet45.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/sonnet46.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/sonnet5.json +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/project_md.py +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/runner.py +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/tools.py +0 -0
- {agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/examples/clingo/clingo.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: agentic-python-coder
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.3
|
|
4
4
|
Summary: A lightweight Python coding agent that writes, executes, and iterates on code through natural language instructions
|
|
5
5
|
Author: Stefan Szeider
|
|
6
6
|
License: Apache-2.0
|
|
@@ -11,12 +11,14 @@ Classifier: Intended Audience :: Developers
|
|
|
11
11
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
14
16
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
17
|
Classifier: Topic :: Software Development :: Code Generators
|
|
16
|
-
Requires-Python:
|
|
18
|
+
Requires-Python: >=3.13
|
|
17
19
|
Requires-Dist: ipykernel>=7.3.0
|
|
18
20
|
Requires-Dist: jupyter-client>=8.9.1
|
|
19
|
-
Requires-Dist: mcp
|
|
21
|
+
Requires-Dist: mcp<2,>=1.28.1
|
|
20
22
|
Requires-Dist: openai>=2.45.0
|
|
21
23
|
Requires-Dist: python-dotenv>=1.2.2
|
|
22
24
|
Requires-Dist: pyyaml>=6.0.3
|
|
@@ -33,7 +35,7 @@ Description-Content-Type: text/markdown
|
|
|
33
35
|
|
|
34
36
|
# Agentic Python Coder
|
|
35
37
|
|
|
36
|
-
[](https://www.python.org/downloads/)
|
|
38
|
+
[](https://www.python.org/downloads/)
|
|
37
39
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
38
40
|
[](https://modelcontextprotocol.io/)
|
|
39
41
|
[](https://github.com/astral-sh/uv)
|
|
@@ -51,14 +53,14 @@ For details on architecture and constraint modelling applications, see [[Szeider
|
|
|
51
53
|
|
|
52
54
|
### Prerequisites
|
|
53
55
|
|
|
54
|
-
- Python 3.13
|
|
56
|
+
- Python 3.13 or newer
|
|
55
57
|
- UV package manager: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
|
56
58
|
|
|
57
59
|
### For the Coding Agent
|
|
58
60
|
|
|
59
61
|
```bash
|
|
60
|
-
# Install as CLI tool
|
|
61
|
-
uv tool install agentic-python-coder
|
|
62
|
+
# Install as CLI tool (--python 3.14: ortools, used by cpmpy, has no 3.15 wheels yet)
|
|
63
|
+
uv tool install --python 3.14 agentic-python-coder
|
|
62
64
|
|
|
63
65
|
# Set up OpenRouter API key
|
|
64
66
|
mkdir -p ~/.config/coder
|
|
@@ -97,7 +99,7 @@ Add to your Claude Desktop MCP configuration:
|
|
|
97
99
|
"mcpServers": {
|
|
98
100
|
"ipython": {
|
|
99
101
|
"command": "uvx",
|
|
100
|
-
"args": ["--from", "agentic-python-coder", "ipython_mcp"]
|
|
102
|
+
"args": ["--python", "3.14", "--from", "agentic-python-coder", "ipython_mcp"]
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
}
|
|
@@ -132,7 +134,7 @@ coder -i
|
|
|
132
134
|
| `--version`, `-V` | Show version and exit |
|
|
133
135
|
| `--init [TEMPLATE]` | Initialize example templates (cpmpy, clingo, regex, or all) |
|
|
134
136
|
| `--task`, `-t FILE` | Load task from markdown file |
|
|
135
|
-
| `--model MODEL` | Model name or JSON file (default:
|
|
137
|
+
| `--model MODEL` | Model name or JSON file (default: sonnet55) |
|
|
136
138
|
| `--project`, `-p FILE` | Project template for domain-specific prompts |
|
|
137
139
|
| `--with PACKAGE` | Add packages dynamically (repeatable) |
|
|
138
140
|
| `--dir`, `-d DIR` | Working directory |
|
|
@@ -146,21 +148,25 @@ coder -i
|
|
|
146
148
|
|
|
147
149
|
```bash
|
|
148
150
|
# Built-in models (versioned names)
|
|
149
|
-
coder --model sonnet45 "task" # Claude Sonnet 4.5
|
|
151
|
+
coder --model sonnet45 "task" # Claude Sonnet 4.5
|
|
150
152
|
coder --model sonnet46 "task" # Claude Sonnet 4.6
|
|
151
153
|
coder --model sonnet5 "task" # Claude Sonnet 5
|
|
152
154
|
coder --model opus45 "task" # Claude Opus 4.5
|
|
153
155
|
coder --model opus48 "task" # Claude Opus 4.8
|
|
156
|
+
coder --model sonnet55 "task" # Claude Sonnet 5.5 (default)
|
|
157
|
+
coder --model opus55 "task" # Claude Opus 5.5
|
|
154
158
|
coder --model deepseek31 "task" # DeepSeek v3.1
|
|
155
|
-
coder --model
|
|
159
|
+
coder --model grok47 "task" # X.AI Grok 4.7
|
|
156
160
|
coder --model qwen3 "task" # Qwen3 Coder
|
|
157
161
|
coder --model gemini25 "task" # Gemini Pro 2.5
|
|
158
|
-
coder --model gemini3pro "task" # Gemini 3 Pro Preview
|
|
159
162
|
coder --model gemini31 "task" # Gemini 3.1 Pro Preview
|
|
160
163
|
coder --model gemini3flash "task" # Gemini 3 Flash Preview (fast, low-cost)
|
|
161
164
|
coder --model gpt52 "task" # GPT-5.2
|
|
162
165
|
coder --model gpt56sol "task" # GPT-5.6 Sol
|
|
163
166
|
coder --model gpt56terra "task" # GPT-5.6 Terra
|
|
167
|
+
coder --model gpt6sol "task" # GPT-6 Sol
|
|
168
|
+
coder --model gpt6astra "task" # GPT-6 Astra
|
|
169
|
+
coder --model gemini4argon "task" # Gemini 4 Argon
|
|
164
170
|
|
|
165
171
|
# Custom model (JSON file)
|
|
166
172
|
coder --model ./mymodel.json "task"
|
|
@@ -217,7 +223,7 @@ import agentic_python_coder as coder
|
|
|
217
223
|
messages, stats, log_path = coder.solve_task(
|
|
218
224
|
"Write a fibonacci function",
|
|
219
225
|
working_directory="/tmp/workspace",
|
|
220
|
-
model="
|
|
226
|
+
model="sonnet55",
|
|
221
227
|
quiet=True,
|
|
222
228
|
)
|
|
223
229
|
|
|
@@ -236,7 +242,7 @@ from agentic_python_coder import solve_task
|
|
|
236
242
|
messages, stats, log_path = solve_task(
|
|
237
243
|
task="Your task description",
|
|
238
244
|
working_directory=".", # Where to run and save files
|
|
239
|
-
model=None, # Model name: "
|
|
245
|
+
model=None, # Model name: "sonnet55", "opus55", or JSON file
|
|
240
246
|
system_prompt=None, # Custom system prompt (string)
|
|
241
247
|
system_prompt_path=None, # Path to system prompt file
|
|
242
248
|
project_prompt=None, # Domain-specific context
|
|
@@ -300,9 +306,9 @@ agent = create_coding_agent(
|
|
|
300
306
|
```python
|
|
301
307
|
from agentic_python_coder import get_openrouter_llm, list_available_models
|
|
302
308
|
|
|
303
|
-
llm = get_openrouter_llm(model="
|
|
309
|
+
llm = get_openrouter_llm(model="sonnet55")
|
|
304
310
|
print(list_available_models())
|
|
305
|
-
# ['deepseek31', 'gemini25', '
|
|
311
|
+
# ['deepseek31', 'gemini25', 'gemini31', 'gemini3flash', 'gpt52', ...]
|
|
306
312
|
```
|
|
307
313
|
|
|
308
314
|
---
|
|
@@ -320,7 +326,7 @@ Add to your MCP settings (e.g., `~/.claude/claude_desktop_config.json` or projec
|
|
|
320
326
|
"mcpServers": {
|
|
321
327
|
"ipython": {
|
|
322
328
|
"command": "uvx",
|
|
323
|
-
"args": ["--from", "agentic-python-coder", "ipython_mcp"]
|
|
329
|
+
"args": ["--python", "3.14", "--from", "agentic-python-coder", "ipython_mcp"]
|
|
324
330
|
}
|
|
325
331
|
}
|
|
326
332
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agentic Python Coder
|
|
2
2
|
|
|
3
|
-
[](https://www.python.org/downloads/)
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
4
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
5
5
|
[](https://modelcontextprotocol.io/)
|
|
6
6
|
[](https://github.com/astral-sh/uv)
|
|
@@ -18,14 +18,14 @@ For details on architecture and constraint modelling applications, see [[Szeider
|
|
|
18
18
|
|
|
19
19
|
### Prerequisites
|
|
20
20
|
|
|
21
|
-
- Python 3.13
|
|
21
|
+
- Python 3.13 or newer
|
|
22
22
|
- UV package manager: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
|
23
23
|
|
|
24
24
|
### For the Coding Agent
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
# Install as CLI tool
|
|
28
|
-
uv tool install agentic-python-coder
|
|
27
|
+
# Install as CLI tool (--python 3.14: ortools, used by cpmpy, has no 3.15 wheels yet)
|
|
28
|
+
uv tool install --python 3.14 agentic-python-coder
|
|
29
29
|
|
|
30
30
|
# Set up OpenRouter API key
|
|
31
31
|
mkdir -p ~/.config/coder
|
|
@@ -64,7 +64,7 @@ Add to your Claude Desktop MCP configuration:
|
|
|
64
64
|
"mcpServers": {
|
|
65
65
|
"ipython": {
|
|
66
66
|
"command": "uvx",
|
|
67
|
-
"args": ["--from", "agentic-python-coder", "ipython_mcp"]
|
|
67
|
+
"args": ["--python", "3.14", "--from", "agentic-python-coder", "ipython_mcp"]
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -99,7 +99,7 @@ coder -i
|
|
|
99
99
|
| `--version`, `-V` | Show version and exit |
|
|
100
100
|
| `--init [TEMPLATE]` | Initialize example templates (cpmpy, clingo, regex, or all) |
|
|
101
101
|
| `--task`, `-t FILE` | Load task from markdown file |
|
|
102
|
-
| `--model MODEL` | Model name or JSON file (default:
|
|
102
|
+
| `--model MODEL` | Model name or JSON file (default: sonnet55) |
|
|
103
103
|
| `--project`, `-p FILE` | Project template for domain-specific prompts |
|
|
104
104
|
| `--with PACKAGE` | Add packages dynamically (repeatable) |
|
|
105
105
|
| `--dir`, `-d DIR` | Working directory |
|
|
@@ -113,21 +113,25 @@ coder -i
|
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
115
|
# Built-in models (versioned names)
|
|
116
|
-
coder --model sonnet45 "task" # Claude Sonnet 4.5
|
|
116
|
+
coder --model sonnet45 "task" # Claude Sonnet 4.5
|
|
117
117
|
coder --model sonnet46 "task" # Claude Sonnet 4.6
|
|
118
118
|
coder --model sonnet5 "task" # Claude Sonnet 5
|
|
119
119
|
coder --model opus45 "task" # Claude Opus 4.5
|
|
120
120
|
coder --model opus48 "task" # Claude Opus 4.8
|
|
121
|
+
coder --model sonnet55 "task" # Claude Sonnet 5.5 (default)
|
|
122
|
+
coder --model opus55 "task" # Claude Opus 5.5
|
|
121
123
|
coder --model deepseek31 "task" # DeepSeek v3.1
|
|
122
|
-
coder --model
|
|
124
|
+
coder --model grok47 "task" # X.AI Grok 4.7
|
|
123
125
|
coder --model qwen3 "task" # Qwen3 Coder
|
|
124
126
|
coder --model gemini25 "task" # Gemini Pro 2.5
|
|
125
|
-
coder --model gemini3pro "task" # Gemini 3 Pro Preview
|
|
126
127
|
coder --model gemini31 "task" # Gemini 3.1 Pro Preview
|
|
127
128
|
coder --model gemini3flash "task" # Gemini 3 Flash Preview (fast, low-cost)
|
|
128
129
|
coder --model gpt52 "task" # GPT-5.2
|
|
129
130
|
coder --model gpt56sol "task" # GPT-5.6 Sol
|
|
130
131
|
coder --model gpt56terra "task" # GPT-5.6 Terra
|
|
132
|
+
coder --model gpt6sol "task" # GPT-6 Sol
|
|
133
|
+
coder --model gpt6astra "task" # GPT-6 Astra
|
|
134
|
+
coder --model gemini4argon "task" # Gemini 4 Argon
|
|
131
135
|
|
|
132
136
|
# Custom model (JSON file)
|
|
133
137
|
coder --model ./mymodel.json "task"
|
|
@@ -184,7 +188,7 @@ import agentic_python_coder as coder
|
|
|
184
188
|
messages, stats, log_path = coder.solve_task(
|
|
185
189
|
"Write a fibonacci function",
|
|
186
190
|
working_directory="/tmp/workspace",
|
|
187
|
-
model="
|
|
191
|
+
model="sonnet55",
|
|
188
192
|
quiet=True,
|
|
189
193
|
)
|
|
190
194
|
|
|
@@ -203,7 +207,7 @@ from agentic_python_coder import solve_task
|
|
|
203
207
|
messages, stats, log_path = solve_task(
|
|
204
208
|
task="Your task description",
|
|
205
209
|
working_directory=".", # Where to run and save files
|
|
206
|
-
model=None, # Model name: "
|
|
210
|
+
model=None, # Model name: "sonnet55", "opus55", or JSON file
|
|
207
211
|
system_prompt=None, # Custom system prompt (string)
|
|
208
212
|
system_prompt_path=None, # Path to system prompt file
|
|
209
213
|
project_prompt=None, # Domain-specific context
|
|
@@ -267,9 +271,9 @@ agent = create_coding_agent(
|
|
|
267
271
|
```python
|
|
268
272
|
from agentic_python_coder import get_openrouter_llm, list_available_models
|
|
269
273
|
|
|
270
|
-
llm = get_openrouter_llm(model="
|
|
274
|
+
llm = get_openrouter_llm(model="sonnet55")
|
|
271
275
|
print(list_available_models())
|
|
272
|
-
# ['deepseek31', 'gemini25', '
|
|
276
|
+
# ['deepseek31', 'gemini25', 'gemini31', 'gemini3flash', 'gpt52', ...]
|
|
273
277
|
```
|
|
274
278
|
|
|
275
279
|
---
|
|
@@ -287,7 +291,7 @@ Add to your MCP settings (e.g., `~/.claude/claude_desktop_config.json` or projec
|
|
|
287
291
|
"mcpServers": {
|
|
288
292
|
"ipython": {
|
|
289
293
|
"command": "uvx",
|
|
290
|
-
"args": ["--from", "agentic-python-coder", "ipython_mcp"]
|
|
294
|
+
"args": ["--python", "3.14", "--from", "agentic-python-coder", "ipython_mcp"]
|
|
291
295
|
}
|
|
292
296
|
}
|
|
293
297
|
}
|
{agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/agent.py
RENAMED
|
@@ -19,6 +19,55 @@ from agentic_python_coder.tools import (
|
|
|
19
19
|
# Default maximum number of steps the agent can take before stopping
|
|
20
20
|
DEFAULT_STEP_LIMIT = 200
|
|
21
21
|
|
|
22
|
+
# Retries for API error responses the OpenAI SDK parses without raising:
|
|
23
|
+
# OpenRouter intermittently returns HTTP 200 whose body is an error object
|
|
24
|
+
# ({"choices": null, "error": {...}}), or a choice with finish_reason "error"
|
|
25
|
+
API_ERROR_MAX_RETRIES = 3
|
|
26
|
+
API_ERROR_BACKOFF_SECONDS = 5
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _response_error(response) -> Optional[str]:
|
|
30
|
+
"""Return a description if the response is an error envelope, else None."""
|
|
31
|
+
if not response.choices:
|
|
32
|
+
error = (getattr(response, "model_extra", None) or {}).get("error")
|
|
33
|
+
if error:
|
|
34
|
+
return f"no choices, error: {error}"
|
|
35
|
+
dump = getattr(response, "model_dump_json", None)
|
|
36
|
+
body = dump() if callable(dump) else repr(response)
|
|
37
|
+
return f"no choices, body: {body[:500]}"
|
|
38
|
+
choice = response.choices[0]
|
|
39
|
+
if choice.finish_reason == "error":
|
|
40
|
+
error = (getattr(choice, "model_extra", None) or {}).get("error") or (
|
|
41
|
+
getattr(response, "model_extra", None) or {}
|
|
42
|
+
).get("error")
|
|
43
|
+
return f"finish_reason 'error', error: {error}"
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _create_with_retry(llm_config: LLMConfig, request_kwargs: dict, quiet: bool):
|
|
48
|
+
"""Call the completions API, retrying error responses.
|
|
49
|
+
|
|
50
|
+
Raises RuntimeError when retries are exhausted, so callers see a failure
|
|
51
|
+
instead of a silent early stop reported as success.
|
|
52
|
+
"""
|
|
53
|
+
detail = ""
|
|
54
|
+
for attempt in range(API_ERROR_MAX_RETRIES + 1):
|
|
55
|
+
response = llm_config.client.chat.completions.create(**request_kwargs)
|
|
56
|
+
detail = _response_error(response)
|
|
57
|
+
if detail is None:
|
|
58
|
+
return response
|
|
59
|
+
if not quiet:
|
|
60
|
+
print(
|
|
61
|
+
f"Warning: API returned an error response "
|
|
62
|
+
f"(attempt {attempt + 1}/{API_ERROR_MAX_RETRIES + 1}); {detail}"
|
|
63
|
+
)
|
|
64
|
+
if attempt < API_ERROR_MAX_RETRIES:
|
|
65
|
+
time.sleep(API_ERROR_BACKOFF_SECONDS * (attempt + 1))
|
|
66
|
+
raise RuntimeError(
|
|
67
|
+
f"API returned error responses on {API_ERROR_MAX_RETRIES + 1} attempts; "
|
|
68
|
+
f"last: {detail}"
|
|
69
|
+
)
|
|
70
|
+
|
|
22
71
|
|
|
23
72
|
def load_prompt(prompt_path: Path) -> str:
|
|
24
73
|
"""Load a prompt from file."""
|
|
@@ -246,8 +295,8 @@ def run_agent(
|
|
|
246
295
|
request_kwargs["tools"] = openai_tools
|
|
247
296
|
request_kwargs["tool_choice"] = "auto"
|
|
248
297
|
|
|
249
|
-
# Call API
|
|
250
|
-
response = agent.llm_config
|
|
298
|
+
# Call API (retries transient error responses, raises if they persist)
|
|
299
|
+
response = _create_with_retry(agent.llm_config, request_kwargs, quiet)
|
|
251
300
|
|
|
252
301
|
# Track tokens (some providers return usage objects with None fields)
|
|
253
302
|
if response.usage:
|
|
@@ -261,9 +310,6 @@ def run_agent(
|
|
|
261
310
|
response.usage.total_tokens or 0
|
|
262
311
|
)
|
|
263
312
|
|
|
264
|
-
if not response.choices:
|
|
265
|
-
break
|
|
266
|
-
|
|
267
313
|
assistant_message = response.choices[0].message
|
|
268
314
|
|
|
269
315
|
# Check for tool calls
|
{agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/cli.py
RENAMED
|
@@ -8,6 +8,8 @@ from pathlib import Path
|
|
|
8
8
|
from typing import Optional, Dict, Any
|
|
9
9
|
from importlib import resources
|
|
10
10
|
|
|
11
|
+
from openai import AuthenticationError
|
|
12
|
+
|
|
11
13
|
from agentic_python_coder.runner import (
|
|
12
14
|
solve_task,
|
|
13
15
|
get_system_prompt_path,
|
|
@@ -485,6 +487,10 @@ def main():
|
|
|
485
487
|
display_statistics(stats)
|
|
486
488
|
print(f"Log saved to: {log_path}")
|
|
487
489
|
|
|
490
|
+
except (ValueError, AuthenticationError) as e:
|
|
491
|
+
# Configuration errors (missing or invalid API key): no traceback
|
|
492
|
+
print(f"Error: {e}")
|
|
493
|
+
sys.exit(1)
|
|
488
494
|
except Exception as e:
|
|
489
495
|
print(f"Error: {e}")
|
|
490
496
|
import traceback
|
{agentic_python_coder-3.4.1 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/kernel.py
RENAMED
|
@@ -11,6 +11,7 @@ import os
|
|
|
11
11
|
import re
|
|
12
12
|
import shutil
|
|
13
13
|
import signal
|
|
14
|
+
import sys
|
|
14
15
|
import threading
|
|
15
16
|
import time
|
|
16
17
|
import uuid
|
|
@@ -101,6 +102,10 @@ class UVKernelManager(KernelManager):
|
|
|
101
102
|
# Don't load project dependencies (create clean environment)
|
|
102
103
|
uv_prefix.append("--no-project")
|
|
103
104
|
|
|
105
|
+
# Match the host's Python; uv's default may be a newer version for
|
|
106
|
+
# which packages (e.g. ortools) have no wheels yet
|
|
107
|
+
uv_prefix.extend(["--python", f"{sys.version_info[0]}.{sys.version_info[1]}"])
|
|
108
|
+
|
|
104
109
|
# Add user-specified packages
|
|
105
110
|
for pkg in self.with_packages:
|
|
106
111
|
uv_prefix.extend(["--with", pkg])
|
|
@@ -175,9 +180,8 @@ class PythonKernel:
|
|
|
175
180
|
|
|
176
181
|
# Wait for kernel to be ready
|
|
177
182
|
logger.debug("Waiting for kernel to be ready...")
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
) # Increased timeout for UV package installation
|
|
183
|
+
# Generous timeout: a cold uv cache or a source build can take minutes
|
|
184
|
+
self.kc.wait_for_ready(timeout=180)
|
|
181
185
|
logger.info("Kernel started successfully")
|
|
182
186
|
|
|
183
187
|
# Configure IPython and inject startup code
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Tests for the IPython kernel module."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from agentic_python_coder.kernel import (
|
|
5
|
+
PythonKernel,
|
|
6
|
+
get_kernel,
|
|
7
|
+
shutdown_kernel,
|
|
8
|
+
format_output,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def kernel():
|
|
14
|
+
"""Create a fresh kernel for each test."""
|
|
15
|
+
# Shutdown any existing global kernel
|
|
16
|
+
shutdown_kernel()
|
|
17
|
+
# Create a new kernel
|
|
18
|
+
k = PythonKernel()
|
|
19
|
+
yield k
|
|
20
|
+
# Cleanup
|
|
21
|
+
k.shutdown()
|
|
22
|
+
shutdown_kernel()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestKernelBasics:
|
|
26
|
+
"""Test basic kernel execution."""
|
|
27
|
+
|
|
28
|
+
def test_simple_expression(self, kernel):
|
|
29
|
+
"""Basic expression returns result."""
|
|
30
|
+
output = kernel.execute("2 + 2")
|
|
31
|
+
assert output["error"] is None
|
|
32
|
+
assert output["result"] == "4"
|
|
33
|
+
|
|
34
|
+
def test_print_output(self, kernel):
|
|
35
|
+
"""Print statements captured in stdout."""
|
|
36
|
+
output = kernel.execute("print('hello world')")
|
|
37
|
+
assert output["error"] is None
|
|
38
|
+
assert "hello world" in output["stdout"]
|
|
39
|
+
|
|
40
|
+
def test_multiline_code(self, kernel):
|
|
41
|
+
"""Multiline code executes correctly."""
|
|
42
|
+
code = """
|
|
43
|
+
x = 10
|
|
44
|
+
y = 20
|
|
45
|
+
x + y
|
|
46
|
+
"""
|
|
47
|
+
output = kernel.execute(code)
|
|
48
|
+
assert output["error"] is None
|
|
49
|
+
assert output["result"] == "30"
|
|
50
|
+
|
|
51
|
+
def test_syntax_error(self, kernel):
|
|
52
|
+
"""Syntax errors are captured."""
|
|
53
|
+
output = kernel.execute("def (")
|
|
54
|
+
assert output["error"] is not None
|
|
55
|
+
assert "SyntaxError" in output["error"]
|
|
56
|
+
|
|
57
|
+
def test_runtime_error(self, kernel):
|
|
58
|
+
"""Runtime errors are captured."""
|
|
59
|
+
output = kernel.execute("1 / 0")
|
|
60
|
+
assert output["error"] is not None
|
|
61
|
+
assert "ZeroDivisionError" in output["error"]
|
|
62
|
+
|
|
63
|
+
def test_stderr_capture(self, kernel):
|
|
64
|
+
"""Stderr is captured."""
|
|
65
|
+
output = kernel.execute("import sys; print('error', file=sys.stderr)")
|
|
66
|
+
assert "error" in output["stderr"]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TestKernelStatePersistence:
|
|
70
|
+
"""Test that state persists across executions."""
|
|
71
|
+
|
|
72
|
+
def test_variable_persistence(self, kernel):
|
|
73
|
+
"""Variables persist between calls."""
|
|
74
|
+
kernel.execute("x = 42")
|
|
75
|
+
output = kernel.execute("x * 2")
|
|
76
|
+
assert output["result"] == "84"
|
|
77
|
+
|
|
78
|
+
def test_function_persistence(self, kernel):
|
|
79
|
+
"""Functions persist between calls."""
|
|
80
|
+
kernel.execute("def double(n): return n * 2")
|
|
81
|
+
output = kernel.execute("double(21)")
|
|
82
|
+
assert output["result"] == "42"
|
|
83
|
+
|
|
84
|
+
def test_import_persistence(self, kernel):
|
|
85
|
+
"""Imports persist between calls."""
|
|
86
|
+
kernel.execute("import math")
|
|
87
|
+
output = kernel.execute("math.pi")
|
|
88
|
+
assert "3.14" in output["result"]
|
|
89
|
+
|
|
90
|
+
def test_class_persistence(self, kernel):
|
|
91
|
+
"""Classes persist between calls."""
|
|
92
|
+
kernel.execute("class Counter:\n def __init__(self):\n self.n = 0")
|
|
93
|
+
kernel.execute("c = Counter()")
|
|
94
|
+
kernel.execute("c.n = 5")
|
|
95
|
+
output = kernel.execute("c.n")
|
|
96
|
+
assert output["result"] == "5"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class TestMessageLoopFix:
|
|
100
|
+
"""Test that message loop waits for idle status."""
|
|
101
|
+
|
|
102
|
+
def test_slow_print_captured(self, kernel):
|
|
103
|
+
"""Slow output is fully captured (waits for idle, not Empty)."""
|
|
104
|
+
# This used to break early on Empty before the fix
|
|
105
|
+
code = """
|
|
106
|
+
import time
|
|
107
|
+
for i in range(5):
|
|
108
|
+
print(f"line {i}")
|
|
109
|
+
time.sleep(0.1)
|
|
110
|
+
print("done")
|
|
111
|
+
"""
|
|
112
|
+
output = kernel.execute(code)
|
|
113
|
+
assert output["error"] is None
|
|
114
|
+
assert "line 0" in output["stdout"]
|
|
115
|
+
assert "line 4" in output["stdout"]
|
|
116
|
+
assert "done" in output["stdout"]
|
|
117
|
+
|
|
118
|
+
def test_delayed_result(self, kernel):
|
|
119
|
+
"""Result from delayed computation is captured."""
|
|
120
|
+
code = """
|
|
121
|
+
import time
|
|
122
|
+
time.sleep(0.5)
|
|
123
|
+
42 * 2
|
|
124
|
+
"""
|
|
125
|
+
output = kernel.execute(code)
|
|
126
|
+
assert output["error"] is None
|
|
127
|
+
assert output["result"] == "84"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestFormatOutput:
|
|
131
|
+
"""Test output formatting."""
|
|
132
|
+
|
|
133
|
+
def test_success_format(self):
|
|
134
|
+
"""Successful output is formatted correctly."""
|
|
135
|
+
output = {"stdout": "hello", "stderr": "", "result": "42", "error": None}
|
|
136
|
+
result = format_output(output)
|
|
137
|
+
assert '"success": true' in result
|
|
138
|
+
assert '"stdout": "hello"' in result
|
|
139
|
+
assert '"result": "42"' in result
|
|
140
|
+
|
|
141
|
+
def test_error_format(self):
|
|
142
|
+
"""Error output is formatted correctly."""
|
|
143
|
+
output = {
|
|
144
|
+
"stdout": "",
|
|
145
|
+
"stderr": "",
|
|
146
|
+
"result": None,
|
|
147
|
+
"error": "ValueError: bad",
|
|
148
|
+
}
|
|
149
|
+
result = format_output(output)
|
|
150
|
+
assert '"success": false' in result
|
|
151
|
+
assert "ValueError" in result
|
|
152
|
+
|
|
153
|
+
def test_none_values_removed(self):
|
|
154
|
+
"""None values are removed from output."""
|
|
155
|
+
output = {"stdout": "", "stderr": None, "result": None, "error": None}
|
|
156
|
+
result = format_output(output)
|
|
157
|
+
assert "stderr" not in result
|
|
158
|
+
assert "result" not in result
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class TestGlobalKernel:
|
|
162
|
+
"""Test global kernel management."""
|
|
163
|
+
|
|
164
|
+
def test_get_kernel_creates_kernel(self):
|
|
165
|
+
"""get_kernel creates a new kernel if none exists."""
|
|
166
|
+
shutdown_kernel() # Ensure clean state
|
|
167
|
+
k = get_kernel()
|
|
168
|
+
assert k is not None
|
|
169
|
+
output = k.execute("1 + 1")
|
|
170
|
+
assert output["result"] == "2"
|
|
171
|
+
shutdown_kernel()
|
|
172
|
+
|
|
173
|
+
def test_get_kernel_reuses_kernel(self):
|
|
174
|
+
"""get_kernel reuses existing kernel with same config."""
|
|
175
|
+
shutdown_kernel()
|
|
176
|
+
k1 = get_kernel()
|
|
177
|
+
k1.execute("x = 123")
|
|
178
|
+
k2 = get_kernel()
|
|
179
|
+
output = k2.execute("x")
|
|
180
|
+
assert output["result"] == "123"
|
|
181
|
+
shutdown_kernel()
|
|
182
|
+
|
|
183
|
+
def test_shutdown_clears_state(self):
|
|
184
|
+
"""shutdown_kernel clears the global kernel."""
|
|
185
|
+
shutdown_kernel()
|
|
186
|
+
k = get_kernel()
|
|
187
|
+
k.execute("y = 456")
|
|
188
|
+
shutdown_kernel()
|
|
189
|
+
k2 = get_kernel()
|
|
190
|
+
output = k2.execute("y")
|
|
191
|
+
assert output["error"] is not None
|
|
192
|
+
assert "NameError" in output["error"]
|
|
193
|
+
shutdown_kernel()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class TestDeadlineTimeout:
|
|
197
|
+
"""Deadline overrun must report a timeout error, not silent success."""
|
|
198
|
+
|
|
199
|
+
def test_timeout_reports_error_and_interrupts(self, kernel):
|
|
200
|
+
output = kernel.execute("import time; time.sleep(30)", deadline_timeout=2)
|
|
201
|
+
assert output["error"] is not None
|
|
202
|
+
assert "timed out" in output["error"]
|
|
203
|
+
|
|
204
|
+
# The interrupt (plus grace period) must leave the kernel usable
|
|
205
|
+
output2 = kernel.execute("21 * 2")
|
|
206
|
+
assert output2["error"] is None
|
|
207
|
+
assert output2["result"] == "42"
|
|
208
|
+
|
|
209
|
+
def test_fast_code_unaffected_by_deadline(self, kernel):
|
|
210
|
+
output = kernel.execute("1 + 1", deadline_timeout=30)
|
|
211
|
+
assert output["error"] is None
|
|
212
|
+
assert output["result"] == "2"
|