agentic-python-coder 3.2.0__tar.gz → 3.4.0__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.2.0 → agentic_python_coder-3.4.0}/.gitignore +0 -4
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/PKG-INFO +54 -12
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/README.md +42 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/__init__.py +6 -1
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/agent.py +52 -14
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/cli.py +19 -4
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/clingo.md +64 -1
- agentic_python_coder-3.4.0/coder/src/agentic_python_coder/examples/cpmpy/cpmpy.md +100 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/kernel.py +42 -1
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/llm.py +19 -5
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/mcp_server.py +79 -7
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/deepseek31.json +0 -1
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gemini25.json +0 -1
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gemini31.json +0 -1
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gemini3pro.json +0 -1
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gpt52.json +0 -1
- agentic_python_coder-3.4.0/coder/src/agentic_python_coder/models/gpt56sol.json +5 -0
- agentic_python_coder-3.4.0/coder/src/agentic_python_coder/models/gpt56terra.json +5 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/grok41.json +0 -1
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/opus45.json +1 -2
- agentic_python_coder-3.4.0/coder/src/agentic_python_coder/models/opus48.json +5 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/qwen3.json +0 -1
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/sonnet45.json +1 -2
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/sonnet46.json +1 -2
- agentic_python_coder-3.4.0/coder/src/agentic_python_coder/models/sonnet5.json +5 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/project_md.py +6 -2
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/runner.py +8 -1
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/tools.py +28 -7
- agentic_python_coder-3.4.0/examples/clingo/clingo.md +5 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/pyproject.toml +16 -12
- agentic_python_coder-3.2.0/MEM/MEM_000.md +0 -114
- agentic_python_coder-3.2.0/MEM/MEM_001_frameworkless_react_agent.md +0 -110
- agentic_python_coder-3.2.0/MEM/MEM_002_tool_description_quality.md +0 -107
- agentic_python_coder-3.2.0/coder/src/agentic_python_coder/examples/cpmpy/cpmpy.md +0 -57
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/LICENSE +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/.gitignore +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/prompts/system.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/prompts/system_todo.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/__init__.py +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/README.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/bird_reasoning.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/diagnosis.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/simple_coloring.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/stable_marriage.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/sudoku_mini.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/cpmpy/README.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/cpmpy/sample_tasks/magic_square.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/cpmpy/sample_tasks/n_queens.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/regex/README.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/regex/regex.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/regex/sample_tasks/email_extraction.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/regex/sample_tasks/phone_validation.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/examples/regex/sample_tasks/url_parsing.md +0 -0
- {agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/models/gemini3flash.json +0 -0
|
@@ -154,7 +154,6 @@ uv.lock
|
|
|
154
154
|
# Project specific
|
|
155
155
|
tests/
|
|
156
156
|
coder-examples/
|
|
157
|
-
CLAUDE-archive.md
|
|
158
157
|
EXPERTISE/
|
|
159
158
|
PROCESS_NOTES.md
|
|
160
159
|
conversation_log.json
|
|
@@ -162,8 +161,6 @@ coder_output.log
|
|
|
162
161
|
task.md
|
|
163
162
|
solution.py
|
|
164
163
|
*.tmp
|
|
165
|
-
CLAUDE.md
|
|
166
|
-
**/CLAUDE.md
|
|
167
164
|
TRASH/
|
|
168
165
|
LOCAL/
|
|
169
166
|
ASP/
|
|
@@ -172,7 +169,6 @@ CPMPY/
|
|
|
172
169
|
PAPER/
|
|
173
170
|
PAPER-ASP/
|
|
174
171
|
ZEBRA/
|
|
175
|
-
.mcp.json
|
|
176
172
|
|
|
177
173
|
# Test files and folders (root level only)
|
|
178
174
|
/test-*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-python-coder
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
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
|
|
@@ -14,21 +14,21 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
15
|
Classifier: Topic :: Software Development :: Code Generators
|
|
16
16
|
Requires-Python: <3.14,>=3.13
|
|
17
|
-
Requires-Dist: ipykernel>=7.
|
|
18
|
-
Requires-Dist: jupyter-client>=8.
|
|
19
|
-
Requires-Dist: mcp>=1.
|
|
20
|
-
Requires-Dist: openai>=2.
|
|
21
|
-
Requires-Dist: python-dotenv>=1.2.
|
|
17
|
+
Requires-Dist: ipykernel>=7.3.0
|
|
18
|
+
Requires-Dist: jupyter-client>=8.9.1
|
|
19
|
+
Requires-Dist: mcp>=1.28.1
|
|
20
|
+
Requires-Dist: openai>=2.45.0
|
|
21
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
22
22
|
Requires-Dist: pyyaml>=6.0.3
|
|
23
|
-
Requires-Dist: rich>=
|
|
23
|
+
Requires-Dist: rich>=15.0.0
|
|
24
24
|
Provides-Extra: dev
|
|
25
|
-
Requires-Dist: mypy>=
|
|
26
|
-
Requires-Dist: ruff>=0.
|
|
25
|
+
Requires-Dist: mypy>=2.2.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.15.21; extra == 'dev'
|
|
27
27
|
Provides-Extra: test
|
|
28
|
-
Requires-Dist: pytest-asyncio>=1.
|
|
29
|
-
Requires-Dist: pytest-cov>=7.
|
|
28
|
+
Requires-Dist: pytest-asyncio>=1.4.0; extra == 'test'
|
|
29
|
+
Requires-Dist: pytest-cov>=7.1.0; extra == 'test'
|
|
30
30
|
Requires-Dist: pytest-watch>=4.2.0; extra == 'test'
|
|
31
|
-
Requires-Dist: pytest>=9.
|
|
31
|
+
Requires-Dist: pytest>=9.1.1; extra == 'test'
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
33
33
|
|
|
34
34
|
# Agentic Python Coder
|
|
@@ -147,19 +147,41 @@ coder -i
|
|
|
147
147
|
```bash
|
|
148
148
|
# Built-in models (versioned names)
|
|
149
149
|
coder --model sonnet45 "task" # Claude Sonnet 4.5 (default)
|
|
150
|
+
coder --model sonnet46 "task" # Claude Sonnet 4.6
|
|
151
|
+
coder --model sonnet5 "task" # Claude Sonnet 5
|
|
150
152
|
coder --model opus45 "task" # Claude Opus 4.5
|
|
153
|
+
coder --model opus48 "task" # Claude Opus 4.8
|
|
151
154
|
coder --model deepseek31 "task" # DeepSeek v3.1
|
|
152
155
|
coder --model grok41 "task" # X.AI Grok 4.1
|
|
153
156
|
coder --model qwen3 "task" # Qwen3 Coder
|
|
154
157
|
coder --model gemini25 "task" # Gemini Pro 2.5
|
|
155
158
|
coder --model gemini3pro "task" # Gemini 3 Pro Preview
|
|
159
|
+
coder --model gemini31 "task" # Gemini 3.1 Pro Preview
|
|
156
160
|
coder --model gemini3flash "task" # Gemini 3 Flash Preview (fast, low-cost)
|
|
157
161
|
coder --model gpt52 "task" # GPT-5.2
|
|
162
|
+
coder --model gpt56sol "task" # GPT-5.6 Sol
|
|
163
|
+
coder --model gpt56terra "task" # GPT-5.6 Terra
|
|
158
164
|
|
|
159
165
|
# Custom model (JSON file)
|
|
160
166
|
coder --model ./mymodel.json "task"
|
|
161
167
|
```
|
|
162
168
|
|
|
169
|
+
A custom model JSON can also point at any OpenAI-compatible endpoint
|
|
170
|
+
(OpenAI directly, a local Ollama/vLLM server, etc.) instead of OpenRouter:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"path": "gpt-4o-mini",
|
|
175
|
+
"base_url": "https://api.openai.com/v1",
|
|
176
|
+
"api_key_env": "OPENAI_API_KEY",
|
|
177
|
+
"temperature": 0.0
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`base_url` overrides the OpenRouter endpoint; `api_key_env` names the
|
|
182
|
+
environment variable holding the key for that endpoint (default:
|
|
183
|
+
`OPENROUTER_API_KEY`).
|
|
184
|
+
|
|
163
185
|
### Project Templates
|
|
164
186
|
|
|
165
187
|
Domain-specific templates improve results. Bundled examples are available on GitHub at [`coder/src/agentic_python_coder/examples/`](coder/src/agentic_python_coder/examples/). Use `--init` to copy them locally:
|
|
@@ -225,6 +247,7 @@ messages, stats, log_path = solve_task(
|
|
|
225
247
|
save_log=True, # Save conversation log
|
|
226
248
|
task_basename=None, # Base name for output files
|
|
227
249
|
step_limit=None, # Max agent steps (default: 200)
|
|
250
|
+
llm_config=None, # Pre-built LLMConfig (overrides model/api_key)
|
|
228
251
|
)
|
|
229
252
|
```
|
|
230
253
|
|
|
@@ -253,6 +276,25 @@ messages2, stats2 = run_agent(agent, "Now plot column A", quiet=True)
|
|
|
253
276
|
print(get_final_response(messages2))
|
|
254
277
|
```
|
|
255
278
|
|
|
279
|
+
Instead of a model name, you can pass a pre-built `LLMConfig` wrapping any
|
|
280
|
+
OpenAI-compatible client:
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
from openai import OpenAI
|
|
284
|
+
from agentic_python_coder import LLMConfig, create_coding_agent
|
|
285
|
+
|
|
286
|
+
llm_config = LLMConfig(
|
|
287
|
+
client=OpenAI(api_key="...", base_url="https://api.openai.com/v1"),
|
|
288
|
+
model="gpt-4o-mini",
|
|
289
|
+
api_params={"temperature": 0.0},
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
agent = create_coding_agent(
|
|
293
|
+
working_directory="/tmp/workspace",
|
|
294
|
+
llm_config=llm_config,
|
|
295
|
+
)
|
|
296
|
+
```
|
|
297
|
+
|
|
256
298
|
#### `get_openrouter_llm()` — LLM Access
|
|
257
299
|
|
|
258
300
|
```python
|
|
@@ -114,19 +114,41 @@ coder -i
|
|
|
114
114
|
```bash
|
|
115
115
|
# Built-in models (versioned names)
|
|
116
116
|
coder --model sonnet45 "task" # Claude Sonnet 4.5 (default)
|
|
117
|
+
coder --model sonnet46 "task" # Claude Sonnet 4.6
|
|
118
|
+
coder --model sonnet5 "task" # Claude Sonnet 5
|
|
117
119
|
coder --model opus45 "task" # Claude Opus 4.5
|
|
120
|
+
coder --model opus48 "task" # Claude Opus 4.8
|
|
118
121
|
coder --model deepseek31 "task" # DeepSeek v3.1
|
|
119
122
|
coder --model grok41 "task" # X.AI Grok 4.1
|
|
120
123
|
coder --model qwen3 "task" # Qwen3 Coder
|
|
121
124
|
coder --model gemini25 "task" # Gemini Pro 2.5
|
|
122
125
|
coder --model gemini3pro "task" # Gemini 3 Pro Preview
|
|
126
|
+
coder --model gemini31 "task" # Gemini 3.1 Pro Preview
|
|
123
127
|
coder --model gemini3flash "task" # Gemini 3 Flash Preview (fast, low-cost)
|
|
124
128
|
coder --model gpt52 "task" # GPT-5.2
|
|
129
|
+
coder --model gpt56sol "task" # GPT-5.6 Sol
|
|
130
|
+
coder --model gpt56terra "task" # GPT-5.6 Terra
|
|
125
131
|
|
|
126
132
|
# Custom model (JSON file)
|
|
127
133
|
coder --model ./mymodel.json "task"
|
|
128
134
|
```
|
|
129
135
|
|
|
136
|
+
A custom model JSON can also point at any OpenAI-compatible endpoint
|
|
137
|
+
(OpenAI directly, a local Ollama/vLLM server, etc.) instead of OpenRouter:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"path": "gpt-4o-mini",
|
|
142
|
+
"base_url": "https://api.openai.com/v1",
|
|
143
|
+
"api_key_env": "OPENAI_API_KEY",
|
|
144
|
+
"temperature": 0.0
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`base_url` overrides the OpenRouter endpoint; `api_key_env` names the
|
|
149
|
+
environment variable holding the key for that endpoint (default:
|
|
150
|
+
`OPENROUTER_API_KEY`).
|
|
151
|
+
|
|
130
152
|
### Project Templates
|
|
131
153
|
|
|
132
154
|
Domain-specific templates improve results. Bundled examples are available on GitHub at [`coder/src/agentic_python_coder/examples/`](coder/src/agentic_python_coder/examples/). Use `--init` to copy them locally:
|
|
@@ -192,6 +214,7 @@ messages, stats, log_path = solve_task(
|
|
|
192
214
|
save_log=True, # Save conversation log
|
|
193
215
|
task_basename=None, # Base name for output files
|
|
194
216
|
step_limit=None, # Max agent steps (default: 200)
|
|
217
|
+
llm_config=None, # Pre-built LLMConfig (overrides model/api_key)
|
|
195
218
|
)
|
|
196
219
|
```
|
|
197
220
|
|
|
@@ -220,6 +243,25 @@ messages2, stats2 = run_agent(agent, "Now plot column A", quiet=True)
|
|
|
220
243
|
print(get_final_response(messages2))
|
|
221
244
|
```
|
|
222
245
|
|
|
246
|
+
Instead of a model name, you can pass a pre-built `LLMConfig` wrapping any
|
|
247
|
+
OpenAI-compatible client:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from openai import OpenAI
|
|
251
|
+
from agentic_python_coder import LLMConfig, create_coding_agent
|
|
252
|
+
|
|
253
|
+
llm_config = LLMConfig(
|
|
254
|
+
client=OpenAI(api_key="...", base_url="https://api.openai.com/v1"),
|
|
255
|
+
model="gpt-4o-mini",
|
|
256
|
+
api_params={"temperature": 0.0},
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
agent = create_coding_agent(
|
|
260
|
+
working_directory="/tmp/workspace",
|
|
261
|
+
llm_config=llm_config,
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
223
265
|
#### `get_openrouter_llm()` — LLM Access
|
|
224
266
|
|
|
225
267
|
```python
|
{agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/__init__.py
RENAMED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"""Python Coding Agent - A minimal coding assistant using direct OpenAI API and OpenRouter."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version as _package_version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = _package_version("agentic-python-coder")
|
|
7
|
+
except PackageNotFoundError: # running from source without installation
|
|
8
|
+
__version__ = "0.0.0+unknown"
|
|
4
9
|
|
|
5
10
|
# High-level API (recommended for most users)
|
|
6
11
|
from agentic_python_coder.runner import solve_task
|
{agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/agent.py
RENAMED
|
@@ -50,6 +50,7 @@ def create_coding_agent(
|
|
|
50
50
|
api_key: Optional[str] = None,
|
|
51
51
|
todo: bool = False,
|
|
52
52
|
verbose: bool = False,
|
|
53
|
+
llm_config: Optional[LLMConfig] = None,
|
|
53
54
|
) -> CodingAgent:
|
|
54
55
|
"""Create a ReAct agent for Python coding tasks.
|
|
55
56
|
|
|
@@ -65,6 +66,8 @@ def create_coding_agent(
|
|
|
65
66
|
api_key: Optional API key override
|
|
66
67
|
todo: If True, includes todo_write tool for task tracking
|
|
67
68
|
verbose: If True, print progress info (default False for library use)
|
|
69
|
+
llm_config: Optional pre-built LLMConfig (any OpenAI-compatible
|
|
70
|
+
client); takes precedence over model/api_key
|
|
68
71
|
|
|
69
72
|
Returns:
|
|
70
73
|
Configured CodingAgent
|
|
@@ -77,17 +80,19 @@ def create_coding_agent(
|
|
|
77
80
|
set_task_basename(task_basename)
|
|
78
81
|
|
|
79
82
|
# Store packages for kernel initialization (clear if None to avoid state leaks)
|
|
83
|
+
# JSON-encoded: a comma join would corrupt specs like "pkg[extra1,extra2]"
|
|
80
84
|
if with_packages is not None:
|
|
81
|
-
os.environ["CODER_WITH_PACKAGES"] =
|
|
85
|
+
os.environ["CODER_WITH_PACKAGES"] = json.dumps(with_packages)
|
|
82
86
|
else:
|
|
83
87
|
os.environ.pop("CODER_WITH_PACKAGES", None)
|
|
84
88
|
|
|
85
|
-
# Get LLM config
|
|
86
|
-
llm_config
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
# Get LLM config (a pre-built one takes precedence)
|
|
90
|
+
if llm_config is None:
|
|
91
|
+
llm_config = get_openrouter_llm(
|
|
92
|
+
model=model or DEFAULT_MODEL,
|
|
93
|
+
api_key=api_key,
|
|
94
|
+
verbose=verbose,
|
|
95
|
+
)
|
|
91
96
|
|
|
92
97
|
# Create tool registry
|
|
93
98
|
tools = create_tool_registry(todo=todo)
|
|
@@ -199,8 +204,9 @@ def run_agent(
|
|
|
199
204
|
Returns:
|
|
200
205
|
Tuple of (List of new messages from this call, Statistics dictionary)
|
|
201
206
|
"""
|
|
202
|
-
#
|
|
203
|
-
#
|
|
207
|
+
# The working directory is a process-wide singleton; set it at execution
|
|
208
|
+
# time so it always matches the agent currently running (concurrent
|
|
209
|
+
# run_agent calls in one process are not supported)
|
|
204
210
|
working_dir.set(agent.working_directory)
|
|
205
211
|
|
|
206
212
|
limit = step_limit if step_limit is not None else DEFAULT_STEP_LIMIT
|
|
@@ -228,7 +234,7 @@ def run_agent(
|
|
|
228
234
|
openai_tools = agent.tools.get_openai_tools()
|
|
229
235
|
|
|
230
236
|
# ReAct loop
|
|
231
|
-
for
|
|
237
|
+
for _ in range(limit):
|
|
232
238
|
# Build request kwargs
|
|
233
239
|
request_kwargs: dict[str, Any] = {
|
|
234
240
|
"model": agent.llm_config.model,
|
|
@@ -243,13 +249,17 @@ def run_agent(
|
|
|
243
249
|
# Call API
|
|
244
250
|
response = agent.llm_config.client.chat.completions.create(**request_kwargs)
|
|
245
251
|
|
|
246
|
-
# Track tokens
|
|
252
|
+
# Track tokens (some providers return usage objects with None fields)
|
|
247
253
|
if response.usage:
|
|
248
|
-
stats["token_consumption"]["input_tokens"] +=
|
|
254
|
+
stats["token_consumption"]["input_tokens"] += (
|
|
255
|
+
response.usage.prompt_tokens or 0
|
|
256
|
+
)
|
|
249
257
|
stats["token_consumption"]["output_tokens"] += (
|
|
250
|
-
response.usage.completion_tokens
|
|
258
|
+
response.usage.completion_tokens or 0
|
|
259
|
+
)
|
|
260
|
+
stats["token_consumption"]["total_tokens"] += (
|
|
261
|
+
response.usage.total_tokens or 0
|
|
251
262
|
)
|
|
252
|
-
stats["token_consumption"]["total_tokens"] += response.usage.total_tokens
|
|
253
263
|
|
|
254
264
|
if not response.choices:
|
|
255
265
|
break
|
|
@@ -324,6 +334,29 @@ def run_agent(
|
|
|
324
334
|
}
|
|
325
335
|
)
|
|
326
336
|
|
|
337
|
+
elif response.choices[0].finish_reason == "length":
|
|
338
|
+
# Response cut off at max_tokens with no tool call — don't accept
|
|
339
|
+
# the truncated text as a final answer; ask the model to continue
|
|
340
|
+
agent.messages.append(
|
|
341
|
+
{
|
|
342
|
+
"role": "assistant",
|
|
343
|
+
"content": assistant_message.content or "",
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
agent.messages.append(
|
|
347
|
+
{
|
|
348
|
+
"role": "user",
|
|
349
|
+
"content": (
|
|
350
|
+
"Your previous response was truncated at the max_tokens "
|
|
351
|
+
"limit. Continue, and be more concise."
|
|
352
|
+
),
|
|
353
|
+
}
|
|
354
|
+
)
|
|
355
|
+
if not quiet:
|
|
356
|
+
print(
|
|
357
|
+
"Warning: response truncated at max_tokens; asking model to continue"
|
|
358
|
+
)
|
|
359
|
+
|
|
327
360
|
else:
|
|
328
361
|
# No tool calls — final answer
|
|
329
362
|
agent.messages.append(
|
|
@@ -333,6 +366,11 @@ def run_agent(
|
|
|
333
366
|
}
|
|
334
367
|
)
|
|
335
368
|
break
|
|
369
|
+
else:
|
|
370
|
+
# Loop exhausted without a final answer
|
|
371
|
+
stats["step_limit_reached"] = True
|
|
372
|
+
if not quiet:
|
|
373
|
+
print(f"Warning: step limit ({limit}) reached without a final response")
|
|
336
374
|
|
|
337
375
|
stats["execution_time_seconds"] = time.time() - start_time
|
|
338
376
|
|
{agentic_python_coder-3.2.0 → agentic_python_coder-3.4.0}/coder/src/agentic_python_coder/cli.py
RENAMED
|
@@ -260,6 +260,9 @@ def validate_model(model):
|
|
|
260
260
|
print(f" - {m}")
|
|
261
261
|
print("\nOr provide a path to a custom model JSON file.")
|
|
262
262
|
sys.exit(1)
|
|
263
|
+
except ValueError as e:
|
|
264
|
+
print(f"Error: {e}")
|
|
265
|
+
sys.exit(1)
|
|
263
266
|
|
|
264
267
|
|
|
265
268
|
def load_project_prompt(project_path: str) -> Optional[str]:
|
|
@@ -290,12 +293,19 @@ def load_project_prompt(project_path: str) -> Optional[str]:
|
|
|
290
293
|
|
|
291
294
|
|
|
292
295
|
def run_interactive(
|
|
293
|
-
working_dir: Path,
|
|
296
|
+
working_dir: Path,
|
|
297
|
+
model: str,
|
|
298
|
+
project_prompt: str,
|
|
299
|
+
api_key: str,
|
|
300
|
+
todo: bool,
|
|
301
|
+
with_packages=None,
|
|
294
302
|
):
|
|
295
303
|
"""Run the agent in interactive mode."""
|
|
296
304
|
print(f"\nInteractive mode - working in: {working_dir}")
|
|
297
305
|
if project_prompt:
|
|
298
306
|
print("Project configuration loaded")
|
|
307
|
+
if with_packages:
|
|
308
|
+
print(f"Dynamic packages: {', '.join(with_packages)}")
|
|
299
309
|
print("Type 'exit' or 'quit' to stop.\n")
|
|
300
310
|
|
|
301
311
|
# Load system prompt
|
|
@@ -307,12 +317,12 @@ def run_interactive(
|
|
|
307
317
|
system_prompt=system_prompt,
|
|
308
318
|
model=model,
|
|
309
319
|
project_prompt=project_prompt,
|
|
320
|
+
with_packages=with_packages,
|
|
310
321
|
api_key=api_key,
|
|
311
322
|
todo=todo,
|
|
312
323
|
verbose=True,
|
|
313
324
|
)
|
|
314
325
|
|
|
315
|
-
thread_id = "interactive"
|
|
316
326
|
all_messages = []
|
|
317
327
|
cumulative_stats = {
|
|
318
328
|
"tool_usage": {},
|
|
@@ -333,7 +343,7 @@ def run_interactive(
|
|
|
333
343
|
continue
|
|
334
344
|
|
|
335
345
|
print("\nAgent working...\n")
|
|
336
|
-
messages, stats = run_agent(agent, user_input,
|
|
346
|
+
messages, stats = run_agent(agent, user_input, quiet=False)
|
|
337
347
|
all_messages.extend(messages)
|
|
338
348
|
|
|
339
349
|
# Update cumulative stats
|
|
@@ -440,7 +450,12 @@ def main():
|
|
|
440
450
|
try:
|
|
441
451
|
if args.interactive:
|
|
442
452
|
run_interactive(
|
|
443
|
-
working_dir,
|
|
453
|
+
working_dir,
|
|
454
|
+
args.model,
|
|
455
|
+
project_prompt,
|
|
456
|
+
args.api_key,
|
|
457
|
+
args.todo,
|
|
458
|
+
with_packages=args.with_packages,
|
|
444
459
|
)
|
|
445
460
|
else:
|
|
446
461
|
# Use solve_task for the main flow
|
|
@@ -29,9 +29,10 @@ Before completing your solution, verify:
|
|
|
29
29
|
☐ 2. **Model solves correctly** - At least one answer set is found (or UNSAT handled)
|
|
30
30
|
☐ 3. **Optimization applied** - If problem asks for optimal, use #minimize/#maximize
|
|
31
31
|
☐ 4. **Solution extracted** - Answer set atoms properly extracted and formatted
|
|
32
|
+
☐ 4b. **No Model escapes on_model** - All solution data extracted to plain Python objects inside the callback (touching a Model after solve() returns segfaults)
|
|
32
33
|
☐ 5. **Output format correct** - JSON output with appropriate structure
|
|
33
34
|
☐ 6. **Predicates meaningful** - Clear names that reflect the problem domain
|
|
34
|
-
☐ 7. **Constraints verified** - All problem constraints are encoded
|
|
35
|
+
☐ 7. **Constraints verified independently** - All problem constraints are encoded AND re-checked by a plain-Python verify() whose expectations are re-derived from the problem text, not from your encoding's own interpretation
|
|
35
36
|
☐ 8. **File written** - Create `solution.py` with complete working code
|
|
36
37
|
|
|
37
38
|
## Section 2: Critical Rules of Engagement
|
|
@@ -474,6 +475,38 @@ def extract_solution(model):
|
|
|
474
475
|
return solution
|
|
475
476
|
```
|
|
476
477
|
|
|
478
|
+
### CRITICAL: The Model Object Is Only Valid Inside on_model (Segfault Risk)
|
|
479
|
+
|
|
480
|
+
The `clingo.Model` object passed to your `on_model` callback is only valid DURING
|
|
481
|
+
that callback. After `ctl.solve()` returns, clingo frees the underlying model —
|
|
482
|
+
touching it later is a use-after-free that crashes the Python process with a
|
|
483
|
+
segmentation fault (exit code 139) and NO Python traceback.
|
|
484
|
+
|
|
485
|
+
Extract everything you need into plain Python objects (ints, strings, lists, dicts)
|
|
486
|
+
INSIDE the callback. Never store the Model object itself for later use.
|
|
487
|
+
|
|
488
|
+
```python
|
|
489
|
+
# WRONG - segfaults after solve() returns:
|
|
490
|
+
saved = None
|
|
491
|
+
def on_model(m):
|
|
492
|
+
global saved
|
|
493
|
+
saved = m # Model outlives the callback
|
|
494
|
+
ctl.solve(on_model=on_model)
|
|
495
|
+
atoms = saved.symbols(atoms=True) # use-after-free -> SIGSEGV, no traceback
|
|
496
|
+
|
|
497
|
+
# CORRECT - extract inside the callback:
|
|
498
|
+
result = []
|
|
499
|
+
def on_model(m):
|
|
500
|
+
for a in m.symbols(atoms=True):
|
|
501
|
+
if a.match("assigned", 2):
|
|
502
|
+
result.append((str(a.arguments[0]), str(a.arguments[1])))
|
|
503
|
+
ctl.solve(on_model=on_model)
|
|
504
|
+
# work with the plain tuples in result
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
This also applies to verification: snapshot the atoms into plain Python inside
|
|
508
|
+
`on_model`, then run your checks on the snapshot after solving.
|
|
509
|
+
|
|
477
510
|
## Section 4: Problem-Solving Pattern Library
|
|
478
511
|
|
|
479
512
|
### 4.1 Modeling State and Change (NEW - CRITICAL)
|
|
@@ -964,6 +997,36 @@ possible_col(C) :- col(C).
|
|
|
964
997
|
4. **Let solver determine gaps**: Don't over-constrain non-run cells
|
|
965
998
|
5. **Different is adjacent**: Cells of different colors/states CAN touch
|
|
966
999
|
|
|
1000
|
+
### 4.11 "Reachable Only After" / Ordered Reachability Constraints
|
|
1001
|
+
|
|
1002
|
+
When a problem requires that some target element become reachable only after all
|
|
1003
|
+
other elements (or only after specific ones) under an evolving reachability process,
|
|
1004
|
+
do NOT model it as an existential ordering — guessing a permutation with the target
|
|
1005
|
+
pinned last only proves that SOME order exists, which is weaker than what such
|
|
1006
|
+
problems ask. The intended reading is almost always about the actual reachability
|
|
1007
|
+
process: everything reachable gets reached as soon as it is reachable.
|
|
1008
|
+
|
|
1009
|
+
Model the process directly with a time-indexed fixpoint and constrain first-reach
|
|
1010
|
+
times:
|
|
1011
|
+
|
|
1012
|
+
```asp
|
|
1013
|
+
step(0..max_step).
|
|
1014
|
+
reached(S, 0) :- start(S).
|
|
1015
|
+
reached(Y, T+1) :- edge(X, Y), reached(X, T), step(T+1), unlocked(X, Y, T).
|
|
1016
|
+
reached(X, T+1) :- reached(X, T), step(T+1). % persistence
|
|
1017
|
+
first_reached(X, 0) :- reached(X, 0).
|
|
1018
|
+
first_reached(X, T) :- reached(X, T), not reached(X, T-1), T > 0, step(T).
|
|
1019
|
+
|
|
1020
|
+
% target must not become reachable before (or together with) any other element:
|
|
1021
|
+
:- first_reached(target, Tg), first_reached(X, Tx), X != target, Tx >= Tg.
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
Adapt `unlocked/3` to whatever gates progression (collected items, satisfied
|
|
1025
|
+
prerequisites, ...). Verify with a plain-Python simulation of the same process:
|
|
1026
|
+
repeatedly add everything currently reachable, one step at a time, and check the
|
|
1027
|
+
required ordering of first-reach steps against the problem text — do not reuse
|
|
1028
|
+
your encoding's ordering predicates in the verifier.
|
|
1029
|
+
|
|
967
1030
|
## Section 5: Debugging & Advanced Techniques
|
|
968
1031
|
|
|
969
1032
|
### 5.1 Efficient Generation: Constrain Choices Early (Anti-Pattern: Generate-and-Filter)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# CPMPY project prompt
|
|
2
|
+
|
|
3
|
+
You are solving constraint programming problems using CPMpy.
|
|
4
|
+
|
|
5
|
+
## Core Rules
|
|
6
|
+
|
|
7
|
+
1. Use CPMpy's constraint modeling - never write search algorithms
|
|
8
|
+
2. Output ONLY valid JSON using `json.dumps()` - no other text
|
|
9
|
+
3. Always `import json` if outputting JSON
|
|
10
|
+
4. Check the exact output format required
|
|
11
|
+
5. Test your solution manually to verify it satisfies the problem
|
|
12
|
+
|
|
13
|
+
## Basic Template
|
|
14
|
+
```python
|
|
15
|
+
from cpmpy import *
|
|
16
|
+
import json
|
|
17
|
+
|
|
18
|
+
# Variables
|
|
19
|
+
# Constraints
|
|
20
|
+
# Solve
|
|
21
|
+
if model.solve():
|
|
22
|
+
# Build result dict as specified
|
|
23
|
+
result = {...}
|
|
24
|
+
# Verify solution satisfies problem requirements
|
|
25
|
+
print(json.dumps(result))
|
|
26
|
+
else:
|
|
27
|
+
print(json.dumps({"error": "No solution"}))
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Essential Constraints
|
|
31
|
+
- `AllDifferent(vars)` - all different values
|
|
32
|
+
- `sum(vars) == total` - sum constraint
|
|
33
|
+
- `Circuit(x)` - variables x form a Hamiltonian circuit (for routing/tour problems)
|
|
34
|
+
- `InDomain(var, [values])` - restrict variable to a set of values. **Must be explicitly imported**: `from cpmpy import InDomain`
|
|
35
|
+
- Logical: `&`, `|`, `~` on constraints; `(cond).implies(other)` on constraints only
|
|
36
|
+
- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=` between variables and expressions
|
|
37
|
+
- Element: `vars[idx]` where idx is a decision variable (direct indexing works)
|
|
38
|
+
|
|
39
|
+
## CPMpy Pitfalls - Avoid These
|
|
40
|
+
- Do NOT use `.is_in()`, `.in_domain()`, or other methods that don't exist — use `InDomain(var, list)` instead
|
|
41
|
+
- Do NOT use `~var` on integer variables — `~` only works on boolean constraints
|
|
42
|
+
- Do NOT call `.implies()` on integer variables — only on boolean expressions/constraints
|
|
43
|
+
- If an operator/method fails, fall back to explicit OR/AND over individual equality constraints
|
|
44
|
+
- When in doubt, use simple comparisons and loops instead of advanced abstractions
|
|
45
|
+
|
|
46
|
+
## Optimization
|
|
47
|
+
- Use `model.minimize(objective)` or `model.maximize(objective)`
|
|
48
|
+
- CPMpy automatically finds the OPTIMAL solution, not just first valid
|
|
49
|
+
- The solver continues searching until it proves optimality
|
|
50
|
+
- Always verify the objective value matches your expectation
|
|
51
|
+
- Example:
|
|
52
|
+
```python
|
|
53
|
+
profit = sum(price[i] * x[i] for i in range(n))
|
|
54
|
+
model.maximize(profit)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Output Format Rules
|
|
58
|
+
- Follow the output JSON format specification EXACTLY — do not invent alternative encodings
|
|
59
|
+
- If a field is described as nested arrays, output nested arrays (e.g., `[[1,2],[3,4]]`), never compress to single integers
|
|
60
|
+
- If the narrative text conflicts with the explicit "Output format" block, the Output format block wins
|
|
61
|
+
- If the narrative mentions optional variants or extensions of the problem ("one extension is...", "another version..."), solve the base problem as specified by the input data and the Output format block — do not output an extended variant unless the output spec explicitly requires it
|
|
62
|
+
- Derive every output array's dimensions from parameters stated in the problem and from the axis names in the format spec: `result[a][b]` means outer axis `a` and inner axis `b` — never swap axes or substitute a different quantity
|
|
63
|
+
- Check array dimensions and value ranges match the spec before saving
|
|
64
|
+
|
|
65
|
+
## Verification Requirement (MANDATORY)
|
|
66
|
+
|
|
67
|
+
CPMpy models can easily contain subtle logic bugs. Before saving, you MUST:
|
|
68
|
+
|
|
69
|
+
1. **Solve and extract** the solution values
|
|
70
|
+
2. **Write a `verify()` function** that checks every constraint from the problem statement using plain Python loops and asserts — independent of your CPMpy model:
|
|
71
|
+
```python
|
|
72
|
+
def verify(result):
|
|
73
|
+
# Check array shapes — sizes re-derived from the problem text,
|
|
74
|
+
# NOT from variables your model introduced
|
|
75
|
+
assert len(result["schedule"]) == n_weeks
|
|
76
|
+
# Check each constraint with plain Python
|
|
77
|
+
for w in range(n_weeks):
|
|
78
|
+
teams_this_week = [result["schedule"][w][p] for p in range(n_periods)]
|
|
79
|
+
assert len(set(teams_this_week)) == len(teams_this_week), "Duplicate team in week"
|
|
80
|
+
# ... check ALL constraints from the problem statement
|
|
81
|
+
print("All checks passed")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The verifier must be derived from the problem text alone: re-read the problem
|
|
85
|
+
statement and re-derive every expected dimension, entry encoding, and constraint
|
|
86
|
+
from its own parameters. Never assert against quantities or interpretations that
|
|
87
|
+
your model introduced — `assert len(x) == my_total` proves nothing if `my_total`
|
|
88
|
+
encodes a misreading. If your model and your verifier share an assumption, a
|
|
89
|
+
wrong assumption passes silently.
|
|
90
|
+
3. **Execute the verification** via python_exec and confirm it passes
|
|
91
|
+
4. **For optimization**: verify optimality by re-solving with a stricter bound:
|
|
92
|
+
```python
|
|
93
|
+
model2 = Model(model.constraints)
|
|
94
|
+
model2 += objective < best_value # or > for maximize
|
|
95
|
+
assert not model2.solve(), "Solution is not optimal"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Do NOT call save_code until verification passes. If it fails, fix the model and re-verify.
|
|
99
|
+
|
|
100
|
+
That's it. Read the problem carefully, model it declaratively, verify independently, and let CPMpy find the optimal solution.
|