agentic-python-coder 3.1.0__py3-none-any.whl → 3.3.0__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.
Files changed (31) hide show
  1. agentic_python_coder/__init__.py +6 -1
  2. agentic_python_coder/agent.py +52 -14
  3. agentic_python_coder/cli.py +19 -4
  4. agentic_python_coder/examples/clingo/clingo.md +64 -1
  5. agentic_python_coder/examples/cpmpy/cpmpy.md +45 -2
  6. agentic_python_coder/kernel.py +17 -0
  7. agentic_python_coder/llm.py +23 -5
  8. agentic_python_coder/mcp_server.py +5 -6
  9. agentic_python_coder/models/deepseek31.json +0 -1
  10. agentic_python_coder/models/gemini25.json +0 -1
  11. agentic_python_coder/models/gemini31.json +6 -0
  12. agentic_python_coder/models/gemini3pro.json +0 -1
  13. agentic_python_coder/models/gpt52.json +0 -1
  14. agentic_python_coder/models/gpt56sol.json +5 -0
  15. agentic_python_coder/models/gpt56terra.json +5 -0
  16. agentic_python_coder/models/grok41.json +0 -1
  17. agentic_python_coder/models/opus45.json +1 -2
  18. agentic_python_coder/models/opus48.json +5 -0
  19. agentic_python_coder/models/qwen3.json +0 -1
  20. agentic_python_coder/models/sonnet45.json +1 -2
  21. agentic_python_coder/models/sonnet46.json +5 -0
  22. agentic_python_coder/models/sonnet5.json +5 -0
  23. agentic_python_coder/project_md.py +6 -2
  24. agentic_python_coder/runner.py +8 -1
  25. agentic_python_coder/tools.py +28 -7
  26. {agentic_python_coder-3.1.0.dist-info → agentic_python_coder-3.3.0.dist-info}/METADATA +54 -12
  27. agentic_python_coder-3.3.0.dist-info/RECORD +48 -0
  28. {agentic_python_coder-3.1.0.dist-info → agentic_python_coder-3.3.0.dist-info}/WHEEL +1 -1
  29. agentic_python_coder-3.1.0.dist-info/RECORD +0 -42
  30. {agentic_python_coder-3.1.0.dist-info → agentic_python_coder-3.3.0.dist-info}/entry_points.txt +0 -0
  31. {agentic_python_coder-3.1.0.dist-info → agentic_python_coder-3.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,11 @@
1
1
  """Python Coding Agent - A minimal coding assistant using direct OpenAI API and OpenRouter."""
2
2
 
3
- __version__ = "3.0.0"
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
@@ -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"] = ",".join(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 = get_openrouter_llm(
87
- model=model or DEFAULT_MODEL,
88
- api_key=api_key,
89
- verbose=verbose,
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
- # Set working directory at execution time (not creation time)
203
- # This prevents race conditions when multiple agents are created
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 step_num in range(limit):
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"] += response.usage.prompt_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
 
@@ -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, model: str, project_prompt: str, api_key: str, todo: bool
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, thread_id, quiet=False)
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, args.model, project_prompt, args.api_key, args.todo
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)
@@ -31,7 +31,7 @@ else:
31
31
  - `AllDifferent(vars)` - all different values
32
32
  - `sum(vars) == total` - sum constraint
33
33
  - `Circuit(x)` - variables x form a Hamiltonian circuit (for routing/tour problems)
34
- - `InDomain(var, [values])` - restrict variable to a set of values (import from cpmpy)
34
+ - `InDomain(var, [values])` - restrict variable to a set of values. **Must be explicitly imported**: `from cpmpy import InDomain`
35
35
  - Logical: `&`, `|`, `~` on constraints; `(cond).implies(other)` on constraints only
36
36
  - Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=` between variables and expressions
37
37
  - Element: `vars[idx]` where idx is a decision variable (direct indexing works)
@@ -54,4 +54,47 @@ else:
54
54
  model.maximize(profit)
55
55
  ```
56
56
 
57
- That's it. Read the problem carefully, model it declaratively, and let CPMpy find the optimal solution.
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.
@@ -250,6 +250,7 @@ except ImportError:
250
250
 
251
251
  # Wait for and collect messages with hard deadline
252
252
  deadline = time.monotonic() + deadline_timeout
253
+ completed = False
253
254
 
254
255
  while time.monotonic() < deadline:
255
256
  try:
@@ -274,10 +275,26 @@ except ImportError:
274
275
  if content.get("traceback"):
275
276
  output["error"] += "\n" + "\n".join(content["traceback"])
276
277
  elif msg_type == "status" and content["execution_state"] == "idle":
278
+ completed = True
277
279
  break # Only exit on idle status
278
280
  except Empty:
279
281
  continue # Keep waiting, don't break
280
282
 
283
+ if not completed:
284
+ # Deadline hit while the kernel was still running: report a
285
+ # timeout instead of silently returning partial output as success,
286
+ # and interrupt so the kernel doesn't block the next execution
287
+ if output["error"] is None:
288
+ output["error"] = (
289
+ f"Execution timed out after {deadline_timeout} seconds"
290
+ " (partial output returned)"
291
+ )
292
+ try:
293
+ self.km.interrupt_kernel()
294
+ time.sleep(0.5) # grace period so the next execute isn't aborted
295
+ except Exception:
296
+ pass # Best effort
297
+
281
298
  return output
282
299
 
283
300
  def execute(self, code: str, deadline_timeout: int = 60) -> Dict[str, str]:
@@ -133,6 +133,10 @@ def get_openrouter_llm(
133
133
  ) -> LLMConfig:
134
134
  """Create a fully configured LLMConfig for OpenRouter.
135
135
 
136
+ The model JSON may override the endpoint with "base_url" (any
137
+ OpenAI-compatible server) and "api_key_env" (name of the environment
138
+ variable holding the key for that endpoint).
139
+
136
140
  Args:
137
141
  model: Model name (e.g., "sonnet45") or path to JSON file
138
142
  api_key: Optional API key
@@ -158,17 +162,27 @@ def get_openrouter_llm(
158
162
 
159
163
  # Get API key
160
164
  if not api_key:
161
- api_key = get_api_key()
165
+ if "api_key_env" in config:
166
+ api_key = os.getenv(config["api_key_env"])
167
+ if not api_key:
168
+ raise ValueError(
169
+ f"Environment variable '{config['api_key_env']}' not set "
170
+ f"(required by model config '{model}')"
171
+ )
172
+ else:
173
+ api_key = get_api_key()
162
174
 
163
175
  # Build client kwargs
164
176
  client_kwargs: dict[str, Any] = {
165
177
  "api_key": api_key,
166
- "base_url": "https://openrouter.ai/api/v1",
167
- "default_headers": {
178
+ "base_url": config.get("base_url", "https://openrouter.ai/api/v1"),
179
+ }
180
+ if "base_url" not in config:
181
+ # OpenRouter attribution headers; skip for custom endpoints
182
+ client_kwargs["default_headers"] = {
168
183
  "HTTP-Referer": "https://github.com/szeider/agentic-python-coder",
169
184
  "X-Title": "Agentic Python Coder",
170
- },
171
- }
185
+ }
172
186
 
173
187
  # Add request_timeout as client timeout
174
188
  if "request_timeout" in config:
@@ -201,4 +215,8 @@ def get_openrouter_llm(
201
215
  if "provider" in config:
202
216
  api_params.setdefault("extra_body", {})["provider"] = config["provider"]
203
217
 
218
+ # Reasoning/thinking tokens (e.g. Gemini 3 Flash thinking levels)
219
+ if "reasoning" in config:
220
+ api_params.setdefault("extra_body", {})["reasoning"] = config["reasoning"]
221
+
204
222
  return LLMConfig(client=client, model=model_id, api_params=api_params)
@@ -39,16 +39,15 @@ _session_lock = asyncio.Lock()
39
39
 
40
40
 
41
41
  def truncate_output(result: dict) -> dict:
42
- """Truncate stdout/stderr if too large and add success flag."""
42
+ """Truncate oversized text fields and add success flag."""
43
43
  # Add success flag based on error field
44
44
  result["success"] = result.get("error") is None
45
45
 
46
46
  max_kb = MAX_OUTPUT // 1024
47
- for key in ["stdout", "stderr"]:
48
- if key in result and result[key] and len(result[key]) > MAX_OUTPUT:
49
- result[key] = (
50
- result[key][:MAX_OUTPUT] + f"\n[{key} truncated at {max_kb}KB]"
51
- )
47
+ for key in ["stdout", "stderr", "result", "error"]:
48
+ value = result.get(key)
49
+ if isinstance(value, str) and len(value) > MAX_OUTPUT:
50
+ result[key] = value[:MAX_OUTPUT] + f"\n[{key} truncated at {max_kb}KB]"
52
51
  return result
53
52
 
54
53
 
@@ -2,6 +2,5 @@
2
2
  "path": "deepseek/deepseek-chat-v3.1",
3
3
  "temperature": 0.2,
4
4
  "max_tokens": 8192,
5
- "streaming": true,
6
5
  "top_p": 1.0
7
6
  }
@@ -2,7 +2,6 @@
2
2
  "path": "google/gemini-2.5-pro",
3
3
  "temperature": 0.3,
4
4
  "max_tokens": 2048,
5
- "streaming": true,
6
5
  "top_p": 0.9,
7
6
  "request_timeout": 60
8
7
  }
@@ -0,0 +1,6 @@
1
+ {
2
+ "path": "google/gemini-3.1-pro-preview",
3
+ "temperature": 0.0,
4
+ "max_tokens": 16384,
5
+ "request_timeout": 120
6
+ }
@@ -2,7 +2,6 @@
2
2
  "path": "google/gemini-3-pro-preview",
3
3
  "temperature": 0.3,
4
4
  "max_tokens": 16384,
5
- "streaming": true,
6
5
  "top_p": 0.9,
7
6
  "request_timeout": 120
8
7
  }
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "path": "openai/gpt-5.2",
3
3
  "max_tokens": 16384,
4
- "streaming": true,
5
4
  "no_sampling_params": true
6
5
  }
@@ -0,0 +1,5 @@
1
+ {
2
+ "path": "openai/gpt-5.6-sol",
3
+ "max_tokens": 16384,
4
+ "no_sampling_params": true
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "path": "openai/gpt-5.6-terra",
3
+ "max_tokens": 16384,
4
+ "no_sampling_params": true
5
+ }
@@ -2,6 +2,5 @@
2
2
  "path": "x-ai/grok-4.1-fast",
3
3
  "temperature": 0.15,
4
4
  "max_tokens": 2000,
5
- "streaming": true,
6
5
  "top_p": 0.9
7
6
  }
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "path": "anthropic/claude-opus-4.5",
3
3
  "temperature": 0.0,
4
- "max_tokens": 16384,
5
- "streaming": true
4
+ "max_tokens": 16384
6
5
  }
@@ -0,0 +1,5 @@
1
+ {
2
+ "path": "anthropic/claude-opus-4.8",
3
+ "temperature": 0.0,
4
+ "max_tokens": 16384
5
+ }
@@ -2,6 +2,5 @@
2
2
  "path": "qwen/qwen3-coder",
3
3
  "temperature": 0.15,
4
4
  "max_tokens": 2048,
5
- "streaming": true,
6
5
  "top_p": 0.9
7
6
  }
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "path": "anthropic/claude-sonnet-4.5",
3
3
  "temperature": 0.0,
4
- "max_tokens": 16384,
5
- "streaming": true
4
+ "max_tokens": 16384
6
5
  }
@@ -0,0 +1,5 @@
1
+ {
2
+ "path": "anthropic/claude-sonnet-4.6",
3
+ "temperature": 0.0,
4
+ "max_tokens": 16384
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "path": "anthropic/claude-sonnet-5",
3
+ "temperature": 0.0,
4
+ "max_tokens": 32768
5
+ }
@@ -53,8 +53,12 @@ def check_packages_available(packages: List[str]) -> List[str]:
53
53
  unavailable = []
54
54
 
55
55
  for package in packages:
56
- # Try to find the package
57
- spec = importlib.util.find_spec(package)
56
+ # Try to find the package; find_spec raises for dotted names with a
57
+ # missing parent or otherwise invalid names — treat those as unavailable
58
+ try:
59
+ spec = importlib.util.find_spec(package)
60
+ except (ImportError, ValueError):
61
+ spec = None
58
62
  if spec is None:
59
63
  unavailable.append(package)
60
64
 
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
  from typing import Optional, List, Any, Dict
6
6
 
7
7
  from agentic_python_coder.agent import create_coding_agent, run_agent
8
+ from agentic_python_coder.llm import LLMConfig
8
9
 
9
10
 
10
11
  def get_system_prompt_path(todo: bool = False) -> Path:
@@ -233,7 +234,9 @@ def save_conversation_log(
233
234
  json.dumps(
234
235
  {
235
236
  "event": "complete",
236
- "status": "success",
237
+ "status": "step_limit_reached"
238
+ if stats and stats.get("step_limit_reached")
239
+ else "success",
237
240
  }
238
241
  )
239
242
  + "\n"
@@ -256,6 +259,7 @@ def solve_task(
256
259
  save_log: bool = True,
257
260
  task_basename: Optional[str] = None,
258
261
  step_limit: Optional[int] = None,
262
+ llm_config: Optional[LLMConfig] = None,
259
263
  ) -> tuple[List[Any], Dict[str, Any], Optional[Path]]:
260
264
  """Run a complete coding task end-to-end.
261
265
 
@@ -275,6 +279,8 @@ def solve_task(
275
279
  save_log: Save conversation log to file (default: True)
276
280
  task_basename: Base name for output files
277
281
  step_limit: Maximum agent steps before stopping (default: 200)
282
+ llm_config: Optional pre-built LLMConfig (any OpenAI-compatible
283
+ client); takes precedence over model/api_key
278
284
 
279
285
  Returns:
280
286
  Tuple of (messages, stats, log_path)
@@ -313,6 +319,7 @@ def solve_task(
313
319
  api_key=api_key,
314
320
  todo=todo,
315
321
  verbose=not quiet,
322
+ llm_config=llm_config,
316
323
  )
317
324
 
318
325
  # Run agent
@@ -5,7 +5,7 @@ from dataclasses import dataclass
5
5
  from pathlib import Path
6
6
  from typing import List, Dict, Any, Callable
7
7
 
8
- from .kernel import get_kernel, format_output
8
+ from .kernel import get_kernel, format_output, shutdown_kernel
9
9
 
10
10
 
11
11
  @dataclass
@@ -50,10 +50,6 @@ class ToolRegistry:
50
50
  """Get a tool by name."""
51
51
  return self._tools.get(name)
52
52
 
53
- def has(self, name: str) -> bool:
54
- """Check if a tool is registered."""
55
- return name in self._tools
56
-
57
53
  def get_openai_tools(self) -> list[dict[str, Any]]:
58
54
  """Get all tools in OpenAI format."""
59
55
  return [tool.to_openai_tool() for tool in self._tools.values()]
@@ -171,7 +167,14 @@ def python_exec(code: str) -> str:
171
167
  with_packages = None
172
168
  if "CODER_WITH_PACKAGES" in os.environ:
173
169
  packages_str = os.environ["CODER_WITH_PACKAGES"]
174
- with_packages = packages_str.split(",") if packages_str else []
170
+ if not packages_str:
171
+ with_packages = []
172
+ else:
173
+ try:
174
+ with_packages = json.loads(packages_str)
175
+ except json.JSONDecodeError:
176
+ # Legacy comma-joined format
177
+ with_packages = packages_str.split(",")
175
178
 
176
179
  try:
177
180
  kernel = get_kernel(cwd=str(working_dir.get()), with_packages=with_packages)
@@ -214,6 +217,16 @@ def save_code(code: str) -> str:
214
217
  else:
215
218
  filename = "solution.py"
216
219
 
220
+ # Reject non-compiling code so the model gets feedback instead of a
221
+ # silently broken file (e.g. from a bad escape in the tool-call JSON)
222
+ try:
223
+ compile(code, filename, "exec")
224
+ except SyntaxError as e:
225
+ return error_response(
226
+ f"Code NOT saved — syntax error at line {e.lineno}: {e.msg}. "
227
+ "Fix the code and call save_code again."
228
+ )
229
+
217
230
  output_path = working_dir.get() / filename
218
231
  output_path.write_text(code)
219
232
 
@@ -223,10 +236,16 @@ def save_code(code: str) -> str:
223
236
 
224
237
 
225
238
  def reset_global_state():
226
- """Reset all global state to avoid accumulation across runs."""
239
+ """Reset all global state to avoid accumulation across runs.
240
+
241
+ Also shuts down the default kernel so the next execution starts a fresh
242
+ one — otherwise a second agent in the same process would silently reuse
243
+ the previous agent's kernel (its cwd, packages, and variables).
244
+ """
227
245
  global _todos, _task_basename
228
246
  _todos = []
229
247
  _task_basename = None
248
+ shutdown_kernel()
230
249
 
231
250
 
232
251
  # Tool definitions with explicit JSON schemas
@@ -281,6 +300,8 @@ SAVE_CODE_TOOL = Tool(
281
300
  "2. Verified the output format matches the specification exactly (JSON keys, array shapes, value ranges)\n"
282
301
  "3. For constraint/logic problems: run an independent verification checking every constraint\n"
283
302
  "Do NOT save unverified code. If verification fails, fix the code and re-verify first.\n\n"
303
+ "The code is syntax-checked before saving; on a syntax error nothing is\n"
304
+ "written and an error is returned — fix the code and save again.\n\n"
284
305
  "Args:\n"
285
306
  " code: The complete Python code\n\n"
286
307
  "Returns:\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-python-coder
3
- Version: 3.1.0
3
+ Version: 3.3.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.2.0
18
- Requires-Dist: jupyter-client>=8.8.0
19
- Requires-Dist: mcp>=1.26.0
20
- Requires-Dist: openai>=2.21.0
21
- Requires-Dist: python-dotenv>=1.2.1
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>=14.3.2
23
+ Requires-Dist: rich>=15.0.0
24
24
  Provides-Extra: dev
25
- Requires-Dist: mypy>=1.19.1; extra == 'dev'
26
- Requires-Dist: ruff>=0.14.14; extra == 'dev'
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.3.0; extra == 'test'
29
- Requires-Dist: pytest-cov>=7.0.0; extra == 'test'
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.0.2; extra == 'test'
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
@@ -0,0 +1,48 @@
1
+ agentic_python_coder/__init__.py,sha256=e6o-k__u5xNIiQAkZbHtbZC2-NwpeOdi4MdQtAqJBjE,2112
2
+ agentic_python_coder/agent.py,sha256=gzDFf4n7J1JdWawYPzWQFVwcHZ2rsRHUzVZVcGYjQWo,14451
3
+ agentic_python_coder/cli.py,sha256=HS9nlqsiWZAvg0Hc8ExoVT8eEcy2t3Bux6UGukERWqQ,14663
4
+ agentic_python_coder/kernel.py,sha256=PQrKMi0_mKVeG1q2wXTu3-W2ZYKwYNjtPI_3A4m4Gvg,21284
5
+ agentic_python_coder/llm.py,sha256=zjsk4-Kdx92hFSxywPGIkLv8zflWnE2cMEcpeXu6ttw,6998
6
+ agentic_python_coder/mcp_server.py,sha256=2Z5O9n5QgZrgn3GdFljjF7Yq5e9tE1zNlAWBPyckrlI,18503
7
+ agentic_python_coder/project_md.py,sha256=4xkQHlWb4cDPEkvegw1YwcfaTJ3aFbrl0CYcC_xxABY,3249
8
+ agentic_python_coder/runner.py,sha256=Kbw1ceYJ4MVfUKgSIkiuBhMb8ZtbZuJwbBeTUsvPRts,13045
9
+ agentic_python_coder/tools.py,sha256=tjhQbsF3n3bFIegnEOtE8czjzbtMSXy6B9gYeb-YAJ4,12088
10
+ agentic_python_coder/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ agentic_python_coder/examples/clingo/README.md,sha256=SgWBqdJ2cHIfYWRV1YYqzfY0rth7mjCR5Act_hchcx0,2345
12
+ agentic_python_coder/examples/clingo/clingo.md,sha256=n8JOtpOww4T494OgLI_at8Zoz5pl7lv3AGOKlCA-cVE,52959
13
+ agentic_python_coder/examples/clingo/sample_tasks/bird_reasoning.md,sha256=OsOZDOzaj_wDEEhvq2raIK_P8VJY0Se6pgt3UJ8DY30,1660
14
+ agentic_python_coder/examples/clingo/sample_tasks/diagnosis.md,sha256=s5Q9WoLvInKcwitgY2TCdqFt4xdnxfd5FcMNywfT7p8,1818
15
+ agentic_python_coder/examples/clingo/sample_tasks/simple_coloring.md,sha256=T_44hh5nY-RSNw021DL-flEoLlfwyZMOheIf5-C0hE4,538
16
+ agentic_python_coder/examples/clingo/sample_tasks/stable_marriage.md,sha256=mWNAHmMstUq0VIYkDzrgK4PuiS59n6gMU3lMJrQ02c4,679
17
+ agentic_python_coder/examples/clingo/sample_tasks/sudoku_mini.md,sha256=rdEd3dkuh0V4sfosoXwEVe049T4QcOA4ZcgwdLQnC-M,475
18
+ agentic_python_coder/examples/cpmpy/README.md,sha256=4XqG8kvdBON6WGw5TPrGpqqBspuH2nsQ3NkZl9FzAM0,1399
19
+ agentic_python_coder/examples/cpmpy/cpmpy.md,sha256=O5WtJOoJl8i-MgzrFCbuCCGRrfo-zI8hZcCUkhzOqO0,4971
20
+ agentic_python_coder/examples/cpmpy/sample_tasks/magic_square.md,sha256=lUsnYoiJy7i6tXmFFISkTB6OrFTRPBDoxkJv9mceY-o,1127
21
+ agentic_python_coder/examples/cpmpy/sample_tasks/n_queens.md,sha256=WybTtZMlD28B1XAUf91IWqvrXIh5MTKWXHXnqfvpysM,848
22
+ agentic_python_coder/examples/regex/README.md,sha256=3K8nK-nVFrjweusS0Zdjt3Oh-UIc0mhgUuxVKGfi3QU,1378
23
+ agentic_python_coder/examples/regex/regex.md,sha256=3D64k-hoLiV_Io5LAjOXcdSrCNVAkKoqTzieOfXqM14,3126
24
+ agentic_python_coder/examples/regex/sample_tasks/email_extraction.md,sha256=M5_CZ1Tf-o1HBCqTlWszGQSYQ2QCkOxiIA08tRfoDiE,785
25
+ agentic_python_coder/examples/regex/sample_tasks/phone_validation.md,sha256=yoij3IlEDfWsNQ6UXkDUaNHPVbOb3pMcYO5v1pdLS-E,748
26
+ agentic_python_coder/examples/regex/sample_tasks/url_parsing.md,sha256=C2E6ze-AeIXbzM-gB0aJ3_QSiaJB3c2MbMTNJXzuYhA,1683
27
+ agentic_python_coder/models/deepseek31.json,sha256=42qWgfBn2ErZndbpp-4-JtqRna2xlwzkfCaYnKFEboQ,104
28
+ agentic_python_coder/models/gemini25.json,sha256=I3nNMz2GwhFQLtHtZotV3hNUyWXq9ToMm96byM98LFA,123
29
+ agentic_python_coder/models/gemini31.json,sha256=q8y8pPEZE1hsv2vV7c1KckPHKE7Gfbi51xQgwM62i-o,117
30
+ agentic_python_coder/models/gemini3flash.json,sha256=S0AMVpzi3O5CQk68Q3Hi8iCHXMQQFnM4NbSh23x0qIM,91
31
+ agentic_python_coder/models/gemini3pro.json,sha256=5yWdmtkKSpAvr8svGjAgSoXvCnu1SCoVnhdbx-1L4fQ,131
32
+ agentic_python_coder/models/gpt52.json,sha256=oDYxC21iQTKC9uVbFRlAs-hCnu_4cEDFK3mmqPVIIes,84
33
+ agentic_python_coder/models/gpt56sol.json,sha256=xFNDxSz0ZC2qtDldgZ3ms1ela1InmhL5x_-UZktR1K8,88
34
+ agentic_python_coder/models/gpt56terra.json,sha256=NnVvnnF7EMK-pADCZsm1fJ8O8DLCT4obHc0x_00kcus,90
35
+ agentic_python_coder/models/grok41.json,sha256=l76RtbnFiNNQEe_dFoxn0HNbjgTcOMk5MswYqhcw764,96
36
+ agentic_python_coder/models/opus45.json,sha256=wbRqoIrn1CSlu0pRnpR2S-1j1zWGD_jza7udZNnU3wY,87
37
+ agentic_python_coder/models/opus48.json,sha256=grMqaY0sCQnZeH4VxsPfwUIqlHpxNxTjmM47hHuyPkY,87
38
+ agentic_python_coder/models/qwen3.json,sha256=TJuIx5EllPIc8QBnagINlhNVxkLlsSDWRZAsFMqP_lo,94
39
+ agentic_python_coder/models/sonnet45.json,sha256=WeWezJxt4r7IugBMzRbnSIocmmcd6qc_756M4CHdEa8,89
40
+ agentic_python_coder/models/sonnet46.json,sha256=Ops1FpBoyx9PBGRhIXG8u-YGBgIui9DqvYxIc2BkJFM,89
41
+ agentic_python_coder/models/sonnet5.json,sha256=kf6DIkQlcGVZ40IzRv3GXFtNncZF_gKqQeGzZAxE_KM,87
42
+ agentic_python_coder/prompts/system.md,sha256=hREDVAhJXwCBAgJ3AE9JD6j-JRHREUPlkRSvThOvM3A,3807
43
+ agentic_python_coder/prompts/system_todo.md,sha256=TuNeeUNS9KqyF62Da6t2mlUCYhKsgzwGe5ic_vFPDqw,5054
44
+ agentic_python_coder-3.3.0.dist-info/METADATA,sha256=ec4FIlQitVNa_5bMQhu2NPpJV-5VwDRQy1hzK_4b_G8,12872
45
+ agentic_python_coder-3.3.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
46
+ agentic_python_coder-3.3.0.dist-info/entry_points.txt,sha256=XIW3nZzL4kJ-Igohilj_mTvBcCyHBouDq2jmGzDILrk,107
47
+ agentic_python_coder-3.3.0.dist-info/licenses/LICENSE,sha256=R9-M_V-SPdN71IphOVzM7kCNpUYs_hwj7rtroTwDhs0,11362
48
+ agentic_python_coder-3.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.28.0
2
+ Generator: hatchling 1.31.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,42 +0,0 @@
1
- agentic_python_coder/__init__.py,sha256=AE7tDIivH7H0BBwhrR9C64rY6pO1d6AWDQYGAvGjeuk,1881
2
- agentic_python_coder/agent.py,sha256=Z7I0zvh3XEouPgt9hvkSIKYuZyHfsTrsTEFiJjsK4hk,12821
3
- agentic_python_coder/cli.py,sha256=pjmKHwLmC41wJ4Z6tQPFNeS2VwC-HCvtXklmv__xOco,14349
4
- agentic_python_coder/kernel.py,sha256=Xjqsl-Rzb6JBqWMTE4JBsxNsy1eRVUL5FvtfT1OFLD4,20541
5
- agentic_python_coder/llm.py,sha256=8BxNK8FmECbcJhP5qeB8IxErbm1NKBZO4Z-bB5-JtaE,6157
6
- agentic_python_coder/mcp_server.py,sha256=U_YfjeUvaVJ4poCsIqkzOx70J190RYH2Ln_H5tDc6Zg,18508
7
- agentic_python_coder/project_md.py,sha256=c4A-rbsc3ryqUkCJLGOKBHH8QJL5kAqvTSsM0tDeD4Y,3041
8
- agentic_python_coder/runner.py,sha256=AtdtRusD5ThTlsdL2SDjYk73jD0fYRWkJ4vmpeIWBl4,12683
9
- agentic_python_coder/tools.py,sha256=Qo_VI5ABqrHOFb80JlwYbH_RJmgCNZagFsTv7RmJNNE,11127
10
- agentic_python_coder/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- agentic_python_coder/examples/clingo/README.md,sha256=SgWBqdJ2cHIfYWRV1YYqzfY0rth7mjCR5Act_hchcx0,2345
12
- agentic_python_coder/examples/clingo/clingo.md,sha256=VhIcECJwbmoV8T-GKok27Er_AvDpKj1qnzz4Zn7ddBI,49901
13
- agentic_python_coder/examples/clingo/sample_tasks/bird_reasoning.md,sha256=OsOZDOzaj_wDEEhvq2raIK_P8VJY0Se6pgt3UJ8DY30,1660
14
- agentic_python_coder/examples/clingo/sample_tasks/diagnosis.md,sha256=s5Q9WoLvInKcwitgY2TCdqFt4xdnxfd5FcMNywfT7p8,1818
15
- agentic_python_coder/examples/clingo/sample_tasks/simple_coloring.md,sha256=T_44hh5nY-RSNw021DL-flEoLlfwyZMOheIf5-C0hE4,538
16
- agentic_python_coder/examples/clingo/sample_tasks/stable_marriage.md,sha256=mWNAHmMstUq0VIYkDzrgK4PuiS59n6gMU3lMJrQ02c4,679
17
- agentic_python_coder/examples/clingo/sample_tasks/sudoku_mini.md,sha256=rdEd3dkuh0V4sfosoXwEVe049T4QcOA4ZcgwdLQnC-M,475
18
- agentic_python_coder/examples/cpmpy/README.md,sha256=4XqG8kvdBON6WGw5TPrGpqqBspuH2nsQ3NkZl9FzAM0,1399
19
- agentic_python_coder/examples/cpmpy/cpmpy.md,sha256=tQpZpM2CO1lG0r-_TGYi-fd9KrdyGr6kyqyeWjyPECs,2207
20
- agentic_python_coder/examples/cpmpy/sample_tasks/magic_square.md,sha256=lUsnYoiJy7i6tXmFFISkTB6OrFTRPBDoxkJv9mceY-o,1127
21
- agentic_python_coder/examples/cpmpy/sample_tasks/n_queens.md,sha256=WybTtZMlD28B1XAUf91IWqvrXIh5MTKWXHXnqfvpysM,848
22
- agentic_python_coder/examples/regex/README.md,sha256=3K8nK-nVFrjweusS0Zdjt3Oh-UIc0mhgUuxVKGfi3QU,1378
23
- agentic_python_coder/examples/regex/regex.md,sha256=3D64k-hoLiV_Io5LAjOXcdSrCNVAkKoqTzieOfXqM14,3126
24
- agentic_python_coder/examples/regex/sample_tasks/email_extraction.md,sha256=M5_CZ1Tf-o1HBCqTlWszGQSYQ2QCkOxiIA08tRfoDiE,785
25
- agentic_python_coder/examples/regex/sample_tasks/phone_validation.md,sha256=yoij3IlEDfWsNQ6UXkDUaNHPVbOb3pMcYO5v1pdLS-E,748
26
- agentic_python_coder/examples/regex/sample_tasks/url_parsing.md,sha256=C2E6ze-AeIXbzM-gB0aJ3_QSiaJB3c2MbMTNJXzuYhA,1683
27
- agentic_python_coder/models/deepseek31.json,sha256=m3h2-rieY23gTXj9AOFoWxnrYUfqNpDpyNpPW0SMqVE,125
28
- agentic_python_coder/models/gemini25.json,sha256=Z9qJrIUNel366A3hSdg3qQixTh-EU8lehXKebW-XEUY,144
29
- agentic_python_coder/models/gemini3flash.json,sha256=S0AMVpzi3O5CQk68Q3Hi8iCHXMQQFnM4NbSh23x0qIM,91
30
- agentic_python_coder/models/gemini3pro.json,sha256=ptozne-ZwpmuXzpt2x0ZApMIvb5VsWskqI36XscCsrg,152
31
- agentic_python_coder/models/gpt52.json,sha256=rGYoD83_yioSDJvO3o4FSFhi0B2yTaOpCgr_XpRaPWk,105
32
- agentic_python_coder/models/grok41.json,sha256=bUFtp2orkBmfiLH7mE8SaS0YEe2kJFF1Ex2jb03p9SA,117
33
- agentic_python_coder/models/opus45.json,sha256=ujBiZiA-6YNbQxd3xWlGT7YGtEtbWMC8e2TP0I8Gtho,108
34
- agentic_python_coder/models/qwen3.json,sha256=Lj39r787EcfUcKXfO52aplYurkpM9mG1qg71UkaqVfQ,115
35
- agentic_python_coder/models/sonnet45.json,sha256=SXDBHwSRMlqG77i4OhvNDlHL7fESnCoqmEplXm3gP4k,110
36
- agentic_python_coder/prompts/system.md,sha256=hREDVAhJXwCBAgJ3AE9JD6j-JRHREUPlkRSvThOvM3A,3807
37
- agentic_python_coder/prompts/system_todo.md,sha256=TuNeeUNS9KqyF62Da6t2mlUCYhKsgzwGe5ic_vFPDqw,5054
38
- agentic_python_coder-3.1.0.dist-info/METADATA,sha256=xnk9vllNKDqyvwZrIc2LH-g7d2O87wGzd5B2i6PAqps,11564
39
- agentic_python_coder-3.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
40
- agentic_python_coder-3.1.0.dist-info/entry_points.txt,sha256=XIW3nZzL4kJ-Igohilj_mTvBcCyHBouDq2jmGzDILrk,107
41
- agentic_python_coder-3.1.0.dist-info/licenses/LICENSE,sha256=R9-M_V-SPdN71IphOVzM7kCNpUYs_hwj7rtroTwDhs0,11362
42
- agentic_python_coder-3.1.0.dist-info/RECORD,,