You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 2fe3eae
Browse filesBrowse the repository at this point in the historyBrowse files
Merge branch 'main' into feature/109-retry-improvements
Bring in latest main changes including usage metrics (#110),
LLMChatError traceability (#237), ThinkingAwareOpenAILike (#238),
pipeline versioning, plan resume improvements, and error
classification in usage metrics.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Then set `base_url` to `http://localhost:1234` while the tunnel is running.
89
89
90
+
## Thinking Tokens and Reasoning Content
91
+
92
+
Some advanced models (like Qwen3.5 with extended thinking) can be configured in LM Studio to emit **thinking tokens** — internal chain-of-thought reasoning that doesn't appear in the final output.
93
+
94
+
### The Gotcha
95
+
96
+
When thinking tokens are enabled:
97
+
- The model may return `reasoning_content` (internal reasoning) but `content: null` (final output empty)
98
+
- llama_index's standard `OpenAILike` class only reads `content`, causing a crash if it's None
99
+
100
+
### Solution
101
+
102
+
Use the `ThinkingAwareOpenAILike` class instead of `OpenAILike` in your config:
103
+
104
+
```json
105
+
"lmstudio-qwen3.5-9b-with-thinking": {
106
+
"class": "ThinkingAwareOpenAILike",
107
+
"arguments": {
108
+
"model": "qwen/qwen3.5-9b",
109
+
"api_base": "http://127.0.0.1:1234/v1",
110
+
"api_key": "lm-studio",
111
+
"temperature": 0.55,
112
+
"timeout": 600.0,
113
+
"is_function_calling_model": false,
114
+
"is_chat_model": true,
115
+
"context_window": 32768,
116
+
"max_tokens": 4096,
117
+
"should_use_structured_outputs": true
118
+
}
119
+
}
120
+
```
121
+
122
+
`ThinkingAwareOpenAILike` safely handles both:
123
+
1.**With thinking:** Falls back to `reasoning_content` if `content` is null
124
+
2.**Without thinking:** Works exactly like `OpenAILike` for normal responses
125
+
126
+
### Best Practice
127
+
128
+
If you're enabling thinking in LM Studio (e.g., via the Developer preset), always use `ThinkingAwareOpenAILike` to avoid crashes. See [issue #228](https://github.com/PlanExeOrg/PlanExe/pull/228) for technical details.
Copy file name to clipboardExpand all lines: docs/mcp/planexe_mcp_interface.md
+27-8Lines changed: 27 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,7 +15,7 @@ The plan is a **project plan**: a DAG of steps (Luigi pipeline stages) that prod
15
15
Implementors should expose the following to agents so they understand what PlanExe does:
16
16
17
17
-**What:** PlanExe turns a plain-English goal into a strategic project-plan draft (20+ sections) in ~10–20 min. Sections include executive summary, interactive Gantt charts, investor pitch, SWOT, governance, team profiles, work breakdown, scenario comparison, expert criticism, and adversarial sections (premortem, self-audit, premise attacks) that stress-test the plan. The output is a draft to refine, not an executable or final document — but it surfaces hard questions the prompter may not have considered.
18
-
- **Required interaction order:** Call `example_plans` (optional) and `example_prompts` first. Optional before `plan_create`: call `model_profiles` to inspect profile guidance and available models in each profile. Then complete a non-tool step: formulate a detailed prompt as flowing prose (not structured markdown), typically ~300-800 words, using the examples as a baseline; include objective, scope, constraints, timeline, stakeholders, budget/resources, and success criteria; get user approval. Only after approval, call `plan_create`. Then poll `plan_status` (about every 5 minutes); use `plan_download` (mcp_local helper) or `plan_file_info` (mcp_cloud tool) when complete (`pending`/`processing` = keep polling, `completed` = download now, `failed` = terminal). If a plan fails mid-pipeline, call `plan_resume` to continue from where it left off without discarding completed tasks. Use `plan_retry` for a full restart. Both accept the failed `plan_id` and optional `model_profile` (default `baseline`). To stop, call `plan_stop` with the `plan_id` from `plan_create`.
18
+
- **Required interaction order:** Call `example_plans` (optional) and `example_prompts` first. Optional before `plan_create`: call `model_profiles` to inspect profile guidance and available models in each profile. Then complete a non-tool step: formulate a detailed prompt as flowing prose (not structured markdown), typically ~300-800 words, using the examples as a baseline; include objective, scope, constraints, timeline, stakeholders, budget/resources, and success criteria; get user approval. Only after approval, call `plan_create`. Then poll `plan_status` (about every 5 minutes); use `plan_download` (mcp_local helper) or `plan_file_info` (mcp_cloud tool) when complete (`pending`/`processing` = keep polling, `completed` = download now, `failed` = terminal). If a plan fails before completing all steps, call `plan_resume` to continue from where it left off without discarding completed work. Use `plan_retry` for a full restart (plan must be in failed state). Both accept the failed `plan_id` and optional `model_profile` (default `baseline`). To stop, call `plan_stop` with the `plan_id` from `plan_create`.
19
19
-**Output:** Self-contained interactive HTML report (~700KB) with collapsible sections and interactive Gantt charts — open in a browser. The zip contains the intermediary pipeline files (md, json, csv) that fed the report.
20
20
21
21
### 1.3 Scope of this document
@@ -438,9 +438,9 @@ Retries a plan that is currently in `failed` state.
438
438
439
439
### 6.6 plan_resume
440
440
441
-
Resume a failed plan without discarding completed pipeline outputs. The pipeline restarts from the first incomplete task, skipping all tasks that already produced output files.
441
+
Resume a failed plan without discarding completed intermediary files. Plan generation restarts from the first incomplete step, skipping all steps that already produced output files.
442
442
443
-
Use `plan_resume` when `plan_status` shows `failed` and the run was interrupted mid-pipeline (network drop, timeout, `plan_stop`, worker crash). For a full restart or to change `model_profile`, use `plan_retry` instead.
443
+
Use `plan_resume` when `plan_status` shows `failed` and plan generation was interrupted before completing all steps (network drop, timeout, `plan_stop`, worker crash). For a full restart or to change `model_profile`, use `plan_retry` instead.
444
444
445
445
**Request**
446
446
@@ -470,25 +470,42 @@ Use `plan_resume` when `plan_status` shows `failed` and the run was interrupted
470
470
471
471
**Required semantics**
472
472
473
-
-Only failed plans are resumable.
473
+
-The MCP tool only accepts plans in `failed` state. However, the underlying Luigi mechanism is more general: Luigi skips any task whose output file already exists and re-executes any task whose output file is missing. This means a completed plan can be partially re-run by deleting `999-pipeline_complete.txt` and the output files of the tasks you want to regenerate — Luigi will re-execute those tasks and all their downstream dependents. The MCP API does not yet expose this capability; it is available when running the pipeline locally via `run_plan_pipeline.py`.
474
474
- On success, the same plan_id is reset to `pending` and requeued.
475
-
- Prior artifacts are **preserved** — the worker restores the run directory from the stored zip snapshot.
476
-
- Luigi skips tasks whose output files already exist; only incomplete tasks are re-executed.
475
+
- Prior artifacts are **preserved** — the worker restores the output directory from the stored zip snapshot.
477
476
-`resume_count` tracks how many times the plan has been resumed.
|Interrupted before completing all steps (network drop, timeout, stop, crash) |`plan_resume`|
484
483
| Full restart from scratch |`plan_retry`|
485
-
| Change model_profile for a fresh run|`plan_retry`|
484
+
| Change model_profile for a fresh start|`plan_retry`|
486
485
| Continue where you left off, preserving completed work |`plan_resume`|
487
486
487
+
**Pipeline compatibility caveat**
488
+
489
+
Resuming restores the output directory from a stored zip snapshot and relies on Luigi's file-based step skipping: if an output file exists, the step is skipped. This works reliably when the server code matches the code that produced the snapshot. It can break when the server has changed between the original plan generation and the resume attempt:
490
+
491
+
-**Renamed or removed filenames** (`FilenameEnum` in `filenames.py`): if a step's output filename changes, Luigi won't find the old file and will re-execute the step — but downstream steps that already completed may reference the old filename's content, producing inconsistencies.
492
+
-**New steps added**: new steps may expect upstream outputs that don't exist in the snapshot.
493
+
-**Changed step dependencies**: if the DAG wiring changes, Luigi may execute steps in a different order or skip steps that should re-execute.
494
+
-**Changed JSON schemas**: resumed steps may read snapshot files whose internal structure no longer matches what the current code expects.
495
+
496
+
In practice, resuming a plan that failed minutes or hours ago on the same deployment is safe. Resuming a plan that is weeks or months old — after the server has been updated with pipeline changes — may produce errors or inconsistent output. When in doubt, use `plan_retry` for a clean restart.
497
+
498
+
**Pipeline version check**
499
+
500
+
A `PIPELINE_VERSION` integer constant (in `worker_plan_api/pipeline_version.py`) is stamped into the plan's `parameters` on every `plan_create`, `plan_retry`, and `plan_resume`. On `plan_resume`, the stored version is compared to the current constant using strict equality. If they differ, resume is rejected with error code `PIPELINE_VERSION_MISMATCH` and the caller should use `plan_retry` for a clean restart. Legacy plans with no stored version are treated as incompatible.
501
+
502
+
Bump `PIPELINE_VERSION` whenever the pipeline changes in a way that would break resume from an older snapshot (renamed filenames, new tasks, changed DAG wiring, changed JSON schemas). Finer-grained approaches (manifest, per-task schema versions) can be layered on later if needed.
Copy file name to clipboardExpand all lines: docs/proposals/103-pipeline-hardening-roadmap-local-model-reliability.md
+57Lines changed: 57 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -42,6 +42,63 @@ The target is practical: reduce repeated run failures caused by structured-outpu
42
42
3.**Schema echo** (model repeats schema shape/descriptions, not values).
43
43
4.**Code-path assumption bugs** (`KeyError` on non-API path).
44
44
45
+
### 1.4 Local model testing findings: llama_index silent prompt truncation (March 2026)
46
+
47
+
#### Root cause: llama_index default constants
48
+
49
+
Testing on Mac Mini M4 Pro (March 4-6, 2026) with qwen/qwen3.5-35b-a3b via LM Studio revealed the underlying cause of silent structured-output failures:
50
+
51
+
**File**: `llama_index/core/constants.py`
52
+
```python
53
+
DEFAULT_CONTEXT_WINDOW=3900# applies to ALL OpenAILike models unless overridden
54
+
DEFAULT_NUM_OUTPUT=256# affects PromptHelper budget, NOT model output limit
55
+
```
56
+
57
+
These defaults apply to **all OpenAILike models** unless explicitly overridden in the model config. The `PromptHelper` computes:
`DeduplicateLeversTask` input was ~8,400 tokens. With the default budget of 3644 tokens, the prompt was **silently truncated to 3644 tokens** before being sent to the model. The model received a mangled, mid-sentence prompt, produced thinking text instead of valid JSON, and the pipeline failed with `ValueError: Could not extract json string`.
65
+
66
+
This is **silent** — no warning, no log entry, no error at truncation time. The failure manifests downstream as a JSON extraction error, making it extremely hard to diagnose.
67
+
68
+
#### Critical insight: `num_output` does NOT limit model output
69
+
70
+
A common misunderstanding: setting `num_output: 4096` does NOT cap the model's actual output length. The LM Studio API generates freely (10K+ tokens observed). The limit ONLY affects llama_index's internal `PromptHelper` budget calculation for **input** truncation. This is the root cause of misdiagnosis.
71
+
72
+
#### The fix
73
+
74
+
Add explicit overrides to each LM Studio model entry in `llm_config/custom.json`:
75
+
```json
76
+
"context_window": 8192,
77
+
"num_output": 4096
78
+
```
79
+
80
+
This expands the available prompt budget from 3644 to 4096 tokens, accommodating tasks with large combined inputs. For models with larger context windows (e.g., 32K), use `context_window: 32768, num_output: 4096`.
81
+
82
+
#### Tasks most vulnerable to silent truncation
83
+
84
+
Tasks with large combined inputs (multiple prior outputs concatenated):
If values are 3900/256 and your model config doesn't override them, you're vulnerable to silent truncation.
97
+
98
+
#### Relationship to this roadmap
99
+
100
+
Silent truncation is exactly the class of mid-pipeline failures that the containment and telemetry work packages (WP-1, WP-2, WP-3) are designed to catch and surface. By adding structured failure logging (WP-3), future truncation events will be diagnosable instantly. By adding a preflight smoke test (WP-5), bad model profiles can be caught before expensive full-pipeline runs.
1.**Ollama field mapping**: Handled by `extract_token_count()` and `TrackActivity._extract_token_usage()` which already support multiple field name variations.
214
+
2.**Thread safety**: JSONL append-per-line is safe for concurrent Luigi workers.
215
+
3.**Thinking tokens**: Recorded when available (e.g. from OpenRouter reasoning models). `null` for providers that don't expose them.
216
+
4.**Retention on resume**: Appended. A resumed run adds only the new calls alongside the restored snapshot's existing metrics.
217
+
218
+
---
219
+
220
+
## Follow-up: Error Classification (PR #236)
221
+
222
+
Raw Python exception strings in the `"error"` field were verbose and could leak raw model output.
223
+
PR #236 added `classify_error()` to replace them with short category labels:
0 commit comments